Revert "temporarily empty repository"
This reverts commit dc466d6d0d588fefbec8f009ac57cfa520679050.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a23d348
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,91 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.taverna</groupId>
+ <artifactId>taverna-parent</artifactId>
+ <version>1-incubating-SNAPSHOT</version>
+ </parent>
+ <groupId>org.apache.taverna.workbench</groupId>
+ <artifactId>taverna-workbench</artifactId>
+ <version>3.1.0-incubating-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Apache Taverna Workbench</name>
+ <description>Graphical workbench for editing and running Apache Taverna workflows</description>
+ <properties>
+ <taverna.language.version>0.15.0-incubating-SNAPSHOT</taverna.language.version>
+ <taverna.osgi.version>0.2.0-incubating-SNAPSHOT</taverna.osgi.version>
+ <taverna.engine.version>3.1.0-incubating-SNAPSHOT</taverna.engine.version>
+ <taverna.commonactivities.version>2.1.0-incubating-SNAPSHOT</taverna.commonactivities.version>
+ </properties>
+ <modules>
+ <module>taverna-dataflow-activity-ui</module>
+ <module>taverna-disabled-activity-ui</module>
+ <module>taverna-stringconstant-activity-ui</module>
+ <module>taverna-unrecognized-activity-ui</module>
+ <module>taverna-workbench-activity-icons-api</module>
+ <module>taverna-workbench-activity-palette-api</module>
+ <module>taverna-workbench-activity-palette-impl</module>
+ <module>taverna-workbench-activity-palette-ui</module>
+ <module>taverna-workbench-activity-tools</module>
+ <module>taverna-workbench-configuration-api</module>
+ <module>taverna-workbench-configuration-impl</module>
+ <module>taverna-workbench-contextual-views</module>
+ <module>taverna-workbench-contextual-views-api</module>
+ <module>taverna-workbench-contextual-views-impl</module>
+ <module>taverna-workbench-credential-manager-ui</module>
+ <module>taverna-workbench-data-management-config-ui</module>
+ <module>taverna-workbench-design-ui</module>
+ <module>taverna-workbench-edits-api</module>
+ <module>taverna-workbench-edits-impl</module>
+ <module>taverna-workbench-file-api</module>
+ <module>taverna-workbench-file-impl</module>
+ <module>taverna-workbench-graph-model</module>
+ <module>taverna-workbench-graph-view</module>
+ <module>taverna-workbench-helper</module>
+ <module>taverna-workbench-helper-api</module>
+ <module>taverna-workbench-httpproxy-config</module>
+ <module>taverna-workbench-iteration-strategy-ui</module>
+ <module>taverna-workbench-loop-ui</module>
+ <module>taverna-workbench-menu-api</module>
+ <module>taverna-workbench-menu-impl</module>
+ <module>taverna-workbench-menu-items</module>
+ <module>taverna-workbench-monitor-view</module>
+ <module>taverna-workbench-parallelize-ui</module>
+ <module>taverna-workbench-perspective-biocatalogue</module>
+ <module>taverna-workbench-perspective-design</module>
+ <module>taverna-workbench-perspective-myexperiment</module>
+ <module>taverna-workbench-perspective-results</module>
+ <module>taverna-workbench-plugin-manager</module>
+ <module>taverna-workbench-plugins-gui</module>
+ <module>taverna-workbench-reference-ui</module>
+ <module>taverna-workbench-renderers-api</module>
+ <module>taverna-workbench-renderers-exts</module>
+ <module>taverna-workbench-renderers-impl</module>
+ <module>taverna-workbench-report-api</module>
+ <module>taverna-workbench-report-explainer</module>
+ <module>taverna-workbench-report-impl</module>
+ <module>taverna-workbench-report-view</module>
+ <module>taverna-workbench-results-view</module>
+ <module>taverna-workbench-retry-ui</module>
+ <module>taverna-workbench-run-ui</module>
+ <module>taverna-workbench-selection-api</module>
+ <module>taverna-workbench-selection-impl</module>
+ <module>taverna-workbench-update-manager</module>
+ <module>taverna-workbench-workbench-api</module>
+ <module>taverna-workbench-workbench-impl</module>
+ <module>taverna-workbench-workflow-explorer</module>
+ <module>taverna-workbench-workflow-view</module>
+ </modules>
+ <repositories>
+ <repository>
+ <id>taverna-incubating</id>
+ <name>Apache Taverna incubating Repository</name>
+ <url>http://repository.mygrid.org.uk/artifactory/incubator-snapshot-local/</url>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ </repository>
+ </repositories>
+</project>
diff --git a/taverna-dataflow-activity-ui/pom.xml b/taverna-dataflow-activity-ui/pom.xml
new file mode 100644
index 0000000..d93cf07
--- /dev/null
+++ b/taverna-dataflow-activity-ui/pom.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna</groupId>
+ <artifactId>taverna-parent</artifactId>
+ <version>3.0.1-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>dataflow-activity-ui</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>Taverna 2 Dataflow Activity UI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ <version>${javahelp.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-tools</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+ <!-- testing dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ <version> ${junit.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>file-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>edits-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-t2flow</artifactId>
+ <version>${scufl2.version}</version>
+ <scope>test</scope>
+ </dependency> -->
+ </dependencies>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+</project>
+
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/actions/EditNestedDataflowAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/actions/EditNestedDataflowAction.java
new file mode 100644
index 0000000..679209c
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/actions/EditNestedDataflowAction.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.activities.dataflow.actions;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowTemplateService;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+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.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+@SuppressWarnings("serial")
+public class EditNestedDataflowAction extends AbstractAction {
+
+ private final Activity activity;
+ private final SelectionManager selectionManager;
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public EditNestedDataflowAction(Activity activity, SelectionManager selectionManager) {
+ super("Edit nested workflow");
+ this.activity = activity;
+ this.selectionManager = selectionManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (activity.getType().equals(DataflowTemplateService.ACTIVITY_TYPE)) {
+ for (Configuration configuration : scufl2Tools.configurationsFor(activity, selectionManager.getSelectedProfile())) {
+ JsonNode nested = configuration.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = selectionManager.getSelectedWorkflowBundle().getWorkflows().getByName(nested.asText());
+ if (nestedWorkflow != null) {
+ selectionManager.setSelectedWorkflow(nestedWorkflow);
+ break;
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/menu/EditNestedDataflowMenuAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/menu/EditNestedDataflowMenuAction.java
new file mode 100644
index 0000000..8bdf0e1
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/menu/EditNestedDataflowMenuAction.java
@@ -0,0 +1,28 @@
+package net.sf.taverna.t2.activities.dataflow.menu;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.activities.dataflow.actions.EditNestedDataflowAction;
+import net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowTemplateService;
+import net.sf.taverna.t2.workbench.activitytools.AbstractConfigureActivityMenuAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+public class EditNestedDataflowMenuAction extends AbstractConfigureActivityMenuAction {
+
+ private SelectionManager selectionManager;
+
+ public EditNestedDataflowMenuAction() {
+ super(DataflowTemplateService.ACTIVITY_TYPE);
+ }
+
+ @Override
+ protected Action createAction() {
+ EditNestedDataflowAction configAction = new EditNestedDataflowAction(findActivity(), selectionManager);
+ return configAction;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/servicedescriptions/DataflowActivityIcon.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/servicedescriptions/DataflowActivityIcon.java
new file mode 100644
index 0000000..6d7e766
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/servicedescriptions/DataflowActivityIcon.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.activities.dataflow.servicedescriptions;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI;
+
+/**
+ *
+ * @author Alex Nenadic
+ * @author alanrw
+ *
+ */
+public class DataflowActivityIcon implements ActivityIconSPI{
+
+ private static Icon icon;
+
+ public int canProvideIconScore(URI activityType) {
+ if (DataflowTemplateService.ACTIVITY_TYPE.equals(activityType))
+ return DEFAULT_ICON + 1;
+ else
+ return NO_ICON;
+ }
+
+ public Icon getIcon(URI activityType) {
+ return getDataflowIcon();
+ }
+
+ public static Icon getDataflowIcon() {
+ if (icon == null) {
+ icon = new ImageIcon(DataflowActivityIcon.class.getResource("/dataflow.png"));
+ }
+ return icon;
+ }
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/servicedescriptions/DataflowTemplateService.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/servicedescriptions/DataflowTemplateService.java
new file mode 100644
index 0000000..f5cd8f2
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/servicedescriptions/DataflowTemplateService.java
@@ -0,0 +1,54 @@
+package net.sf.taverna.t2.activities.dataflow.servicedescriptions;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.t2.servicedescriptions.AbstractTemplateService;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+public class DataflowTemplateService extends AbstractTemplateService {
+
+ public static final URI ACTIVITY_TYPE = URI.create("http://ns.taverna.org.uk/2010/activity/nested-workflow");
+
+ private static final String A_CONFIGURABLE_NESTED_WORKFLOW = "A service that allows you to have one workflow nested within another";
+ private static final String DATAFLOW = "Nested workflow";
+
+ private static final URI providerId = URI.create("http://taverna.sf.net/2010/service-provider/dataflow");
+
+ @Override
+ public URI getActivityType() {
+ return ACTIVITY_TYPE;
+ }
+
+ @Override
+ public Configuration getActivityConfiguration() {
+ Configuration configuration = new Configuration();
+ configuration.setType(ACTIVITY_TYPE.resolve("#Config"));
+ return configuration;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return DataflowActivityIcon.getDataflowIcon();
+ }
+
+ public String getName() {
+ return DATAFLOW;
+ }
+
+ public String getDescription() {
+ return A_CONFIGURABLE_NESTED_WORKFLOW;
+ }
+
+ public static ServiceDescription getServiceDescription() {
+ DataflowTemplateService dts = new DataflowTemplateService();
+ return dts.templateService;
+ }
+
+ public String getId() {
+ return providerId.toString();
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/views/DataflowActivityContextualView.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/views/DataflowActivityContextualView.java
new file mode 100644
index 0000000..7bc44cb
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/views/DataflowActivityContextualView.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * 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.activities.dataflow.views;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.activities.dataflow.actions.EditNestedDataflowAction;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.actions.ReplaceNestedWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.HTMLBasedActivityContextualView;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+@SuppressWarnings("serial")
+public class DataflowActivityContextualView extends HTMLBasedActivityContextualView {
+
+ static Logger logger = Logger.getLogger(DataflowActivityContextualView.class);
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+ private final ActivityIconManager activityIconManager;
+ private final ColourManager colourManager;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ private final SelectionManager selectionManager;
+
+ public DataflowActivityContextualView(Activity activity, EditManager editManager,
+ FileManager fileManager, MenuManager menuManager,
+ ActivityIconManager activityIconManager, ColourManager colourManager,
+ ServiceDescriptionRegistry serviceDescriptionRegistry,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ super(activity, colourManager);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.activityIconManager = activityIconManager;
+ this.colourManager = colourManager;
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+ addEditButtons();
+ }
+
+ @Override
+ public Activity getActivity() {
+ return super.getActivity();
+ }
+
+ public void addEditButtons() {
+ JComponent mainFrame = getMainFrame();
+ JButton viewWorkflowButton = new JButton("Edit workflow");
+ viewWorkflowButton.addActionListener(new EditNestedDataflowAction(getActivity(),
+ selectionManager));
+ JButton configureButton = new JButton(new ReplaceNestedWorkflowAction(getActivity(),
+ editManager, fileManager, menuManager, activityIconManager, colourManager,
+ serviceDescriptionRegistry, workbenchConfiguration, selectionManager));
+ configureButton.setIcon(null);
+ JPanel flowPanel = new JPanel(new FlowLayout());
+ flowPanel.add(viewWorkflowButton);
+ flowPanel.add(configureButton);
+ mainFrame.add(flowPanel, BorderLayout.SOUTH);
+ mainFrame.revalidate();
+ }
+
+// @Override
+// public JComponent getMainFrame() {
+// JComponent mainFrame = super.getMainFrame();
+// JButton viewWorkflowButton = new JButton("Edit workflow");
+// viewWorkflowButton.addActionListener(new EditNestedDataflowAction(getActivity(),
+// selectionManager));
+// JButton configureButton = new JButton(new ReplaceNestedWorkflowAction(getActivity(),
+// editManager, fileManager, menuManager, activityIconManager, colourManager,
+// serviceDescriptionRegistry, workbenchConfiguration, selectionManager));
+// configureButton.setIcon(null);
+// JPanel flowPanel = new JPanel(new FlowLayout());
+// flowPanel.add(viewWorkflowButton);
+// flowPanel.add(configureButton);
+// mainFrame.add(flowPanel, BorderLayout.SOUTH);
+// return mainFrame;
+// }
+
+ @Override
+ protected String getRawTableRowsHtml() {
+ return ("<tr><td colspan=2>" + getActivity().getName() + "</td></tr>");
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Nested workflow";
+ }
+
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return null;
+ // return new OpenNestedDataflowFromFileAction(
+ // (DataflowActivity) getActivity(), owner);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/views/DataflowActivityViewFactory.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/views/DataflowActivityViewFactory.java
new file mode 100644
index 0000000..e5d8f33
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/activities/dataflow/views/DataflowActivityViewFactory.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.activities.dataflow.views;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowTemplateService;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+public class DataflowActivityViewFactory implements ContextualViewFactory<Activity> {
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private ColourManager colourManager;
+ private ActivityIconManager activityIconManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private SelectionManager selectionManager;
+
+ public boolean canHandle(Object object) {
+ return object instanceof Activity
+ && ((Activity) object).getType().equals(DataflowTemplateService.ACTIVITY_TYPE);
+ }
+
+ public List<ContextualView> getViews(Activity activity) {
+ return Arrays.asList(new ContextualView[] { new DataflowActivityContextualView(activity,
+ editManager, fileManager, menuManager, activityIconManager, colourManager,
+ serviceDescriptionRegistry, workbenchConfiguration, selectionManager) });
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setServiceDescriptionRegistry(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/DataflowMerger.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/DataflowMerger.java
new file mode 100644
index 0000000..327e5a7
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/DataflowMerger.java
@@ -0,0 +1,124 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.AddDataLinkEdit;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+import net.sf.taverna.t2.workflow.edits.AddWorkflowInputPortEdit;
+import net.sf.taverna.t2.workflow.edits.AddWorkflowOutputPortEdit;
+import uk.org.taverna.scufl2.api.common.AbstractCloneable;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * A tool that allows merging of two workflow.
+ * <p>
+ * The merge is performed as a series of edit, inserting a copy of the source
+ * workflow into the destination workflow.
+ *
+ * @author Stian Soiland-Reyes
+ * @author David Withers
+ */
+public class DataflowMerger {
+
+ /**
+ * Make a copy of a workflow.
+ *
+ * @param source
+ * workflow to copy
+ * @return A copy of the workflow.
+ */
+ public static Workflow copyWorkflow(Workflow source) {
+ WorkflowBundle workflowBundle = AbstractCloneable.cloneWorkflowBean(source.getParent());
+ return workflowBundle.getWorkflows().getByName(source.getName());
+ }
+
+ private final Workflow destinationWorkflow;
+
+ /**
+ * Construct a {@link DataflowMerger} for the given destination workflow.
+ *
+ * @param destinationWorkflow
+ * Workflow to be merged into
+ */
+ public DataflowMerger(Workflow destinationWorkflow) {
+ this.destinationWorkflow = destinationWorkflow;
+ }
+
+ /**
+ * Make an {@link Edit} that when performed merges the given source dataflow
+ * into the destination dataflow.
+ * <p>
+ * Internally a copy is made of the source dataflow, to avoid modifying the
+ * links and processors.
+ *
+ * @param sourceDataflow
+ * Dataflow to merge from
+ * @return An edit that can perform and undo the insertion of the components
+ * from the source dataflow.
+ * @throws MergeException
+ * If the merge cannot be performed.
+ */
+ public CompoundEdit getMergeEdit(Workflow sourceDataflow)
+ throws MergeException {
+ return getMergeEdit(sourceDataflow, "");
+ }
+
+ /**
+ * Make an {@link Edit} that when performed merges the given source dataflow
+ * into the destination dataflow.
+ * <p>
+ * Internally a copy is made of the source dataflow, to avoid modifying the
+ * links and processors.
+ *
+ * @param sourceWorkflow
+ * Dataflow to merge from
+ * @param prefix
+ * A prefix which will be inserted in front of the names for the
+ * merged workflow components.
+ * @return An edit that can perform and undo the insertion of the components
+ * from the source dataflow.
+ * @throws MergeException
+ * If the merge cannot be performed.
+ */
+ public CompoundEdit getMergeEdit(Workflow sourceWorkflow, String prefix)
+ throws MergeException {
+ List<Edit<?>> compoundEdit = new ArrayList<>();
+
+ Workflow workflow = copyWorkflow(sourceWorkflow);
+
+ for (InputWorkflowPort input : workflow.getInputPorts()) {
+ destinationWorkflow.getInputPorts().addWithUniqueName(input);
+ destinationWorkflow.getInputPorts().remove(input);
+ compoundEdit.add(new AddWorkflowInputPortEdit(destinationWorkflow, input));
+ }
+ for (OutputWorkflowPort output : workflow.getOutputPorts()) {
+ destinationWorkflow.getOutputPorts().addWithUniqueName(output);
+ destinationWorkflow.getOutputPorts().remove(output);
+ compoundEdit.add(new AddWorkflowOutputPortEdit(destinationWorkflow, output));
+ }
+ for (Processor processor : workflow.getProcessors()) {
+ processor.setName(prefix + processor.getName());
+ compoundEdit.add(new AddProcessorEdit(destinationWorkflow, processor));
+ }
+ for (DataLink dataLink : workflow.getDataLinks()) {
+ compoundEdit.add(new AddDataLinkEdit(destinationWorkflow, dataLink));
+ }
+ for (ControlLink controlLink : workflow.getControlLinks()) {
+ compoundEdit.add(new AddChildEdit<Workflow>(destinationWorkflow, controlLink));
+ }
+
+ return new CompoundEdit(compoundEdit);
+
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/MergeException.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/MergeException.java
new file mode 100644
index 0000000..3645f91
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/MergeException.java
@@ -0,0 +1,22 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+public class MergeException extends Exception {
+ private static final long serialVersionUID = 6018700359518335402L;
+
+ public MergeException() {
+ super();
+ }
+
+ public MergeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public MergeException(String message) {
+ super(message);
+ }
+
+ public MergeException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/AddNestedWorkflowAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/AddNestedWorkflowAction.java
new file mode 100644
index 0000000..d6f04dd
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/AddNestedWorkflowAction.java
@@ -0,0 +1,59 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.actions;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowActivityIcon;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.gui.ImportWorkflowWizard;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Utils;
+
+/**
+ * An action for adding a nested workflow.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class AddNestedWorkflowAction extends AbstractAction {
+ private static final long serialVersionUID = -2242979457902699028L;
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+ private final ColourManager colourManager;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final SelectionManager selectionManager;
+
+ public AddNestedWorkflowAction(EditManager editManager, FileManager fileManager,
+ MenuManager menuManager, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ super("Add nested workflow", DataflowActivityIcon.getDataflowIcon());
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.colourManager = colourManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component) {
+ parentComponent = (Component) e.getSource();
+ } else {
+ parentComponent = null;
+ }
+ ImportWorkflowWizard wizard = new ImportWorkflowWizard(
+ Utils.getParentFrame(parentComponent), editManager, fileManager, menuManager,
+ colourManager, workbenchConfiguration, selectionManager);
+ wizard.setMergeEnabled(false);
+ wizard.setVisible(true);
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/ImportWorkflowAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/ImportWorkflowAction.java
new file mode 100644
index 0000000..6d9fffb
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/ImportWorkflowAction.java
@@ -0,0 +1,59 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.actions;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowActivityIcon;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.gui.ImportWorkflowWizard;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Utils;
+
+/**
+ * A general version of {@link AddNestedWorkflowAction} and {@link MergeWorkflowAction} that allows
+ * the user to choose which action to perform.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class ImportWorkflowAction extends AbstractAction {
+ private static final long serialVersionUID = -2242979457902699028L;
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+ private final ColourManager colourManager;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final SelectionManager selectionManager;
+
+ public ImportWorkflowAction(EditManager editManager, FileManager fileManager,
+ MenuManager menuManager, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ super("Import workflow", DataflowActivityIcon.getDataflowIcon());
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.colourManager = colourManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component) {
+ parentComponent = (Component) e.getSource();
+ } else {
+ parentComponent = null;
+ }
+ ImportWorkflowWizard wizard = new ImportWorkflowWizard(
+ Utils.getParentFrame(parentComponent), editManager, fileManager, menuManager,
+ colourManager, workbenchConfiguration, selectionManager);
+ wizard.setVisible(true);
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/MergeWorkflowAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/MergeWorkflowAction.java
new file mode 100644
index 0000000..d86f97c
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/MergeWorkflowAction.java
@@ -0,0 +1,58 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.actions;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.gui.ImportWorkflowWizard;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Utils;
+
+/**
+ * An action for merging two workflows
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class MergeWorkflowAction extends AbstractAction {
+ private static final long serialVersionUID = -2242979457902699028L;
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+ private final ColourManager colourManager;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final SelectionManager selectionManager;
+
+ public MergeWorkflowAction(EditManager editManager, FileManager fileManager,
+ MenuManager menuManager, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ super("Merge workflow");
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.colourManager = colourManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component) {
+ parentComponent = (Component) e.getSource();
+ } else {
+ parentComponent = null;
+ }
+ ImportWorkflowWizard wizard = new ImportWorkflowWizard(
+ Utils.getParentFrame(parentComponent), editManager, fileManager, menuManager,
+ colourManager, workbenchConfiguration, selectionManager);
+ wizard.setNestedEnabled(false);
+ wizard.setVisible(true);
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/OpenSourceWorkflowAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/OpenSourceWorkflowAction.java
new file mode 100644
index 0000000..f392405
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/OpenSourceWorkflowAction.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.file.importworkflow.actions;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class OpenSourceWorkflowAction extends AbstractAction {
+
+ private static Logger logger = Logger.getLogger(OpenSourceWorkflowAction.class);
+
+ private static final String OPEN_WORKFLOW = "Open workflow...";
+
+ protected FileManager fileManager;
+
+ public OpenSourceWorkflowAction(FileManager fileManager) {
+ super(OPEN_WORKFLOW, WorkbenchIcons.openIcon);
+ this.fileManager = fileManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component) {
+ parentComponent = (Component) e.getSource();
+ } else {
+ parentComponent = null;
+ }
+ openWorkflows(parentComponent);
+ }
+
+ public abstract void openWorkflows(Component parentComponent, File[] files);
+
+ /**
+ * Pop up an Open-dialogue to select one or more workflow files to open.
+ *
+ * @param parentComponent
+ * The UI parent component to use for pop up dialogues
+ * @param openCallback
+ * An {@link OpenCallback} to be called during the file opening.
+ * The callback will be invoked for each file that has been
+ * opened, as file opening happens in a separate thread that
+ * might execute after the return of this method.
+ * @return <code>false</code> if no files were selected or the dialogue was
+ * cancelled, or <code>true</code> if the process of opening one or
+ * more files has been started.
+ */
+ public boolean openWorkflows(final Component parentComponent) {
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get("currentDir", System.getProperty("user.home"));
+ fileChooser.setDialogTitle(OPEN_WORKFLOW);
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ List<FileFilter> fileFilters = fileManager.getOpenFileFilters();
+ if (fileFilters.isEmpty()) {
+ logger.warn("No file types found for opening workflow");
+ JOptionPane
+ .showMessageDialog(parentComponent,
+ "No file types found for opening workflow.", "Error",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ for (FileFilter fileFilter : fileFilters) {
+ fileChooser.addChoosableFileFilter(fileFilter);
+ }
+
+ fileChooser.setFileFilter(fileFilters.get(0));
+
+ fileChooser.setCurrentDirectory(new File(curDir));
+ fileChooser.setMultiSelectionEnabled(true);
+
+ int returnVal = fileChooser.showOpenDialog(parentComponent);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ prefs.put("currentDir", fileChooser.getCurrentDirectory().toString());
+ final File[] selectedFiles = fileChooser.getSelectedFiles();
+ if (selectedFiles.length == 0) {
+ logger.warn("No files selected");
+ return false;
+ }
+ new FileOpenerThread(parentComponent, selectedFiles).start();
+ return true;
+ }
+ return false;
+ }
+
+ private final class FileOpenerThread extends Thread {
+ private final File[] files;
+ private final Component parentComponent;
+
+ private FileOpenerThread(Component parentComponent, File[] selectedFiles) {
+ super("Opening workflows(s) " + Arrays.asList(selectedFiles));
+ this.parentComponent = parentComponent;
+ this.files = selectedFiles;
+ }
+
+ @Override
+ public void run() {
+ openWorkflows(parentComponent, files);
+ }
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/ReplaceNestedWorkflowAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/ReplaceNestedWorkflowAction.java
new file mode 100644
index 0000000..9199ab5
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/actions/ReplaceNestedWorkflowAction.java
@@ -0,0 +1,84 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.actions;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.gui.ImportWorkflowWizard;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Utils;
+import net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction;
+import net.sf.taverna.t2.workflow.edits.ConfigureEdit;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class ReplaceNestedWorkflowAction extends ActivityConfigurationAction {
+ private static final long serialVersionUID = 1L;
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+
+ private final ColourManager colourManager;
+
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ private final SelectionManager selectionManager;
+
+ public ReplaceNestedWorkflowAction(Activity activity, EditManager editManager,
+ FileManager fileManager, MenuManager menuManager,
+ ActivityIconManager activityIconManager, ColourManager colourManager,
+ ServiceDescriptionRegistry serviceDescriptionRegistry,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ super(activity, activityIconManager, serviceDescriptionRegistry);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.colourManager = colourManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+ putValue(NAME, "Replace nested workflow");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component) {
+ parentComponent = (Component) e.getSource();
+ } else {
+ parentComponent = null;
+ }
+ ImportWorkflowWizard wizard = new ImportWorkflowWizard(
+ Utils.getParentFrame(parentComponent), editManager, fileManager, menuManager,
+ colourManager, workbenchConfiguration, selectionManager) {
+ private static final long serialVersionUID = 1L;
+
+// @Override
+// protected Edit<?> makeInsertNestedWorkflowEdit(Workflow nestedFlow, String name) {
+// Configuration configuration = new Configuration();
+// configuration.setType(null);
+// // TODO use service registry
+// return new ConfigureEdit<Activity>(getActivity(), null, configuration);
+// }
+
+// @Override
+// protected Activity getInsertedActivity() {
+// return getActivity();
+// }
+ };
+
+ wizard.setMergeEnabled(false);
+// wizard.setCustomDestinationDataflow(fileManager.getCurrentDataflow(),
+// "Existing nested workflow");
+// wizard.setDestinationEnabled(false);
+ wizard.setVisible(true);
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/gui/ImportWorkflowWizard.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/gui/ImportWorkflowWizard.java
new file mode 100644
index 0000000..b8ddf1a
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/gui/ImportWorkflowWizard.java
@@ -0,0 +1,1272 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.ButtonModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+import javax.swing.ProgressMonitor;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowTemplateService;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.importworkflow.DataflowMerger;
+import net.sf.taverna.t2.workbench.file.importworkflow.MergeException;
+import net.sf.taverna.t2.workbench.file.importworkflow.actions.OpenSourceWorkflowAction;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphController;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.log4j.Logger;
+
+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.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.iterationstrategy.CrossProduct;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+@SuppressWarnings("serial")
+public class ImportWorkflowWizard extends HelpEnabledDialog {
+
+ private static Logger logger = Logger.getLogger(ImportWorkflowWizard.class);
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ protected BrowseFileOnClick browseFileOnClick = new BrowseFileOnClick();
+ protected JButton buttonBrowse;
+ protected JComboBox chooseDataflow;
+ protected DataflowOpenerThread dataflowOpenerThread;
+
+ private WorkflowBundle destinationWorkflowBundle;
+ private Workflow destinationWorkflow;
+ private Profile destinationProfile;
+ private Workflow sourceWorkflow;
+
+ protected JTextField fieldFile;
+
+ protected JTextField fieldUrl;
+ protected boolean mergeEnabled = true;
+ protected boolean nestedEnabled = true;
+ protected JSVGCanvas previewSource = new JSVGCanvas(null, false, false);
+ protected JSVGCanvas previewDestination = new JSVGCanvas(null, false, false);
+ protected JTextField prefixField;
+ protected JRadioButton radioFile;
+ protected JRadioButton radioNew;
+ protected JRadioButton radioOpened;
+ protected JRadioButton radioUrl;
+ protected ButtonGroup sourceSelection;
+ protected ActionListener updateChosenListener = new UpdateChosenListener();
+ protected Thread updatePreviewsThread;
+ protected Component sourceSelectionPanel;
+ protected JLabel prefixLabel;
+ protected JLabel prefixHelp;
+// protected JPanel destinationSelectionPanel;
+// protected ButtonGroup destinationSelection;
+// protected JRadioButton radioNewDestination;
+// protected JRadioButton radioOpenDestination;
+// protected JComboBox destinationAlreadyOpen;
+ protected JPanel introductionPanel;
+ protected ButtonGroup actionSelection;
+ protected JRadioButton actionNested;
+ protected JRadioButton actionMerge;
+ protected JRadioButton radioCustomSource;
+ protected JRadioButton radioCustomDestination;
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+ private final ColourManager colourManager;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final SelectionManager selectionManager;
+
+ private WorkflowBundle customSourceDataFlow = null;
+// private Workflow customDestinationDataflow = null;
+ private String customSourceName = "";
+// private String customDestinationName = "";
+
+ private boolean sourceEnabled = true;
+// private boolean destinationEnabled = true;
+ private Activity insertedActivity;
+
+ public ImportWorkflowWizard(Frame parentFrame, EditManager editManager,
+ FileManager fileManager, MenuManager menuManager, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ super(parentFrame, "Import workflow", true, null);
+ this.selectionManager = selectionManager;
+ destinationWorkflow = selectionManager.getSelectedWorkflow();
+ destinationProfile = selectionManager.getSelectedProfile();
+ destinationWorkflowBundle = selectionManager.getSelectedWorkflowBundle();
+
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.colourManager = colourManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+
+ setSize(600, 600);
+ add(makeContentPane(), BorderLayout.CENTER);
+ // Add some space
+ add(new JPanel(), BorderLayout.WEST);
+ add(new JPanel(), BorderLayout.NORTH);
+ add(new JPanel(), BorderLayout.SOUTH);
+ add(new JPanel(), BorderLayout.EAST);
+ findChosenDataflow(this, true);
+ updateAll();
+ }
+
+ public void setMergeEnabled(boolean importEnabled) {
+ this.mergeEnabled = importEnabled;
+ updateAll();
+ }
+
+ public void setNestedEnabled(boolean nestedEnabled) {
+ this.nestedEnabled = nestedEnabled;
+ updateAll();
+ }
+
+ /**
+ * Silly workaround to avoid "Cannot call invokeAndWait from the event dispatcher thread"
+ * exception.
+ *
+ * @param runnable
+ */
+ public static void invokeAndWait(Runnable runnable) {
+ if (SwingUtilities.isEventDispatchThread()) {
+ runnable.run();
+ return;
+ }
+ try {
+ SwingUtilities.invokeAndWait(runnable);
+ } catch (InterruptedException ex) {
+ // logger.warn("Runnable " + runnable + " was interrupted " + runnable, ex);
+ } catch (InvocationTargetException e) {
+ logger.warn("Can't invoke " + runnable, e);
+ }
+ }
+
+ protected Component makeWorkflowImage() {
+ JPanel workflowImages = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridy = 0;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.weighty = 0.1;
+
+ gbc.weightx = 0.1;
+ workflowImages.add(new JPanel(), gbc);// filler
+
+ gbc.weightx = 0.0;
+ previewSource.setBackground(workflowImages.getBackground());
+ workflowImages.add(previewSource, gbc);
+
+ JLabel arrow = new JLabel("\u2192");
+ arrow.setFont(arrow.getFont().deriveFont(48f));
+ workflowImages.add(arrow, gbc);
+
+ previewDestination.setBackground(workflowImages.getBackground());
+ workflowImages.add(previewDestination, gbc);
+
+ gbc.weightx = 0.1;
+ workflowImages.add(new JPanel(), gbc);
+ gbc.weightx = 0.0;
+
+ return workflowImages;
+ }
+
+ protected void updateAll() {
+ updatePreviews(); // will go in separate thread anyway, do it first
+ updateHeader();
+ updateSourceSection();
+// updateDestinationSection();
+ updateFooter();
+ }
+
+// protected void updateDestinationSection() {
+//
+// radioNewDestination.setVisible(false);
+//
+// radioCustomDestination.setText(customDestinationName);
+// radioCustomDestination.setVisible(customDestinationDataflow != null);
+//
+// // radioNewDestination.setVisible(nestedEnabled);
+// // radioNewDestination.setEnabled(actionNested.isSelected());
+//
+// destinationSelectionPanel.setVisible(destinationEnabled);
+//
+// }
+
+ protected synchronized void updatePreviews() {
+ if (updatePreviewsThread != null && updatePreviewsThread.isAlive()) {
+ updatePreviewsThread.interrupt();
+ }
+ updatePreviewsThread = new UpdatePreviewsThread();
+ updatePreviewsThread.start();
+ }
+
+ protected void updateDestinationPreview() {
+ updateWorkflowGraphic(previewDestination, destinationWorkflow, destinationProfile);
+ }
+
+ protected void updateSourcePreview() {
+ Profile sourceProfile = null;
+ if (sourceWorkflow != null) {
+ sourceProfile = sourceWorkflow.getParent().getMainProfile();
+ }
+ updateWorkflowGraphic(previewSource, sourceWorkflow, sourceProfile);
+ }
+
+ protected void updateFooter() {
+ prefixField.setVisible(mergeEnabled);
+ prefixLabel.setVisible(mergeEnabled);
+ prefixHelp.setVisible(mergeEnabled);
+
+ prefixField.setEnabled(actionMerge.isSelected());
+ prefixLabel.setEnabled(actionMerge.isSelected());
+ prefixHelp.setEnabled(actionMerge.isSelected());
+ if (actionMerge.isSelected()) {
+ prefixHelp.setForeground(prefixLabel.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
+ prefixHelp.setForeground(Color.gray);
+ }
+
+ }
+
+ protected void updateHeader() {
+ makeIntroductionPanel();
+ }
+
+ protected void updateSourceSection() {
+ radioCustomSource.setText(customSourceName);
+ radioCustomSource.setVisible(customSourceDataFlow != null);
+
+ radioNew.setVisible(nestedEnabled);
+ radioNew.setEnabled(actionNested.isSelected());
+
+ if (actionNested.isSelected() && sourceSelection.getSelection() == null) {
+ // Preselect the new workflow
+ radioNew.setSelected(true);
+ }
+
+ sourceSelectionPanel.setVisible(sourceEnabled);
+ }
+
+ /**
+ * Create a PNG image of the workflow and place inside an ImageIcon
+ *
+ * @param dataflow
+ * @return
+ * @throws InvocationTargetException
+ * @throws InterruptedException
+ */
+ protected void updateWorkflowGraphic(final JSVGCanvas svgCanvas, final Workflow workflow, final Profile profile) {
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ // Set it to blank while reloading
+ svgCanvas.setSVGDocument(null);
+ if (workflow != null) {
+ SVGGraphController currentWfGraphController = new SVGGraphController(
+ workflow, profile, false, svgCanvas,
+ editManager, menuManager, colourManager, workbenchConfiguration);
+ }
+ }
+ });
+ } catch (InterruptedException e) {
+ // logger.error(e);
+ } catch (InvocationTargetException e) {
+ // logger.error(e);
+ }
+ }
+
+ /**
+ * Open the selected source and destination workflows. If background is true, this method will
+ * return immediately while a {@link DataflowOpenerThread} performs the updates. If a
+ * DataflowOpenerThread is already running, it will be interrupted and stopped.
+ *
+ * @param parentComponent
+ * The parent component for showing dialogues
+ * @param background
+ * If true, will run in separate thread.
+ * @return <code>false</code> if running in the background, or if a dialogue was shown and the
+ * operation is aborted by the user, or <code>true</code> if not running in the
+ * background and the method completed without user interruption.
+ */
+ protected synchronized boolean findChosenDataflow(Component parentComponent, boolean background) {
+ if (dataflowOpenerThread != null && dataflowOpenerThread.isAlive()) {
+ if (background) {
+ // We've changed our mind
+ dataflowOpenerThread.interrupt();
+ } else {
+ // We'll let it finish, we don't need to do it again
+ try {
+ dataflowOpenerThread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return !dataflowOpenerThread.shownWarning;
+ }
+ }
+ dataflowOpenerThread = new DataflowOpenerThread(parentComponent, background);
+
+ if (background) {
+ dataflowOpenerThread.start();
+ return false;
+ } else {
+ dataflowOpenerThread.run();
+ return !dataflowOpenerThread.shownWarning;
+ }
+
+ }
+
+ protected Container makeContentPane() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.ipadx = 5;
+ gbc.ipady = 5;
+
+ gbc.gridx = 0;
+ gbc.weightx = 0.1;
+ gbc.fill = GridBagConstraints.BOTH;
+
+ introductionPanel = makeIntroductionPanel();
+ panel.add(introductionPanel, gbc);
+
+ sourceSelectionPanel = makeSourceSelectionPanel();
+ panel.add(sourceSelectionPanel, gbc);
+
+// destinationSelectionPanel = makeDestinationSelectionPanel();
+// panel.add(destinationSelectionPanel, gbc);
+
+ gbc.weighty = 0.1;
+ panel.add(makeImportStylePanel(), gbc);
+
+ return panel;
+ }
+
+ protected JPanel makeIntroductionPanel() {
+ if (introductionPanel == null) {
+ introductionPanel = new JPanel(new GridBagLayout());
+ } else {
+ introductionPanel.removeAll();
+ }
+ boolean bothEnabled = mergeEnabled && nestedEnabled;
+ if (bothEnabled) {
+ introductionPanel.setBorder(BorderFactory.createTitledBorder("Import method"));
+ } else {
+ introductionPanel.setBorder(BorderFactory.createEmptyBorder());
+ }
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ // gbc.gridy = 0;
+ gbc.weightx = 0.1;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.anchor = GridBagConstraints.FIRST_LINE_START;
+
+ StringBuilder nestedHelp = new StringBuilder();
+ nestedHelp.append("<html><small>");
+ nestedHelp.append("Add a <strong>nested workflow</strong> ");
+ nestedHelp.append("into the ");
+ nestedHelp.append("destination workflow as a single service. ");
+ nestedHelp.append("The nested workflow ");
+ nestedHelp.append("can be <em>edited separately</em>, but is shown ");
+ nestedHelp.append("expanded in the diagram of the parent ");
+ nestedHelp.append("workflow. In the parent workflow you can ");
+ nestedHelp.append("connect to the input and output ports of the nested ");
+ nestedHelp.append("workflow. ");
+ nestedHelp.append("</small></html>");
+
+ StringBuilder mergeHelp = new StringBuilder();
+ mergeHelp.append("<html><small>");
+ mergeHelp.append("<strong>Merge</strong> a workflow ");
+ mergeHelp.append("by copying all services, ports and links ");
+ mergeHelp.append("directly into the destination workflow. This can be ");
+ mergeHelp.append("useful for merging smaller workflow fragments. For ");
+ mergeHelp.append("inclusion of larger workflows you might find using ");
+ mergeHelp.append("<em>nested workflows</em> more beneficial.");
+ mergeHelp.append("</small></html>");
+
+ actionSelection = new ButtonGroup();
+ actionNested = new JRadioButton(nestedHelp.toString());
+ ActionListener updateListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ updateSourceSection();
+// updateDestinationSection();
+ updateFooter();
+ }
+ };
+ actionNested.addActionListener(updateListener);
+ actionSelection.add(actionNested);
+
+ actionMerge = new JRadioButton(mergeHelp.toString());
+ actionMerge.addActionListener(updateListener);
+ actionSelection.add(actionMerge);
+
+ if (bothEnabled) {
+ introductionPanel.add(actionNested, gbc);
+ introductionPanel.add(actionMerge, gbc);
+ actionNested.setSelected(true);
+ } else if (nestedEnabled) {
+ introductionPanel.add(new JLabel(nestedHelp.toString()), gbc);
+ actionNested.setSelected(true);
+ } else if (mergeEnabled) {
+ introductionPanel.add(new JLabel(mergeHelp.toString()), gbc);
+ actionMerge.setSelected(true);
+ }
+ return introductionPanel;
+ }
+
+// protected JPanel makeDestinationSelectionPanel() {
+// JPanel j = new JPanel(new GridBagLayout());
+// j.setBorder(BorderFactory.createTitledBorder("Workflow destination"));
+//
+// GridBagConstraints gbc = new GridBagConstraints();
+// gbc.gridx = 0;
+// gbc.gridy = 0;
+// gbc.fill = GridBagConstraints.BOTH;
+//
+// destinationSelection = new ButtonGroup();
+// radioNewDestination = new JRadioButton("New workflow");
+// gbc.gridy = 0;
+// j.add(radioNewDestination, gbc);
+// destinationSelection.add(radioNewDestination);
+// radioNewDestination.addActionListener(updateChosenListener);
+//
+// radioOpenDestination = new JRadioButton("Already opened workflow");
+// gbc.gridy = 2;
+// j.add(radioOpenDestination, gbc);
+// destinationSelection.add(radioOpenDestination);
+// radioOpenDestination.addActionListener(updateChosenListener);
+// gbc.weightx = 0.1;
+// gbc.gridx = 1;
+// destinationAlreadyOpen = makeSelectOpenWorkflowComboBox(true);
+// j.add(destinationAlreadyOpen, gbc);
+//
+// radioCustomDestination = new JRadioButton(customDestinationName);
+// radioCustomDestination.setVisible(customDestinationName != null);
+// gbc.gridx = 0;
+// gbc.gridy = 3;
+// gbc.gridwidth = 2;
+// j.add(radioCustomDestination, gbc);
+// destinationSelection.add(radioCustomDestination);
+// radioCustomDestination.addActionListener(updateChosenListener);
+// gbc.gridwidth = 1;
+//
+// radioOpenDestination.setSelected(true);
+// return j;
+// }
+
+ protected Component makeImportStylePanel() {
+ JPanel j = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = GridBagConstraints.BOTH;
+
+ j.setBorder(BorderFactory.createTitledBorder("Import"));
+
+ prefixLabel = new JLabel("Prefix");
+ j.add(prefixLabel, gbc);
+ gbc.weightx = 0.1;
+ gbc.gridx = 1;
+
+ prefixField = new JTextField(10);
+ prefixLabel.setLabelFor(prefixField);
+ j.add(prefixField, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+
+ prefixHelp = new JLabel(
+ "<html><small>Optional prefix to be prepended to the name of the "
+ + "inserted services and workflow ports. Even if no prefix is given, duplicate names will be "
+ + "resolved by adding numbers, for instance <code>my_service_2</code> if <code>my_service</code> already "
+ + "existed." + "</small></html>");
+ prefixHelp.setLabelFor(prefixField);
+ j.add(prefixHelp, gbc);
+
+ gbc.gridy = 2;
+ gbc.weightx = 0.1;
+ gbc.weighty = 0.1;
+
+ j.add(makeWorkflowImage(), gbc);
+
+ gbc.gridy = 3;
+ gbc.weighty = 0.0;
+ j.add(new JPanel(), gbc);
+
+ gbc.gridy = 4;
+ gbc.fill = GridBagConstraints.NONE;
+ JButton comp = new JButton(new ImportWorkflowAction());
+ j.add(comp, gbc);
+ return j;
+
+ }
+
+ protected Component makeSelectFile() {
+ JPanel j = new JPanel(new GridBagLayout());
+ j.setBorder(BorderFactory.createEtchedBorder());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.weightx = 0.1;
+
+ fieldFile = new JTextField(20);
+ fieldFile.setEditable(false);
+ fieldFile.addFocusListener(new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ radioFile.setSelected(true);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ findChosenDataflow(e.getComponent(), true);
+ }
+ });
+ j.add(fieldFile, gbc);
+ radioFile.addItemListener(new ItemListener() {
+
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ browseFileOnClick.checkEmptyFile();
+ }
+ }
+ });
+
+ gbc.gridx = 1;
+ gbc.weightx = 0.0;
+ gbc.fill = GridBagConstraints.NONE;
+ buttonBrowse = new JButton(new OpenSourceWorkflowAction(fileManager) {
+ @Override
+ public void openWorkflows(Component parentComponent, File[] files) {
+ if (files.length == 0) {
+ radioFile.setSelected(false);
+ fieldFile.setText("");
+ radioFile.requestFocus();
+ return;
+ }
+ fieldFile.setText(files[0].getPath());
+ if (!radioFile.isSelected()) {
+ radioFile.setSelected(true);
+ }
+ findChosenDataflow(parentComponent, true);
+ }
+ });
+ buttonBrowse.setText("Browse");
+ j.add(buttonBrowse, gbc);
+
+ // This just duplicates things - we already have actions on
+ // the radioFile and fieldFile that will handle the events
+ // radioFile.addActionListener(browseFileOnClick);
+ // fieldFile.addActionListener(browseFileOnClick);
+ return j;
+ }
+
+ protected JComboBox makeSelectOpenWorkflowComboBox(boolean selectCurrent) {
+ List<DataflowSelection> openDataflows = new ArrayList<DataflowSelection>();
+ DataflowSelection current = null;
+ for (WorkflowBundle df : fileManager.getOpenDataflows()) {
+ String name = df.getMainWorkflow().getName();
+ boolean isCurrent = df.equals(fileManager.getCurrentDataflow());
+ if (isCurrent) {
+ // Wrapping as HTML causes weird drop-down box under MAC, so
+ // we just use normal text
+ // name = "<html><body>" + name
+ // + " <i>(current)</i></body></html>";
+ name = name + " (current)";
+ }
+ DataflowSelection selection = new DataflowSelection(df, name);
+ openDataflows.add(selection);
+ if (isCurrent) {
+ current = selection;
+ }
+ }
+ JComboBox chooseDataflow = new JComboBox(openDataflows.toArray());
+ if (selectCurrent) {
+ chooseDataflow.setSelectedItem(current);
+ }
+ chooseDataflow.addActionListener(updateChosenListener);
+ return chooseDataflow;
+
+ }
+
+ protected Component makeSourceSelectionPanel() {
+ JPanel j = new JPanel(new GridBagLayout());
+ j.setBorder(BorderFactory.createTitledBorder("Workflow source"));
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = GridBagConstraints.BOTH;
+
+ sourceSelection = new ButtonGroup();
+ radioNew = new JRadioButton("New workflow");
+ gbc.gridy = 0;
+ j.add(radioNew, gbc);
+ sourceSelection.add(radioNew);
+
+ radioNew.addActionListener(updateChosenListener);
+
+ radioFile = new JRadioButton("Import from file");
+ gbc.gridy = 1;
+ j.add(radioFile, gbc);
+ sourceSelection.add(radioFile);
+ radioFile.addActionListener(updateChosenListener);
+
+ radioUrl = new JRadioButton("Import from URL");
+ gbc.gridy = 2;
+ j.add(radioUrl, gbc);
+ sourceSelection.add(radioUrl);
+ radioUrl.addActionListener(updateChosenListener);
+
+ radioOpened = new JRadioButton("Already opened workflow");
+ gbc.gridy = 3;
+ j.add(radioOpened, gbc);
+ sourceSelection.add(radioOpened);
+ radioOpened.addActionListener(updateChosenListener);
+
+ radioCustomSource = new JRadioButton(customSourceName);
+ radioCustomSource.setVisible(customSourceDataFlow != null);
+ gbc.gridy = 4;
+ gbc.gridwidth = 2;
+ j.add(radioCustomSource, gbc);
+ sourceSelection.add(radioCustomSource);
+ radioCustomSource.addActionListener(updateChosenListener);
+ gbc.gridwidth = 1;
+
+ gbc.gridx = 1;
+ gbc.gridy = 1;
+ gbc.weightx = 0.1;
+ j.add(makeSelectFile(), gbc);
+
+ gbc.gridy = 2;
+ fieldUrl = new JTextField(20);
+ j.add(fieldUrl, gbc);
+ fieldUrl.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ radioUrl.setSelected(true);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ findChosenDataflow(e.getComponent(), true);
+ }
+ });
+
+ gbc.gridy = 3;
+ chooseDataflow = makeSelectOpenWorkflowComboBox(false);
+ chooseDataflow.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ radioOpened.setSelected(true);
+ }
+ });
+ j.add(chooseDataflow, gbc);
+
+ return j;
+ }
+
+ protected Edit<?> makeInsertNestedWorkflowEdit(Workflow nestedFlow) {
+ Processor processor = new Processor();
+ processor.setName("nestedWorkflow");
+
+ CrossProduct crossProduct = new CrossProduct();
+ crossProduct.setParent(processor.getIterationStrategyStack());
+
+ Activity activity = new Activity();
+ activity.setType(DataflowTemplateService.ACTIVITY_TYPE);
+ Configuration configuration = new Configuration();
+ configuration.setType(DataflowTemplateService.ACTIVITY_TYPE.resolve("#Config"));
+ destinationWorkflowBundle.getWorkflows().addWithUniqueName(nestedFlow);
+ ((ObjectNode) configuration.getJson()).put("nestedWorkflow", nestedFlow.getName());
+ destinationWorkflowBundle.getWorkflows().remove(nestedFlow);
+ configuration.setConfigures(activity);
+
+ ProcessorBinding processorBinding = new ProcessorBinding();
+ processorBinding.setBoundProcessor(processor);
+ processorBinding.setBoundActivity(activity);
+
+ for (InputWorkflowPort workflowPort : nestedFlow.getInputPorts()) {
+ InputActivityPort activityPort = new InputActivityPort(activity, workflowPort.getName());
+ activityPort.setDepth(workflowPort.getDepth());
+ // create processor port
+ InputProcessorPort processorPort = new InputProcessorPort(processor, activityPort.getName());
+ processorPort.setDepth(activityPort.getDepth());
+ // add a new port binding
+ new ProcessorInputPortBinding(processorBinding, processorPort, activityPort);
+ }
+ for (OutputWorkflowPort workflowPort : nestedFlow.getOutputPorts()) {
+ OutputActivityPort activityPort = new OutputActivityPort(activity, workflowPort.getName());
+ // TODO calculate output depth
+ activityPort.setDepth(0);
+ activityPort.setGranularDepth(0);
+ // create processor port
+ OutputProcessorPort processorPort = new OutputProcessorPort(processor, activityPort.getName());
+ processorPort.setDepth(activityPort.getDepth());
+ processorPort.setGranularDepth(activityPort.getGranularDepth());
+ // add a new port binding
+ new ProcessorOutputPortBinding(processorBinding, activityPort, processorPort);
+ }
+
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ editList.add(new AddChildEdit<Profile>(destinationProfile, activity));
+ editList.add(new AddChildEdit<Profile>(destinationProfile, configuration));
+ editList.add(new AddChildEdit<Profile>(destinationProfile, processorBinding));
+ editList.add(new AddProcessorEdit(destinationWorkflow, processor));
+
+ editList.add(makeInsertWorkflowEdit(nestedFlow, nestedFlow.getParent().getMainProfile()));
+
+ return new CompoundEdit(editList);
+ }
+
+ protected Edit<?> makeInsertWorkflowEdit(Workflow nestedFlow, Profile profile) {
+ return makeInsertWorkflowEdit(nestedFlow, profile, new HashSet<>());
+ }
+
+ protected Edit<?> makeInsertWorkflowEdit(Workflow nestedFlow, Profile profile, Set<Object> seen) {
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ // add the nested workflow to the workflow bundle
+ editList.add(new AddChildEdit<WorkflowBundle>(destinationWorkflowBundle, nestedFlow));
+ seen.add(nestedFlow);
+ for (Processor processor : nestedFlow.getProcessors()) {
+ // add processor bindings to the profile
+ List<ProcessorBinding> processorBindings = scufl2Tools.processorBindingsForProcessor(processor, profile);
+ for (ProcessorBinding processorBinding : processorBindings) {
+ editList.add(new AddChildEdit<Profile>(destinationProfile, processorBinding));
+ // add activity to the profile
+ Activity activity = processorBinding.getBoundActivity();
+ if (!seen.contains(activity)) {
+ editList.add(new AddChildEdit<Profile>(destinationProfile, activity));
+ // add activity configurations to the profile
+ for (Configuration configuration : scufl2Tools.configurationsFor(activity, profile)) {
+ editList.add(new AddChildEdit<Profile>(destinationProfile, configuration));
+ }
+ seen.add(activity);
+ }
+ }
+ // add processor configurations to the profile
+ List<Configuration> configurations = scufl2Tools.configurationsFor(processor, profile);
+ for (Configuration configuration : configurations) {
+ editList.add(new AddChildEdit<Profile>(destinationProfile, configuration));
+ }
+
+ for (Workflow workflow : scufl2Tools.nestedWorkflowsForProcessor(processor, profile)) {
+ if (!seen.contains(workflow)) {
+ // recursively add nested workflows
+ editList.add(makeInsertWorkflowEdit(workflow, profile, seen));
+ }
+ }
+ }
+ return new CompoundEdit(editList);
+ }
+
+// protected Activity getInsertedActivity() {
+// return insertedActivity;
+// }
+
+ protected class ImportWorkflowAction extends AbstractAction implements Runnable {
+ private static final String VALID_NAME_REGEX = "[\\p{L}\\p{Digit}_.]+";
+ private Component parentComponent;
+ private ProgressMonitor progressMonitor;
+
+ protected ImportWorkflowAction() {
+ super("Import workflow");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ /*
+ * if (e.getSource() instanceof Component) { parentComponent = (Component)
+ * e.getSource(); } else { parentComponent = null; }
+ */
+ parentComponent = MainWindow.getMainWindow();
+ Thread t = new Thread(this, "Import workflow");
+ progressMonitor = new ProgressMonitor(parentComponent, "Importing workflow", "", 0, 100);
+ progressMonitor.setMillisToDecideToPopup(200);
+ progressMonitor.setProgress(5);
+ t.start();
+ setVisible(false);
+ }
+
+ protected void nested() {
+ if (progressMonitor.isCanceled()) {
+ return;
+ }
+ progressMonitor.setProgress(15);
+ selectionManager.setSelectedWorkflowBundle(destinationWorkflowBundle);
+ if (progressMonitor.isCanceled()) {
+ return;
+ }
+
+ progressMonitor.setNote("Copying source workflow");
+ Workflow nestedFlow;
+ try {
+ nestedFlow = DataflowMerger.copyWorkflow(sourceWorkflow);
+ } catch (Exception ex) {
+ logger.warn("Could not copy nested workflow", ex);
+ progressMonitor.setProgress(100);
+ JOptionPane.showMessageDialog(parentComponent,
+ "An error occured while copying workflow:\n" + ex.getLocalizedMessage(),
+ "Could not copy nested workflow", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ if (progressMonitor.isCanceled()) {
+ return;
+ }
+
+ progressMonitor.setNote("Creating nested workflow");
+ progressMonitor.setProgress(45);
+
+ Edit<?> edit = makeInsertNestedWorkflowEdit(nestedFlow);
+ if (progressMonitor.isCanceled()) {
+ return;
+ }
+
+ progressMonitor.setNote("Inserting nested workflow");
+ progressMonitor.setProgress(65);
+
+ try {
+ editManager.doDataflowEdit(destinationWorkflowBundle, edit);
+ } catch (EditException e) {
+ progressMonitor.setProgress(100);
+ logger.warn("Could not import nested workflow", e);
+ JOptionPane.showMessageDialog(parentComponent,
+ "An error occured while importing workflow:\n" + e.getLocalizedMessage(),
+ "Could not import workflows", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ if (radioNew.isSelected()) {
+ progressMonitor.setNote("Opening new nested workflow for editing");
+ progressMonitor.setProgress(90);
+ selectionManager.setSelectedWorkflow(nestedFlow);
+ }
+ progressMonitor.setProgress(100);
+ }
+
+ protected void merge() {
+ progressMonitor.setProgress(10);
+ DataflowMerger merger = new DataflowMerger(destinationWorkflow);
+ progressMonitor.setProgress(25);
+ progressMonitor.setNote("Planning workflow merging");
+
+ String prefix = prefixField.getText();
+ if (!prefix.equals("")) {
+ if (!prefix.matches("[_.]$")) {
+ prefix = prefix + "_";
+ }
+ if (!prefix.matches(VALID_NAME_REGEX)) {
+ progressMonitor.setProgress(100);
+ final String wrongPrefix = prefix;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ JOptionPane.showMessageDialog(parentComponent, "The merge prefix '"
+ + wrongPrefix + "' is not valid. Try "
+ + "using only letters, numbers, " + "underscore and dot.",
+ "Invalid merge prefix", JOptionPane.ERROR_MESSAGE);
+ prefixField.requestFocus();
+ ImportWorkflowWizard.this.setVisible(true);
+ }
+ });
+ return;
+ }
+ }
+
+ CompoundEdit mergeEdit;
+ try {
+ mergeEdit = merger.getMergeEdit(ImportWorkflowWizard.this.sourceWorkflow, prefix);
+ } catch (MergeException e1) {
+ progressMonitor.setProgress(100);
+ logger.warn("Could not merge workflow", e1);
+ JOptionPane.showMessageDialog(parentComponent,
+ "An error occured while merging workflows:\n" + e1.getLocalizedMessage(),
+ "Could not merge workflows", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ progressMonitor.setProgress(55);
+ selectionManager.setSelectedWorkflowBundle(destinationWorkflowBundle);
+
+ progressMonitor.setNote("Merging workflows");
+ progressMonitor.setProgress(75);
+
+ if (progressMonitor.isCanceled()) {
+ return;
+ }
+
+ try {
+ editManager.doDataflowEdit(destinationWorkflowBundle, mergeEdit);
+ } catch (EditException e1) {
+ progressMonitor.setProgress(100);
+ JOptionPane.showMessageDialog(parentComponent,
+ "An error occured while merging workflows:\n" + e1.getLocalizedMessage(),
+ "Could not merge workflows", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ progressMonitor.setProgress(100);
+
+ }
+
+ public void run() {
+ boolean completed = findChosenDataflow(parentComponent, false);
+ if (!completed) {
+ return;
+ }
+ if (actionMerge.isSelected()) {
+ merge();
+ } else if (actionNested.isSelected()) {
+ nested();
+ }
+ }
+ }
+
+ protected class UpdatePreviewsThread extends Thread {
+ protected UpdatePreviewsThread() {
+ super("Updating destination previews");
+ }
+
+ public void run() {
+ if (Thread.interrupted()) {
+ return;
+ }
+ updateSourcePreview();
+
+ if (Thread.interrupted()) {
+ return;
+ }
+ updateDestinationPreview();
+ }
+ }
+
+ protected class BrowseFileOnClick implements ActionListener {
+ public void actionPerformed(ActionEvent e) {
+ checkEmptyFile();
+ }
+
+ public void checkEmptyFile() {
+ if (radioFile.isSelected() && fieldFile.getText().equals("")) {
+ // On first label click pop up Browse dialogue.
+ buttonBrowse.doClick();
+ }
+ }
+ }
+
+ protected class DataflowOpenerThread extends Thread {
+ private final boolean background;
+ private final Component parentComponent;
+ private boolean shouldStop = false;
+ private boolean shownWarning = false;
+
+ protected DataflowOpenerThread(Component parentComponent, boolean background) {
+ super("Inspecting selected workflow");
+ this.parentComponent = parentComponent;
+ this.background = background;
+ }
+
+ @Override
+ public void interrupt() {
+ this.shouldStop = true;
+ super.interrupt();
+ }
+
+ public void run() {
+ updateSource();
+// updateDestination();
+ }
+
+// public void updateDestination() {
+// ButtonModel selection = destinationSelection.getSelection();
+// Workflow chosenDataflow = null;
+// if (selection == null) {
+// chosenDataflow = null;
+// } else if (selection.equals(radioNewDestination.getModel())) {
+// chosenDataflow = new Workflow();
+// } else if (selection.equals(radioOpenDestination.getModel())) {
+// DataflowSelection chosen = (DataflowSelection) destinationAlreadyOpen
+// .getSelectedItem();
+// chosenDataflow = chosen.getDataflow();
+// } else if (selection.equals(radioCustomDestination.getModel())) {
+// chosenDataflow = customDestinationDataflow;
+// } else {
+// logger.error("Unknown selection " + selection);
+// }
+//
+// if (chosenDataflow == null) {
+// if (!background && !shownWarning) {
+// shownWarning = true;
+// SwingUtilities.invokeLater(new Runnable() {
+// public void run() {
+// JOptionPane.showMessageDialog(parentComponent,
+// "You need to choose a destination workflow",
+// "No destination workflow chosen", JOptionPane.ERROR_MESSAGE);
+// setVisible(true);
+// }
+// });
+// return;
+// }
+// }
+// if (checkInterrupted()) {
+// return;
+// }
+// if (chosenDataflow != ImportWorkflowWizard.this.destinationDataflow) {
+// updateWorkflowGraphic(previewDestination, chosenDataflow);
+// if (checkInterrupted()) {
+// return;
+// }
+// ImportWorkflowWizard.this.destinationDataflow = chosenDataflow;
+// }
+//
+// }
+
+ public void updateSource() {
+ ButtonModel selection = sourceSelection.getSelection();
+ Workflow chosenDataflow = null;
+ if (selection == null) {
+ chosenDataflow = null;
+ } else if (selection.equals(radioNew.getModel())) {
+ WorkflowBundle workflowBundle = new WorkflowBundle();
+ workflowBundle.setMainWorkflow(new Workflow());
+ workflowBundle.getMainWorkflow().setName(fileManager.getDefaultWorkflowName());
+ workflowBundle.setMainProfile(new Profile());
+ scufl2Tools.setParents(workflowBundle);
+ chosenDataflow = workflowBundle.getMainWorkflow();
+ } else if (selection.equals(radioFile.getModel())) {
+ final String filePath = fieldFile.getText();
+ try {
+ DataflowInfo opened = fileManager
+ .openDataflowSilently(null, new File(filePath));
+ if (checkInterrupted()) {
+ return;
+ }
+ chosenDataflow = opened.getDataflow().getMainWorkflow();
+ } catch (final OpenException e1) {
+ if (!background && !shownWarning) {
+ shownWarning = true;
+ logger.warn("Could not open workflow for merging: " + filePath, e1);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ radioFile.requestFocus();
+ JOptionPane.showMessageDialog(parentComponent,
+ "An error occured while trying to open " + filePath + "\n"
+ + e1.getMessage(), "Could not open workflow",
+ JOptionPane.WARNING_MESSAGE);
+ setVisible(true);
+ }
+ });
+ }
+ }
+ } else if (selection.equals(radioUrl.getModel())) {
+ final String url = fieldUrl.getText();
+ try {
+ DataflowInfo opened = fileManager.openDataflowSilently(null, new URL(url));
+ if (checkInterrupted()) {
+ return;
+ }
+ chosenDataflow = opened.getDataflow().getMainWorkflow();
+ } catch (final OpenException e1) {
+ if (!background && !shownWarning) {
+ logger.warn("Could not open source workflow: " + url, e1);
+ shownWarning = true;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ fieldUrl.requestFocus();
+ JOptionPane.showMessageDialog(
+ parentComponent,
+ "An error occured while trying to open " + url + "\n"
+ + e1.getMessage(), "Could not open workflow",
+ JOptionPane.WARNING_MESSAGE);
+ setVisible(true);
+ }
+ });
+
+ }
+ if (checkInterrupted()) {
+ return;
+ }
+ } catch (final MalformedURLException e1) {
+ if (!background && !shownWarning) {
+ logger.warn("Invalid workflow URL: " + url, e1);
+ shownWarning = true;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ fieldUrl.requestFocus();
+ JOptionPane.showMessageDialog(
+ parentComponent,
+ "The workflow location " + url + " is invalid\n"
+ + e1.getLocalizedMessage(), "Invalid URL",
+ JOptionPane.ERROR_MESSAGE);
+ setVisible(true);
+ }
+ });
+ }
+ if (checkInterrupted()) {
+ return;
+ }
+ }
+ } else if (selection.equals(radioOpened.getModel())) {
+ DataflowSelection chosen = (DataflowSelection) chooseDataflow.getSelectedItem();
+ chosenDataflow = chosen.getDataflow().getMainWorkflow();
+ } else if (selection.equals(radioCustomSource.getModel())) {
+ chosenDataflow = customSourceDataFlow.getMainWorkflow();
+ } else {
+ logger.error("Unknown selection " + selection);
+ }
+ if (checkInterrupted()) {
+ return;
+ }
+ if (chosenDataflow != ImportWorkflowWizard.this.sourceWorkflow) {
+ Profile chosenProfile = null;
+ if (chosenDataflow != null) {
+ chosenProfile = chosenDataflow.getParent().getMainProfile();
+ }
+ updateWorkflowGraphic(previewSource, chosenDataflow, chosenProfile);
+ if (checkInterrupted()) {
+ return;
+ }
+ ImportWorkflowWizard.this.sourceWorkflow = chosenDataflow;
+ }
+ if (chosenDataflow == null) {
+ if (!background && !shownWarning) {
+ shownWarning = true;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ JOptionPane.showMessageDialog(parentComponent,
+ "You need to choose a workflow for merging",
+ "No workflow chosen", JOptionPane.ERROR_MESSAGE);
+ setVisible(true);
+ }
+ });
+ }
+ }
+ }
+
+ private boolean checkInterrupted() {
+ if (Thread.interrupted() || this.shouldStop) {
+ // ImportWorkflowWizard.this.chosenDataflow = null;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static class DataflowSelection {
+ private final WorkflowBundle dataflow;
+ private final String name;
+
+ public DataflowSelection(WorkflowBundle dataflow, String name) {
+ this.dataflow = dataflow;
+ this.name = name;
+ }
+
+ public WorkflowBundle getDataflow() {
+ return dataflow;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ }
+
+ protected class UpdateChosenListener implements ActionListener {
+ public void actionPerformed(ActionEvent e) {
+ Component parentComponent;
+ if (e.getSource() instanceof Component) {
+ parentComponent = (Component) e.getSource();
+ } else {
+ parentComponent = null;
+ }
+ findChosenDataflow(parentComponent, true);
+
+ }
+ }
+
+ public void setCustomSourceDataflow(WorkflowBundle sourceDataflow, String label) {
+ this.customSourceDataFlow = sourceDataflow;
+ this.customSourceName = label;
+ updateSourceSection();
+ radioCustomSource.doClick();
+ }
+
+// public void setCustomDestinationDataflow(Workflow destinationDataflow, String label) {
+// this.customDestinationDataflow = destinationDataflow;
+// this.customDestinationName = label;
+// updateDestinationSection();
+// radioCustomDestination.doClick();
+// }
+
+// public void setDestinationEnabled(boolean destinationEnabled) {
+// this.destinationEnabled = destinationEnabled;
+// updateDestinationSection();
+// }
+
+ public void setSourceEnabled(boolean sourceEnabled) {
+ this.sourceEnabled = sourceEnabled;
+ updateSourceSection();
+ }
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/AddNestedWorkflowMenuAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/AddNestedWorkflowMenuAction.java
new file mode 100644
index 0000000..a37e308
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/AddNestedWorkflowMenuAction.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.file.importworkflow.menu;
+
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.net.URI;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.actions.AddNestedWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.menu.InsertMenu;
+
+/**
+ * An action to add a nested workflow activity + a wrapping processor to the
+ * workflow.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class AddNestedWorkflowMenuAction extends AbstractMenuAction {
+
+ private static final String ADD_NESTED_WORKFLOW = "Nested workflow";
+
+ private static final URI ADD_NESTED_WORKFLOW_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuAddNestedWorkflow");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private ColourManager colourManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private SelectionManager selectionManager;
+
+ public AddNestedWorkflowMenuAction() {
+ super(InsertMenu.INSERT, 400, ADD_NESTED_WORKFLOW_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ AddNestedWorkflowAction a = new AddNestedWorkflowAction(editManager, fileManager,
+ menuManager, colourManager, workbenchConfiguration, selectionManager);
+ // Override name to avoid "Add "
+ a.putValue(Action.NAME, ADD_NESTED_WORKFLOW);
+ a.putValue(Action.SHORT_DESCRIPTION, ADD_NESTED_WORKFLOW);
+ a.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
+ KeyEvent.VK_N, InputEvent.SHIFT_DOWN_MASK
+ | InputEvent.ALT_DOWN_MASK));
+ return a;
+
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/ImportWorkflowMenuAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/ImportWorkflowMenuAction.java
new file mode 100644
index 0000000..1c8b40b
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/ImportWorkflowMenuAction.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.file.importworkflow.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.actions.ImportWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * An action to import nested/merged workflows.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class ImportWorkflowMenuAction extends AbstractContextualMenuAction {
+
+ private static final URI insertSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/insert");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private ColourManager colourManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private SelectionManager selectionManager;
+
+ public ImportWorkflowMenuAction() {
+ super(insertSection, 400);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && getContextualSelection().getSelection() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ ImportWorkflowAction myAction = new ImportWorkflowAction(editManager, fileManager,
+ menuManager, colourManager, workbenchConfiguration, selectionManager);
+ // Just "Workflow" as we go under the "Insert" menu
+ myAction.putValue(Action.NAME, "Nested workflow");
+ return myAction;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/MergeWorkflowMenuAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/MergeWorkflowMenuAction.java
new file mode 100644
index 0000000..7ce4891
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/MergeWorkflowMenuAction.java
@@ -0,0 +1,65 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.actions.MergeWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+public class MergeWorkflowMenuAction extends AbstractMenuAction {
+
+ public static final URI INSERT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#insert");
+
+ public static final URI IMPORT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#insert");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private ColourManager colourManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private SelectionManager selectionManager;
+
+ public MergeWorkflowMenuAction() {
+ super(INSERT_URI, 2000, IMPORT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new MergeWorkflowAction(editManager, fileManager, menuManager, colourManager,
+ workbenchConfiguration, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/ReplaceNestedWorkflowMenuAction.java b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/ReplaceNestedWorkflowMenuAction.java
new file mode 100644
index 0000000..3d424df
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/java/net/sf/taverna/t2/workbench/file/importworkflow/menu/ReplaceNestedWorkflowMenuAction.java
@@ -0,0 +1,76 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.activitytools.AbstractConfigureActivityMenuAction;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.importworkflow.actions.ReplaceNestedWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+public class ReplaceNestedWorkflowMenuAction extends AbstractConfigureActivityMenuAction {
+
+ private static final URI NESTED_ACTIVITY = URI.create("http://ns.taverna.org.uk/2010/activity/nested-workflow");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private ActivityIconManager activityIconManager;
+ private ColourManager colourManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private SelectionManager selectionManager;
+
+ public ReplaceNestedWorkflowMenuAction() {
+ super(NESTED_ACTIVITY);
+ }
+
+ @Override
+ protected Action createAction() {
+ ReplaceNestedWorkflowAction configAction = new ReplaceNestedWorkflowAction(findActivity(),
+ editManager, fileManager, menuManager, activityIconManager, colourManager,
+ serviceDescriptionRegistry, workbenchConfiguration, selectionManager);
+ addMenuDots(configAction);
+ return configAction;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setServiceDescriptionRegistry(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider
new file mode 100644
index 0000000..bf42bef
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowTemplateService
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6e7eec5
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,9 @@
+# Needs to be first AbstractConfigureActivityMenuAction to be
+# picked up as the automatic 'configure' action for template services
+net.sf.taverna.t2.workbench.file.importworkflow.menu.ReplaceNestedWorkflowMenuAction
+
+net.sf.taverna.t2.workbench.file.importworkflow.menu.AddNestedWorkflowMenuAction
+net.sf.taverna.t2.workbench.file.importworkflow.menu.ImportWorkflowMenuAction
+net.sf.taverna.t2.workbench.file.importworkflow.menu.MergeWorkflowMenuAction
+
+net.sf.taverna.t2.activities.dataflow.menu.EditNestedDataflowMenuAction
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI
new file mode 100644
index 0000000..5cb0543
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowActivityIcon
\ No newline at end of file
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
new file mode 100644
index 0000000..a334e66
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.dataflow.filemanager.NestedDataflowPersistenceHandler
\ No newline at end of file
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..39d7ec2
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.dataflow.views.DataflowActivityViewFactory
\ No newline at end of file
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/spring/dataflow-activity-ui-context-osgi.xml b/taverna-dataflow-activity-ui/src/main/resources/META-INF/spring/dataflow-activity-ui-context-osgi.xml
new file mode 100644
index 0000000..e664429
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/spring/dataflow-activity-ui-context-osgi.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="DataflowActivityIcon" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI" />
+
+ <service ref="DataflowTemplateService" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider" />
+
+ <service ref="ReplaceNestedWorkflowMenuAction" auto-export="interfaces" />
+ <service ref="AddNestedWorkflowMenuAction" auto-export="interfaces" />
+ <service ref="ImportWorkflowMenuAction" auto-export="interfaces" />
+ <service ref="MergeWorkflowMenuAction" auto-export="interfaces" />
+ <service ref="EditNestedDataflowMenuAction" auto-export="interfaces" />
+
+ <!-- <service ref="NestedDataflowPersistenceHandler" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" /> -->
+
+ <service ref="DataflowActivityViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="edits" interface="net.sf.taverna.t2.workflowmodel.Edits" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <reference id="serviceDescriptionRegistry" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry" />
+ <reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+</beans:beans>
diff --git a/taverna-dataflow-activity-ui/src/main/resources/META-INF/spring/dataflow-activity-ui-context.xml b/taverna-dataflow-activity-ui/src/main/resources/META-INF/spring/dataflow-activity-ui-context.xml
new file mode 100644
index 0000000..f72abd2
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/META-INF/spring/dataflow-activity-ui-context.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="DataflowActivityIcon" class="net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowActivityIcon" />
+
+ <bean id="DataflowTemplateService" class="net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowTemplateService" />
+
+ <bean id="ReplaceNestedWorkflowMenuAction" class="net.sf.taverna.t2.workbench.file.importworkflow.menu.ReplaceNestedWorkflowMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="AddNestedWorkflowMenuAction" class="net.sf.taverna.t2.workbench.file.importworkflow.menu.AddNestedWorkflowMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="ImportWorkflowMenuAction" class="net.sf.taverna.t2.workbench.file.importworkflow.menu.ImportWorkflowMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="MergeWorkflowMenuAction" class="net.sf.taverna.t2.workbench.file.importworkflow.menu.MergeWorkflowMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="EditNestedDataflowMenuAction" class="net.sf.taverna.t2.activities.dataflow.menu.EditNestedDataflowMenuAction">
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+ <!-- <bean id="NestedDataflowPersistenceHandler" class="net.sf.taverna.t2.activities.dataflow.filemanager.NestedDataflowPersistenceHandler">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean> -->
+
+ <bean id="DataflowActivityViewFactory" class="net.sf.taverna.t2.activities.dataflow.views.DataflowActivityViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-dataflow-activity-ui/src/main/resources/dataflow.png b/taverna-dataflow-activity-ui/src/main/resources/dataflow.png
new file mode 100644
index 0000000..71b188c
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/main/resources/dataflow.png
Binary files differ
diff --git a/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/AbstractTestHelper.java b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/AbstractTestHelper.java
new file mode 100644
index 0000000..7a4d2f6
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/AbstractTestHelper.java
@@ -0,0 +1,266 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Before;
+
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.ProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+
+public abstract class AbstractTestHelper {
+
+ private static final String Q_T2FLOW = "/q.t2flow";
+
+ private static final String ABC_T2FLOW = "/abc.t2flow";
+
+ private static final String P_T2FLOW = "/p.t2flow";
+
+ private WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
+
+ protected Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ protected Workflow abc;
+
+ protected Workflow p;
+
+ protected Workflow q;
+
+ protected void assertHasConditionals(Workflow dataflow,
+ String... expectedConditionalDef) {
+ Set<String> expectedConditionals = new HashSet<String>();
+ for (String expected : expectedConditionalDef) {
+ expectedConditionals.add(expected);
+ }
+
+ Set<String> foundConditionals = new HashSet<String>();
+
+ for (ControlLink c : dataflow.getControlLinks()) {
+ if (c instanceof BlockingControlLink) {
+ BlockingControlLink bcl = (BlockingControlLink) c;
+ foundConditionals.add(bcl.getUntilFinished().getName() + ";"
+ + bcl.getBlock().getName());
+ }
+ }
+
+ Set<String> extras = new HashSet<String>(foundConditionals);
+ extras.removeAll(expectedConditionals);
+ assertTrue("Unexpected conditional " + extras, extras.isEmpty());
+
+ Set<String> missing = new HashSet<String>(expectedConditionals);
+ missing.removeAll(foundConditionals);
+ assertTrue("Could not find conditional " + missing, missing.isEmpty());
+ }
+
+ protected void assertHasDatalinks(Workflow dataflow,
+ String... expectedLinkDef) {
+ Set<String> expectedLinks = new HashSet<String>();
+ for (String expected : expectedLinkDef) {
+ expectedLinks.add(expected);
+ }
+
+ Set<String> foundLinks = new HashSet<String>();
+
+ for (DataLink link : dataflow.getDataLinks()) {
+ StringBuilder linkRef = new StringBuilder();
+ SenderPort source = link.getReceivesFrom();
+ if (source instanceof ProcessorPort) {
+ linkRef.append(((ProcessorPort) source).getParent()
+ .getName());
+ linkRef.append('.');
+ }
+ linkRef.append(source.getName());
+
+ linkRef.append("->");
+
+ ReceiverPort sink = link.getSendsTo();
+ if (sink instanceof ProcessorPort) {
+ linkRef.append(((ProcessorPort) sink).getParent()
+ .getName());
+ linkRef.append('.');
+ }
+ linkRef.append(sink.getName());
+
+ String linkStr = linkRef.toString();
+ foundLinks.add(linkStr);
+ }
+
+ Set<String> extras = new HashSet<String>(foundLinks);
+ extras.removeAll(expectedLinks);
+ assertTrue("Unexpected links " + extras, extras.isEmpty());
+
+ Set<String> missing = new HashSet<String>(expectedLinks);
+ missing.removeAll(foundLinks);
+ assertTrue("Could not find links " + missing, missing.isEmpty());
+ }
+
+ protected void assertHasInputPorts(Workflow dataflow,
+ String... expectedInputPorts) {
+ Set<String> expectedNames = new HashSet<String>();
+ for (String expected : expectedInputPorts) {
+ expectedNames.add(expected);
+ }
+ Set<String> foundNames = new HashSet<String>();
+ for (InputWorkflowPort port : dataflow.getInputPorts()) {
+ String name = port.getName();
+ foundNames.add(name);
+ }
+
+ Set<String> extras = new HashSet<String>(foundNames);
+ extras.removeAll(expectedNames);
+ assertTrue("Unexpected input port " + extras, extras.isEmpty());
+
+ Set<String> missing = new HashSet<String>(expectedNames);
+ missing.removeAll(foundNames);
+ assertTrue("Could not find input port " + missing, missing.isEmpty());
+
+ }
+
+ protected void assertHasOutputPorts(Workflow dataflow,
+ String... expectedOutputPorts) {
+ Set<String> expectedNames = new HashSet<String>();
+ for (String expected : expectedOutputPorts) {
+ expectedNames.add(expected);
+ }
+ Set<String> foundNames = new HashSet<String>();
+ for (OutputWorkflowPort port : dataflow.getOutputPorts()) {
+ String name = port.getName();
+ foundNames.add(name);
+ }
+
+ Set<String> extras = new HashSet<String>(foundNames);
+ extras.removeAll(expectedNames);
+ assertTrue("Unexpected output port " + extras, extras.isEmpty());
+
+ Set<String> missing = new HashSet<String>(expectedNames);
+ missing.removeAll(foundNames);
+ assertTrue("Could not find output port " + missing, missing.isEmpty());
+ }
+
+ protected void assertHasProcessors(Workflow dataflow,
+ String... expectedProcessors) {
+ Set<String> expectedNames = new HashSet<String>();
+ for (String expected : expectedProcessors) {
+ expectedNames.add(expected);
+ }
+ Set<String> foundNames = new HashSet<String>();
+
+ for (Processor proc : dataflow.getProcessors()) {
+ String processorName = proc.getName();
+ foundNames.add(processorName);
+ }
+
+ Set<String> extras = new HashSet<String>(foundNames);
+ extras.removeAll(expectedNames);
+ assertTrue("Unexpected processor " + extras, extras.isEmpty());
+
+ Set<String> missing = new HashSet<String>(expectedNames);
+ missing.removeAll(foundNames);
+ assertTrue("Could not find processor " + missing, missing.isEmpty());
+ }
+
+ protected void checkAbc() throws Exception {
+ assertHasProcessors(abc, "A", "B", "C");
+ assertHasInputPorts(abc, "in1", "in2");
+ assertHasOutputPorts(abc, "a", "b", "c");
+ assertHasDatalinks(abc, "in2->B.inputlist", "in1->A.string1",
+ "in2->A.string2", "Merge0:Merge0_output->C.inputlist",
+ "A.output->a", "B.outputlist->b",
+ "B.outputlist->Merge0:outputlistToMerge0_input0",
+ "A.output->Merge0:outputToMerge0_input0", "C.outputlist->c");
+ assertHasConditionals(abc, "A;B");
+ }
+
+ protected void checkP() throws Exception {
+ assertHasProcessors(p, "P");
+ assertHasInputPorts(p, "i");
+ assertHasOutputPorts(p, "o");
+ assertHasDatalinks(p, "i->P.inputlist", "P.outputlist->o");
+ assertHasConditionals(p);
+
+ }
+
+ protected void checkQ() throws Exception {
+ assertHasProcessors(q, "Q");
+ assertHasInputPorts(q, "p");
+ assertHasOutputPorts(q, "p", "q");
+ assertHasDatalinks(q, "p->Q.inputlist", "Q.outputlist->q", "p->p");
+ assertHasConditionals(q);
+
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(findOutputPort(q, "p"));
+ assertEquals(1, datalinksTo.size());
+ SenderPort source = datalinksTo.get(0).getReceivesFrom();
+ assertEquals("out port P not linked to input P", source, findInputPort(q, "p"));
+
+ }
+
+ protected Workflow loadAbc() throws Exception {
+ return openWorkflow(getClass().getResourceAsStream(ABC_T2FLOW));
+ }
+
+ protected Workflow loadP() throws Exception {
+ return openWorkflow(getClass().getResourceAsStream(P_T2FLOW));
+ }
+
+ protected Workflow loadQ() throws Exception {
+ return openWorkflow(getClass().getResourceAsStream(Q_T2FLOW));
+ }
+
+ @Before
+ public void loadWorkflows() throws Exception {
+ abc = loadAbc();
+ p = loadP();
+ q = loadQ();
+ }
+
+ protected Workflow openWorkflow(InputStream workflowXMLstream) throws Exception {
+ assertNotNull(workflowXMLstream);
+ WorkflowBundle workflowBundle = workflowBundleIO.readBundle(workflowXMLstream, "application/vnd.taverna.t2flow+xml");
+ return workflowBundle.getMainWorkflow();
+ }
+
+ protected InputWorkflowPort findInputPort(Workflow wf, String name) {
+ for (InputWorkflowPort inp : wf.getInputPorts()) {
+ if (inp.getName().equals(name)) {
+ return inp;
+ }
+ }
+ throw new IllegalArgumentException("Unknown input port: " + name);
+ }
+
+ protected OutputWorkflowPort findOutputPort(Workflow wf, String name) {
+ for (OutputWorkflowPort outp : wf.getOutputPorts()) {
+ if (outp.getName().equals(name)) {
+ return outp;
+ }
+ }
+ throw new IllegalArgumentException("Unknown output port: " + name);
+ }
+
+ protected Processor findProcessor(Workflow wf, String name) {
+ for (Processor proc : wf.getProcessors()) {
+ if (proc.getName().equals(name)) {
+ return proc;
+ }
+ }
+ throw new IllegalArgumentException("Unknown processor: " + name);
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestPortMerge.java b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestPortMerge.java
new file mode 100644
index 0000000..9141693
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestPortMerge.java
@@ -0,0 +1,38 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.List;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+
+@Ignore
+public class TestPortMerge extends AbstractTestHelper {
+
+ @Test
+ public void mergeQintoP() throws Exception {
+ DataflowMerger merger = new DataflowMerger(p);
+ merger.getMergeEdit(q).doEdit();
+ Workflow merged = p;
+ checkQ();
+
+ assertHasProcessors(merged, "P", "Q");
+ assertHasInputPorts(merged, "i", "p");
+ assertHasOutputPorts(merged, "o", "p", "q");
+ assertHasDatalinks(merged, "i->P.inputlist", "P.outputlist->o", "p->Q.inputlist",
+ "Q.outputlist->q", "p->p");
+
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(findOutputPort(merged, "p"));
+ assertEquals(1, datalinksTo.size());
+ SenderPort source = datalinksTo.get(0).getReceivesFrom();
+ assertSame("out port P not linked to input P", source, findInputPort(merged, "p"));
+
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestRename.java b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestRename.java
new file mode 100644
index 0000000..c235c98
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestRename.java
@@ -0,0 +1,58 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+@Ignore
+public class TestRename extends AbstractTestHelper {
+
+ @Test
+ public void mergePintoP() throws Exception {
+ DataflowMerger merger = new DataflowMerger(p);
+ merger.getMergeEdit(p).doEdit();
+ Workflow merged = p;
+
+ assertHasProcessors(merged, "P", "P_2");
+ assertHasInputPorts(merged, "i", "i_2");
+ assertHasOutputPorts(merged, "o", "o_2");
+ assertHasDatalinks(merged, "i->P.inputlist", "P.outputlist->o",
+ "i_2->P_2.inputlist", "P_2.outputlist->o_2");
+ }
+
+ @Test
+ public void mergePintoPintoP() throws Exception {
+ // Don't put p in constructor, or we would get exponential merging!
+ Workflow merged = new Workflow();
+ DataflowMerger merger = new DataflowMerger(merged);
+ merger.getMergeEdit(p).doEdit();
+ merger.getMergeEdit(p).doEdit();
+ merger.getMergeEdit(p).doEdit();
+
+ assertHasProcessors(merged, "P", "P_2", "P_3");
+ assertHasInputPorts(merged, "i", "i_2", "i_3");
+ assertHasOutputPorts(merged, "o", "o_2", "o_3");
+ assertHasDatalinks(merged, "i->P.inputlist", "P.outputlist->o",
+ "i_2->P_2.inputlist", "P_2.outputlist->o_2",
+ "i_3->P_3.inputlist", "P_3.outputlist->o_3");
+ }
+
+ @Test
+ public void mergePintoPWithPrefix() throws Exception {
+ // Don't put p in constructor, or we would get exponential merging!
+ Workflow merged = new Workflow();
+ DataflowMerger merger = new DataflowMerger(merged);
+ merger.getMergeEdit(p).doEdit();
+ merger.getMergeEdit(p, "fish_").doEdit();
+ merger.getMergeEdit(p, "soup_").doEdit();
+
+ assertHasProcessors(merged, "P", "fish_P", "soup_P");
+ assertHasInputPorts(merged, "i", "fish_i", "soup_i");
+ assertHasOutputPorts(merged, "o", "fish_o", "soup_o");
+ assertHasDatalinks(merged, "i->P.inputlist", "P.outputlist->o",
+ "fish_i->fish_P.inputlist", "fish_P.outputlist->fish_o",
+ "soup_i->soup_P.inputlist", "soup_P.outputlist->soup_o");
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestSimpleMerge.java b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestSimpleMerge.java
new file mode 100644
index 0000000..811678e
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestSimpleMerge.java
@@ -0,0 +1,98 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import java.util.List;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+
+@Ignore
+public class TestSimpleMerge extends AbstractTestHelper {
+
+ private void checkMergedAbcP(Workflow merged) {
+ // Check that it has everything from both
+ assertHasProcessors(merged, "A", "B", "C", "P");
+ assertHasInputPorts(merged, "in1", "in2", "i");
+ assertHasOutputPorts(merged, "a", "b", "c", "o");
+ assertHasDatalinks(merged, "in2->B.inputlist", "in1->A.string1",
+ "in2->A.string2", "Merge0:Merge0_output->C.inputlist",
+ "A.output->a", "B.outputlist->b",
+ "B.outputlist->Merge0:outputlistToMerge0_input0",
+ "A.output->Merge0:outputToMerge0_input0", "C.outputlist->c",
+ "i->P.inputlist", "P.outputlist->o");
+ assertHasConditionals(merged, "A;B");
+ }
+
+ private void checkCopiedFromP(Workflow merged) {
+ Processor newProcP = findProcessor(merged, "P");
+ Processor originalProcP = findProcessor(p, "P");
+ assertNotSame("Did not copy processor P", newProcP, originalProcP);
+
+ InputProcessorPort inp = newProcP.getInputPorts().first();
+ InputWorkflowPort newInI = findInputPort(merged, "i");
+ assertEquals(0, newInI.getDepth().intValue());
+
+ InputWorkflowPort originalInI = findInputPort(p, "i");
+ assertNotSame("Did not copy port 'i'", originalInI, newInI);
+
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(inp);
+ assertEquals(1, datalinksTo.size());
+ SenderPort source = datalinksTo.get(0).getReceivesFrom();
+
+ assertSame("Not linked to new port", source, newInI);
+ assertNotSame("Still linked to old port", source, originalInI);
+ }
+
+
+ @Test
+ public void mergeAbcAndPIntoNew() throws Exception {
+ Workflow merged = new Workflow();
+ DataflowMerger merger = new DataflowMerger(merged);
+ merger.getMergeEdit(abc).doEdit();
+
+ assertNotSame(abc, merged);
+ merger.getMergeEdit(p).doEdit();
+
+
+ // Assert abc and p were not modified
+ checkAbc();
+ checkP();
+
+ checkMergedAbcP(merged);
+ checkCopiedFromP(merged);
+ }
+
+ @Test
+ public void mergePintoAbc() throws Exception {
+ DataflowMerger merger = new DataflowMerger(abc);
+ Workflow merged = abc;
+
+ merger.getMergeEdit(p).doEdit();
+ checkMergedAbcP(merged);
+ checkCopiedFromP(merged);
+ // Assert P did not change
+ checkP();
+ }
+
+ @Test
+ public void mergeAbcintoP() throws Exception {
+ Workflow merged = p;
+ DataflowMerger merger = new DataflowMerger(merged);
+ merger.getMergeEdit(abc).doEdit();
+
+ checkMergedAbcP(merged);
+ // Assert ABC did not change
+ checkAbc();
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestTestHelper.java b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestTestHelper.java
new file mode 100644
index 0000000..2165a67
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/TestTestHelper.java
@@ -0,0 +1,24 @@
+package net.sf.taverna.t2.workbench.file.importworkflow;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+@Ignore
+public class TestTestHelper extends AbstractTestHelper {
+
+ @Test
+ public void checkAbc() throws Exception {
+ super.checkAbc();
+ }
+
+ @Test
+ public void checkP() throws Exception {
+ super.checkP();
+ }
+
+ @Test
+ public void checkQ() throws Exception {
+ super.checkQ();
+ }
+
+}
diff --git a/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/gui/ImportWizardLauncher.java b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/gui/ImportWizardLauncher.java
new file mode 100644
index 0000000..b45a774
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/java/net/sf/taverna/t2/workbench/file/importworkflow/gui/ImportWizardLauncher.java
@@ -0,0 +1,24 @@
+package net.sf.taverna.t2.workbench.file.importworkflow.gui;
+
+import javax.swing.UIManager;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.FileManagerImpl;
+
+
+public class ImportWizardLauncher {
+
+ public static void main(String[] args) throws Exception {
+
+ UIManager.setLookAndFeel(UIManager
+ .getSystemLookAndFeelClassName());
+
+ EditManager editManager = new EditManagerImpl();
+ FileManager fileManager = new FileManagerImpl(editManager);
+
+ ImportWorkflowWizard s = new ImportWorkflowWizard(null, editManager, fileManager, null, null, null, null);
+ s.setVisible(true);
+ }
+}
diff --git a/taverna-dataflow-activity-ui/src/test/resources/abc.t2flow b/taverna-dataflow-activity-ui/src/test/resources/abc.t2flow
new file mode 100644
index 0000000..a30cdc6
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/resources/abc.t2flow
@@ -0,0 +1,116 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow" version="1" producedBy="taverna-2.1-beta-2"><dataflow id="55a3691f-127a-4fd3-b51c-a7ed27f6ec88" role="top"><name>Workflow2</name><inputPorts><port><name>in1</name><depth>0</depth><granularDepth>0</granularDepth><annotations /></port><port><name>in2</name><depth>1</depth><granularDepth>1</granularDepth><annotations /></port></inputPorts><outputPorts><port><name>a</name></port><port><name>b</name></port><port><name>c</name></port></outputPorts><processors><processor><name>B</name><inputPorts><port><name>inputlist</name><depth>1</depth></port></inputPorts><outputPorts><port><name>outputlist</name><depth>1</depth><granularDepth>1</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2.activities</group><artifact>localworker-activity</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.activities.localworker.LocalworkerActivity</class><inputMap><map from="inputlist" to="inputlist" /></inputMap><outputMap><map from="outputlist" to="outputlist" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean xmlns="">
+ <script>outputlist = inputlist;</script>
+ <dependencies />
+ <classLoaderSharing>workflow</classLoaderSharing>
+ <localDependencies />
+ <artifactDependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>[B</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>inputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>1</granularDepth>
+ <name>outputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean></configBean><annotations /></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>1000</initialDelay>
+ <maxDelay>5000</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="inputlist" depth="1" /></cross></strategy></iteration></iterationStrategyStack></processor><processor><name>A</name><inputPorts><port><name>string1</name><depth>0</depth></port><port><name>string2</name><depth>0</depth></port></inputPorts><outputPorts><port><name>output</name><depth>0</depth><granularDepth>0</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2.activities</group><artifact>localworker-activity</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.activities.localworker.LocalworkerActivity</class><inputMap><map from="string2" to="string2" /><map from="string1" to="string1" /></inputMap><outputMap><map from="output" to="output" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean xmlns="">
+ <script>output = string1 + string2;</script>
+ <dependencies />
+ <classLoaderSharing>workflow</classLoaderSharing>
+ <localDependencies />
+ <artifactDependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>string1</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>string2</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>0</granularDepth>
+ <name>output</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean></configBean><annotations /></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>1000</initialDelay>
+ <maxDelay>5000</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="string1" depth="0" /><port name="string2" depth="0" /></cross></strategy></iteration></iterationStrategyStack></processor><processor><name>C</name><inputPorts><port><name>inputlist</name><depth>1</depth></port></inputPorts><outputPorts><port><name>outputlist</name><depth>1</depth><granularDepth>1</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2.activities</group><artifact>localworker-activity</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.activities.localworker.LocalworkerActivity</class><inputMap><map from="inputlist" to="inputlist" /></inputMap><outputMap><map from="outputlist" to="outputlist" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean xmlns="">
+ <script>outputlist = inputlist;</script>
+ <dependencies />
+ <classLoaderSharing>workflow</classLoaderSharing>
+ <localDependencies />
+ <artifactDependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>[B</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>inputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>1</granularDepth>
+ <name>outputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean></configBean><annotations /></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>1000</initialDelay>
+ <maxDelay>5000</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="inputlist" depth="1" /></cross></strategy></iteration></iterationStrategyStack></processor></processors><conditions><condition control="A" target="B" /></conditions><datalinks><datalink><sink type="processor"><processor>B</processor><port>inputlist</port></sink><source type="dataflow"><port>in2</port></source></datalink><datalink><sink type="processor"><processor>A</processor><port>string1</port></sink><source type="dataflow"><port>in1</port></source></datalink><datalink><sink type="processor"><processor>A</processor><port>string2</port></sink><source type="dataflow"><port>in2</port></source></datalink><datalink><sink type="merge"><processor>C</processor><port>inputlist</port></sink><source type="processor"><processor>B</processor><port>outputlist</port></source></datalink><datalink><sink type="merge"><processor>C</processor><port>inputlist</port></sink><source type="processor"><processor>A</processor><port>output</port></source></datalink><datalink><sink type="dataflow"><port>a</port></sink><source type="processor"><processor>A</processor><port>output</port></source></datalink><datalink><sink type="dataflow"><port>b</port></sink><source type="processor"><processor>B</processor><port>outputlist</port></source></datalink><datalink><sink type="dataflow"><port>c</port></sink><source type="processor"><processor>C</processor><port>outputlist</port></source></datalink></datalinks><annotations /></dataflow></workflow>
\ No newline at end of file
diff --git a/taverna-dataflow-activity-ui/src/test/resources/p.t2flow b/taverna-dataflow-activity-ui/src/test/resources/p.t2flow
new file mode 100644
index 0000000..d4e191c
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/resources/p.t2flow
@@ -0,0 +1,36 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow" version="1" producedBy="taverna-2.1-beta-2"><dataflow id="a158f691-3561-424f-bec1-e6359b6b486f" role="top"><name>Workflow7</name><inputPorts><port><name>i</name><depth>0</depth><granularDepth>0</granularDepth><annotations /></port></inputPorts><outputPorts><port><name>o</name></port></outputPorts><processors><processor><name>P</name><inputPorts><port><name>inputlist</name><depth>1</depth></port></inputPorts><outputPorts><port><name>outputlist</name><depth>1</depth><granularDepth>1</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2.activities</group><artifact>localworker-activity</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.activities.localworker.LocalworkerActivity</class><inputMap><map from="inputlist" to="inputlist" /></inputMap><outputMap><map from="outputlist" to="outputlist" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean xmlns="">
+ <script>outputlist = inputlist;</script>
+ <dependencies />
+ <classLoaderSharing>workflow</classLoaderSharing>
+ <localDependencies />
+ <artifactDependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>[B</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>inputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>1</granularDepth>
+ <name>outputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean></configBean><annotations /></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>1000</initialDelay>
+ <maxDelay>5000</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="inputlist" depth="1" /></cross></strategy></iteration></iterationStrategyStack></processor></processors><conditions /><datalinks><datalink><sink type="processor"><processor>P</processor><port>inputlist</port></sink><source type="dataflow"><port>i</port></source></datalink><datalink><sink type="dataflow"><port>o</port></sink><source type="processor"><processor>P</processor><port>outputlist</port></source></datalink></datalinks><annotations /></dataflow></workflow>
\ No newline at end of file
diff --git a/taverna-dataflow-activity-ui/src/test/resources/q.t2flow b/taverna-dataflow-activity-ui/src/test/resources/q.t2flow
new file mode 100644
index 0000000..03a3cd2
--- /dev/null
+++ b/taverna-dataflow-activity-ui/src/test/resources/q.t2flow
@@ -0,0 +1,36 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow" version="1" producedBy="taverna-2.1-beta-2"><dataflow id="0833816b-d18b-41b4-b2f7-dae317023444" role="top"><name>Workflow2</name><inputPorts><port><name>p</name><depth>1</depth><granularDepth>1</granularDepth><annotations /></port></inputPorts><outputPorts><port><name>q</name></port><port><name>p</name></port></outputPorts><processors><processor><name>Q</name><inputPorts><port><name>inputlist</name><depth>1</depth></port></inputPorts><outputPorts><port><name>outputlist</name><depth>1</depth><granularDepth>1</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2.activities</group><artifact>localworker-activity</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.activities.localworker.LocalworkerActivity</class><inputMap><map from="inputlist" to="inputlist" /></inputMap><outputMap><map from="outputlist" to="outputlist" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean xmlns="">
+ <script>outputlist = inputlist;</script>
+ <dependencies />
+ <classLoaderSharing>workflow</classLoaderSharing>
+ <localDependencies />
+ <artifactDependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>[B</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>inputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>1</granularDepth>
+ <name>outputlist</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.localworker.LocalworkerActivityConfigurationBean></configBean><annotations /></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>1000</initialDelay>
+ <maxDelay>5000</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2.core</group><artifact>workflowmodel-impl</artifact><version>0.8</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="inputlist" depth="1" /></cross></strategy></iteration></iterationStrategyStack></processor></processors><conditions /><datalinks><datalink><sink type="processor"><processor>Q</processor><port>inputlist</port></sink><source type="dataflow"><port>p</port></source></datalink><datalink><sink type="dataflow"><port>q</port></sink><source type="processor"><processor>Q</processor><port>outputlist</port></source></datalink><datalink><sink type="dataflow"><port>p</port></sink><source type="dataflow"><port>p</port></source></datalink></datalinks><annotations /></dataflow></workflow>
\ No newline at end of file
diff --git a/taverna-disabled-activity-ui/pom.xml b/taverna-disabled-activity-ui/pom.xml
new file mode 100644
index 0000000..ddbaf29
--- /dev/null
+++ b/taverna-disabled-activity-ui/pom.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna</groupId>
+ <artifactId>taverna-parent</artifactId>
+ <version>3.0.1-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>disabled-activity-ui</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>Taverna 2 Disabled Activity UI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-tools</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.activities</groupId>
+ <artifactId>beanshell-activity</artifactId>
+ <version>${t2.activities.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ <version>${javahelp.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>uibuilder</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>helper</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ </dependencies>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+ <scm>
+ <connection>scm:git:https://github.com/taverna/taverna-disabled-activity-ui.git</connection>
+ <developerConnection>scm:git:ssh://git@github.com/taverna/taverna-disabled-activity-ui.git</developerConnection>
+ <url>https://github.com/taverna/taverna-disabled-activity-ui/</url>
+ <tag>HEAD</tag>
+ </scm>
+</project>
+
diff --git a/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/actions/DisabledActivityConfigurationAction.java b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/actions/DisabledActivityConfigurationAction.java
new file mode 100644
index 0000000..c71d483
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/actions/DisabledActivityConfigurationAction.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * 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.activities.disabled.actions;
+
+import java.awt.Component;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+import net.sf.taverna.t2.activities.disabled.views.DisabledConfigView;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationDialog;
+
+@SuppressWarnings("serial")
+public class DisabledActivityConfigurationAction extends ActivityConfigurationAction {
+
+ public static final String FIX_DISABLED = "Edit properties";
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final ReportManager reportManager;
+
+ public DisabledActivityConfigurationAction(Activity activity, Frame owner,
+ EditManager editManager, FileManager fileManager, ReportManager reportManager,
+ ActivityIconManager activityIconManager, ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super(activity, activityIconManager, serviceDescriptionRegistry);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManager = reportManager;
+ putValue(NAME, FIX_DISABLED);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ ActivityConfigurationDialog currentDialog = ActivityConfigurationAction
+ .getDialog(getActivity());
+ if (currentDialog != null) {
+ currentDialog.toFront();
+ return;
+ }
+ int answer = JOptionPane.showConfirmDialog((Component) e.getSource(),
+ "Directly editing properties can be dangerous. Are you sure you want to proceed?",
+ "Confirm editing", JOptionPane.YES_NO_OPTION);
+ if (answer != JOptionPane.YES_OPTION) {
+ return;
+ }
+
+ final DisabledConfigView disabledConfigView = new DisabledConfigView(getActivity());
+ final DisabledActivityConfigurationDialog dialog = new DisabledActivityConfigurationDialog(
+ getActivity(), disabledConfigView);
+
+ ActivityConfigurationAction.setDialog(getActivity(), dialog, fileManager);
+
+ }
+
+ private class DisabledActivityConfigurationDialog extends ActivityConfigurationDialog {
+ public DisabledActivityConfigurationDialog(Activity a, DisabledConfigView p) {
+ super(a, p, editManager);
+ this.setModal(true);
+ super.applyButton.setEnabled(false);
+ super.applyButton.setVisible(false);
+ }
+
+ public void configureActivity(Dataflow df, Activity a, Object bean) {
+ Edit<?> configureActivityEdit = editManager.getEdits()
+ .getConfigureActivityEdit(a, bean);
+ try {
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ editList.add(configureActivityEdit);
+ Processor p = findProcessor(df, a);
+ if (p != null && p.getActivityList().size() == 1) {
+ editList.add(editManager.getEdits().getMapProcessorPortsForActivityEdit(p));
+ }
+ Edit e = Tools.getEnableDisabledActivityEdit(super.owningProcessor, activity,
+ editManager.getEdits());
+ if (e != null) {
+ editList.add(e);
+ editManager.doDataflowEdit(df, new CompoundEdit(editList));
+ reportManager.updateObjectReport(super.owningDataflow, super.owningProcessor);
+
+ }
+ } catch (IllegalStateException e) {
+ // TODO Auto-generated catch block
+ logger.error(e);
+ } catch (EditException e) {
+ logger.error(e);
+ }
+ }
+
+ }
+
+}
diff --git a/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/menu/ConfigureDisabledMenuAction.java b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/menu/ConfigureDisabledMenuAction.java
new file mode 100644
index 0000000..68a906e
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/menu/ConfigureDisabledMenuAction.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.activities.disabled.menu;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.activities.disabled.actions.DisabledActivityConfigurationAction;
+import net.sf.taverna.t2.activities.disabled.views.DisabledActivityViewFactory;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.activitytools.AbstractConfigureActivityMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+
+public class ConfigureDisabledMenuAction extends AbstractConfigureActivityMenuAction {
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ReportManager reportManager;
+ private ActivityIconManager activityIconManager;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public ConfigureDisabledMenuAction() {
+ super(DisabledActivityViewFactory.ACTIVITY_TYPE);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new DisabledActivityConfigurationAction(findActivity(), getParentFrame(),
+ editManager, fileManager, reportManager, activityIconManager, serviceDescriptionRegistry);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setServiceDescriptionRegistry(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+}
diff --git a/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledActivityViewFactory.java b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledActivityViewFactory.java
new file mode 100644
index 0000000..a168974
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledActivityViewFactory.java
@@ -0,0 +1,80 @@
+package net.sf.taverna.t2.activities.disabled.views;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+/**
+ * This class generates a contextual view for a DisabledActivity
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public class DisabledActivityViewFactory implements ContextualViewFactory<Activity> {
+
+ public static final URI ACTIVITY_TYPE = URI.create("http://ns.taverna.org.uk/2010/activity/disabled");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ReportManager reportManager;
+ private ActivityIconManager activityIconManager;
+ private ColourManager colourManager;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ /**
+ * The factory can handle a DisabledActivity
+ *
+ * @param object
+ * @return
+ */
+ public boolean canHandle(Object object) {
+ return object instanceof Activity && ((Activity) object).getType().equals(ACTIVITY_TYPE);
+ }
+
+ /**
+ * Return a contextual view that can display information about a DisabledActivity
+ *
+ * @param activity
+ * @return
+ */
+ public List<ContextualView> getViews(Activity activity) {
+ return Arrays.asList(new ContextualView[] { new DisabledContextualView(activity,
+ editManager, fileManager, reportManager, colourManager, activityIconManager,
+ serviceDescriptionRegistry) });
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setServiceDescriptionRegistry(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+}
diff --git a/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledConfigView.java b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledConfigView.java
new file mode 100644
index 0000000..9c8c9cd
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledConfigView.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * 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.activities.disabled.views;
+
+import java.awt.BorderLayout;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.help.CSH;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationPanel;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+@SuppressWarnings("serial")
+public class DisabledConfigView extends ActivityConfigurationPanel {
+
+ private ActivityAndBeanWrapper configuration;
+ private List<String> fieldNames;
+
+ private Object clonedConfig = null;
+ String origConfigXML = "";
+
+ public DisabledConfigView(Activity activity) {
+ super(activity);
+ setLayout(new BorderLayout());
+ fieldNames = null;
+ initialise();
+ }
+
+ private void initialise() {
+ CSH.setHelpIDString(
+ this,
+ "net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.DisabledConfigView");
+ configuration = activity.getConfiguration();
+ XStream xstream = new XStream(new DomDriver());
+ Activity a = configuration.getActivity();
+ xstream.setClassLoader(a.getClass().getClassLoader());
+ Object origConfig = configuration.getBean();
+ if (fieldNames == null) {
+ fieldNames = getFieldNames(origConfig);
+ }
+ origConfigXML = xstream.toXML(origConfig);
+ clonedConfig = xstream.fromXML(origConfigXML);
+ JPanel panel = UIBuilder.buildEditor(clonedConfig, (String[]) fieldNames.toArray(new String[0]));
+ this.add(panel, BorderLayout.CENTER);
+ this.revalidate();
+ }
+
+ @Override
+ public void refreshConfiguration() {
+ this.removeAll();
+ initialise();
+ }
+
+ public boolean checkValues() {
+ boolean result = false;
+ result = activity.configurationWouldWork(clonedConfig);
+ if (!result) {
+ JOptionPane.showMessageDialog(
+ this,
+ "The new properties are invalid or not consistent with the workflow",
+ "Invalid properties", JOptionPane.WARNING_MESSAGE);
+ }
+ return result;
+ }
+
+ public void noteConfiguration() {
+ if (isConfigurationChanged()) {
+ ActivityAndBeanWrapper newConfig = new ActivityAndBeanWrapper();
+ newConfig.setActivity(configuration.getActivity());
+ newConfig.setBean(clonedConfig);
+ configuration = newConfig;
+
+ XStream xstream = new XStream(new DomDriver());
+ xstream.setClassLoader(configuration.getActivity().getClass().getClassLoader());
+
+ origConfigXML = xstream.toXML(clonedConfig);
+ }
+ }
+
+ @Override
+ public ActivityAndBeanWrapper getConfiguration() {
+ return configuration;
+ }
+
+ public boolean isConfigurationChanged() {
+ XStream xstream = new XStream(new DomDriver());
+ xstream.setClassLoader(configuration.getActivity().getClass().getClassLoader());
+ return (!xstream.toXML(clonedConfig).equals(origConfigXML));
+ }
+
+ private List<String> getFieldNames(Object config) {
+ List<String> result = new ArrayList<String>();
+ try {
+ BeanInfo beanInfo = Introspector.getBeanInfo(config.getClass());
+ for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
+ Method readMethod = pd.getReadMethod();
+ if ((readMethod != null) && !(pd.getName().equals("class"))) {
+ try {
+ result.add(pd.getName());
+ } catch (IllegalArgumentException ex) {
+ // ignore
+ }
+ }
+ }
+ } catch (IntrospectionException e) {
+ // ignore
+ }
+ return result;
+ }
+}
diff --git a/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledContextualView.java b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledContextualView.java
new file mode 100644
index 0000000..9d60faa
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/java/net/sf/taverna/t2/activities/disabled/views/DisabledContextualView.java
@@ -0,0 +1,128 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.activities.disabled.views;
+
+import java.awt.Frame;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.activities.disabled.actions.DisabledActivityConfigurationAction;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.HTMLBasedActivityContextualView;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * A DisabledContextualView displays information about a DisabledActivity
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class DisabledContextualView extends HTMLBasedActivityContextualView {
+
+ private List<String> fieldNames;
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final ReportManager reportManager;
+ private final ActivityIconManager activityIconManager;
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public DisabledContextualView(Activity activity, EditManager editManager,
+ FileManager fileManager, ReportManager reportManager, ColourManager colourManager,
+ ActivityIconManager activityIconManager, ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super(activity, colourManager);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManager = reportManager;
+ this.activityIconManager = activityIconManager;
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ /**
+ * The table for the DisabledActivity shows its ports and the information within the offline
+ * Activity's configuration.
+ *
+ * @return
+ */
+ @Override
+ protected String getRawTableRowsHtml() {
+ StringBuilder html = new StringBuilder();
+ html.append("<tr><th>Input Port Name</th><th>Depth</th></tr>");
+ for (InputActivityPort inputActivityPort : getActivity().getInputPorts()) {
+ html.append("<tr><td>" + inputActivityPort.getName() + "</td><td>");
+ html.append(inputActivityPort.getDepth() + "</td></tr>");
+ }
+ html.append("<tr><th>Output Port Name</th><th>Depth</th></tr>");
+ for (OutputActivityPort outputActivityPort : getActivity().getOutputPorts()) {
+ html.append("<tr><td>" + outputActivityPort.getName() + "</td><td>");
+ html.append(outputActivityPort.getDepth() + "</td></tr>");
+ }
+
+ JsonNode config = getConfigBean().getJson();
+ try {
+ html.append("<tr><th>Property Name</th><th>Property Value</th></tr>");
+ BeanInfo beanInfo = Introspector.getBeanInfo(config.getClass());
+ for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
+ Method readMethod = pd.getReadMethod();
+ if ((readMethod != null) && !(pd.getName().equals("class"))) {
+ try {
+ html.append("<tr><td>");
+ html.append(pd.getName());
+ html.append("</td><td>");
+ html.append(readMethod.invoke(config));
+ html.append("</td></tr>");
+ if (fieldNames == null) {
+ fieldNames = new ArrayList<String>();
+ }
+ fieldNames.add(pd.getName());
+ } catch (IllegalAccessException ex) {
+ // ignore
+ } catch (IllegalArgumentException ex) {
+ // ignore
+ } catch (InvocationTargetException ex) {
+ // ignore
+ }
+ }
+ }
+ } catch (IntrospectionException e) {
+ // ignore
+ }
+ return html.toString();
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Unavailable service";
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new DisabledActivityConfigurationAction(getActivity(), owner,
+ editManager, fileManager, reportManager, activityIconManager, serviceDescriptionRegistry);
+ }
+
+}
diff --git a/taverna-disabled-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-disabled-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..022189a
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.disabled.menu.ConfigureDisabledMenuAction
\ No newline at end of file
diff --git a/taverna-disabled-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-disabled-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..80b0bf3
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.disabled.views.DisabledActivityViewFactory
diff --git a/taverna-disabled-activity-ui/src/main/resources/META-INF/spring/disabled-activity-ui-context-osgi.xml b/taverna-disabled-activity-ui/src/main/resources/META-INF/spring/disabled-activity-ui-context-osgi.xml
new file mode 100644
index 0000000..f938272
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/resources/META-INF/spring/disabled-activity-ui-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ConfigureDisabledMenuAction" auto-export="interfaces" />
+
+ <service ref="DisabledActivityViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="reportManager" interface="net.sf.taverna.t2.workbench.report.ReportManager" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <reference id="serviceDescriptionRegistry" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry" />
+
+</beans:beans>
diff --git a/taverna-disabled-activity-ui/src/main/resources/META-INF/spring/disabled-activity-ui-context.xml b/taverna-disabled-activity-ui/src/main/resources/META-INF/spring/disabled-activity-ui-context.xml
new file mode 100644
index 0000000..fbc0aa0
--- /dev/null
+++ b/taverna-disabled-activity-ui/src/main/resources/META-INF/spring/disabled-activity-ui-context.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ConfigureDisabledMenuAction" class="net.sf.taverna.t2.activities.disabled.menu.ConfigureDisabledMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ </bean>
+
+ <bean id="DisabledActivityViewFactory" class="net.sf.taverna.t2.activities.disabled.views.DisabledActivityViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ </bean>
+
+</beans>
diff --git a/taverna-stringconstant-activity-ui/pom.xml b/taverna-stringconstant-activity-ui/pom.xml
new file mode 100644
index 0000000..761981f
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna</groupId>
+ <artifactId>taverna-parent</artifactId>
+ <version>3.0.1-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>stringconstant-activity-ui</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>Taverna 2 StringConstant Activity UI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>com.springsource.org.apache.commons.lang</artifactId>
+ <version>${commons.lang.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>activity-palette-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency> -->
+ <!-- <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-tools</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency> -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-tools</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ </dependencies>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+</project>
+
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/actions/StringConstantActivityConfigurationAction.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/actions/StringConstantActivityConfigurationAction.java
new file mode 100644
index 0000000..fa8bafc
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/actions/StringConstantActivityConfigurationAction.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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.activities.stringconstant.actions;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import net.sf.taverna.t2.activities.stringconstant.views.StringConstantConfigView;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationDialog;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+public class StringConstantActivityConfigurationAction extends
+ ActivityConfigurationAction {
+ private static final long serialVersionUID = 2518716617809186972L;
+ public static final String CONFIGURE_STRINGCONSTANT = "Edit value";
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public StringConstantActivityConfigurationAction(Activity activity,
+ Frame owner, EditManager editManager, FileManager fileManager,
+ ActivityIconManager activityIconManager,
+ ServiceDescriptionRegistry serviceDescriptionRegistry,
+ ServiceRegistry serviceRegistry) {
+ super(activity, activityIconManager, serviceDescriptionRegistry);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.serviceRegistry = serviceRegistry;
+ putValue(NAME, CONFIGURE_STRINGCONSTANT);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ActivityConfigurationDialog currentDialog = getDialog(getActivity());
+ if (currentDialog != null) {
+ currentDialog.toFront();
+ return;
+ }
+
+ StringConstantConfigView configView = new StringConstantConfigView(
+ activity, serviceRegistry);
+ ActivityConfigurationDialog dialog = new ActivityConfigurationDialog(
+ getActivity(), configView, editManager);
+ setDialog(getActivity(), dialog, fileManager);
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/AddStringConstantTemplateAction.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/AddStringConstantTemplateAction.java
new file mode 100644
index 0000000..fb9d069
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/AddStringConstantTemplateAction.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.activities.stringconstant.menu;
+
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.importServiceDescription;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * An action to add a string constant activity + a wrapping processor to the
+ * workflow.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AddStringConstantTemplateAction extends
+ AbstractContextualMenuAction {
+ private static final URI ACTIVITY_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+ private static final URI insertSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/insert");
+
+ private EditManager editManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ActivityIconManager activityIconManager;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private ServiceRegistry serviceRegistry;
+
+ public AddStringConstantTemplateAction() {
+ super(insertSection, 800);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ AbstractAction action = new AbstractAction("Text constant",
+ activityIconManager.iconForActivity(ACTIVITY_TYPE)) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importServiceDescription(
+ serviceDescriptionRegistry
+ .getServiceDescription(ACTIVITY_TYPE),
+ false, editManager, menuManager, selectionManager,
+ serviceRegistry);
+ }
+ };
+ return action;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setServiceDescriptionRegistry(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/AddStringConstantTemplateMenuAction.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/AddStringConstantTemplateMenuAction.java
new file mode 100644
index 0000000..cb1682d
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/AddStringConstantTemplateMenuAction.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.activities.stringconstant.menu;
+
+import static java.awt.event.InputEvent.ALT_DOWN_MASK;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.importServiceDescription;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+/**
+ * An action to add a string constant activity + a wrapping processor to the
+ * workflow.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AddStringConstantTemplateMenuAction extends AbstractMenuAction {
+ private static final URI ACTIVITY_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+ private static final URI INSERT = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#insert");
+ private static final String ADD_STRING_CONSTANT = "Text constant";
+ private static final URI ADD_STRING_CONSTANT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuAddStringConstant");
+
+ private EditManager editManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ActivityIconManager activityIconManager;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private ServiceRegistry serviceRegistry;
+
+ public AddStringConstantTemplateMenuAction() {
+ super(INSERT, 800, ADD_STRING_CONSTANT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddStringConstantMenuAction();
+ }
+
+ protected class AddStringConstantMenuAction extends AbstractAction
+ implements DesignOnlyAction {
+ AddStringConstantMenuAction() {
+ super();
+ putValue(SMALL_ICON,
+ activityIconManager.iconForActivity(ACTIVITY_TYPE));
+ putValue(NAME, ADD_STRING_CONSTANT);
+ putValue(SHORT_DESCRIPTION, ADD_STRING_CONSTANT);
+ putValue(ACCELERATOR_KEY,
+ getKeyStroke(VK_S, SHIFT_DOWN_MASK | ALT_DOWN_MASK));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importServiceDescription(
+ serviceDescriptionRegistry
+ .getServiceDescription(ACTIVITY_TYPE),
+ false, editManager, menuManager, selectionManager,
+ serviceRegistry);
+ }
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setServiceDescriptionRegistry(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/ConfigureStringConstantMenuAction.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/ConfigureStringConstantMenuAction.java
new file mode 100644
index 0000000..46bdde9
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/menu/ConfigureStringConstantMenuAction.java
@@ -0,0 +1,64 @@
+package net.sf.taverna.t2.activities.stringconstant.menu;
+
+import static javax.swing.Action.NAME;
+import static net.sf.taverna.t2.activities.stringconstant.actions.StringConstantActivityConfigurationAction.CONFIGURE_STRINGCONSTANT;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.activities.stringconstant.actions.StringConstantActivityConfigurationAction;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.activitytools.AbstractConfigureActivityMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+public class ConfigureStringConstantMenuAction extends
+ AbstractConfigureActivityMenuAction {
+ private static final URI ACTIVITY_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ActivityIconManager activityIconManager;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private ServiceRegistry serviceRegistry;
+
+ public ConfigureStringConstantMenuAction() {
+ super(ACTIVITY_TYPE);
+ }
+
+ @Override
+ protected Action createAction() {
+ StringConstantActivityConfigurationAction configAction = new StringConstantActivityConfigurationAction(
+ findActivity(), getParentFrame(), editManager, fileManager,
+ activityIconManager, serviceDescriptionRegistry,
+ serviceRegistry);
+ configAction.putValue(NAME, CONFIGURE_STRINGCONSTANT);
+ addMenuDots(configAction);
+ return configAction;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setServiceDescriptionRegistry(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/servicedescriptions/StringConstantActivityIcon.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/servicedescriptions/StringConstantActivityIcon.java
new file mode 100644
index 0000000..409c0f5
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/servicedescriptions/StringConstantActivityIcon.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.activities.stringconstant.servicedescriptions;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI;
+
+/**
+ * @author Alex Nenadic
+ */
+public class StringConstantActivityIcon implements ActivityIconSPI {
+ private static final URI ACTIVITY_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+ private static Icon icon = null;
+
+ @Override
+ public int canProvideIconScore(URI activityType) {
+ if (activityType.equals(ACTIVITY_TYPE))
+ return DEFAULT_ICON + 1;
+ else
+ return NO_ICON;
+ }
+
+ @Override
+ public Icon getIcon(URI activityType) {
+ return getStringConstantIcon();
+ }
+
+ public static Icon getStringConstantIcon() {
+ if (icon == null)
+ icon = new ImageIcon(
+ StringConstantActivityIcon.class
+ .getResource("/stringconstant.png"));
+ return icon;
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/servicedescriptions/StringConstantTemplateService.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/servicedescriptions/StringConstantTemplateService.java
new file mode 100644
index 0000000..157f3b6
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/servicedescriptions/StringConstantTemplateService.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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.activities.stringconstant.servicedescriptions;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.t2.servicedescriptions.AbstractTemplateService;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class StringConstantTemplateService extends AbstractTemplateService {
+ private static final URI ACTIVITY_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+ private static final URI providerId = URI
+ .create("http://taverna.sf.net/2010/service-provider/stringconstant");
+ public static final String DEFAULT_VALUE = "Add your own value here";
+ private static final String STRINGCONSTANT = "Text constant";
+
+ @Override
+ public URI getActivityType() {
+ return ACTIVITY_TYPE;
+ }
+
+ @Override
+ public Configuration getActivityConfiguration() {
+ Configuration configuration = new Configuration();
+ configuration.setType(ACTIVITY_TYPE.resolve("#Config"));
+ ((ObjectNode) configuration.getJson()).put("string", DEFAULT_VALUE);
+ return configuration;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return StringConstantActivityIcon.getStringConstantIcon();
+ }
+
+ @Override
+ public String getName() {
+ return STRINGCONSTANT;
+ }
+
+ @Override
+ public String getDescription() {
+ return "A string value that you can set";
+ }
+
+ public static ServiceDescription getServiceDescription() {
+ StringConstantTemplateService scts = new StringConstantTemplateService();
+ return scts.templateService;
+ }
+
+ @Override
+ public String getId() {
+ return providerId.toString();
+ }
+
+ @Override
+ public ServiceDescriptionProvider newInstance() {
+ return new StringConstantTemplateService();
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantActivityContextualView.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantActivityContextualView.java
new file mode 100644
index 0000000..803692d
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantActivityContextualView.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.activities.stringconstant.views;
+
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+import static org.apache.commons.lang.StringUtils.abbreviate;
+
+import java.awt.Frame;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.activities.stringconstant.actions.StringConstantActivityConfigurationAction;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.HTMLBasedActivityContextualView;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class StringConstantActivityContextualView extends
+ HTMLBasedActivityContextualView {
+ private static final long serialVersionUID = -553974544001808511L;
+ private static final int MAX_LENGTH = 100;
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final ActivityIconManager activityIconManager;
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private final ServiceRegistry serviceRegistry;
+
+ public StringConstantActivityContextualView(Activity activity,
+ EditManager editManager, FileManager fileManager,
+ ActivityIconManager activityIconManager,
+ ColourManager colourManager,
+ ServiceDescriptionRegistry serviceDescriptionRegistry,
+ ServiceRegistry serviceRegistry) {
+ super(activity, colourManager);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.activityIconManager = activityIconManager;
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Text constant";
+ }
+
+ @Override
+ protected String getRawTableRowsHtml() {
+ JsonNode json = getConfigBean().getJson();
+ String value = json.get("string").textValue();
+ value = abbreviate(value, MAX_LENGTH);
+ value = escapeHtml(value);
+ String html = "<tr><td>Value</td><td>" + value + "</td></tr>";
+ return html;
+ }
+
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new StringConstantActivityConfigurationAction(getActivity(),
+ owner, editManager, fileManager, activityIconManager,
+ serviceDescriptionRegistry, serviceRegistry);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantActivityViewFactory.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantActivityViewFactory.java
new file mode 100644
index 0000000..3f1e480
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantActivityViewFactory.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * 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.activities.stringconstant.views;
+
+import static java.util.Arrays.asList;
+
+import java.net.URI;
+import java.util.List;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+public class StringConstantActivityViewFactory implements
+ ContextualViewFactory<Activity> {
+ private static final URI ACTIVITY_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ActivityIconManager activityIconManager;
+ private ColourManager colourManager;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private ServiceRegistry serviceRegistry;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof Activity
+ && ((Activity) object).getType().equals(ACTIVITY_TYPE);
+ }
+
+ @Override
+ public List<ContextualView> getViews(Activity activity) {
+ return asList(new ContextualView[] { new StringConstantActivityContextualView(
+ activity, editManager, fileManager, activityIconManager,
+ colourManager, serviceDescriptionRegistry, serviceRegistry) });
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setServiceDescriptionRegistry(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantConfigView.java b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantConfigView.java
new file mode 100644
index 0000000..b371adb
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/java/net/sf/taverna/t2/activities/stringconstant/views/StringConstantConfigView.java
@@ -0,0 +1,243 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.activities.stringconstant.views;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Color.WHITE;
+import static java.awt.Font.PLAIN;
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.FIRST_LINE_START;
+import static java.lang.String.format;
+import static javax.swing.BorderFactory.createTitledBorder;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION;
+import static javax.swing.border.TitledBorder.DEFAULT_POSITION;
+import static net.sf.taverna.t2.activities.stringconstant.servicedescriptions.StringConstantTemplateService.DEFAULT_VALUE;
+import static net.sf.taverna.t2.lang.ui.FileTools.readStringFromFile;
+import static net.sf.taverna.t2.lang.ui.FileTools.saveStringToFile;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JEditorPane;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
+
+import net.sf.taverna.t2.lang.ui.LineEnabledTextPanel;
+import net.sf.taverna.t2.lang.ui.LinePainter;
+import net.sf.taverna.t2.lang.ui.NoWrapEditorKit;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationPanel;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+/**
+ * @author alanrw
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class StringConstantConfigView extends ActivityConfigurationPanel {
+ private static final String CONTENT_PROPERTY = "string";
+ private static final String TEXT_FILE_EXTENSION = ".txt";
+ public static Logger logger = Logger.getLogger(StringConstantConfigView.class);
+ private static final Color LINE_COLOR = WHITE;
+ @SuppressWarnings("unused")
+ private static final String HELP_TOKEN = "net.sf.taverna.t2.activities.stringconstant.views.StringConstantConfigView";
+
+ /** The text */
+ private JEditorPane scriptTextArea;
+ private final ServiceRegistry serviceRegistry;
+
+ public StringConstantConfigView(Activity activity,
+ Configuration configuration, ServiceRegistry serviceRegistry) {
+ super(activity, configuration);
+ this.serviceRegistry = serviceRegistry;
+ setLayout(new GridBagLayout());
+ initialise();
+ addAncestorListener(new AncestorListener() {
+ @Override
+ public void ancestorAdded(AncestorEvent event) {
+ whenOpened();
+ }
+
+ @Override
+ public void ancestorMoved(AncestorEvent event) {
+ }
+
+ @Override
+ public void ancestorRemoved(AncestorEvent event) {
+ }
+ });
+ }
+
+ public StringConstantConfigView(Activity activity,
+ ServiceRegistry serviceRegistry) {
+ super(activity);
+ this.serviceRegistry = serviceRegistry;
+ setLayout(new GridBagLayout());
+ initialise();
+ addAncestorListener(new AncestorListener() {
+ @Override
+ public void ancestorAdded(AncestorEvent event) {
+ whenOpened();
+ }
+
+ @Override
+ public void ancestorMoved(AncestorEvent event) {
+ }
+
+ @Override
+ public void ancestorRemoved(AncestorEvent event) {
+ }
+ });
+ }
+
+ @Override
+ public void whenOpened() {
+ scriptTextArea.requestFocus();
+ if (scriptTextArea.getText().equals(DEFAULT_VALUE))
+ scriptTextArea.selectAll();
+ }
+
+ /** The name of the thing we are working with. */
+ protected String entityName() {
+ return "text";
+ }
+
+ @Override
+ protected void initialise() {
+ super.initialise();
+ // CSH.setHelpIDString(this, HELP_TOKEN);
+
+ setBorder(createTitledBorder(null, null, DEFAULT_JUSTIFICATION,
+ DEFAULT_POSITION, new Font("Lucida Grande", 1, 12)));
+
+ JPanel scriptEditPanel = new JPanel(new BorderLayout());
+
+ scriptTextArea = new JTextPane();
+ new LinePainter(scriptTextArea, LINE_COLOR);
+
+ // NOTE: Due to T2-1145 - always set editor kit BEFORE setDocument
+ scriptTextArea.setEditorKit(new NoWrapEditorKit());
+ scriptTextArea.setFont(new Font("Monospaced", PLAIN, 14));
+ scriptTextArea.setText(getProperty(CONTENT_PROPERTY));
+ scriptTextArea.setCaretPosition(0);
+ scriptTextArea.setPreferredSize(new Dimension(200, 100));
+
+ scriptEditPanel.add(new LineEnabledTextPanel(scriptTextArea), CENTER);
+
+ GridBagConstraints outerConstraint = new GridBagConstraints();
+ outerConstraint.anchor = FIRST_LINE_START;
+ outerConstraint.gridx = 0;
+ outerConstraint.gridy = 0;
+
+ outerConstraint.fill = BOTH;
+ outerConstraint.weighty = 0.1;
+ outerConstraint.weightx = 0.1;
+ add(scriptEditPanel, outerConstraint);
+
+ JButton loadScriptButton = new JButton("Load " + entityName());
+ loadScriptButton.setToolTipText(format("Load %s from a file",
+ entityName()));
+ loadScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ loadText();
+ }
+ });
+
+ JButton saveRScriptButton = new JButton("Save " + entityName());
+ saveRScriptButton.setToolTipText(format("Save the %s to a file",
+ entityName()));
+ saveRScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveText();
+ }
+ });
+
+ JButton clearScriptButton = new JButton("Clear " + entityName());
+ clearScriptButton.setToolTipText(format(
+ "Clear current %s from the edit area", entityName()));
+ clearScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ clearText();
+ }
+ });
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout());
+ buttonPanel.add(loadScriptButton);
+ buttonPanel.add(saveRScriptButton);
+ buttonPanel.add(clearScriptButton);
+
+ scriptEditPanel.add(buttonPanel, SOUTH);
+ setPreferredSize(new Dimension(600, 500));
+ this.validate();
+ }
+
+ /**
+ * Method for loading the value
+ */
+ private void loadText() {
+ String newScript = readStringFromFile(this, "Load " + entityName(),
+ TEXT_FILE_EXTENSION);
+ if (newScript != null) {
+ scriptTextArea.setText(newScript);
+ scriptTextArea.setCaretPosition(0);
+ }
+ }
+
+ /**
+ * Method for saving the value
+ */
+ private void saveText() {
+ saveStringToFile(this, "Save " + entityName(), TEXT_FILE_EXTENSION,
+ scriptTextArea.getText());
+ }
+
+ /**
+ * Method for clearing the value
+ */
+ private void clearText() {
+ if (showConfirmDialog(this,
+ format("Do you really want to clear the %s?", entityName()),
+ "Clearing the " + entityName(), YES_NO_OPTION) == YES_OPTION)
+ scriptTextArea.setText("");
+ }
+
+ @Override
+ public boolean checkValues() {
+ return true;
+ }
+
+ @Override
+ public boolean isConfigurationChanged() {
+ return !scriptTextArea.getText().equals(getProperty(CONTENT_PROPERTY));
+ }
+
+ @Override
+ public void noteConfiguration() {
+ setProperty(CONTENT_PROPERTY, scriptTextArea.getText());
+ configureInputPorts(serviceRegistry);
+ configureOutputPorts(serviceRegistry);
+ }
+}
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider
new file mode 100644
index 0000000..7a14b56
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.stringconstant.servicedescriptions.StringConstantTemplateService
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..22938a2
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.activities.stringconstant.menu.AddStringConstantTemplateAction
+net.sf.taverna.t2.activities.stringconstant.menu.AddStringConstantTemplateMenuAction
+net.sf.taverna.t2.activities.stringconstant.menu.ConfigureStringConstantMenuAction
\ No newline at end of file
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI
new file mode 100644
index 0000000..58228ef
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.stringconstant.servicedescriptions.StringConstantActivityIcon
\ No newline at end of file
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..73ca2a1
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.stringconstant.views.StringConstantActivityViewFactory
\ No newline at end of file
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/META-INF/spring/stringconstant-activity-ui-context-osgi.xml b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/spring/stringconstant-activity-ui-context-osgi.xml
new file mode 100644
index 0000000..359a72d
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/spring/stringconstant-activity-ui-context-osgi.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="StringConstantActivityIcon" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI" />
+ <service ref="StringConstantTemplateService" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider" />
+ <service ref="StringConstantActivityViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <service ref="AddStringConstantTemplateAction" auto-export="interfaces" />
+ <service ref="AddStringConstantTemplateMenuAction" auto-export="interfaces" />
+ <service ref="ConfigureStringConstantMenuAction" auto-export="interfaces" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <reference id="serviceDescriptionRegistry" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry" />
+ <reference id="serviceRegistry" interface="uk.org.taverna.commons.services.ServiceRegistry" />
+</beans:beans>
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/META-INF/spring/stringconstant-activity-ui-context.xml b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/spring/stringconstant-activity-ui-context.xml
new file mode 100644
index 0000000..19bb6fd
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/META-INF/spring/stringconstant-activity-ui-context.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="StringConstantActivityIcon"
+ class="net.sf.taverna.t2.activities.stringconstant.servicedescriptions.StringConstantActivityIcon" />
+
+ <bean id="StringConstantTemplateService"
+ class="net.sf.taverna.t2.activities.stringconstant.servicedescriptions.StringConstantTemplateService" />
+
+ <bean id="AddStringConstantTemplateAction"
+ class="net.sf.taverna.t2.activities.stringconstant.menu.AddStringConstantTemplateAction">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+ <bean id="AddStringConstantTemplateMenuAction"
+ class="net.sf.taverna.t2.activities.stringconstant.menu.AddStringConstantTemplateMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+ <bean id="ConfigureStringConstantMenuAction"
+ class="net.sf.taverna.t2.activities.stringconstant.menu.ConfigureStringConstantMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+
+ <bean id="StringConstantActivityViewFactory"
+ class="net.sf.taverna.t2.activities.stringconstant.views.StringConstantActivityViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+</beans>
diff --git a/taverna-stringconstant-activity-ui/src/main/resources/stringconstant.png b/taverna-stringconstant-activity-ui/src/main/resources/stringconstant.png
new file mode 100644
index 0000000..0810c97
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/main/resources/stringconstant.png
Binary files differ
diff --git a/taverna-stringconstant-activity-ui/src/test/java/net/sf/taverna/t2/activities/stringconstant/views/TestStringConstantContextualView.java b/taverna-stringconstant-activity-ui/src/test/java/net/sf/taverna/t2/activities/stringconstant/views/TestStringConstantContextualView.java
new file mode 100644
index 0000000..4555d0c
--- /dev/null
+++ b/taverna-stringconstant-activity-ui/src/test/java/net/sf/taverna/t2/activities/stringconstant/views/TestStringConstantContextualView.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * 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.activities.stringconstant.views;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import net.sf.taverna.t2.activities.stringconstant.actions.StringConstantActivityConfigurationAction;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+public class TestStringConstantContextualView {
+ Activity activity;
+
+ @Before
+ public void setup() {
+ activity = new Activity();
+ }
+
+ @Test
+ @Ignore
+ public void testGetConfigureAction() throws Exception {
+ ContextualView view = new StringConstantActivityContextualView(
+ activity, null, null, null, null, null, null);
+ assertNotNull("The action should not be null",
+ view.getConfigureAction(null));
+ assertTrue(
+ "Should be a StringConstantActivityConfigurationAction",
+ view.getConfigureAction(null) instanceof StringConstantActivityConfigurationAction);
+ }
+}
diff --git a/taverna-unrecognized-activity-ui/pom.xml b/taverna-unrecognized-activity-ui/pom.xml
new file mode 100644
index 0000000..eb7a7c6
--- /dev/null
+++ b/taverna-unrecognized-activity-ui/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna</groupId>
+ <artifactId>taverna-parent</artifactId>
+ <version>3.0.1-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>unrecognized-activity-ui</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>Taverna 2 Unrecognized Activity UI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ </dependencies>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+</project>
+
diff --git a/taverna-unrecognized-activity-ui/src/main/java/net/sf/taverna/t2/activities/unrecognized/views/UnrecognizedActivityViewFactory.java b/taverna-unrecognized-activity-ui/src/main/java/net/sf/taverna/t2/activities/unrecognized/views/UnrecognizedActivityViewFactory.java
new file mode 100644
index 0000000..9388016
--- /dev/null
+++ b/taverna-unrecognized-activity-ui/src/main/java/net/sf/taverna/t2/activities/unrecognized/views/UnrecognizedActivityViewFactory.java
@@ -0,0 +1,48 @@
+package net.sf.taverna.t2.activities.unrecognized.views;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+/**
+ * This class generates a contextual view for a UnrecognizedActivity
+ *
+ * @author alanrw
+ */
+public class UnrecognizedActivityViewFactory implements ContextualViewFactory<Activity> {
+
+ public static final URI ACTIVITY_TYPE = URI.create("http://ns.taverna.org.uk/2010/activity/unrecognized");
+
+ private ColourManager colourManager;
+
+ /**
+ * The factory can handle a UnrecognizedActivity
+ *
+ * @param object
+ * @return
+ */
+ public boolean canHandle(Object object) {
+ return object instanceof Activity && ((Activity) object).getType().equals(ACTIVITY_TYPE);
+ }
+
+ /**
+ * Return a contextual view that can display information about a UnrecognizedActivity
+ *
+ * @param activity
+ * @return
+ */
+ public List<ContextualView> getViews(Activity activity) {
+ return Arrays.asList(new ContextualView[] { new UnrecognizedContextualView(activity,
+ colourManager) });
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+}
diff --git a/taverna-unrecognized-activity-ui/src/main/java/net/sf/taverna/t2/activities/unrecognized/views/UnrecognizedContextualView.java b/taverna-unrecognized-activity-ui/src/main/java/net/sf/taverna/t2/activities/unrecognized/views/UnrecognizedContextualView.java
new file mode 100644
index 0000000..783704c
--- /dev/null
+++ b/taverna-unrecognized-activity-ui/src/main/java/net/sf/taverna/t2/activities/unrecognized/views/UnrecognizedContextualView.java
@@ -0,0 +1,56 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.activities.unrecognized.views;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.HTMLBasedActivityContextualView;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+
+/**
+ * A UnrecognizedContextualView displays information about a UnrecognizedActivity
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class UnrecognizedContextualView extends HTMLBasedActivityContextualView {
+
+ public UnrecognizedContextualView(Activity activity, ColourManager colourManager) {
+ super(activity, colourManager);
+ }
+
+ /**
+ * The table for the UnrecognizedActivity shows its ports.
+ *
+ * @return
+ */
+ @Override
+ protected String getRawTableRowsHtml() {
+ StringBuilder html = new StringBuilder();
+ html.append("<tr><th>Input Port Name</th><th>Depth</th></tr>");
+ for (InputActivityPort inputActivityPort : getActivity().getInputPorts()) {
+ html.append("<tr><td>" + inputActivityPort.getName() + "</td><td>");
+ html.append(inputActivityPort.getDepth() + "</td></tr>");
+ }
+ html.append("<tr><th>Output Port Name</th><th>Depth</th></tr>");
+ for (OutputActivityPort outputActivityPort : getActivity().getOutputPorts()) {
+ html.append("<tr><td>" + outputActivityPort.getName() + "</td><td>");
+ html.append(outputActivityPort.getDepth() + "</td></tr>");
+ }
+ return html.toString();
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Unrecognized service";
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+
+}
diff --git a/taverna-unrecognized-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-unrecognized-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..0ec5cf1
--- /dev/null
+++ b/taverna-unrecognized-activity-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.activities.unrecognized.views.UnrecognizedActivityViewFactory
diff --git a/taverna-unrecognized-activity-ui/src/main/resources/META-INF/spring/unrecognized-activity-ui-context-osgi.xml b/taverna-unrecognized-activity-ui/src/main/resources/META-INF/spring/unrecognized-activity-ui-context-osgi.xml
new file mode 100644
index 0000000..e5ec9ac
--- /dev/null
+++ b/taverna-unrecognized-activity-ui/src/main/resources/META-INF/spring/unrecognized-activity-ui-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UnrecognizedActivityViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+
+</beans:beans>
diff --git a/taverna-unrecognized-activity-ui/src/main/resources/META-INF/spring/unrecognized-activity-ui-context.xml b/taverna-unrecognized-activity-ui/src/main/resources/META-INF/spring/unrecognized-activity-ui-context.xml
new file mode 100644
index 0000000..940693e
--- /dev/null
+++ b/taverna-unrecognized-activity-ui/src/main/resources/META-INF/spring/unrecognized-activity-ui-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UnrecognizedActivityViewFactory" class="net.sf.taverna.t2.activities.unrecognized.views.UnrecognizedActivityViewFactory">
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-activity-icons-api/pom.xml b/taverna-workbench-activity-icons-api/pom.xml
new file mode 100644
index 0000000..0024571
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Activity icon manager API</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/ActivityIconManager.java b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/ActivityIconManager.java
new file mode 100644
index 0000000..d2c8fff
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/ActivityIconManager.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.activityicons;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+/**
+ * Manager for activities' icons.
+ *
+ * @author David Withers
+ */
+public interface ActivityIconManager {
+ /** Returns an icon for the Activity. */
+ Icon iconForActivity(URI activityType);
+
+ Icon iconForActivity(Activity activity);
+
+ void resetIcon(URI activityType);
+}
diff --git a/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/ActivityIconSPI.java b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/ActivityIconSPI.java
new file mode 100644
index 0000000..7270dfc
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/ActivityIconSPI.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.activityicons;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+
+/**
+ * Defines an interface for getting an icon for an Activity.
+ *
+ * @author Alex Nenadic
+ */
+public interface ActivityIconSPI {
+ /**
+ * A return value for {@link canProvideIconScore()} indicating an SPI cannot
+ * provide an icon for a given activity.
+ */
+ int NO_ICON = 0;
+
+ /**
+ * {@link DefaultActivityIcon} returns this value that will be used when an
+ * activity that has no other SPI providing an icon for. Any SPI shour
+ * return value of at least DEFAULT_ICON + 1 if they want to 'override' the
+ * default icon.
+ */
+ int DEFAULT_ICON = 10;
+
+ /**
+ * Returns a positive number if the class can provide an icon for the given
+ * activity or {@link NO_ICON} otherwise. Out of two SPIs capable of
+ * providing an icon for the same activity, the one returning a higher score
+ * will be used.
+ */
+ int canProvideIconScore(URI activityType);
+
+ /** Returns an icon for the Activity. */
+ Icon getIcon(URI activityType);
+}
diff --git a/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/DefaultActivityIcon.java b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/DefaultActivityIcon.java
new file mode 100644
index 0000000..c474e69
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/DefaultActivityIcon.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.activityicons;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+public class DefaultActivityIcon implements ActivityIconSPI {
+ private static final String ICON_RESOURCE = "/default-activity-icon.png";
+
+ @Override
+ public int canProvideIconScore(URI activityType) {
+ // For any activity we can provide a default icon
+ return DEFAULT_ICON;
+ }
+
+ @Override
+ public Icon getIcon(URI activityType) {
+ return getDefaultIcon();
+ }
+
+ public static Icon getDefaultIcon() {
+ return IconLoader.icon;
+ }
+
+ private static class IconLoader {
+ static final Icon icon = loadDefaultIcon();
+
+ private static Icon loadDefaultIcon() {
+ return new ImageIcon(
+ DefaultActivityIcon.class.getResource(ICON_RESOURCE));
+ }
+ }
+}
diff --git a/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/impl/ActivityIconManagerImpl.java b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/impl/ActivityIconManagerImpl.java
new file mode 100644
index 0000000..f8294fc
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/java/net/sf/taverna/t2/workbench/activityicons/impl/ActivityIconManagerImpl.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.activityicons.impl;
+
+import static net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI.NO_ICON;
+
+import java.net.URI;
+import java.util.List;
+import java.util.WeakHashMap;
+
+import javax.swing.Icon;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI;
+
+/**
+ * Manager for activities' icons.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class ActivityIconManagerImpl implements ActivityIconManager {
+ /** Cache of already obtained icons; maps activities to their icons*/
+ private WeakHashMap<URI, Icon> iconsMap = new WeakHashMap<>();
+
+ private List<ActivityIconSPI> activityIcons;
+
+ /** Returns an icon for the Activity. */
+ @Override
+ public Icon iconForActivity(URI activityType) {
+ Icon icon = iconsMap.get(activityType);
+ if (icon != null)
+ return icon;
+ int bestScore = NO_ICON;
+ ActivityIconSPI bestSPI = null;
+ for (ActivityIconSPI spi : activityIcons) {
+ int spiScore = spi.canProvideIconScore(activityType);
+ if (spiScore > bestScore) {
+ bestSPI = spi;
+ bestScore = spiScore;
+ }
+ }
+ if (bestSPI == null)
+ return null;
+ icon = bestSPI.getIcon(activityType);
+ iconsMap.put(activityType, icon);
+ return icon;
+ }
+
+ @Override
+ public Icon iconForActivity(Activity activity) {
+ return iconForActivity(activity.getType());
+ }
+
+ @Override
+ public void resetIcon(URI activityType) {
+ Icon icon = iconsMap.get(activityType);
+ if (icon != null)
+ iconsMap.remove(activityType);
+ iconForActivity(activityType);
+ }
+
+ public void setActivityIcons(List<ActivityIconSPI> activityIcons) {
+ this.activityIcons = activityIcons;
+ }
+}
diff --git a/taverna-workbench-activity-icons-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI b/taverna-workbench-activity-icons-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI
new file mode 100644
index 0000000..d268c81
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.activityicons.DefaultActivityIcon
\ No newline at end of file
diff --git a/taverna-workbench-activity-icons-api/src/main/resources/META-INF/spring/activity-icons-api-context-osgi.xml b/taverna-workbench-activity-icons-api/src/main/resources/META-INF/spring/activity-icons-api-context-osgi.xml
new file mode 100644
index 0000000..5c67640
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/resources/META-INF/spring/activity-icons-api-context-osgi.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="DefaultActivityIcon" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI" />
+
+ <service ref="ActivityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+
+ <list id="activityIcons" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconSPI" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-activity-icons-api/src/main/resources/META-INF/spring/activity-icons-api-context.xml b/taverna-workbench-activity-icons-api/src/main/resources/META-INF/spring/activity-icons-api-context.xml
new file mode 100644
index 0000000..93c98c4
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/resources/META-INF/spring/activity-icons-api-context.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="DefaultActivityIcon" class="net.sf.taverna.t2.workbench.activityicons.DefaultActivityIcon" />
+
+ <bean id="ActivityIconManager" class="net.sf.taverna.t2.workbench.activityicons.impl.ActivityIconManagerImpl">
+ <property name="activityIcons" ref="activityIcons" />
+ </bean>
+
+
+</beans>
diff --git a/taverna-workbench-activity-icons-api/src/main/resources/default-activity-icon.png b/taverna-workbench-activity-icons-api/src/main/resources/default-activity-icon.png
new file mode 100644
index 0000000..b7ed3e9
--- /dev/null
+++ b/taverna-workbench-activity-icons-api/src/main/resources/default-activity-icon.png
Binary files differ
diff --git a/taverna-workbench-activity-palette-api/pom.xml b/taverna-workbench-activity-palette-api/pom.xml
new file mode 100644
index 0000000..1c9ab8c
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Activity Palette API</name>
+ <description>Activity Palette API</description>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>java.awt.headless</name>
+ <value>false</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>beans</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/AbstractConfigurableServiceProvider.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/AbstractConfigurableServiceProvider.java
new file mode 100644
index 0000000..18cb176
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/AbstractConfigurableServiceProvider.java
@@ -0,0 +1,53 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+public abstract class AbstractConfigurableServiceProvider extends
+ IdentifiedObject implements ConfigurableServiceProvider {
+ protected Configuration serviceProviderConfig;
+
+ /**
+ * Construct configurable service provider.
+ *
+ * @param configTemplate
+ * Template configuration
+ */
+ public AbstractConfigurableServiceProvider(Configuration configTemplate) {
+ if (configTemplate == null)
+ throw new NullPointerException("Default config can't be null");
+ serviceProviderConfig = configTemplate;
+ }
+
+ /**
+ * Package access constructor - only used with {@link #clone()} - otherwise
+ * use {@link #AbstractConfigurableServiceProvider(Object)}
+ */
+ AbstractConfigurableServiceProvider() {
+ }
+
+ @Override
+ public AbstractConfigurableServiceProvider clone() {
+ AbstractConfigurableServiceProvider provider = (AbstractConfigurableServiceProvider) newInstance();
+ Configuration configuration = getConfiguration();
+ if (configuration != null)
+ provider.configure(configuration);
+ return provider;
+ }
+
+ @Override
+ public synchronized void configure(Configuration conf) {
+ if (conf == null)
+ throw new IllegalArgumentException("Config can't be null");
+ this.serviceProviderConfig = conf;
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return serviceProviderConfig;
+ }
+
+ @Override
+ public String toString() {
+ return getName() + " " + getConfiguration();
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/AbstractTemplateService.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/AbstractTemplateService.java
new file mode 100644
index 0000000..d4909b1
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/AbstractTemplateService.java
@@ -0,0 +1,85 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import static java.util.Collections.singleton;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.Icon;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+public abstract class AbstractTemplateService implements
+ ServiceDescriptionProvider {
+ protected TemplateServiceDescription templateService = new TemplateServiceDescription();
+
+ @Override
+ public void findServiceDescriptionsAsync(
+ FindServiceDescriptionsCallBack callBack) {
+ callBack.partialResults(singleton(templateService));
+ callBack.finished();
+ }
+
+ @Override
+ public abstract Icon getIcon();
+
+ public URI getActivityType() {
+ return null;
+ }
+
+ public abstract Configuration getActivityConfiguration();
+
+ public class TemplateServiceDescription extends ServiceDescription {
+ @Override
+ public Icon getIcon() {
+ return AbstractTemplateService.this.getIcon();
+ }
+
+ @Override
+ public String getName() {
+ return AbstractTemplateService.this.getName();
+ }
+
+ @Override
+ public List<String> getPath() {
+ return Arrays.asList(SERVICE_TEMPLATES);
+ }
+
+ @Override
+ public boolean isTemplateService() {
+ return true;
+ }
+
+ @Override
+ protected List<Object> getIdentifyingData() {
+ // Do it by object identity
+ return null;
+ }
+
+ @Override
+ public URI getActivityType() {
+ return AbstractTemplateService.this.getActivityType();
+ }
+
+ @Override
+ public Configuration getActivityConfiguration() {
+ return AbstractTemplateService.this.getActivityConfiguration();
+ }
+
+ @Override
+ public String getDescription() {
+ return AbstractTemplateService.this.getDescription();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Template service " + getName();
+ }
+
+ public String getDescription() {
+ // Default to an empty string
+ return "";
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ConfigurableServiceProvider.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ConfigurableServiceProvider.java
new file mode 100644
index 0000000..0bf01bd
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ConfigurableServiceProvider.java
@@ -0,0 +1,10 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import uk.org.taverna.scufl2.api.common.Configurable;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+public interface ConfigurableServiceProvider extends
+ ServiceDescriptionProvider, Configurable, Cloneable {
+ void configure(Configuration configuration) throws Exception;
+ Configuration getConfiguration();
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/CustomizedConfigurePanelProvider.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/CustomizedConfigurePanelProvider.java
new file mode 100644
index 0000000..8bb5331
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/CustomizedConfigurePanelProvider.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.servicedescriptions;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+public interface CustomizedConfigurePanelProvider extends
+ ConfigurableServiceProvider {
+ void createCustomizedConfigurePanel(CustomizedConfigureCallBack callBack);
+
+ interface CustomizedConfigureCallBack {
+ void newProviderConfiguration(Configuration providerConfig);
+
+ Configuration getTemplateConfig();
+
+ ServiceDescriptionRegistry getServiceDescriptionRegistry();
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/IdentifiedObject.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/IdentifiedObject.java
new file mode 100644
index 0000000..596f502
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/IdentifiedObject.java
@@ -0,0 +1,30 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import java.util.List;
+
+import net.sf.taverna.t2.lang.beans.PropertyAnnotated;
+
+public abstract class IdentifiedObject extends PropertyAnnotated {
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IdentifiedObject))
+ return false;
+ List<? extends Object> myIdentifyingData = getIdentifyingData();
+ if (myIdentifyingData == null)
+ return super.equals(obj);
+ if (!getClass().isInstance(obj) && obj.getClass().isInstance(this))
+ return false;
+ IdentifiedObject id = (IdentifiedObject) obj;
+ return myIdentifyingData.equals(id.getIdentifyingData());
+ }
+
+ @Override
+ public int hashCode() {
+ List<? extends Object> identifyingData = getIdentifyingData();
+ if (identifyingData == null)
+ return super.hashCode();
+ return identifyingData.hashCode();
+ }
+
+ protected abstract List<? extends Object> getIdentifyingData();
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescription.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescription.java
new file mode 100644
index 0000000..8551934
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescription.java
@@ -0,0 +1,80 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.t2.lang.beans.PropertyAnnotation;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public abstract class ServiceDescription extends IdentifiedObject {
+ public static final String SERVICE_TEMPLATES = "Service templates";
+ private static final String NAME = "Name";
+ private static final String SERVICE_CONFIGURATION = "Service configuration";
+ private static final String SERVICE_IMPLEMENTATION_URI = "Service implementation URI";
+ private static final String DESCRIPTION = "Description";
+ public static final String LOCAL_SERVICES = "Local services";
+
+ private String description = "";
+
+ @PropertyAnnotation(expert = true, displayName = SERVICE_IMPLEMENTATION_URI)
+ public abstract URI getActivityType();
+
+ @PropertyAnnotation(expert = true, displayName = SERVICE_CONFIGURATION)
+ public Configuration getActivityConfiguration() {
+ Configuration configuration = new Configuration();
+ configuration.setType(getActivityType().resolve("#Config"));
+ return configuration;
+ }
+
+ @PropertyAnnotation(displayName = DESCRIPTION)
+ public String getDescription() {
+ return this.description;
+ }
+
+ @PropertyAnnotation(expert = true)
+ public abstract Icon getIcon();
+
+ @PropertyAnnotation(displayName = NAME)
+ public abstract String getName();
+
+ @PropertyAnnotation(expert = true)
+ public abstract List<? extends Comparable<?>> getPath();
+
+ @PropertyAnnotation(hidden = true)
+ public boolean isTemplateService() {
+ return false;
+ }
+
+ /**
+ * @param description
+ * the description to set
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return "Service description " + getName();
+ }
+
+ /**
+ * Any additional edit that needs to be performed upon insertion of an
+ * instance of the ServiceDescription into the {@link Workflow} within the
+ * specified {@link Processor}
+ *
+ * @param dataflow
+ * @param p
+ * @param a
+ * @return
+ */
+ public Edit<?> getInsertionEdit(Workflow dataflow, Processor p, Activity a) {
+ return null;
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionProvider.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionProvider.java
new file mode 100644
index 0000000..8170819
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionProvider.java
@@ -0,0 +1,61 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import java.util.Collection;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.t2.lang.beans.PropertyAnnotation;
+
+/**
+ * A provider of service descriptions
+ *
+ * @author Stian Soiland-Reyes
+ */
+public interface ServiceDescriptionProvider {
+ /**
+ * Get all service descriptions.
+ *
+ * @param callBack
+ */
+ void findServiceDescriptionsAsync(FindServiceDescriptionsCallBack callBack);
+
+ /**
+ * @author stain
+ */
+ interface FindServiceDescriptionsCallBack {
+ void partialResults(
+ Collection<? extends ServiceDescription> serviceDescriptions);
+
+ void status(String message);
+
+ void warning(String message);
+
+ void finished();
+
+ void fail(String message, Throwable ex);
+ }
+
+ /**
+ * Name of this service description provider, for instance "BioCatalogue" or
+ * "WSDL". This name is typically used in a "Add service..." menu.
+ *
+ * @return Name of provider
+ */
+ String getName();
+
+ @PropertyAnnotation(expert = true)
+ abstract Icon getIcon();
+
+ /**
+ * @return unique id of this provider.
+ */
+ String getId();
+
+ /**
+ * Create a new copy of this service provider. It <i>need not be
+ * configured</i> at the point where it is returned.
+ *
+ * @return The copy.
+ */
+ ServiceDescriptionProvider newInstance();
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionRegistry.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionRegistry.java
new file mode 100644
index 0000000..e9b6c04
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionRegistry.java
@@ -0,0 +1,50 @@
+package net.sf.taverna.t2.servicedescriptions;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionRegistryEvent;
+
+public interface ServiceDescriptionRegistry extends
+ Observable<ServiceDescriptionRegistryEvent> {
+ void addServiceDescriptionProvider(ServiceDescriptionProvider provider);
+
+ Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProviders();
+
+ Set<ServiceDescriptionProvider> getServiceDescriptionProviders();
+
+ Set<ServiceDescriptionProvider> getServiceDescriptionProviders(
+ ServiceDescription sd);
+
+ Set<ServiceDescription> getServiceDescriptions();
+
+ ServiceDescription getServiceDescription(URI activityType);
+
+ List<ConfigurableServiceProvider> getUnconfiguredServiceProviders();
+
+ Set<ServiceDescriptionProvider> getUserAddedServiceProviders();
+
+ Set<ServiceDescriptionProvider> getUserRemovedServiceProviders();
+
+ void loadServiceProviders();
+
+ void loadServiceProviders(File serviceProvidersURL);
+
+ void loadServiceProviders(URL serviceProvidersURL);
+
+ void refresh();
+
+ void removeServiceDescriptionProvider(ServiceDescriptionProvider provider);
+
+ void saveServiceDescriptions();
+
+ void saveServiceDescriptions(File serviceDescriptionsFile);
+
+ void exportCurrentServiceDescriptions(File serviceDescriptionsFile);
+
+ boolean isDefaultSystemConfigurableProvidersLoaded();
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionsConfiguration.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionsConfiguration.java
new file mode 100644
index 0000000..7fbcbfc
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/ServiceDescriptionsConfiguration.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.servicedescriptions;
+
+import uk.org.taverna.configuration.Configurable;
+
+/**
+ * @author David Withers
+ */
+public interface ServiceDescriptionsConfiguration extends Configurable {
+ public boolean isIncludeDefaults();
+
+ public void setIncludeDefaults(boolean includeDefaults);
+
+ public boolean isRemovePermanently();
+
+ public void setRemovePermanently(boolean removePermanently);
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AbstractProviderEvent.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AbstractProviderEvent.java
new file mode 100644
index 0000000..1fd224e
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AbstractProviderEvent.java
@@ -0,0 +1,16 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public abstract class AbstractProviderEvent extends
+ ServiceDescriptionRegistryEvent {
+ private final ServiceDescriptionProvider provider;
+
+ public AbstractProviderEvent(ServiceDescriptionProvider provider) {
+ this.provider = provider;
+ }
+
+ public ServiceDescriptionProvider getProvider() {
+ return provider;
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AbstractProviderNotification.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AbstractProviderNotification.java
new file mode 100644
index 0000000..2cabf90
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AbstractProviderNotification.java
@@ -0,0 +1,18 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class AbstractProviderNotification extends AbstractProviderEvent {
+
+ private final String message;
+
+ public AbstractProviderNotification(ServiceDescriptionProvider provider, String message) {
+ super(provider);
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AddedProviderEvent.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AddedProviderEvent.java
new file mode 100644
index 0000000..6e003d7
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/AddedProviderEvent.java
@@ -0,0 +1,10 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class AddedProviderEvent extends AbstractProviderEvent {
+
+ public AddedProviderEvent(ServiceDescriptionProvider provider) {
+ super(provider);
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/PartialServiceDescriptionsNotification.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/PartialServiceDescriptionsNotification.java
new file mode 100644
index 0000000..3bd8c7f
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/PartialServiceDescriptionsNotification.java
@@ -0,0 +1,22 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import java.util.Collection;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class PartialServiceDescriptionsNotification extends
+ AbstractProviderNotification {
+ private final Collection<? extends ServiceDescription> serviceDescriptions;
+
+ public PartialServiceDescriptionsNotification(
+ ServiceDescriptionProvider provider,
+ Collection<? extends ServiceDescription> serviceDescriptions) {
+ super(provider, "Found " + serviceDescriptions.size() + " services");
+ this.serviceDescriptions = serviceDescriptions;
+ }
+
+ public Collection<? extends ServiceDescription> getServiceDescriptions() {
+ return serviceDescriptions;
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderErrorNotification.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderErrorNotification.java
new file mode 100644
index 0000000..a712124
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderErrorNotification.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class ProviderErrorNotification extends AbstractProviderNotification {
+
+ private final Throwable cause;
+
+ public ProviderErrorNotification(ServiceDescriptionProvider provider,
+ String message, Throwable cause) {
+ super(provider, message);
+ this.cause = cause;
+ }
+
+ public Throwable getCause() {
+ return cause;
+ }
+
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderStatusNotification.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderStatusNotification.java
new file mode 100644
index 0000000..f094e47
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderStatusNotification.java
@@ -0,0 +1,12 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class ProviderStatusNotification extends AbstractProviderNotification {
+
+ public ProviderStatusNotification(ServiceDescriptionProvider provider,
+ String message) {
+ super(provider, message);
+ }
+
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderUpdatingNotification.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderUpdatingNotification.java
new file mode 100644
index 0000000..e2947e1
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderUpdatingNotification.java
@@ -0,0 +1,11 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class ProviderUpdatingNotification extends AbstractProviderNotification {
+
+ public ProviderUpdatingNotification(ServiceDescriptionProvider provider) {
+ super(provider, "Updating");
+ }
+
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderWarningNotification.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderWarningNotification.java
new file mode 100644
index 0000000..d7476aa
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ProviderWarningNotification.java
@@ -0,0 +1,12 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class ProviderWarningNotification extends AbstractProviderNotification {
+
+ public ProviderWarningNotification(ServiceDescriptionProvider provider,
+ String message) {
+ super(provider, message);
+ }
+
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/RemovedProviderEvent.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/RemovedProviderEvent.java
new file mode 100644
index 0000000..a382bdf
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/RemovedProviderEvent.java
@@ -0,0 +1,10 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class RemovedProviderEvent extends AbstractProviderEvent {
+
+ public RemovedProviderEvent(ServiceDescriptionProvider provider) {
+ super(provider);
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ServiceDescriptionProvidedEvent.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ServiceDescriptionProvidedEvent.java
new file mode 100644
index 0000000..76ef22d
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ServiceDescriptionProvidedEvent.java
@@ -0,0 +1,20 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+import java.util.Set;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+
+public class ServiceDescriptionProvidedEvent extends AbstractProviderEvent {
+ private final Set<ServiceDescription> serviceDescriptions;
+
+ public ServiceDescriptionProvidedEvent(ServiceDescriptionProvider provider,
+ Set<ServiceDescription> serviceDescriptions) {
+ super(provider);
+ this.serviceDescriptions = serviceDescriptions;
+ }
+
+ public Set<ServiceDescription> getServiceDescriptions() {
+ return serviceDescriptions;
+ }
+}
diff --git a/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ServiceDescriptionRegistryEvent.java b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ServiceDescriptionRegistryEvent.java
new file mode 100644
index 0000000..0604510
--- /dev/null
+++ b/taverna-workbench-activity-palette-api/src/main/java/net/sf/taverna/t2/servicedescriptions/events/ServiceDescriptionRegistryEvent.java
@@ -0,0 +1,4 @@
+package net.sf.taverna.t2.servicedescriptions.events;
+
+public abstract class ServiceDescriptionRegistryEvent {
+}
diff --git a/taverna-workbench-activity-palette-impl/pom.xml b/taverna-workbench-activity-palette-impl/pom.xml
new file mode 100644
index 0000000..4926a94
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>activity-palette-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Activity Palette Impl</name>
+ <description>Activity Palette Implementation</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-api</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ </dependency>
+
+ <!-- TODO Remove non-test impl dependency -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-impl</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-impl</artifactId>
+ <version>${taverna.configuration.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-impl</artifactId>
+ <version>${taverna.configuration.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java
new file mode 100644
index 0000000..c5221be
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java
@@ -0,0 +1,10 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+public interface ServiceDescriptionConstants {
+ String SERVICE_PANEL_CONFIGURATION = "servicePanelConfiguration";
+ String PROVIDERS = "providers";
+ String IGNORED = "ignored";
+ String PROVIDER_ID = "providerID";
+ String CONFIGURATION = "configuration";
+ String TYPE = "type";
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java
new file mode 100644
index 0000000..0a40bda
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java
@@ -0,0 +1,167 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.IGNORED;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDERS;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDER_ID;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.TYPE;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class ServiceDescriptionDeserializer {
+ private List<ServiceDescriptionProvider> serviceDescriptionProviders;
+
+ ServiceDescriptionDeserializer(
+ List<ServiceDescriptionProvider> serviceDescriptionProviders) {
+ this.serviceDescriptionProviders = serviceDescriptionProviders;
+ }
+
+ public void deserialize(ServiceDescriptionRegistry registry,
+ File serviceDescriptionsFile) throws DeserializationException {
+ try (FileInputStream serviceDescriptionFileStream = new FileInputStream(
+ serviceDescriptionsFile)) {
+ deserialize(registry, serviceDescriptionFileStream);
+ } catch (FileNotFoundException ex) {
+ throw new DeserializationException("Could not locate file "
+ + serviceDescriptionsFile.getAbsolutePath()
+ + " containing service descriptions.");
+ } catch (IOException ex) {
+ throw new DeserializationException(
+ "Could not read stream containing service descriptions from "
+ + serviceDescriptionsFile.getAbsolutePath(), ex);
+ }
+ }
+
+ public void deserialize(ServiceDescriptionRegistry registry,
+ URL serviceDescriptionsURL) throws DeserializationException {
+ try (InputStream serviceDescriptionInputStream = serviceDescriptionsURL
+ .openStream()) {
+ deserialize(registry, serviceDescriptionInputStream);
+ } catch (FileNotFoundException ex) {
+ throw new DeserializationException("Could not open URL "
+ + serviceDescriptionsURL
+ + " containing service descriptions.");
+ } catch (IOException ex) {
+ throw new DeserializationException(
+ "Could not read stream containing service descriptions from "
+ + serviceDescriptionsURL, ex);
+ }
+ }
+
+ private static final JsonFactory factory = new JsonFactory();
+
+ private void deserialize(ServiceDescriptionRegistry registry,
+ InputStream serviceDescriptionsInputStream) throws IOException,
+ DeserializationException {
+ ObjectNode node = (ObjectNode) new ObjectMapper(factory)
+ .readTree(serviceDescriptionsInputStream);
+ List<ServiceDescriptionProvider> providers = deserializeProviders(node,
+ true);
+ for (ServiceDescriptionProvider provider : providers)
+ registry.addServiceDescriptionProvider(provider);
+ }
+
+ public Collection<? extends ServiceDescriptionProvider> deserializeDefaults(
+ ServiceDescriptionRegistry registry,
+ File defaultConfigurableServiceProvidersFile)
+ throws DeserializationException {
+ ObjectNode node;
+ try (FileInputStream serviceDescriptionStream = new FileInputStream(
+ defaultConfigurableServiceProvidersFile)) {
+ node = (ObjectNode) new ObjectMapper(factory)
+ .readTree(serviceDescriptionStream);
+ } catch (IOException e) {
+ throw new DeserializationException("Can't read "
+ + defaultConfigurableServiceProvidersFile);
+ }
+ return deserializeProviders(node, false);
+ }
+
+ private List<ServiceDescriptionProvider> deserializeProviders(
+ ObjectNode rootNode, boolean obeyIgnored)
+ throws DeserializationException {
+ List<ServiceDescriptionProvider> providers = new ArrayList<>();
+
+ ArrayNode providersNode = (ArrayNode) rootNode.get(PROVIDERS);
+ if (providersNode != null)
+ for (JsonNode provider : providersNode)
+ providers.add(deserializeProvider((ObjectNode) provider));
+
+ if (obeyIgnored) {
+ ArrayNode ignoredNode = (ArrayNode) rootNode.get(IGNORED);
+ if (ignoredNode != null)
+ for (JsonNode provider : ignoredNode)
+ providers
+ .remove(deserializeProvider((ObjectNode) provider));
+ }
+
+ return providers;
+ }
+
+ private ServiceDescriptionProvider deserializeProvider(
+ ObjectNode providerNode) throws DeserializationException {
+ String providerId = providerNode.get(PROVIDER_ID).asText().trim();
+ ServiceDescriptionProvider provider = null;
+ for (ServiceDescriptionProvider serviceProvider : serviceDescriptionProviders)
+ if (serviceProvider.getId().equals(providerId)) {
+ provider = serviceProvider;
+ break;
+ }
+ if (provider == null)
+ throw new DeserializationException(
+ "Could not find provider with id " + providerId);
+
+ /*
+ * So we know the service provider now, but we need a separate instance
+ * of that provider for each providerElem. E.g. we can have 2 or more
+ * WSDL provider elements and need to return a separate provider
+ * instance for each as they will have different configurations.
+ */
+ ServiceDescriptionProvider instance = provider.newInstance();
+
+ if (instance instanceof ConfigurableServiceProvider)
+ try {
+ Configuration config = new Configuration();
+ config.setType(URI.create(providerNode.get(TYPE).textValue()));
+ config.setJson(providerNode.get(CONFIGURATION));
+ if (config != null)
+ ((ConfigurableServiceProvider) instance).configure(config);
+ } catch (Exception e) {
+ throw new DeserializationException(
+ "Could not configure provider " + providerId
+ + " using bean " + providerNode, e);
+ }
+ return instance;
+ }
+
+ @SuppressWarnings("serial")
+ static class DeserializationException extends Exception {
+ public DeserializationException(String string) {
+ super(string);
+ }
+
+ public DeserializationException(String string, Exception ex) {
+ super(string, ex);
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java
new file mode 100644
index 0000000..9dc3f00
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java
@@ -0,0 +1,652 @@
+/*******************************************************************************
+ * 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.servicedescriptions.impl;
+
+import static java.lang.System.currentTimeMillis;
+import static java.lang.Thread.MIN_PRIORITY;
+import static java.lang.Thread.currentThread;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider.FindServiceDescriptionsCallBack;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.events.AddedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.PartialServiceDescriptionsNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderErrorNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderStatusNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderUpdatingNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderWarningNotification;
+import net.sf.taverna.t2.servicedescriptions.events.RemovedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionProvidedEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionRegistryEvent;
+import net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionDeserializer.DeserializationException;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class ServiceDescriptionRegistryImpl implements ServiceDescriptionRegistry {
+ /**
+ * If a writable property of this name on a provider exists (ie. the provider has a method
+ * setServiceDescriptionRegistry(ServiceDescriptionRegistry registry) - then this property will
+ * be set to the current registry.
+ */
+ public static final String SERVICE_DESCRIPTION_REGISTRY = "serviceDescriptionRegistry";
+ public static Logger logger = Logger.getLogger(ServiceDescriptionRegistryImpl.class);
+ public static final ThreadGroup threadGroup = new ThreadGroup("Service description providers");
+ /**
+ * Total maximum timeout while waiting for description threads to finish
+ */
+ private static final long DESCRIPTION_THREAD_TIMEOUT_MS = 3000;
+ protected static final String CONF_DIR = "conf";
+ protected static final String SERVICE_PROVIDERS_FILENAME = "service_providers.xml";
+ private static final String DEFAULT_CONFIGURABLE_SERVICE_PROVIDERS_FILENAME = "default_service_providers.xml";
+
+ private ServiceDescriptionsConfiguration serviceDescriptionsConfig;
+ private ApplicationConfiguration applicationConfiguration;
+ /**
+ * <code>false</code> until first call to {@link #loadServiceProviders()} - which is done by
+ * first call to {@link #getServiceDescriptionProviders()}.
+ */
+ private boolean hasLoadedProviders = false;
+ /**
+ * <code>true</code> while {@link #loadServiceProviders(File)},
+ * {@link #loadServiceProviders(URL)} or {@link #loadServiceProviders()} is in progress, avoids
+ * triggering {@link #saveServiceDescriptions()} on
+ * {@link #addServiceDescriptionProvider(ServiceDescriptionProvider)} calls.
+ */
+ private boolean loading = false;
+ private MultiCaster<ServiceDescriptionRegistryEvent> observers = new MultiCaster<>(this);
+ private List<ServiceDescriptionProvider> serviceDescriptionProviders;
+ private Set<ServiceDescriptionProvider> allServiceProviders;
+ private Map<ServiceDescriptionProvider, Set<ServiceDescription>> providerDescriptions = new HashMap<>();
+ private Map<ServiceDescriptionProvider, Thread> serviceDescriptionThreads = new HashMap<>();
+ /**
+ * Service providers added by the user, should be saved
+ */
+ private Set<ServiceDescriptionProvider> userAddedProviders = new HashSet<>();
+ private Set<ServiceDescriptionProvider> userRemovedProviders = new HashSet<>();
+ private Set<ServiceDescriptionProvider> defaultServiceDescriptionProviders;
+ /**
+ * File containing a list of configured ConfigurableServiceProviders which is used to get the
+ * default set of service descriptions together with those provided by AbstractTemplateServiceS.
+ * This file is located in the conf directory of the Taverna startup directory.
+ */
+ private File defaultConfigurableServiceProvidersFile;
+ private boolean defaultSystemConfigurableProvidersLoaded = false;
+
+ static {
+ threadGroup.setMaxPriority(MIN_PRIORITY);
+ }
+
+ public ServiceDescriptionRegistryImpl(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ defaultConfigurableServiceProvidersFile = new File(
+ getTavernaStartupConfigurationDirectory(),
+ DEFAULT_CONFIGURABLE_SERVICE_PROVIDERS_FILENAME);
+ }
+
+ /**
+ * Get the Taverna distribution (startup) configuration directory.
+ */
+ private File getTavernaStartupConfigurationDirectory() {
+ File distroHome = null;
+ File configDirectory = null;
+ distroHome = applicationConfiguration.getStartupDir();
+ configDirectory = new File(distroHome, "conf");
+ if (!configDirectory.exists())
+ configDirectory.mkdir();
+ return configDirectory;
+ }
+
+ private static void joinThreads(Collection<? extends Thread> threads,
+ long descriptionThreadTimeoutMs) {
+ long finishJoinBy = currentTimeMillis() + descriptionThreadTimeoutMs;
+ for (Thread thread : threads) {
+ // No shorter timeout than 1 ms (thread.join(0) waits forever!)
+ long timeout = Math.max(1, finishJoinBy - currentTimeMillis());
+ try {
+ thread.join(timeout);
+ } catch (InterruptedException e) {
+ currentThread().interrupt();
+ return;
+ }
+ if (thread.isAlive())
+ logger.debug("Thread did not finish " + thread);
+ }
+ }
+
+
+ @Override
+ public void addObserver(Observer<ServiceDescriptionRegistryEvent> observer) {
+ observers.addObserver(observer);
+ }
+
+ @Override
+ public void addServiceDescriptionProvider(ServiceDescriptionProvider provider) {
+ synchronized (this) {
+ userRemovedProviders.remove(provider);
+ if (!getDefaultServiceDescriptionProviders().contains(provider))
+ userAddedProviders.add(provider);
+ allServiceProviders.add(provider);
+ }
+
+ // Spring-like auto-config
+ try {
+ // BeanUtils should ignore this if provider does not have that property
+ BeanUtils.setProperty(provider, SERVICE_DESCRIPTION_REGISTRY, this);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ logger.warn("Could not set serviceDescriptionRegistry on "
+ + provider, e);
+ }
+
+ if (!loading)
+ saveServiceDescriptions();
+ observers.notify(new AddedProviderEvent(provider));
+ updateServiceDescriptions(false, false);
+ }
+
+ private File findServiceDescriptionsFile() {
+ File confDir = new File(
+ applicationConfiguration.getApplicationHomeDir(), CONF_DIR);
+ confDir.mkdirs();
+ if (!confDir.isDirectory())
+ throw new RuntimeException("Invalid directory: " + confDir);
+ File serviceDescriptionsFile = new File(confDir,
+ SERVICE_PROVIDERS_FILENAME);
+ return serviceDescriptionsFile;
+ }
+
+ @Override
+ public List<Observer<ServiceDescriptionRegistryEvent>> getObservers() {
+ return observers.getObservers();
+ }
+
+ // Fallback to this method that uses hardcoded default services if you cannot read them from
+ // the file.
+// @SuppressWarnings("unchecked")
+// public synchronized Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProvidersFallback() {
+// /*if (defaultServiceDescriptionProviders != null) {
+// return defaultServiceDescriptionProviders;
+// }
+// defaultServiceDescriptionProviders = new HashSet<ServiceDescriptionProvider>();
+// */
+// for (ServiceDescriptionProvider provider : serviceDescriptionProviders) {
+//
+// /* We do not need these - already loaded them from getDefaultServiceDescriptionProviders()
+// if (!(provider instanceof ConfigurableServiceProvider)) {
+// defaultServiceDescriptionProviders.add(provider);
+// continue;
+// }*/
+//
+// // Just load the hard coded default configurable service providers
+// if (provider instanceof ConfigurableServiceProvider){
+// ConfigurableServiceProvider<Object> template = ((ConfigurableServiceProvider<Object>)
+// provider);
+// // Get configurations
+// List<Object> configurables = template.getDefaultConfigurations();
+// for (Object config : configurables) {
+// // Make a copy that we can configure
+// ConfigurableServiceProvider<Object> configurableProvider = template.clone();
+// try {
+// configurableProvider.configure(config);
+// } catch (ConfigurationException e) {
+// logger.warn("Can't configure provider "
+// + configurableProvider + " with " + config);
+// continue;
+// }
+// defaultServiceDescriptionProviders.add(configurableProvider);
+// }
+// }
+// }
+// return defaultServiceDescriptionProviders;
+// }
+
+ // Get the default services.
+ @Override
+ public synchronized Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProviders() {
+ if (defaultServiceDescriptionProviders != null)
+ return defaultServiceDescriptionProviders;
+ defaultServiceDescriptionProviders = new HashSet<>();
+
+ /*
+ * Add default configurable service description providers from the
+ * default_service_providers.xml file
+ */
+ if (defaultConfigurableServiceProvidersFile.exists()) {
+ try {
+ ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+ serviceDescriptionProviders);
+ defaultServiceDescriptionProviders.addAll(deserializer
+ .deserializeDefaults(this,
+ defaultConfigurableServiceProvidersFile));
+ /*
+ * We have successfully loaded the defaults for system
+ * configurable providers. Note that there are still defaults
+ * for third party configurable providers, which will be loaded
+ * below using getDefaultConfigurations().
+ */
+ defaultSystemConfigurableProvidersLoaded = true;
+ } catch (Exception e) {
+ logger.error("Could not load default service providers from "
+ + defaultConfigurableServiceProvidersFile.getAbsolutePath(), e);
+
+ /*
+ * Fallback on the old hardcoded method of loading default
+ * system configurable service providers using
+ * getDefaultConfigurations().
+ */
+ defaultSystemConfigurableProvidersLoaded = false;
+ }
+ } else {
+ logger.warn("Could not find the file "
+ + defaultConfigurableServiceProvidersFile.getAbsolutePath()
+ + " containing default system service providers. "
+ + "Using the hardcoded list of default system providers.");
+
+ /*
+ * Fallback on the old hardcoded method of loading default system
+ * configurable service providers using getDefaultConfigurations().
+ */
+ defaultSystemConfigurableProvidersLoaded = false;
+ }
+
+ /*
+ * Load other default service description providers - template, local
+ * workers and third party configurable service providers
+ */
+ for (ServiceDescriptionProvider provider : serviceDescriptionProviders) {
+ /*
+ * Template service providers (beanshell, string constant, etc. )
+ * and providers of local workers.
+ */
+ if (!(provider instanceof ConfigurableServiceProvider)) {
+ defaultServiceDescriptionProviders.add(provider);
+ continue;
+ }
+
+ /*
+ * Default system or third party configurable service description
+ * provider. System ones are read from the
+ * default_service_providers.xml file so getDefaultConfigurations()
+ * on them will not have much effect here unless
+ * defaultSystemConfigurableProvidersLoaded is set to false.
+ */
+ //FIXME needs to be designed to work using Configuration instances
+ //FIXME needs to get configurations via OSGi discovery
+ /*
+ ConfigurableServiceProvider template = (ConfigurableServiceProvider) provider;
+ // Get configurations
+ for (ObjectNode config : template.getDefaultConfigurations()) {
+ // Make a copy that we can configure
+ ConfigurableServiceProvider configurableProvider = template.clone();
+ try {
+ configurableProvider.configure(config);
+ } catch (ConfigurationException e) {
+ logger.warn("Can't configure provider "
+ + configurableProvider + " with " + config);
+ continue;
+ }
+ defaultServiceDescriptionProviders.add(configurableProvider);
+ }
+ */
+ }
+
+ return defaultServiceDescriptionProviders;
+ }
+
+ @Override
+ public synchronized Set<ServiceDescriptionProvider> getServiceDescriptionProviders() {
+ if (allServiceProviders != null)
+ return new HashSet<>(allServiceProviders);
+ allServiceProviders = new HashSet<>(userAddedProviders);
+ synchronized (this) {
+ if (!hasLoadedProviders)
+ try {
+ loadServiceProviders();
+ } catch (Exception e) {
+ logger.error("Could not load service providers", e);
+ } finally {
+ hasLoadedProviders = true;
+ }
+ }
+ for (ServiceDescriptionProvider provider : getDefaultServiceDescriptionProviders()) {
+ if (userRemovedProviders.contains(provider))
+ continue;
+ if (provider instanceof ConfigurableServiceProvider
+ && !serviceDescriptionsConfig.isIncludeDefaults())
+ // We'll skip the default configurable service provders
+ continue;
+ allServiceProviders.add(provider);
+ }
+ return new HashSet<>(allServiceProviders);
+ }
+
+ @Override
+ public Set<ServiceDescriptionProvider> getServiceDescriptionProviders(
+ ServiceDescription sd) {
+ Set<ServiceDescriptionProvider> result = new HashSet<>();
+ for (ServiceDescriptionProvider sdp : providerDescriptions.keySet())
+ if (providerDescriptions.get(sdp).contains(sd))
+ result.add(sdp);
+ return result;
+ }
+
+ @Override
+ public Set<ServiceDescription> getServiceDescriptions() {
+ updateServiceDescriptions(false, true);
+ Set<ServiceDescription> serviceDescriptions = new HashSet<>();
+ synchronized (providerDescriptions) {
+ for (Set<ServiceDescription> providerDesc : providerDescriptions
+ .values())
+ serviceDescriptions.addAll(providerDesc);
+ }
+ return serviceDescriptions;
+ }
+
+ @Override
+ public ServiceDescription getServiceDescription(URI serviceType) {
+ for (ServiceDescription serviceDescription : getServiceDescriptions())
+ if (serviceDescription.getActivityType().equals(serviceType))
+ return serviceDescription;
+ return null;
+ }
+
+ @Override
+ public List<ConfigurableServiceProvider> getUnconfiguredServiceProviders() {
+ List<ConfigurableServiceProvider> providers = new ArrayList<>();
+ for (ServiceDescriptionProvider provider : serviceDescriptionProviders)
+ if (provider instanceof ConfigurableServiceProvider)
+ providers.add((ConfigurableServiceProvider) provider);
+ return providers;
+ }
+
+ @Override
+ public Set<ServiceDescriptionProvider> getUserAddedServiceProviders() {
+ return new HashSet<>(userAddedProviders);
+ }
+
+ @Override
+ public Set<ServiceDescriptionProvider> getUserRemovedServiceProviders() {
+ return new HashSet<>(userRemovedProviders);
+ }
+
+ @Override
+ public void loadServiceProviders() {
+ File serviceProviderFile = findServiceDescriptionsFile();
+ if (serviceProviderFile.isFile())
+ loadServiceProviders(serviceProviderFile);
+ hasLoadedProviders = true;
+ }
+
+ @Override
+ public void loadServiceProviders(File serviceProvidersFile) {
+ ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+ serviceDescriptionProviders);
+ loading = true;
+ try {
+ deserializer.deserialize(this, serviceProvidersFile);
+ } catch (DeserializationException e) {
+ logger.error("failed to deserialize configuration", e);
+ }
+ loading = false;
+ }
+
+ @Override
+ public void loadServiceProviders(URL serviceProvidersURL) {
+ ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+ serviceDescriptionProviders);
+ loading = true;
+ try {
+ deserializer.deserialize(this, serviceProvidersURL);
+ } catch (DeserializationException e) {
+ logger.error("failed to deserialize configuration", e);
+ }
+ loading = false;
+ }
+
+ @Override
+ public void refresh() {
+ updateServiceDescriptions(true, false);
+ }
+
+ @Override
+ public void removeObserver(Observer<ServiceDescriptionRegistryEvent> observer) {
+ observers.removeObserver(observer);
+ }
+
+ @Override
+ public synchronized void removeServiceDescriptionProvider(
+ ServiceDescriptionProvider provider) {
+ if (!userAddedProviders.remove(provider))
+ // Not previously added - must be a default one.. but should we remove it?
+ if (loading || serviceDescriptionsConfig.isRemovePermanently()
+ && serviceDescriptionsConfig.isIncludeDefaults())
+ userRemovedProviders.add(provider);
+ if (allServiceProviders.remove(provider)) {
+ synchronized (providerDescriptions) {
+ Thread thread = serviceDescriptionThreads.remove(provider);
+ if (thread != null)
+ thread.interrupt();
+ providerDescriptions.remove(provider);
+ }
+ observers.notify(new RemovedProviderEvent(provider));
+ }
+ if (!loading)
+ saveServiceDescriptions();
+ }
+
+ @Override
+ public void saveServiceDescriptions() {
+ File serviceDescriptionsFile = findServiceDescriptionsFile();
+ saveServiceDescriptions(serviceDescriptionsFile);
+ }
+
+ @Override
+ public void saveServiceDescriptions(File serviceDescriptionsFile) {
+ ServiceDescriptionSerializer serializer = new ServiceDescriptionSerializer();
+ try {
+ serializer.serializeRegistry(this, serviceDescriptionsFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Can't save service descriptions to "
+ + serviceDescriptionsFile);
+ }
+ }
+
+ /**
+ * Exports all configurable service providers (that give service
+ * descriptions) currently found in the Service Registry (apart from service
+ * templates and local services) regardless of who added them (user or
+ * default system providers).
+ * <p>
+ * Unlike {@link #saveServiceDescriptions}, this export does not have the
+ * "ignored providers" section as this is just a plain export of everything
+ * in the Service Registry.
+ *
+ * @param serviceDescriptionsFile
+ */
+ @Override
+ public void exportCurrentServiceDescriptions(File serviceDescriptionsFile) {
+ ServiceDescriptionSerializer serializer = new ServiceDescriptionSerializer();
+ try {
+ serializer.serializeFullRegistry(this, serviceDescriptionsFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Could not save service descriptions to "
+ + serviceDescriptionsFile);
+ }
+ }
+
+ public void setServiceDescriptionProvidersList(
+ List<ServiceDescriptionProvider> serviceDescriptionProviders) {
+ this.serviceDescriptionProviders = serviceDescriptionProviders;
+ }
+
+ private void updateServiceDescriptions(boolean refreshAll, boolean waitFor) {
+ List<Thread> threads = new ArrayList<>();
+ for (ServiceDescriptionProvider provider : getServiceDescriptionProviders()) {
+ synchronized (providerDescriptions) {
+ if (providerDescriptions.containsKey(provider) && !refreshAll)
+ // We'll used the cached values
+ continue;
+ Thread oldThread = serviceDescriptionThreads.get(provider);
+ if (oldThread != null && oldThread.isAlive()) {
+ if (refreshAll)
+ // New thread will override the old thread
+ oldThread.interrupt();
+ else {
+ // observers.notify(new ProviderStatusNotification(provider, "Waiting for provider"));
+ continue;
+ }
+ }
+ // Not run yet - we'll start a new tread
+ Thread thread = new FindServiceDescriptionsThread(provider);
+ threads.add(thread);
+ serviceDescriptionThreads.put(provider, thread);
+ thread.start();
+ }
+ }
+ if (waitFor)
+ joinThreads(threads, DESCRIPTION_THREAD_TIMEOUT_MS);
+ }
+
+ @Override
+ public boolean isDefaultSystemConfigurableProvidersLoaded() {
+ return defaultSystemConfigurableProvidersLoaded;
+ }
+
+ /**
+ * Sets the serviceDescriptionsConfig.
+ *
+ * @param serviceDescriptionsConfig
+ * the new value of serviceDescriptionsConfig
+ */
+ public void setServiceDescriptionsConfig(
+ ServiceDescriptionsConfiguration serviceDescriptionsConfig) {
+ this.serviceDescriptionsConfig = serviceDescriptionsConfig;
+ }
+
+ class FindServiceDescriptionsThread extends Thread implements
+ UncaughtExceptionHandler, FindServiceDescriptionsCallBack {
+ private final ServiceDescriptionProvider provider;
+ private boolean aborting = false;
+ private final Set<ServiceDescription> providerDescs = new HashSet<>();
+
+ FindServiceDescriptionsThread(ServiceDescriptionProvider provider) {
+ super(threadGroup, "Find service descriptions from " + provider);
+ this.provider = provider;
+ setUncaughtExceptionHandler(this);
+ setDaemon(true);
+ }
+
+ @Override
+ public void fail(String message, Throwable ex) {
+ logger.warn("Provider " + getProvider() + ": " + message, ex);
+ if (aborting)
+ return;
+ observers.notify(new ProviderErrorNotification(getProvider(),
+ message, ex));
+ }
+
+ @Override
+ public void finished() {
+ if (aborting)
+ return;
+ synchronized (providerDescriptions) {
+ providerDescriptions.put(getProvider(), providerDescs);
+ }
+ observers.notify(new ServiceDescriptionProvidedEvent(getProvider(),
+ providerDescs));
+ }
+
+ @Override
+ public void partialResults(
+ Collection<? extends ServiceDescription> serviceDescriptions) {
+ if (aborting)
+ return;
+ providerDescs.addAll(serviceDescriptions);
+ synchronized (providerDescriptions) {
+ providerDescriptions.put(getProvider(), providerDescs);
+ }
+ observers.notify(new PartialServiceDescriptionsNotification(
+ getProvider(), serviceDescriptions));
+ }
+
+ @Override
+ public void status(String message) {
+ logger.debug("Provider " + getProvider() + ": " + message);
+ if (aborting)
+ return;
+ observers.notify(new ProviderStatusNotification(getProvider(),
+ message));
+ }
+
+ @Override
+ public void warning(String message) {
+ logger.warn("Provider " + getProvider() + ": " + message);
+ if (aborting)
+ return;
+ observers.notify(new ProviderWarningNotification(getProvider(),
+ message));
+ }
+
+ public ServiceDescriptionProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public void interrupt() {
+ aborting = true;
+ super.interrupt();
+ }
+
+ @Override
+ public void run() {
+ observers.notify(new ProviderUpdatingNotification(provider));
+ getProvider().findServiceDescriptionsAsync(this);
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable ex) {
+ logger.error("Uncaught exception in " + t, ex);
+ fail("Uncaught exception", ex);
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java
new file mode 100644
index 0000000..8a047a3
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java
@@ -0,0 +1,102 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.IGNORED;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDERS;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDER_ID;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.SERVICE_PANEL_CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.TYPE;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Set;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import org.apache.log4j.Logger;
+import org.jdom.JDOMException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class ServiceDescriptionSerializer {
+ private static Logger logger = Logger
+ .getLogger(ServiceDescriptionSerializer.class);
+
+ public void serializeRegistry(ServiceDescriptionRegistry registry, File file)
+ throws IOException {
+ Set<ServiceDescriptionProvider> ignoreProviders = registry
+ .getUserRemovedServiceProviders();
+ JsonNode registryElement = serializeRegistry(registry, ignoreProviders);
+ try (BufferedOutputStream bufferedOutStream = new BufferedOutputStream(
+ new FileOutputStream(file))) {
+ bufferedOutStream.write(registryElement.toString()
+ .getBytes("UTF-8"));
+ }
+ }
+
+ /**
+ * Export the whole service registry to an xml file, regardless of who added
+ * the service provider (user or system default). In this case there will be
+ * no "ignored providers" in the saved file.
+ */
+ public void serializeFullRegistry(ServiceDescriptionRegistry registry,
+ File file) throws IOException {
+ JsonNode registryElement = serializeRegistry(registry, ALL_PROVIDERS);
+ try (BufferedOutputStream bufferedOutStream = new BufferedOutputStream(
+ new FileOutputStream(file))) {
+ bufferedOutStream.write(registryElement.toString()
+ .getBytes("UTF-8"));
+ }
+ }
+
+ private static final JsonNodeFactory factory = JsonNodeFactory.instance;
+ private static final Set<ServiceDescriptionProvider> ALL_PROVIDERS = null;
+
+ private JsonNode serializeRegistry(ServiceDescriptionRegistry registry,
+ Set<ServiceDescriptionProvider> ignoreProviders) {
+ ObjectNode overallConfiguration = factory.objectNode();
+ overallConfiguration.put(SERVICE_PANEL_CONFIGURATION,
+ ignoreProviders != ALL_PROVIDERS ? "full" : "defaults only");
+ ArrayNode providers = overallConfiguration.putArray(PROVIDERS);
+
+ for (ServiceDescriptionProvider provider : registry
+ .getUserAddedServiceProviders())
+ try {
+ providers.add(serializeProvider(provider));
+ } catch (JDOMException | IOException e) {
+ logger.warn("Could not serialize " + provider, e);
+ }
+
+ if (ignoreProviders != ALL_PROVIDERS) {
+ ArrayNode ignored = overallConfiguration.putArray(IGNORED);
+ for (ServiceDescriptionProvider provider : ignoreProviders)
+ try {
+ ignored.add(serializeProvider(provider));
+ } catch (JDOMException | IOException e) {
+ logger.warn("Could not serialize " + provider, e);
+ }
+ }
+
+ return overallConfiguration;
+ }
+
+ private JsonNode serializeProvider(ServiceDescriptionProvider provider)
+ throws JDOMException, IOException {
+ ObjectNode node = factory.objectNode();
+ node.put(PROVIDER_ID, provider.getId());
+
+ if (provider instanceof ConfigurableServiceProvider) {
+ ConfigurableServiceProvider configurable = (ConfigurableServiceProvider) provider;
+ node.put(TYPE, configurable.getConfiguration().getType().toString());
+ node.put(CONFIGURATION, configurable.getConfiguration().getJson());
+ }
+ return node;
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java
new file mode 100644
index 0000000..ee180a7
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java
@@ -0,0 +1,15 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import org.jdom.Namespace;
+
+public interface ServiceDescriptionXMLConstants {
+
+ public static final Namespace SERVICE_DESCRIPTION_NS = Namespace
+ .getNamespace("http://taverna.sf.net/2009/xml/servicedescription");
+ public static final String PROVIDER = "provider";
+ public static final String PROVIDERS = "providers";
+ public static final String SERVICE_DESCRIPTIONS = "serviceDescriptions";
+ public static final String IGNORED_PROVIDERS = "ignoredProviders";
+ public static final String PROVIDER_IDENTIFIER = "providerId";
+
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java
new file mode 100644
index 0000000..ef0295c
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * 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.servicedescriptions.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class ServiceDescriptionsConfigurationImpl extends AbstractConfigurable
+ implements ServiceDescriptionsConfiguration {
+ private static final String INCLUDE_DEFAULTS = "includeDefaults";
+ private static final String SERVICE_PALETTE = "Service providers";
+ private static final String SERVICE_PALETTE_PREFIX = "ServiceProviders";
+ private static final String CATEGORY = "Services";
+ private static final String UUID = "f0d1ef24-9337-412f-b2c3-220a01e2efd0";
+ private static final String REMOVE_PERMANENTLY = "removePermanently";
+
+ public ServiceDescriptionsConfigurationImpl(
+ ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return CATEGORY;
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ Map<String, String> defaults = new HashMap<String, String>();
+ defaults.put(INCLUDE_DEFAULTS, "true");
+ defaults.put(REMOVE_PERMANENTLY, "true");
+ return defaults;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return SERVICE_PALETTE;
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return SERVICE_PALETTE_PREFIX;
+ }
+
+ @Override
+ public String getUUID() {
+ return UUID;
+ }
+
+ @Override
+ public boolean isIncludeDefaults() {
+ return Boolean.parseBoolean(getProperty(INCLUDE_DEFAULTS));
+ }
+
+ @Override
+ public void setIncludeDefaults(boolean includeDefaults) {
+ setProperty(INCLUDE_DEFAULTS, Boolean.toString(includeDefaults));
+ }
+
+ @Override
+ public boolean isRemovePermanently() {
+ return Boolean.parseBoolean(getProperty(REMOVE_PERMANENTLY));
+ }
+
+ @Override
+ public void setRemovePermanently(boolean removePermanently) {
+ setProperty(REMOVE_PERMANENTLY, Boolean.toString(removePermanently));
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java
new file mode 100644
index 0000000..46f82c4
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.ui.activitypalette;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class ActivityPaletteConfiguration extends AbstractConfigurable {
+ private Map<String,String> defaultPropertyMap;
+
+ public ActivityPaletteConfiguration(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return "Services";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<>();
+
+ // //wsdl
+ //defaultPropertyMap.put("taverna.defaultwsdl", "http://www.ebi.ac.uk/xembl/XEMBL.wsdl,"+
+ // "http://soap.genome.jp/KEGG.wsdl,"+
+ // "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/soap/eutils.wsdl,"+
+ // "http://soap.bind.ca/wsdl/bind.wsdl,"+
+ // "http://www.ebi.ac.uk/ws/services/urn:Dbfetch?wsdl");
+
+ // //soaplab
+ //defaultPropertyMap.put("taverna.defaultsoaplab", "http://www.ebi.ac.uk/soaplab/services/");
+
+ // //biomart
+ //defaultPropertyMap.put("taverna.defaultmartregistry","http://www.biomart.org/biomart");
+
+ //add property names
+ //defaultPropertyMap.put("name.taverna.defaultwsdl", "WSDL");
+ //defaultPropertyMap.put("name.taverna.defaultsoaplab","Soaplab");
+ //defaultPropertyMap.put("name.taverna.defaultmartregistry", "Biomart");
+ }
+ return defaultPropertyMap;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Activity Palette";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "ActivityPalette";
+ }
+
+ @Override
+ public String getUUID() {
+ return "ad9f3a60-5967-11dd-ae16-0800200c9a66";
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java
new file mode 100644
index 0000000..6166e60
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java
@@ -0,0 +1,284 @@
+/*******************************************************************************
+ * 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.ui.activitypalette;
+
+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.FlowLayout.LEFT;
+import static java.awt.FlowLayout.RIGHT;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.border.BevelBorder.LOWERED;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BoxLayout;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.border.BevelBorder;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ActivityPaletteConfigurationPanel extends JPanel {
+ private static Logger logger = Logger
+ .getLogger(ActivityPaletteConfigurationPanel.class);
+
+ private Map<String,List<String>> values = new HashMap<>();
+ private Map<String,String> names = new HashMap<>();
+ private DefaultComboBoxModel<String> model;
+ private DefaultListModel<String> listModel;
+ private JList<String> propertyListItems;
+ private String selectedKey;
+ private JButton deleteTypeButton;
+ private final ActivityPaletteConfiguration config;
+
+ public ActivityPaletteConfigurationPanel(ActivityPaletteConfiguration config) {
+ super(new BorderLayout());
+ this.config = config;
+
+ model = new DefaultComboBoxModel<>();
+ for (String key : config.getInternalPropertyMap().keySet()) {
+ if (key.startsWith("taverna.")
+ && config.getPropertyStringList(key) != null) {
+ model.addElement(key);
+ values.put(key,
+ new ArrayList<>(config.getPropertyStringList(key)));
+ }
+ if (key.startsWith("name.taverna."))
+ names.put(key, config.getProperty(key).toString());
+ }
+ deleteTypeButton = new JButton("Delete");
+
+ final JButton addTypeButton = new JButton("Add");
+ final JComboBox<String> comboBox = new JComboBox<>(model);
+ comboBox.setRenderer(new DefaultListCellRenderer() {
+ @Override
+ public Component getListCellRendererComponent(JList<?> list,
+ Object value, int index, boolean isSelected,
+ boolean cellHasFocus) {
+ if (value != null && value instanceof String) {
+ String name = names.get("name." + value);
+ if (name != null)
+ value = name;
+ }
+ return super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ }
+ });
+
+ deleteTypeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String displayText = names.get("name." + selectedKey);
+ if (displayText == null)
+ displayText = selectedKey;
+ if (confirm("Confirm removal",
+ "Are you sure you wish to remove the type "
+ + displayText + "?")) {
+ names.remove("name." + selectedKey);
+ values.remove(selectedKey);
+ model.removeElement(selectedKey);
+ comboBox.setSelectedIndex(0);
+ }
+ }
+ });
+
+ addTypeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String key = input("New key", "Provide the new key.");
+ if (key == null)
+ return;
+ String name = input("Name for the key",
+ "Provide the name for the key: " + key);
+ if (name == null)
+ return;
+
+ values.put(key, new ArrayList<String>());
+ names.put("name." + key, name);
+ model.addElement(key);
+ comboBox.setSelectedItem(key);
+ }
+ });
+
+ comboBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (comboBox.getSelectedItem() != null
+ && comboBox.getSelectedItem() instanceof String) {
+ selectedKey = (String) comboBox.getSelectedItem();
+ List<String> selectedList = values.get(selectedKey);
+ populateList(selectedList);
+ deleteTypeButton.setEnabled(selectedList.size() == 0);
+ }
+ }
+ });
+
+ JPanel propertySelectionPanel = new JPanel(new FlowLayout(LEFT));
+ propertySelectionPanel.add(new JLabel("Activity type:"));
+ propertySelectionPanel.add(comboBox);
+ propertySelectionPanel.add(addTypeButton);
+ propertySelectionPanel.add(deleteTypeButton);
+ add(propertySelectionPanel, NORTH);
+
+ JPanel listPanel = new JPanel(new BorderLayout());
+ listModel = new DefaultListModel<>();
+ propertyListItems = new JList<>(listModel);
+ propertyListItems.setBorder(new BevelBorder(LOWERED));
+
+ listPanel.add(propertyListItems, CENTER);
+ listPanel.add(listButtons(), EAST);
+
+ add(listPanel, CENTER);
+
+ add(applyButtonPanel(), SOUTH);
+
+ if (model.getSize() > 0)
+ comboBox.setSelectedItem(model.getElementAt(0));
+ }
+
+ private void populateList(List<String> selectedList) {
+ listModel.removeAllElements();
+ for (String item : selectedList)
+ listModel.addElement(item);
+ }
+
+ private JPanel applyButtonPanel() {
+ JPanel applyPanel = new JPanel(new FlowLayout(RIGHT));
+ JButton applyButton = new JButton("Apply");
+
+ applyButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ config.getInternalPropertyMap().clear();
+ for (String key : values.keySet()) {
+ List<String> properties = values.get(key);
+ config.setPropertyStringList(key, new ArrayList<>(
+ properties));
+ }
+ for (String key : names.keySet())
+ config.setProperty(key, names.get(key));
+ store();
+ }
+ });
+
+ applyPanel.add(applyButton);
+ return applyPanel;
+ }
+
+ private void store() {
+ try {
+ //FIXME
+ //ConfigurationManager.getInstance().store(config);
+ } catch (Exception e1) {
+ logger.error("There was an error storing the configuration:"
+ + config.getFilePrefix() + " (UUID=" + config.getUUID()
+ + ")", e1);
+ }
+ }
+
+ private JPanel listButtons() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, Y_AXIS));
+ JButton addButton = new JButton("+");
+ addButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String value = input("New property", "Provide new value for: "
+ + selectedKey);
+ if (value != null) {
+ listModel.addElement(value);
+ values.get(selectedKey).add(value);
+ deleteTypeButton.setEnabled(false);
+ }
+ }
+ });
+
+ JButton deleteButton = new JButton("-");
+ deleteButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Object value = propertyListItems.getSelectedValue();
+ if (confirm("Confirm removal",
+ "Are you sure you wish to remove " + value + "?")) {
+ listModel.removeElement(value);
+ values.get(selectedKey).remove(value);
+ if (values.get(selectedKey).size() == 0)
+ deleteTypeButton.setEnabled(true);
+ }
+ }
+ });
+
+ panel.add(addButton);
+ panel.add(deleteButton);
+
+ return panel;
+ }
+
+ private boolean confirm(String title, String message) {
+ return showConfirmDialog(this, message, title, YES_NO_OPTION,
+ WARNING_MESSAGE) == YES_OPTION;
+ }
+
+ private String input(String title, String message) {
+ return showInputDialog(this, message, title, INFORMATION_MESSAGE);
+ }
+
+/* private JButton getAddTypeButton() {
+ JButton result = new JButton("Add");
+ result.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String val = input("New property value","New property value");
+ if (val!=null) {
+ if (values.get(val) == null) {
+ model.addElement(val);
+ values.put(val, new ArrayList<String>());
+ } else
+ showMessageDialog(ActivityPaletteConfigurationPanel.this, "This property already exists");
+ }
+ }
+ });
+ return result;
+ }
+*/
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java
new file mode 100644
index 0000000..39c4a5a
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.ui.activitypalette;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+public class ActivityPaletteConfigurationUIFactory implements
+ ConfigurationUIFactory {
+ private ActivityPaletteConfiguration activityPaletteConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid != null && uuid.equals(getConfigurable().getUUID());
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return activityPaletteConfiguration;
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new ActivityPaletteConfigurationPanel(
+ activityPaletteConfiguration);
+ }
+
+ public void setActivityPaletteConfiguration(
+ ActivityPaletteConfiguration activityPaletteConfiguration) {
+ this.activityPaletteConfiguration = activityPaletteConfiguration;
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..6c9fed9
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+#net.sf.taverna.t2.workbench.ui.activitypalette.ActivityPaletteConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml
new file mode 100644
index 0000000..34921f5
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ServiceDescriptionRegistryImpl" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry"/>
+ <service ref="ServiceDescriptionsConfigurationImpl" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration"/>
+
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+ <list id="serviceDescriptionProviders" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider" cardinality="0..N" greedy-proxying="true"/>
+
+</beans:beans>
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml
new file mode 100644
index 0000000..9f7110f
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean name="ServiceDescriptionRegistryImpl" class="net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionRegistryImpl">
+ <constructor-arg name="applicationConfiguration" ref="applicationConfiguration" />
+ <property name="serviceDescriptionProvidersList" ref="serviceDescriptionProviders" />
+ <property name="serviceDescriptionsConfig">
+ <ref local="ServiceDescriptionsConfigurationImpl"/>
+ </property>
+ </bean>
+
+ <bean id="ServiceDescriptionsConfigurationImpl" class="net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionsConfigurationImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+
+ <!-- Don't think ActivityPalette is still used -->
+ <!-- <bean id="ActivityPaletteConfiguration" class="net.sf.taverna.t2.workbench.ui.activitypalette.ActivityPaletteConfiguration">
+ <constructor-arg ref="configurationManager"/>
+ </bean> -->
+
+</beans>
diff --git a/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java b/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java
new file mode 100644
index 0000000..081a9af
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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.ui.activitypalette;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ActivityPaletteConfigurationTest {
+
+ private ActivityPaletteConfiguration conf;
+ private ConfigurationManagerImpl manager;
+
+ @Before
+ public void setup() {
+ File f = new File(System.getProperty("java.io.tmpdir"));
+ final File d = new File(f,UUID.randomUUID().toString());
+ d.mkdir();
+ manager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl() {
+ @Override
+ public File getApplicationHomeDir() {
+ return d;
+ }
+ });
+ conf=new ActivityPaletteConfiguration(manager);
+ conf.restoreDefaults();
+ }
+
+ @Test
+ public void testEmptyList() throws Exception {
+ conf.setPropertyStringList("list", new ArrayList<String>());
+ assertTrue("Result was not a list but was:"+conf.getProperty("list"),conf.getPropertyStringList("list") instanceof List);
+ assertTrue("Result was not a list but was:"+conf.getPropertyStringList("list"),conf.getPropertyStringList("list") instanceof List);
+ List<String> list = conf.getPropertyStringList("list");
+ assertEquals("There should be 0 elements",0,list.size());
+ }
+
+ @Test
+ public void testSingleItem() throws Exception {
+ List<String> list = new ArrayList<>();
+ list.add("fred");
+ conf.setPropertyStringList("single", list);
+
+ assertTrue("should be an ArrayList",conf.getPropertyStringList("single") instanceof List);
+ List<String> l = conf.getPropertyStringList("single");
+ assertEquals("There should be 1 element",1,l.size());
+ assertEquals("Its value should be fred","fred",l.get(0));
+ }
+
+ @Test
+ public void testList() throws Exception {
+ List<String> list = new ArrayList<>();
+ list.add("fred");
+ list.add("bloggs");
+ conf.setPropertyStringList("list", list);
+
+ assertTrue("should be an ArrayList",conf.getPropertyStringList("list") instanceof List);
+ List<String> l = conf.getPropertyStringList("list");
+ assertEquals("There should be 1 element",2,l.size());
+ assertEquals("Its value should be fred","fred",l.get(0));
+ assertEquals("Its value should be bloggs","bloggs",l.get(1));
+ }
+
+ @Test
+ public void testNull() throws Exception {
+ assertNull("Should return null",conf.getProperty("blah blah blah"));
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI
new file mode 100644
index 0000000..9f3c02d
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.partition.DummyPartitionAlgorithmSet
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI
new file mode 100644
index 0000000..f5e7226
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.partition.DummyExtractor1
+net.sf.taverna.t2.partition.DummyExtractor2
+net.sf.taverna.t2.partition.DummyExtractor3
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory
new file mode 100644
index 0000000..2d26d31a
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.partition.DummyActivityQueryFactory
+net.sf.taverna.t2.partition.DummyQueryFactory
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-ui/pom.xml b/taverna-workbench-activity-palette-ui/pom.xml
new file mode 100644
index 0000000..52549d0
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>activity-palette-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Activity Palette UI Component</name>
+ <description>Activity Palette UI</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>uibuilder</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>${commons.beanutils.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/PathElementFilterTreeNode.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/PathElementFilterTreeNode.java
new file mode 100644
index 0000000..5c65bc0
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/PathElementFilterTreeNode.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel;
+
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+
+/**
+ * @author alanrw
+ */
+public class PathElementFilterTreeNode extends FilterTreeNode {
+ private static final long serialVersionUID = 6491242031931630314L;
+
+ public PathElementFilterTreeNode(String userObject) {
+ super(userObject);
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/RootFilterTreeNode.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/RootFilterTreeNode.java
new file mode 100644
index 0000000..86fca05
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/RootFilterTreeNode.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel;
+
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+
+/**
+ * @author alanrw
+ */
+public class RootFilterTreeNode extends FilterTreeNode {
+ private static final long serialVersionUID = 1047743498806473971L;
+
+ public RootFilterTreeNode(String userObject) {
+ super(userObject);
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceFilter.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceFilter.java
new file mode 100644
index 0000000..a83ac2d
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceFilter.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel;
+
+import static java.beans.Introspector.getBeanInfo;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.Filter;
+
+import org.apache.log4j.Logger;
+
+public class ServiceFilter implements Filter {
+ private static Logger logger = Logger.getLogger(ServiceFilter.class);
+
+ private String filterString;
+ private boolean superseded;
+ private String[] filterLowerCaseSplit;
+ private final Object rootToIgnore;
+
+ public ServiceFilter(String filterString, Object rootToIgnore) {
+ this.filterString = filterString;
+ this.rootToIgnore = rootToIgnore;
+ this.filterLowerCaseSplit = filterString.toLowerCase().split(" ");
+ this.superseded = false;
+ }
+
+ private boolean basicFilter(DefaultMutableTreeNode node) {
+ if (node == rootToIgnore)
+ return false;
+ if (filterString.isEmpty())
+ return true;
+
+ if (node.getUserObject() instanceof ServiceDescription) {
+ ServiceDescription serviceDescription = (ServiceDescription) node
+ .getUserObject();
+ for (String searchTerm : filterLowerCaseSplit) {
+ if (superseded)
+ return false;
+ String[] typeSplit = searchTerm.split(":", 2);
+ String type;
+ String keyword;
+ if (typeSplit.length == 2) {
+ type = typeSplit[0];
+ keyword = typeSplit[1].toLowerCase();
+ } else {
+ type = null;
+ keyword = searchTerm.toLowerCase();
+ }
+ try {
+ if (!doesPropertySatisfy(serviceDescription, type, keyword))
+ return false;
+ } catch (IntrospectionException | IllegalArgumentException
+ | IllegalAccessException | InvocationTargetException e) {
+ logger.error(
+ "failed to get properties of service description",
+ e);
+ return false;
+ }
+ }
+ return true;
+ }
+ for (String searchString : filterLowerCaseSplit)
+ if (!node.getUserObject().toString().toLowerCase().contains(
+ searchString))
+ return false;
+ return true;
+ }
+
+ /**
+ * Determine whether a service description satisfies a search term.
+ *
+ * @param serviceDescription
+ * The service description bean to look in.
+ * @param type
+ * The name of the property to look in, or <tt>null</tt> to
+ * search in all public non-expert properties.
+ * @param searchTerm
+ * The string to search for.
+ * @return <tt>true</tt> if-and-only-if the description matches.
+ */
+ private boolean doesPropertySatisfy(ServiceDescription serviceDescription,
+ String type, String searchTerm) throws IllegalAccessException,
+ IllegalArgumentException, InvocationTargetException,
+ IntrospectionException {
+ BeanInfo beanInfo = getBeanInfo(serviceDescription.getClass());
+ for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
+ if (superseded)
+ return false;
+ if ((type == null && !property.isHidden() && !property.isExpert())
+ || property.getName().equalsIgnoreCase(type)) {
+ Method readMethod = property.getReadMethod();
+ if (readMethod == null)
+ continue;
+ Object readProperty = readMethod.invoke(serviceDescription,
+ new Object[0]);
+ if (readProperty == null)
+ continue;
+ if (readProperty.toString().toLowerCase().contains(searchTerm))
+ return true;
+ // Dig deeper?
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean pass(DefaultMutableTreeNode node) {
+ return basicFilter(node);
+ }
+
+ @Override
+ public String filterRepresentation(String original) {
+ return original;
+ }
+
+ /**
+ * @return the superseded
+ */
+ @Override
+ public boolean isSuperseded() {
+ return superseded;
+ }
+
+ /**
+ * @param superseded
+ * the superseded to set
+ */
+ @Override
+ public void setSuperseded(boolean superseded) {
+ this.superseded = superseded;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceFilterTreeNode.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceFilterTreeNode.java
new file mode 100644
index 0000000..0e559ce
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceFilterTreeNode.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+
+/**
+ * @author alanrw
+ */
+public class ServiceFilterTreeNode extends FilterTreeNode {
+ private static final long serialVersionUID = 6066698619971305454L;
+
+ public ServiceFilterTreeNode(ServiceDescription userObject) {
+ super(userObject);
+ }
+
+ @Override
+ public ServiceDescription getUserObject() {
+ return (ServiceDescription) super.getUserObject();
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServicePanel.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServicePanel.java
new file mode 100644
index 0000000..0b01fe2
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServicePanel.java
@@ -0,0 +1,411 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.servicedescriptions.ServiceDescription.LOCAL_SERVICES;
+import static net.sf.taverna.t2.servicedescriptions.ServiceDescription.SERVICE_TEMPLATES;
+
+import java.awt.BorderLayout;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.events.AbstractProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.AbstractProviderNotification;
+import net.sf.taverna.t2.servicedescriptions.events.PartialServiceDescriptionsNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderErrorNotification;
+import net.sf.taverna.t2.servicedescriptions.events.RemovedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionProvidedEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionRegistryEvent;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeModel;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+/**
+ * A panel of available services
+ *
+ * @author Stian Soiland-Reyes
+ */
+@SuppressWarnings("serial")
+public class ServicePanel extends JPanel implements UIComponentSPI {
+ private static Logger logger = Logger.getLogger(ServicePanel.class);
+ private static final int INITIAL_BLANK_OUT_COUNTER = 2;
+ public static final String AVAILABLE_SERVICES = "Available services";
+ public static final String MATCHING_SERVIES = "Matching services";
+ public static final String NO_MATCHING_SERVICES = "No matching services";
+ public static final String MOBY_OBJECTS = "MOBY Objects";
+ /**
+ * A Comparable constant to be used with buildPathMap
+ */
+ private static final String SERVICES = "4DA84170-7746-4817-8C2E-E29AF8B2984D";
+ private static final int STATUS_LINE_MESSAGE_MS = 600;
+ private static ServicePathElementComparator servicePathElementComparator = new ServicePathElementComparator();
+
+ public int blankOutCounter = 0;
+ private TreeUpdaterThread updaterThread;
+ private RootFilterTreeNode root = new RootFilterTreeNode(AVAILABLE_SERVICES);
+ private ServiceTreePanel serviceTreePanel;
+ private JLabel statusLine;
+ private FilterTreeModel treeModel;
+ protected Timer statusUpdateTimer;
+
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+ protected final ServiceDescriptionRegistryObserver serviceDescriptionRegistryObserver = new ServiceDescriptionRegistryObserver();
+ protected final Object updateLock = new Object();
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public ServicePanel(ServiceDescriptionRegistry serviceDescriptionRegistry,
+ EditManager editManager, MenuManager menuManager,
+ SelectionManager selectionManager, ServiceRegistry serviceRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ this.editManager = editManager;
+ this.menuManager = menuManager;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+ serviceDescriptionRegistry.addObserver(serviceDescriptionRegistryObserver);
+ initialise();
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Service panel";
+ }
+
+ @Override
+ public void onDisplay() {
+ }
+
+ @Override
+ public void onDispose() {
+ }
+
+ public void providerStatus(ServiceDescriptionProvider provider, String message) {
+ logger.info(message + " " + provider);
+ final String htmlMessage = "<small>" + message + " [" + provider + "]</small>";
+
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ blankOutCounter = INITIAL_BLANK_OUT_COUNTER;
+ statusLine.setText("<html>" + htmlMessage + "</html>");
+ statusLine.setVisible(true);
+ }
+ });
+ }
+
+ protected void initialise() {
+ removeAll();
+ setLayout(new BorderLayout());
+ treeModel = new FilterTreeModel(root);
+ serviceTreePanel = new ServiceTreePanel(treeModel, serviceDescriptionRegistry, editManager, menuManager, selectionManager, serviceRegistry);
+ serviceTreePanel.setAvailableObjectsString(AVAILABLE_SERVICES);
+ serviceTreePanel.setMatchingObjectsString(MATCHING_SERVIES);
+ serviceTreePanel.setNoMatchingObjectsString(NO_MATCHING_SERVICES);
+ add(serviceTreePanel);
+ statusLine = new JLabel();
+ add(statusLine, BorderLayout.SOUTH);
+ if (statusUpdateTimer != null)
+ statusUpdateTimer.cancel();
+ statusUpdateTimer = new Timer("Clear status line", true);
+ statusUpdateTimer
+ .scheduleAtFixedRate(new UpdateStatusLineTask(), 0, STATUS_LINE_MESSAGE_MS);
+ updateTree();
+ }
+
+ protected void updateTree() {
+ synchronized (updateLock) {
+ if (updaterThread != null && updaterThread.isAlive()) {
+ return;
+ }
+ updaterThread = new TreeUpdaterThread();
+ updaterThread.start();
+ }
+ }
+
+ protected static class ServicePathElementComparator implements Comparator<Object> {
+ @Override
+ public int compare(Object o1, Object o2) {
+ if ((o1 instanceof String) && (o2 instanceof String)) {
+ if (o1.equals(SERVICE_TEMPLATES))
+ return -1;
+ else if (o2.equals(SERVICE_TEMPLATES))
+ return 1;
+ if (o1.equals(LOCAL_SERVICES))
+ return -1;
+ else if (o2.equals(LOCAL_SERVICES))
+ return 1;
+ if (o1.equals(MOBY_OBJECTS))
+ return -1;
+ else if (o2.equals(MOBY_OBJECTS))
+ return 1;
+ }
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ //FIXME this class is type-disastrous! Really bad.
+ public class TreeUpdaterThread extends Thread {
+ private boolean aborting = false;
+
+ private TreeUpdaterThread() {
+ super("Updating service panel");
+ setDaemon(true);
+ }
+
+ public void abort() {
+ aborting = true;
+ interrupt();
+ }
+
+ @Override
+ public void run() {
+ Map<Comparable, Map> pathMap = buildPathMap();
+ populateChildren(root, pathMap);
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ serviceTreePanel.runFilter();
+ } catch (InterruptedException | InvocationTargetException e) {
+ logger.error("failed to filter", e);
+ }
+ }
+ });
+ }
+
+ protected Map<Comparable, Map> buildPathMap() {
+ Map<Comparable, Map> paths = new TreeMap<>();
+ for (ServiceDescription serviceDescription : serviceDescriptionRegistry
+ .getServiceDescriptions()) {
+ if (aborting)
+ return paths;
+ Map currentPath = paths;
+ for (Object pathElem : serviceDescription.getPath()) {
+ Map pathEntry = (Map) currentPath.get(pathElem);
+ if (pathEntry == null) {
+ pathEntry = new TreeMap();
+ currentPath.put(pathElem, pathEntry);
+ }
+ currentPath = pathEntry;
+ }
+ TreeMap<String, Set<ServiceDescription>> services = (TreeMap) currentPath
+ .get(SERVICES);
+ if (services == null) {
+ services = new TreeMap<>();
+ currentPath.put(SERVICES, services);
+ }
+ String serviceDescriptionName = serviceDescription.getName();
+ if (!services.containsKey(serviceDescriptionName)) {
+ Set<ServiceDescription> serviceSet = new HashSet<>();
+ services.put(serviceDescriptionName, serviceSet);
+ }
+ services.get(serviceDescriptionName).add(serviceDescription);
+ }
+ return paths;
+ }
+
+ protected void populateChildren(FilterTreeNode node, Map pathMap) {
+ if (aborting)
+ return;
+ if (node == root) {
+ // Clear top root
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (aborting)
+ return;
+ serviceTreePanel.setFilter(null);
+ root.removeAllChildren();
+ }
+ });
+ }
+
+ Set<Comparable> paths = new TreeSet<>(servicePathElementComparator);
+ Map<String, Set<ServiceDescription>> services = (Map) pathMap
+ .get(SERVICES);
+ if (services == null)
+ services = new TreeMap<>();
+ paths.addAll(pathMap.keySet());
+ paths.addAll(services.keySet());
+
+ for (Comparable pathElement : paths) {
+ if (aborting)
+ return;
+ if (pathElement.equals(SERVICES))
+ continue;
+ Set<FilterTreeNode> childNodes = new HashSet<>();
+ if (services.containsKey(pathElement)) {
+ for (ServiceDescription sd : services.get(pathElement))
+ childNodes.add(new ServiceFilterTreeNode(sd));
+ } else
+ childNodes.add(new PathElementFilterTreeNode((String) pathElement));
+ invokeLater(new AddNodeRunnable(node, childNodes));
+ if ((pathMap.containsKey(pathElement)) && !childNodes.isEmpty())
+ populateChildren(childNodes.iterator().next(), (Map) pathMap.get(pathElement));
+ }
+ // if (!services.isEmpty()) {
+ // Collections.sort(services, serviceComparator);
+ // for (String serviceName : services.keySet()) {
+ // if (aborting) {
+ // return;
+ // }
+ // if (pathMap.containsKey(serviceName)) {
+ // continue;
+ // }
+ // SwingUtilities.invokeLater(new AddNodeRunnable(node,
+ // new ServiceFilterTreeNode(services.get(serviceName))));
+ // }
+ // }
+ }
+
+ public class AddNodeRunnable implements Runnable {
+ private final Set<FilterTreeNode> nodes;
+ private final FilterTreeNode root;
+
+ public AddNodeRunnable(FilterTreeNode root, Set<FilterTreeNode> nodes) {
+ this.root = root;
+ this.nodes = nodes;
+ }
+
+ @Override
+ public void run() {
+ if (aborting)
+ return;
+ for (FilterTreeNode n : nodes)
+ root.add(n);
+ }
+ }
+ }
+
+ public static class RemoveNodeRunnable implements Runnable {
+ private final FilterTreeNode root;
+
+ public RemoveNodeRunnable(FilterTreeNode root) {
+ this.root = root;
+ }
+
+ @Override
+ public void run() {
+ root.removeFromParent();
+ }
+ }
+
+ private final class ServiceDescriptionRegistryObserver implements
+ Observer<ServiceDescriptionRegistryEvent> {
+ Set<ServiceDescriptionProvider> alreadyComplainedAbout = new HashSet<>();
+
+ @Override
+ public void notify(Observable<ServiceDescriptionRegistryEvent> sender,
+ ServiceDescriptionRegistryEvent message) throws Exception {
+ if (message instanceof ProviderErrorNotification)
+ reportServiceProviderError((ProviderErrorNotification) message);
+ else if (message instanceof ServiceDescriptionProvidedEvent
+ || message instanceof RemovedProviderEvent) {
+ AbstractProviderEvent ape = (AbstractProviderEvent) message;
+ alreadyComplainedAbout.remove(ape.getProvider());
+ }
+
+ if (message instanceof AbstractProviderNotification) {
+ AbstractProviderNotification abstractProviderNotification = (AbstractProviderNotification) message;
+ providerStatus(abstractProviderNotification.getProvider(),
+ abstractProviderNotification.getMessage());
+ }
+ if (message instanceof PartialServiceDescriptionsNotification)
+ /*
+ * TODO: Support other events and only update relevant parts of
+ * tree, or at least select the recently added provider
+ */
+ updateTree();
+ else if (message instanceof RemovedProviderEvent)
+ updateTree();
+ }
+
+ private void reportServiceProviderError(
+ final ProviderErrorNotification pen) {
+ ServiceDescriptionProvider provider = pen.getProvider();
+ if (serviceDescriptionRegistry
+ .getDefaultServiceDescriptionProviders().contains(provider))
+ return;
+ if (alreadyComplainedAbout.contains(provider))
+ return;
+
+ alreadyComplainedAbout.add(provider);
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ showMessageDialog(ServicePanel.this, pen.getMessage()
+ + "\n" + pen.getProvider(), "Import service error",
+ ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+
+ private final class UpdateStatusLineTask extends TimerTask {
+ @Override
+ public void run() {
+ if (blankOutCounter < 0 || blankOutCounter-- > 0)
+ // Only clear it once
+ return;
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (blankOutCounter < 0)
+ statusLine.setVisible(false);
+ }
+ });
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServicePanelComponentFactory.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServicePanelComponentFactory.java
new file mode 100644
index 0000000..2eb97ef
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServicePanelComponentFactory.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.servicepanel;
+
+import javax.swing.ImageIcon;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+/**
+ * Service panel factory
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class ServicePanelComponentFactory implements UIComponentFactorySPI {
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private EditManager editManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ServiceRegistry serviceRegistry;
+
+ @Override
+ public UIComponentSPI getComponent() {
+ return new ServicePanel(serviceDescriptionRegistry, editManager,
+ menuManager, selectionManager, serviceRegistry);
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Service panel";
+ }
+
+ public void setServiceDescriptionRegistry(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreeCellRenderer.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreeCellRenderer.java
new file mode 100644
index 0000000..4b7388d
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreeCellRenderer.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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.ui.servicepanel;
+
+import static net.sf.taverna.t2.workbench.activityicons.DefaultActivityIcon.getDefaultIcon;
+
+import java.awt.Component;
+
+import javax.swing.Icon;
+import javax.swing.JTree;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeCellRenderer;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+
+@SuppressWarnings("serial")
+public class ServiceTreeCellRenderer extends FilterTreeCellRenderer {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean sel, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ Component result = super.getTreeCellRendererComponent(tree, value, sel,
+ expanded, leaf, row, hasFocus);
+ if (result instanceof ServiceTreeCellRenderer
+ && value instanceof FilterTreeNode
+ && ((FilterTreeNode) value).getUserObject() instanceof ServiceDescription)
+ prettifyServiceTreeCell((ServiceTreeCellRenderer) result,
+ (ServiceDescription) ((FilterTreeNode) value)
+ .getUserObject());
+ else {
+ // Commented out - these are ugly, use the default folder icons instead
+ /*
+ * if (expanded) { ((ServiceTreeCellRenderer) result)
+ * .setIcon(WorkbenchIcons.folderOpenIcon); } else {
+ * ((ServiceTreeCellRenderer) result)
+ * .setIcon(WorkbenchIcons.folderClosedIcon); }
+ */
+ }
+ return result;
+ }
+
+ private void prettifyServiceTreeCell(ServiceTreeCellRenderer renderer,
+ ServiceDescription item) {
+ String name = item.getName();
+ if (getFilter() != null)
+ name = getFilter().filterRepresentation(name);
+ // serviceTreeCellRenderer.setForeground(Color.red);
+ String displayName = name;
+
+ String textualDescription = item.getDescription();
+ if (textualDescription != null && !textualDescription.isEmpty())
+ displayName = displayName + " - " + textualDescription;
+ renderer.setText(displayName);
+
+ Icon activityIcon = item.getIcon();
+ renderer.setIcon(activityIcon != null ? activityIcon
+ : getDefaultIcon());
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreeClickListener.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreeClickListener.java
new file mode 100644
index 0000000..a76dcc9
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreeClickListener.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * 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.ui.servicepanel;
+
+import static java.awt.Color.RED;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.BLUE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.ORANGE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.halfShade;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.minusIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.plusIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.importServiceDescription;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.ExportServiceDescriptionsAction;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.ImportServiceDescriptionsFromFileAction;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.ImportServiceDescriptionsFromURLAction;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.RemoveDefaultServicesAction;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.RemoveUserServicesAction;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.RestoreDefaultServicesAction;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeSelectionModel;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.TreePanel;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+/**
+ * @author alanrw
+ */
+public class ServiceTreeClickListener extends MouseAdapter {
+ private static Logger logger = Logger.getLogger(ServiceTreeClickListener.class);
+
+ private JTree tree;
+ private TreePanel panel;
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public ServiceTreeClickListener(JTree tree, TreePanel panel,
+ ServiceDescriptionRegistry serviceDescriptionRegistry, EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager, ServiceRegistry serviceRegistry) {
+ this.tree = tree;
+ this.panel = panel;
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ this.editManager = editManager;
+ this.menuManager = menuManager;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ @SuppressWarnings("serial")
+ private void handleMouseEvent(MouseEvent evt) {
+ FilterTreeSelectionModel selectionModel = (FilterTreeSelectionModel) tree
+ .getSelectionModel();
+ // Discover the tree row that was clicked on
+ int selRow = tree.getRowForLocation(evt.getX(), evt.getY());
+ if (selRow == -1)
+ return;
+
+ // Get the selection path for the row
+ TreePath selectionPath = tree
+ .getPathForLocation(evt.getX(), evt.getY());
+ if (selectionPath == null)
+ return;
+
+ // Get the selected node
+ final FilterTreeNode selectedNode = (FilterTreeNode) selectionPath
+ .getLastPathComponent();
+
+ selectionModel.clearSelection();
+ selectionModel.mySetSelectionPath(selectionPath);
+
+ if (evt.isPopupTrigger()) {
+ JPopupMenu menu = new JPopupMenu();
+ Object selectedObject = selectedNode.getUserObject();
+ logger.info(selectedObject.getClass().getName());
+ if (!(selectedObject instanceof ServiceDescription)) {
+ menu.add(new ShadedLabel("Tree", BLUE));
+ menu.add(new JMenuItem(new AbstractAction("Expand all",
+ plusIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ panel.expandAll(selectedNode, true);
+ }
+ });
+ }
+ }));
+ menu.add(new JMenuItem(new AbstractAction("Collapse all",
+ minusIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ panel.expandAll(selectedNode, false);
+ }
+ });
+ }
+ }));
+ }
+
+ if (selectedObject instanceof ServiceDescription) {
+ final ServiceDescription sd = (ServiceDescription) selectedObject;
+ menu.add(new ShadedLabel(sd.getName(), ORANGE));
+ menu.add(new AbstractAction("Add to workflow") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importServiceDescription(sd, false, editManager,
+ menuManager, selectionManager, serviceRegistry);
+ }
+ });
+ menu.add(new AbstractAction("Add to workflow with name...") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importServiceDescription(sd, true, editManager,
+ menuManager, selectionManager, serviceRegistry);
+ }
+ });
+ }
+
+ Map<String, ServiceDescriptionProvider> nameMap = getServiceDescriptionProviderMap(selectedNode);
+
+ boolean first = true;
+ for (String name : nameMap.keySet()) {
+ final ServiceDescriptionProvider sdp = nameMap.get(name);
+ if (!(sdp instanceof ConfigurableServiceProvider))
+ continue;
+ if (first) {
+ menu.add(new ShadedLabel(
+ "Remove individual service provider", GREEN));
+ first = false;
+ }
+ menu.add(new AbstractAction(name) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ serviceDescriptionRegistry
+ .removeServiceDescriptionProvider(sdp);
+ }
+ });
+ }
+
+ if (selectedNode.isRoot()) { // Root "Available services"
+ menu.add(new ShadedLabel("Default and added service providers",
+ ORANGE));
+ menu.add(new RemoveUserServicesAction(
+ serviceDescriptionRegistry));
+ menu.add(new RemoveDefaultServicesAction(
+ serviceDescriptionRegistry));
+ menu.add(new RestoreDefaultServicesAction(
+ serviceDescriptionRegistry));
+
+ menu.add(new ShadedLabel("Import/export services", halfShade(RED)));
+ menu.add(new ImportServiceDescriptionsFromFileAction(
+ serviceDescriptionRegistry));
+ menu.add(new ImportServiceDescriptionsFromURLAction(
+ serviceDescriptionRegistry));
+ menu.add(new ExportServiceDescriptionsAction(
+ serviceDescriptionRegistry));
+ }
+
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ }
+ }
+
+ private Map<String, ServiceDescriptionProvider> getServiceDescriptionProviderMap(
+ FilterTreeNode selectedNode) {
+ Set<ServiceDescriptionProvider> providers;
+
+ if (selectedNode.isRoot())
+ providers = serviceDescriptionRegistry
+ .getServiceDescriptionProviders();
+ else {
+ providers = new HashSet<>();
+ for (FilterTreeNode leaf : selectedNode.getLeaves()) {
+ if (!leaf.isLeaf())
+ logger.info("Not a leaf");
+ if (!(leaf.getUserObject() instanceof ServiceDescription)) {
+ logger.info(leaf.getUserObject().getClass()
+ .getCanonicalName());
+ logger.info(leaf.getUserObject().toString());
+ continue;
+ }
+ providers
+ .addAll(serviceDescriptionRegistry
+ .getServiceDescriptionProviders((ServiceDescription) leaf
+ .getUserObject()));
+ }
+ }
+
+ TreeMap<String, ServiceDescriptionProvider> nameMap = new TreeMap<>();
+ for (ServiceDescriptionProvider sdp : providers)
+ nameMap.put(sdp.toString(), sdp);
+ return nameMap;
+ }
+
+ @Override
+ public void mousePressed(MouseEvent evt) {
+ handleMouseEvent(evt);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent evt) {
+ handleMouseEvent(evt);
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreePanel.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreePanel.java
new file mode 100644
index 0000000..ce3e336
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/ServiceTreePanel.java
@@ -0,0 +1,176 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.servicepanel;
+
+import static java.awt.datatransfer.DataFlavor.javaJVMLocalObjectMimeType;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.TransferHandler;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeWillExpandListener;
+import javax.swing.tree.ExpandVetoException;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.servicepanel.menu.AddServiceProviderMenu;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.Filter;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeModel;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.FilterTreeNode;
+import net.sf.taverna.t2.workbench.ui.servicepanel.tree.TreePanel;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+public class ServiceTreePanel extends TreePanel {
+ private static final long serialVersionUID = 6611462684296693909L;
+ private static Logger logger = Logger.getLogger(ServiceTreePanel.class);
+
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public ServiceTreePanel(FilterTreeModel treeModel,
+ ServiceDescriptionRegistry serviceDescriptionRegistry, EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager, ServiceRegistry serviceRegistry) {
+ super(treeModel);
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ this.editManager = editManager;
+ this.menuManager = menuManager;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+ initialize();
+ }
+
+ @Override
+ protected void initialize() {
+ super.initialize();
+ tree.setDragEnabled(true);
+ tree.setTransferHandler(new ServiceTransferHandler());
+ tree.addTreeWillExpandListener(new AvoidRootCollapse());
+ tree.expandRow(0);
+
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ tree.addMouseListener(new ServiceTreeClickListener(tree,
+ ServiceTreePanel.this, serviceDescriptionRegistry,
+ editManager, menuManager, selectionManager,
+ serviceRegistry));
+ }
+ });
+ }
+
+ @Override
+ protected Component createExtraComponent() {
+ JComponent buttonPanel = new JPanel(new FlowLayout());
+ buttonPanel.add(new AddServiceProviderMenu(serviceDescriptionRegistry));
+ // buttonPanel.add(new JButton(new RefreshProviderRegistryAction()));
+ return buttonPanel;
+ }
+
+ @Override
+ public Filter createFilter(String text) {
+ return new ServiceFilter(text, filterTreeModel.getRoot());
+ }
+
+ @Override
+ protected TreeCellRenderer createCellRenderer() {
+ return new ServiceTreeCellRenderer();
+ }
+
+ public static class AvoidRootCollapse implements TreeWillExpandListener {
+ @Override
+ public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
+ if (event.getPath().getPathCount() == 1)
+ throw new ExpandVetoException(event, "Can't collapse root");
+ }
+
+ @Override
+ public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
+ }
+ }
+
+ private final class ServiceTransferHandler extends TransferHandler {
+ private static final long serialVersionUID = 4347965626386951176L;
+
+ /**
+ * Triggered when a node ie. an {@link ActivityItem} is dragged out of
+ * the tree. Figures out what node it is being dragged and then starts a
+ * drag action with it
+ */
+ @Override
+ protected Transferable createTransferable(JComponent c) {
+ TreePath selectionPath = tree.getSelectionPath();
+ if (selectionPath == null)
+ return null;
+ FilterTreeNode lastPathComponent = (FilterTreeNode) selectionPath
+ .getLastPathComponent();
+ if (!(lastPathComponent.getUserObject() instanceof ServiceDescription))
+ return null;
+ final ServiceDescription serviceDescription = (ServiceDescription) lastPathComponent
+ .getUserObject();
+
+ return new Transferable() {
+ @Override
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ return serviceDescription;
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ DataFlavor[] flavors = new DataFlavor[1];
+ try {
+ flavors[0] = getFlavorForClass(ServiceDescription.class);
+ } catch (ClassNotFoundException e) {
+ logger.error("Error casting Dataflavor", e);
+ flavors[0] = null;
+ }
+ return flavors;
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ DataFlavor thisFlavor = null;
+ try {
+ thisFlavor = getFlavorForClass(ServiceDescription.class);
+ } catch (ClassNotFoundException e) {
+ logger.error("Error casting Dataflavor", e);
+ }
+ return flavor.equals(thisFlavor);
+ }
+ };
+ }
+
+ @Override
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+ }
+
+ private DataFlavor getFlavorForClass(Class<?> clazz)
+ throws ClassNotFoundException {
+ String name = clazz.getName();
+ return new DataFlavor(javaJVMLocalObjectMimeType + ";class=" + clazz,
+ name.substring(name.lastIndexOf('.') + 1),
+ clazz.getClassLoader());
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/AddServiceProviderAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/AddServiceProviderAction.java
new file mode 100644
index 0000000..c8f2bfa
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/AddServiceProviderAction.java
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.BorderLayout.WEST;
+import static java.awt.event.KeyEvent.VK_ENTER;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static org.apache.commons.beanutils.PropertyUtils.getPropertyDescriptors;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.CustomizedConfigurePanelProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.CustomizedConfigurePanelProvider.CustomizedConfigureCallBack;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderErrorNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionProvidedEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionRegistryEvent;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+/**
+ * Action for adding a service provider
+ *
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class AddServiceProviderAction extends AbstractAction {
+ private static Logger logger = getLogger(AddServiceProviderAction.class);
+
+ // protected static Dimension DIALOG_SIZE = new Dimension(400, 300);
+
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ private final ConfigurableServiceProvider confProvider;
+ private final Component owner;
+
+ public AddServiceProviderAction(ConfigurableServiceProvider confProvider,
+ Component owner) {
+ super(confProvider.getName() + "...", confProvider.getIcon());
+ this.confProvider = confProvider;
+ this.owner = owner;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (confProvider instanceof CustomizedConfigurePanelProvider) {
+ final CustomizedConfigurePanelProvider provider = (CustomizedConfigurePanelProvider) confProvider;
+ provider.createCustomizedConfigurePanel(new CustomizedConfigureCallBack() {
+ @Override
+ public Configuration getTemplateConfig() {
+ return (Configuration) provider.getConfiguration().clone();
+ }
+
+ @Override
+ public ServiceDescriptionRegistry getServiceDescriptionRegistry() {
+ return AddServiceProviderAction.this.getServiceDescriptionRegistry();
+ }
+
+ @Override
+ public void newProviderConfiguration(Configuration providerConfig) {
+ addNewProvider(providerConfig);
+ }
+ });
+ return;
+ }
+
+ Configuration configuration;
+ try {
+ configuration = (Configuration) confProvider.getConfiguration().clone();
+ } catch (Exception ex) {
+ throw new RuntimeException("Can't clone configuration bean", ex);
+ }
+ JPanel buildEditor = buildEditor(configuration);
+ String title = "Add " + confProvider.getName();
+ JDialog dialog = new HelpEnabledDialog(getMainWindow(), title, true, null);
+ JPanel iconPanel = new JPanel();
+ iconPanel.add(new JLabel(confProvider.getIcon()), NORTH);
+ dialog.add(iconPanel, WEST);
+ dialog.add(buildEditor, CENTER);
+ JPanel buttonPanel = new JPanel(new BorderLayout());
+ final AddProviderAction addProviderAction = new AddProviderAction(configuration,
+ dialog);
+ JButton addProviderButton = new JButton(addProviderAction);
+ buttonPanel.add(addProviderButton, WEST);
+
+ dialog.add(buttonPanel, SOUTH);
+ // When user presses "Return" key fire the action on the "Add" button
+ addProviderButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER)
+ addProviderAction.actionPerformed(null);
+ }
+ });
+ dialog.getRootPane().setDefaultButton(addProviderButton);
+
+ // dialog.setSize(buttonPanel.getPreferredSize());
+ dialog.pack();
+ dialog.setLocationRelativeTo(owner);
+// dialog.setLocation(owner.getLocationOnScreen().x + owner.getWidth(),
+// owner.getLocationOnScreen().y + owner.getHeight());
+ dialog.setVisible(true);
+ }
+
+ protected void addNewProvider(Configuration configurationBean) {
+ ConfigurableServiceProvider cloned = (ConfigurableServiceProvider) confProvider
+ .newInstance();
+ try {
+ cloned.configure(configurationBean);
+ getServiceDescriptionRegistry().addObserver(
+ new CheckAddedCorrectlyObserver(cloned));
+ getServiceDescriptionRegistry().addServiceDescriptionProvider(
+ cloned);
+ } catch (Exception ex) {
+ logger.warn("Can't configure provider " + cloned + " using "
+ + configurationBean, ex);
+ showMessageDialog(owner, "Can't configure service provider "
+ + cloned.getName(), "Can't add service provider",
+ ERROR_MESSAGE);
+ }
+ }
+
+ private PropertyDescriptor[] getProperties(Configuration configuration) {
+ // FIXME This is *so* wrong!
+ try {
+ return getPropertyDescriptors(configuration);
+ } catch (Exception ex) {
+ throw new RuntimeException("Can't inspect configuration bean", ex);
+ }
+ }
+
+ // TODO This is probably not right
+ protected JPanel buildEditor(Configuration configuration) {
+ List<String> uiBuilderConfig = new ArrayList<>();
+ int lastPreferred = 0;
+ for (PropertyDescriptor property : getProperties(configuration)) {
+ if (property.isHidden() || property.isExpert())
+ // TODO: Add support for expert properties
+ continue;
+ String propertySpec = property.getName() + ":name="
+ + property.getDisplayName();
+ if (property.isPreferred())
+ // Add it to the front
+ uiBuilderConfig.add(lastPreferred++, propertySpec);
+ else
+ uiBuilderConfig.add(propertySpec);
+ }
+
+ return UIBuilder.buildEditor(configuration, uiBuilderConfig
+ .toArray(new String[0]));
+ }
+
+ public void setServiceDescriptionRegistry(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public ServiceDescriptionRegistry getServiceDescriptionRegistry() {
+ return serviceDescriptionRegistry;
+ }
+
+ public class AddProviderAction extends AbstractAction {
+ private final Configuration configurationBean;
+ private final JDialog dialog;
+
+ private AddProviderAction(Configuration configurationBean, JDialog dialog) {
+ super("Add");
+ this.configurationBean = configurationBean;
+ this.dialog = dialog;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ addNewProvider(configurationBean);
+ dialog.setVisible(false);
+ }
+ }
+
+ public class CheckAddedCorrectlyObserver implements
+ Observer<ServiceDescriptionRegistryEvent> {
+ private final ConfigurableServiceProvider provider;
+
+ private CheckAddedCorrectlyObserver(ConfigurableServiceProvider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public void notify(Observable<ServiceDescriptionRegistryEvent> sender,
+ ServiceDescriptionRegistryEvent message) throws Exception {
+ if (message instanceof ProviderErrorNotification)
+ notify((ProviderErrorNotification) message);
+ else if (message instanceof ServiceDescriptionProvidedEvent)
+ notify((ServiceDescriptionProvidedEvent) message);
+ }
+
+ private void notify(ServiceDescriptionProvidedEvent providedMsg) {
+ if (providedMsg.getProvider() == provider)
+ getServiceDescriptionRegistry().removeObserver(this);
+ }
+
+ private void notify(ProviderErrorNotification errorMsg) {
+ if (errorMsg.getProvider() != provider)
+ return;
+ getServiceDescriptionRegistry().removeObserver(this);
+ getServiceDescriptionRegistry().removeServiceDescriptionProvider(
+ provider);
+// showMessageDialog(owner, errorMsg.getMessage(),
+// "Can't add provider " + provider, ERROR_MESSAGE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ExportServiceDescriptionsAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ExportServiceDescriptionsAction.java
new file mode 100644
index 0000000..990e429
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ExportServiceDescriptionsAction.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Action to export the current service descritpions from the Service
+ * Registry to an xml file.
+ *
+ * @author Alex Nenadic
+ */
+//FIXME this file assumes we're writing out as XML
+@SuppressWarnings("serial")
+public class ExportServiceDescriptionsAction extends AbstractAction {
+ private static final String EXTENSION = ".xml";
+ private static final String EXPORT_SERVICES = "Export services to file";
+ private static final String SERVICE_EXPORT_DIR_PROPERTY = "serviceExportDir";
+ private Logger logger = Logger.getLogger(ExportServiceDescriptionsAction.class);
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public ExportServiceDescriptionsAction(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super(EXPORT_SERVICES);
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public static final boolean INHIBIT = true;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JComponent parentComponent = null;
+ if (e.getSource() instanceof JComponent)
+ parentComponent = (JComponent) e.getSource();
+
+ if (INHIBIT) {
+ showMessageDialog(parentComponent,
+ "Operation not currently working correctly",
+ "Not Implemented", ERROR_MESSAGE);
+ return;
+ }
+
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get(SERVICE_EXPORT_DIR_PROPERTY,
+ System.getProperty("user.home"));
+ fileChooser.setDialogTitle("Select file to export services to");
+
+ fileChooser.setFileFilter(new FileFilter() {
+ @Override
+ public boolean accept(File f) {
+ return f.isDirectory()
+ || f.getName().toLowerCase().endsWith(EXTENSION);
+ }
+
+ @Override
+ public String getDescription() {
+ return ".xml files";
+ }
+ });
+
+ fileChooser.setCurrentDirectory(new File(curDir));
+
+ boolean tryAgain = true;
+ while (tryAgain) {
+ tryAgain = false;
+ int returnVal = fileChooser.showSaveDialog(parentComponent);
+ if (returnVal == APPROVE_OPTION) {
+ prefs.put(SERVICE_EXPORT_DIR_PROPERTY, fileChooser.getCurrentDirectory()
+ .toString());
+ File file = fileChooser.getSelectedFile();
+ if (!file.getName().toLowerCase().endsWith(EXTENSION)) {
+ String newName = file.getName() + EXTENSION;
+ file = new File(file.getParentFile(), newName);
+ }
+
+ try {
+ if (file.exists()) {
+ String msg = "Are you sure you want to overwrite existing file "
+ + file + "?";
+ int ret = showConfirmDialog(parentComponent, msg,
+ "File already exists", YES_NO_CANCEL_OPTION);
+ if (ret == NO_OPTION) {
+ tryAgain = true;
+ continue;
+ } else if (ret != YES_OPTION) {
+ logger.info("Service descriptions export: aborted overwrite of "
+ + file.getAbsolutePath());
+ break;
+ }
+ }
+ exportServiceDescriptions(file);
+ break;
+ } catch (Exception ex) {
+ logger.error("Service descriptions export: failed to export services to "
+ + file.getAbsolutePath(), ex);
+ showMessageDialog(
+ parentComponent,
+ "Failed to export services to "
+ + file.getAbsolutePath(), "Error",
+ ERROR_MESSAGE);
+ break;
+ }
+ }
+ }
+
+ if (parentComponent instanceof JButton)
+ // lose the focus from the button after performing the action
+ parentComponent.requestFocusInWindow();
+ }
+
+ private void exportServiceDescriptions(File file) {
+ // TODO: Open in separate thread to avoid hanging UI
+ serviceDescriptionRegistry.exportCurrentServiceDescriptions(file);
+ logger.info("Service descriptions export: saved to file "
+ + file.getAbsolutePath());
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ImportServiceDescriptionsFromFileAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ImportServiceDescriptionsFromFileAction.java
new file mode 100644
index 0000000..1542583
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ImportServiceDescriptionsFromFileAction.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.HashSet;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+/**
+ * Action to import a list of service descriptions from an xml file
+ * into the Service Registry. Users have an option to completely
+ * replace the current services or just add the ones from the file
+ * to the current services.
+ *
+ * @author Alex Nenadic
+ */
+//FIXME this file assumes we're writing out as XML
+@SuppressWarnings("serial")
+public class ImportServiceDescriptionsFromFileAction extends AbstractAction{
+ private static final String EXTENSION = ".xml";
+ private static final String IMPORT_SERVICES = "Import services from file";
+ private static final String SERVICE_IMPORT_DIR_PROPERTY = "serviceImportDir";
+ private static final Logger logger = Logger.getLogger(ExportServiceDescriptionsAction.class);
+
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public ImportServiceDescriptionsFromFileAction(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super(IMPORT_SERVICES);
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ private static final Object[] CHOICES = { "Add to current services",
+ "Replace current services", "Cancel" };
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JComponent parentComponent = null;
+ if (e.getSource() instanceof JComponent)
+ parentComponent = (JComponent) e.getSource();
+
+ if (ExportServiceDescriptionsAction.INHIBIT) {
+ showMessageDialog(parentComponent,
+ "Operation not currently working correctly",
+ "Not Implemented", ERROR_MESSAGE);
+ return;
+ }
+
+ int choice = showOptionDialog(
+ parentComponent,
+ "Do you want to add the imported services to the current ones or replace the current ones?",
+ "Import services", YES_NO_CANCEL_OPTION, QUESTION_MESSAGE,
+ null, CHOICES, CHOICES[0]);
+
+ if (choice != CANCEL_OPTION) {
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get(SERVICE_IMPORT_DIR_PROPERTY, System.getProperty("user.home"));
+
+ fileChooser.setDialogTitle("Select file to import services from");
+
+ fileChooser.setFileFilter(new FileFilter() {
+ @Override
+ public boolean accept(File f) {
+ return f.isDirectory()
+ || f.getName().toLowerCase().endsWith(EXTENSION);
+ }
+
+ @Override
+ public String getDescription() {
+ return EXTENSION + " files";
+ }
+ });
+
+ fileChooser.setCurrentDirectory(new File(curDir));
+
+ if (fileChooser.showOpenDialog(parentComponent) == APPROVE_OPTION) {
+ prefs.put(SERVICE_IMPORT_DIR_PROPERTY, fileChooser
+ .getCurrentDirectory().toString());
+ File file = fileChooser.getSelectedFile();
+
+ try {
+ // Did user want to replace or add services?
+ importServices(file, choice == YES_OPTION);
+ } catch (Exception ex) {
+ logger.error(
+ "Service descriptions import: failed to import services from "
+ + file.getAbsolutePath(), ex);
+ showMessageDialog(parentComponent,
+ "Failed to import services from " + file.getAbsolutePath(), "Error",
+ ERROR_MESSAGE);
+ }
+ }
+ }
+
+ if (parentComponent instanceof JButton)
+ // lose the focus from the button after performing the action
+ parentComponent.requestFocusInWindow();
+ }
+
+ private void importServices(final File file, final boolean addToCurrent)
+ throws Exception {
+ // TODO: Open in separate thread to avoid hanging UI
+
+ if (!addToCurrent)
+ for (ServiceDescriptionProvider provider : new HashSet<>(
+ serviceDescriptionRegistry.getServiceDescriptionProviders()))
+ // remove all configurable service providers
+ if (provider instanceof ConfigurableServiceProvider)
+ serviceDescriptionRegistry
+ .removeServiceDescriptionProvider(provider);
+
+ // import all providers from the file
+ serviceDescriptionRegistry.loadServiceProviders(file);
+ serviceDescriptionRegistry.saveServiceDescriptions();
+ }
+}
+
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ImportServiceDescriptionsFromURLAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ImportServiceDescriptionsFromURLAction.java
new file mode 100644
index 0000000..0dbbe25
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/ImportServiceDescriptionsFromURLAction.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+
+import java.awt.event.ActionEvent;
+import java.net.URL;
+import java.util.HashSet;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Action to import a list of service descriptions from an URL pointing
+ * to an xml file into the Service Registry. Users have an option to
+ * completely replace the current services or just add the ones from the
+ * file to the current services.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class ImportServiceDescriptionsFromURLAction extends AbstractAction{
+ private static final String IMPORT_SERVICES_FROM_URL = "Import services from URL";
+ private static final Logger logger = Logger.getLogger(ExportServiceDescriptionsAction.class);
+
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public ImportServiceDescriptionsFromURLAction(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super(IMPORT_SERVICES_FROM_URL);
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ private static final Object[] CHOICES = { "Add to current services",
+ "Replace current services", "Cancel" };
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JComponent parentComponent = null;
+ if (e.getSource() instanceof JComponent)
+ parentComponent = (JComponent) e.getSource();
+
+ if (ExportServiceDescriptionsAction.INHIBIT) {
+ showMessageDialog(parentComponent,
+ "Operation not currently working correctly",
+ "Not Implemented", ERROR_MESSAGE);
+ return;
+ }
+
+ int choice = showOptionDialog(
+ parentComponent,
+ "Do you want to add the imported services to the current ones or replace the current ones?",
+ "Import services", YES_NO_CANCEL_OPTION, QUESTION_MESSAGE,
+ null, CHOICES, CHOICES[0]);
+
+ if (choice != CANCEL_OPTION) {
+ final String urlString = (String) showInputDialog(parentComponent,
+ "Enter the URL of the service descriptions file to import",
+ "Service Descriptions URL", QUESTION_MESSAGE, null, null,
+ "http://");
+ try {
+ if (urlString != null && !urlString.isEmpty())
+ // Did user want to replace or add services?
+ importServices(urlString, choice == YES_OPTION);
+ } catch (Exception ex) {
+ logger.error(
+ "Service descriptions import: failed to import services from "
+ + urlString, ex);
+ showMessageDialog(parentComponent,
+ "Failed to import services from " + urlString, "Error",
+ ERROR_MESSAGE);
+ }
+ }
+
+ if (parentComponent instanceof JButton)
+ // lose the focus from the button after performing the action
+ parentComponent.requestFocusInWindow();
+ }
+
+ private void importServices(final String urlString, final boolean addToCurrent)
+ throws Exception {
+ // TODO: Open in separate thread to avoid hanging UI
+ URL url = new URL(urlString);
+
+ if (!addToCurrent)
+ for (ServiceDescriptionProvider provider : new HashSet<>(
+ serviceDescriptionRegistry.getServiceDescriptionProviders()))
+ // remove all configurable service providers
+ if (provider instanceof ConfigurableServiceProvider)
+ serviceDescriptionRegistry
+ .removeServiceDescriptionProvider(provider);
+
+ // import all providers from the URL
+ serviceDescriptionRegistry.loadServiceProviders(url);
+ serviceDescriptionRegistry.saveServiceDescriptions();
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RefreshProviderRegistryAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RefreshProviderRegistryAction.java
new file mode 100644
index 0000000..9c4c84b
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RefreshProviderRegistryAction.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+/**
+ * Action for refreshing the service provider registry.
+ * <p>
+ * This would typically re-parse WSDLs, etc.
+ *
+ * @see ServiceDescriptionRegistry#refresh()
+ * @author Stian Soiland-Reyes
+ */
+@SuppressWarnings("serial")
+public class RefreshProviderRegistryAction extends AbstractAction {
+ private static final String REFRESH = "Reload services";
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public RefreshProviderRegistryAction(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super(REFRESH);
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ serviceDescriptionRegistry.refresh();
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RemoveDefaultServicesAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RemoveDefaultServicesAction.java
new file mode 100644
index 0000000..b6ba606
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RemoveDefaultServicesAction.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+@SuppressWarnings("serial")
+public class RemoveDefaultServicesAction extends AbstractAction {
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public RemoveDefaultServicesAction(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super("Remove default service providers");
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ for (ServiceDescriptionProvider provider : serviceDescriptionRegistry
+ .getDefaultServiceDescriptionProviders()) {
+ if (!(provider instanceof ConfigurableServiceProvider))
+ continue;
+ serviceDescriptionRegistry
+ .removeServiceDescriptionProvider(provider);
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RemoveUserServicesAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RemoveUserServicesAction.java
new file mode 100644
index 0000000..bf0a771
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RemoveUserServicesAction.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JLabel;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+@SuppressWarnings("serial")
+public class RemoveUserServicesAction extends AbstractAction {
+ private static final String CONFIRM_MESSAGE = "You are about to remove all services you have added. <br>"
+ + "Are you <b>really</b> sure you want to do this?";
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public RemoveUserServicesAction(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super("Remove all user added service providers");
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int option = showConfirmDialog(null, new JLabel("<html><body>"
+ + CONFIRM_MESSAGE + "</body></html>"),
+ "Confirm service deletion", YES_NO_OPTION);
+
+ if (option == YES_OPTION)
+ for (ServiceDescriptionProvider provider : serviceDescriptionRegistry
+ .getUserAddedServiceProviders())
+ serviceDescriptionRegistry
+ .removeServiceDescriptionProvider(provider);
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RestoreDefaultServicesAction.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RestoreDefaultServicesAction.java
new file mode 100644
index 0000000..c7071ed
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/actions/RestoreDefaultServicesAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.actions;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+@SuppressWarnings("serial")
+public class RestoreDefaultServicesAction extends AbstractAction {
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public RestoreDefaultServicesAction(
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super("Restore default service providers");
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ for (ServiceDescriptionProvider provider : serviceDescriptionRegistry
+ .getDefaultServiceDescriptionProviders()) {
+ if (!(provider instanceof ConfigurableServiceProvider))
+ continue;
+ serviceDescriptionRegistry.addServiceDescriptionProvider(provider);
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/config/ServiceDescriptionConfigPanel.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/config/ServiceDescriptionConfigPanel.java
new file mode 100644
index 0000000..f666877
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/config/ServiceDescriptionConfigPanel.java
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.config;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+import net.sf.taverna.t2.workbench.helper.Helper;
+
+@SuppressWarnings("serial")
+public class ServiceDescriptionConfigPanel extends JPanel {
+ private static final String REMOVE_PERMANENTLY = "Allow permanent removal of default service providers";
+ private static final String INCLUDE_DEFAULTS = "Include default service providers";
+
+ private final ServiceDescriptionsConfiguration config;
+ private JCheckBox includeDefaults;
+ private JCheckBox removePermanently;
+ private final ServiceDescriptionRegistry serviceDescRegistry;
+
+ public ServiceDescriptionConfigPanel(ServiceDescriptionsConfiguration config,
+ ServiceDescriptionRegistry serviceDescRegistry) {
+ super(new GridBagLayout());
+ this.config = config;
+ this.serviceDescRegistry = serviceDescRegistry;
+ initialize();
+ }
+
+ private void initialize() {
+ removeAll();
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea(
+ "Configure behaviour of default service providers in Service Panel");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = WEST;
+ gbc.fill = HORIZONTAL;
+ add(descriptionText, gbc);
+
+ includeDefaults = new JCheckBox(INCLUDE_DEFAULTS);
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.anchor = WEST;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ add(includeDefaults, gbc);
+
+ removePermanently = new JCheckBox(REMOVE_PERMANENTLY);
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ add(removePermanently, gbc);
+
+ // Filler
+ gbc.gridx = 0;
+ gbc.gridy = 3;
+ gbc.weighty = 1;
+ gbc.weightx = 1;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ add(createButtonPanel(), gbc);
+
+ setFields(config);
+ }
+
+ /**
+ * Create the panel to contain the buttons
+ *
+ * @return
+ */
+ private JPanel createButtonPanel() {
+ final JPanel panel = new JPanel();
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ Helper.showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ setFields(config);
+ }
+ });
+ panel.add(resetButton);
+
+ /**
+ * The applyButton applies the shown field values to the
+ * {@link HttpProxyConfiguration} and saves them for future.
+ */
+ JButton applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ applySettings();
+ setFields(config);
+ }
+ });
+ panel.add(applyButton);
+
+ return panel;
+ }
+
+ protected void applySettings() {
+ // Include default service providers
+ config.setIncludeDefaults(includeDefaults.isSelected());
+ for (ServiceDescriptionProvider provider : serviceDescRegistry
+ .getDefaultServiceDescriptionProviders()) {
+ if (! (provider instanceof ConfigurableServiceProvider))
+ continue;
+ if (config.isIncludeDefaults())
+ serviceDescRegistry.addServiceDescriptionProvider(provider);
+ else
+ serviceDescRegistry.removeServiceDescriptionProvider(provider);
+ }
+
+ // Allow permanent removal of default service providers
+ config.setRemovePermanently(removePermanently.isSelected());
+ }
+
+ /**
+ * Set the shown configuration field values to those currently in use
+ * (i.e. last saved configuration).
+ *
+ */
+ private void setFields(ServiceDescriptionsConfiguration configurable) {
+ includeDefaults.setSelected(configurable.isIncludeDefaults());
+ removePermanently.setSelected(configurable.isRemovePermanently());
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/config/ServiceDescriptionConfigUIFactory.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/config/ServiceDescriptionConfigUIFactory.java
new file mode 100644
index 0000000..8746b54
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/config/ServiceDescriptionConfigUIFactory.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+
+public class ServiceDescriptionConfigUIFactory implements ConfigurationUIFactory {
+ private ServiceDescriptionsConfiguration serviceDescriptionsConfiguration;
+ private ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(serviceDescriptionsConfiguration.getUUID());
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return serviceDescriptionsConfiguration;
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new ServiceDescriptionConfigPanel(serviceDescriptionsConfiguration, serviceDescriptionRegistry);
+ }
+
+ public void setServiceDescriptionRegistry(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ }
+
+ public void setServiceDescriptionsConfiguration(ServiceDescriptionsConfiguration serviceDescriptionsConfiguration) {
+ this.serviceDescriptionsConfiguration = serviceDescriptionsConfiguration;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/menu/AddServiceProviderMenu.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/menu/AddServiceProviderMenu.java
new file mode 100644
index 0000000..f975778
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/menu/AddServiceProviderMenu.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.menu;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JPopupMenu;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.ui.servicepanel.ServicePanel;
+import net.sf.taverna.t2.workbench.ui.servicepanel.actions.AddServiceProviderAction;
+
+/**
+ * A menu that provides a set up menu actions for adding new service providers
+ * to the Service Panel.
+ * <p>
+ * The Actions are discovered from the {@link ServiceDescriptionProvider}s found
+ * through the SPI.
+ *
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ *
+ * @see ServiceDescription
+ * @see ServicePanel
+ * @see ServiceDescriptionRegistry#addServiceDescriptionProvider(ServiceDescriptionProvider)
+ */
+@SuppressWarnings("serial")
+public class AddServiceProviderMenu extends JButton {
+ public static class ServiceProviderComparator implements
+ Comparator<ServiceDescriptionProvider> {
+ @Override
+ public int compare(ServiceDescriptionProvider o1,
+ ServiceDescriptionProvider o2) {
+ return o1.getName().toLowerCase().compareTo(
+ o2.getName().toLowerCase());
+ }
+ }
+
+ private final static String ADD_SERVICE_PROVIDER_MENU_NAME = "Import new services";
+
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public AddServiceProviderMenu(ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ super();
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+
+ final Component c = createCustomComponent();
+ setAction(new AbstractAction(ADD_SERVICE_PROVIDER_MENU_NAME) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ((JPopupMenu) c).show(AddServiceProviderMenu.this, 0,
+ AddServiceProviderMenu.this.getHeight());
+ }
+ });
+ }
+
+ private Component createCustomComponent() {
+ JPopupMenu addServiceMenu = new JPopupMenu(
+ ADD_SERVICE_PROVIDER_MENU_NAME);
+ addServiceMenu.setToolTipText("Add a new service provider");
+ boolean isEmpty = true;
+ List<ConfigurableServiceProvider> providers = new ArrayList<>(
+ serviceDescriptionRegistry.getUnconfiguredServiceProviders());
+ Collections.sort(providers, new ServiceProviderComparator());
+ for (ConfigurableServiceProvider provider : providers) {
+ /*
+ * Skip BioCatalogue's ConfigurableServiceProviderS as they should
+ * not be used to add servcie directlry but rather though the
+ * Service Catalogue perspective
+ */
+ if (provider.getId().toLowerCase().contains("servicecatalogue"))
+ continue;
+
+ AddServiceProviderAction addAction = new AddServiceProviderAction(
+ provider, this);
+ addAction.setServiceDescriptionRegistry(serviceDescriptionRegistry);
+ addServiceMenu.add(addAction);
+ isEmpty = false;
+ }
+ if (isEmpty)
+ addServiceMenu.setEnabled(false);
+ return addServiceMenu;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/Filter.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/Filter.java
new file mode 100644
index 0000000..e67e8f5
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/Filter.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel.tree;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public interface Filter {
+ boolean pass(DefaultMutableTreeNode node);
+
+ String filterRepresentation(String original);
+
+ void setSuperseded(boolean superseded);
+
+ boolean isSuperseded();
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeCellRenderer.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeCellRenderer.java
new file mode 100644
index 0000000..21f43c5
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeCellRenderer.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel.tree;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.folderClosedIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.folderOpenIcon;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+@SuppressWarnings("serial")
+public class FilterTreeCellRenderer extends DefaultTreeCellRenderer {
+ private Filter filter = null;
+
+ @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);
+ Filter filter = getFilter();
+ if (filter != null)
+ setText(filter.filterRepresentation(getText()));
+ if (expanded)
+ setIcon(folderOpenIcon);
+ else
+ setIcon(folderClosedIcon);
+ return this;
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(Filter currentFilter) {
+ this.filter = currentFilter;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeModel.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeModel.java
new file mode 100644
index 0000000..191ed66
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeModel.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel.tree;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+
+import org.apache.log4j.Logger;
+
+public final class FilterTreeModel extends DefaultTreeModel {
+ private static final long serialVersionUID = -8931308369832839862L;
+ private static final Logger logger = Logger
+ .getLogger(FilterTreeModel.class);
+
+ Filter currentFilter;
+
+ public FilterTreeModel(FilterTreeNode node) {
+ this(node, null);
+ }
+
+ public FilterTreeModel(FilterTreeNode node, Filter filter) {
+ super(node);
+ currentFilter = filter;
+ node.setFilter(filter);
+ }
+
+ public void setFilter(Filter filter) {
+ if (root != null) {
+ currentFilter = filter;
+ ((FilterTreeNode) root).setFilter(filter);
+ Object[] path = { root };
+ fireTreeStructureChanged(this, path, null, null);
+ }
+ }
+
+ @Override
+ public int getChildCount(Object parent) {
+ if (parent instanceof FilterTreeNode)
+ return (((FilterTreeNode) parent).getChildCount());
+ return 0;
+ }
+
+ @Override
+ public Object getChild(Object parent, int index) {
+ if (parent instanceof FilterTreeNode)
+ return (((FilterTreeNode) parent).getChildAt(index));
+ return null;
+ }
+
+ /**
+ * @return the currentFilter
+ */
+ public Filter getCurrentFilter() {
+ return currentFilter;
+ }
+
+ public TreePath getTreePathForObjectPath(List<Object> path) {
+ List<FilterTreeNode> resultList = new ArrayList<>();
+ FilterTreeNode current = (FilterTreeNode) root;
+ resultList.add(current);
+ for (int i = 1; (i < path.size()) && (current != null); i++) {
+ logger.debug("Looking in " + current.getUserObject() + " for " + path.get(i));
+ current = current.getChildForObject(path.get(i));
+ if (current != null)
+ resultList.add(current);
+ }
+ if (current != null)
+ return new TreePath(resultList.toArray());
+ return null;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeNode.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeNode.java
new file mode 100644
index 0000000..83fd439
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeNode.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel.tree;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.apache.log4j.Logger;
+
+public class FilterTreeNode extends DefaultMutableTreeNode {
+ private static final long serialVersionUID = 1933553584349932151L;
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(FilterTreeNode.class);
+
+ private Filter filter;
+ private boolean passed = true;
+ private List<FilterTreeNode> filteredChildren = new ArrayList<>();
+
+ public FilterTreeNode(Object userObject) {
+ super(userObject);
+ userObject.toString();
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(Filter filter) {
+ if ((filter == null) || !filter.isSuperseded()) {
+ this.filter = filter;
+ passed = false;
+ filteredChildren.clear();
+ if (filter == null) {
+ passed = true;
+ passFilterDown(null);
+ } else if (filter.pass(this)) {
+ passed = true;
+ passFilterDown(null);
+ } else {
+ passFilterDown(filter);
+ passed = filteredChildren.size() != 0;
+ }
+ }
+ }
+
+ private void passFilterDown(Filter filter) {
+ int realChildCount = super.getChildCount();
+ for (int i = 0; i < realChildCount; i++) {
+ FilterTreeNode realChild = (FilterTreeNode) super.getChildAt(i);
+ realChild.setFilter(filter);
+ if (realChild.isPassed())
+ filteredChildren.add(realChild);
+ }
+ }
+
+ public void add(FilterTreeNode node) {
+ super.add(node);
+ node.setFilter(filter);
+ // TODO work up
+ if (node.isPassed())
+ filteredChildren.add(node);
+ }
+
+ @Override
+ public void remove(int childIndex) {
+ if (filter != null)
+ // as child indexes might be inconsistent..
+ throw new IllegalStateException("Can't remove while the filter is active");
+ super.remove(childIndex);
+ }
+
+ @Override
+ public int getChildCount() {
+ if (filter == null)
+ return super.getChildCount();
+ return filteredChildren.size();
+ }
+
+ @Override
+ public FilterTreeNode getChildAt(int index) {
+ if (filter == null)
+ return (FilterTreeNode) super.getChildAt(index);
+ return filteredChildren.get(index);
+ }
+
+ public boolean isPassed() {
+ return passed;
+ }
+
+ public Set<FilterTreeNode> getLeaves() {
+ Set<FilterTreeNode> result = new HashSet<>();
+ if (super.getChildCount() == 0) {
+ result.add(this);
+ return result;
+ }
+
+ for (int i = 0; i < super.getChildCount(); i++) {
+ FilterTreeNode child = (FilterTreeNode) super.getChildAt(i);
+ result.addAll(child.getLeaves());
+ }
+ return result;
+ }
+
+ public FilterTreeNode getChildForObject(Object userObject) {
+ FilterTreeNode result = null;
+ for (int i=0; (i < super.getChildCount()) && (result == null); i++) {
+ FilterTreeNode child = (FilterTreeNode) super.getChildAt(i);
+ Object nodeObject = child.getUserObject();
+// logger.info("nodeObject is a " + nodeObject.getClass() + " - " +
+// "userObject is a " + userObject.getClass());
+ if (nodeObject.toString().equals(userObject.toString())) {
+ result = child;
+// logger.info(nodeObject + " is equal to " + userObject);
+// } else {
+// logger.info(nodeObject + " is not equal to " + userObject);
+ }
+ }
+ return result;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeSelectionModel.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeSelectionModel.java
new file mode 100644
index 0000000..a5adfe9
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/FilterTreeSelectionModel.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.ui.servicepanel.tree;
+
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+public class FilterTreeSelectionModel extends DefaultTreeSelectionModel{
+ private static final long serialVersionUID = 3127644524735089630L;
+
+ public FilterTreeSelectionModel(){
+ super();
+ setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ }
+
+ @Override
+ public void setSelectionPath(TreePath path) {
+ /*
+ * Nothing happens here - only calls to mySetSelectionPath() will have
+ * the effect of a node being selected.
+ */
+ }
+
+ public void mySetSelectionPath(TreePath path) {
+ super.setSelectionPath(path);
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/MyFilter.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/MyFilter.java
new file mode 100644
index 0000000..8baa0eb
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/MyFilter.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel.tree;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class MyFilter implements Filter {
+ private static final String HTML_MATCH_END = "</font><font color=\"black\">";
+ private static final String HTML_MATCH_START = "</font><font color=\"red\">";
+ private static final String HTML_POSTFIX = "</font></html>";
+ private static final String HTML_PREFIX = "<html><font color=\"black\">";
+
+ private String filterString;
+ private boolean superseded;
+ private String filterLowerCase;
+
+ public MyFilter(String filterString) {
+ this.filterString = filterString;
+ this.filterLowerCase = filterString.toLowerCase();
+ this.superseded = false;
+ }
+
+ private boolean basicFilter(DefaultMutableTreeNode node) {
+ if (filterString.isEmpty())
+ return true;
+ return node.getUserObject().toString().toLowerCase()
+ .contains(filterLowerCase);
+ }
+
+ @Override
+ public boolean pass(DefaultMutableTreeNode node) {
+ return basicFilter(node);
+ }
+
+ @Override
+ public String filterRepresentation(String original) {
+ StringBuilder sb = new StringBuilder(HTML_PREFIX);
+ int from = 0;
+ String originalLowerCase = original.toLowerCase();
+ int index = originalLowerCase.indexOf(filterLowerCase, from);
+ while (index > -1) {
+ sb.append(original.substring(from, index));
+ sb.append(HTML_MATCH_START);
+ sb.append(original.substring(index,
+ index + filterLowerCase.length()));
+ sb.append(HTML_MATCH_END);
+ from = index + filterLowerCase.length();
+ index = originalLowerCase.indexOf(filterLowerCase, from);
+ }
+ if (from < original.length())
+ sb.append(original.substring(from, original.length()));
+ return sb.append(HTML_POSTFIX).toString();
+ }
+
+ /**
+ * @return the superseded
+ */
+ @Override
+ public boolean isSuperseded() {
+ return superseded;
+ }
+
+ /**
+ * @param superseded
+ * the superseded to set
+ */
+ @Override
+ public void setSuperseded(boolean superseded) {
+ this.superseded = superseded;
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/TreePanel.java b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/TreePanel.java
new file mode 100644
index 0000000..46eca53
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/java/net/sf/taverna/t2/workbench/ui/servicepanel/tree/TreePanel.java
@@ -0,0 +1,371 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.servicepanel.tree;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.WEST;
+import static java.awt.Color.GRAY;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.lang.ui.EdgeLineBorder.TOP;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.JTree;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.lang.ui.EdgeLineBorder;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public abstract class TreePanel extends JPanel {
+ private static int MAX_EXPANSION = 100;
+ private static final int SEARCH_WIDTH = 15;
+ private static Logger logger = Logger.getLogger(TreePanel.class);
+
+ protected Set<List<Object>> expandedPaths = new HashSet<>();
+ protected FilterTreeModel filterTreeModel;
+ protected JTextField searchField = new JTextField(SEARCH_WIDTH);
+ protected JTree tree = new JTree();
+ protected JScrollPane treeScrollPane;
+
+ private String availableObjectsString = "";
+ private String matchingObjectsString = "";
+ private String noMatchingObjectsString = "";
+
+ private TreeExpandCollapseListener treeExpandListener = new TreeExpandCollapseListener();
+ private Object filterLock = new Object();
+
+ public TreePanel(FilterTreeModel treeModel) {
+ filterTreeModel = treeModel;
+ }
+
+ public void expandTreePaths() throws InterruptedException,
+ InvocationTargetException {
+// Filter appliedFilter = filterTreeModel.getCurrentFilter();
+// if (appliedFilter == null) {
+ for (int i = 0; (i < tree.getRowCount()) && (i < MAX_EXPANSION); i++)
+ tree.expandRow(i);
+// } else {
+// boolean rowsFinished = false;
+// for (int i = 0; (!appliedFilter.isSuperseded()) && (!rowsFinished)
+// && (i < MAX_EXPANSION); i++) {
+// TreePath tp = tree.getPathForRow(i);
+// if (tp == null) {
+// rowsFinished = true;
+// } else {
+// if (!appliedFilter.pass((DefaultMutableTreeNode) tp
+// .getLastPathComponent())) {
+// tree.expandRow(i);
+// }
+// }
+// }
+// }
+ }
+
+ public void expandAll(FilterTreeNode node, boolean expand) {
+ @SuppressWarnings("unused")
+ FilterTreeNode root = (FilterTreeNode) tree.getModel().getRoot();
+
+ // Traverse tree from root
+ expandAll(new TreePath(node.getPath()), expand);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void expandAll(TreePath parent, boolean expand) {
+ // Traverse children
+ FilterTreeNode node = (FilterTreeNode) parent.getLastPathComponent();
+ if (node.getChildCount() >= 0)
+ for (Enumeration e=node.children(); e.hasMoreElements(); ) {
+ FilterTreeNode n = (FilterTreeNode) e.nextElement();
+ TreePath path = parent.pathByAddingChild(n);
+ expandAll(path, expand);
+ }
+
+ // Expansion or collapse must be done bottom-up
+ if (expand)
+ tree.expandPath(parent);
+ else
+ tree.collapsePath(parent);
+ }
+
+ protected void initialize() {
+ setLayout(new BorderLayout());
+ treeScrollPane = new JScrollPane(tree);
+ tree.setModel(filterTreeModel);
+ tree.addTreeExpansionListener(treeExpandListener);
+ tree.setCellRenderer(createCellRenderer());
+ tree.setSelectionModel(new FilterTreeSelectionModel());
+
+ JPanel topPanel = new JPanel();
+ topPanel.setBorder(new CompoundBorder(new EdgeLineBorder(TOP, GRAY), new EmptyBorder(10, 5, 0, 5)));
+ topPanel.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ JLabel filterLabel = new JLabel("Filter: ");
+ c.fill = NONE;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.0;
+ c.anchor = GridBagConstraints.LINE_START;
+ topPanel.add(filterLabel, c);
+
+ c.fill = HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 1.0;
+ topPanel.add(searchField, c);
+
+
+ c.fill = NONE;
+ c.gridx = 2;
+ c.gridy = 0;
+ c.weightx = 0.0;
+ final JButton clearButton = new JButton("Clear");
+ clearButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ searchField.setText("");
+ invokeLater(new RunFilter());
+ clearButton.getParent().requestFocusInWindow();// so that the button does not stay focused after it is clicked on and did its action
+ }
+ });
+ topPanel.add(clearButton, c);
+
+ c.gridx = 3;
+ c.weightx = 0.2;
+ topPanel.add(new JPanel(), c);
+
+ JPanel topExtraPanel = new JPanel(new BorderLayout());
+
+ topExtraPanel.add(topPanel, NORTH);
+
+ Component extraComponent = createExtraComponent();
+ if (extraComponent != null) {
+ JPanel extraPanel = new JPanel();
+ extraPanel.setLayout(new BorderLayout());
+ extraPanel.add(extraComponent, WEST);
+ topExtraPanel.add(extraPanel, CENTER);
+ }
+
+ add(topExtraPanel, NORTH);
+ add(treeScrollPane, CENTER);
+
+ searchField.addKeyListener(new SearchFieldKeyAdapter());
+ }
+
+ protected Component createExtraComponent() {
+ return null;
+ }
+
+ protected TreeCellRenderer createCellRenderer() {
+ return new FilterTreeCellRenderer();
+ }
+
+ public void runFilter() throws InterruptedException,
+ InvocationTargetException {
+ /*
+ * Special lock object, don't do a synchronized model, as the lock on
+ * JComponent might deadlock when painting the panel - see comments at
+ * http://www.mygrid.org.uk/dev/issues/browse/T2-1438
+ */
+ synchronized (filterLock) {
+ tree.removeTreeExpansionListener(treeExpandListener);
+ String text = searchField.getText();
+ FilterTreeNode root = (FilterTreeNode) tree.getModel().getRoot();
+ if (text.isEmpty()) {
+ setFilter(null);
+ root.setUserObject(getAvailableObjectsString());
+ filterTreeModel.nodeChanged(root);
+ for (List<Object> tp : expandedPaths) {
+ // for (int i = 0; i < tp.length; i++)
+ // logger.info("Trying to expand " + tp[i]);
+ tree.expandPath(filterTreeModel.getTreePathForObjectPath(tp));
+ }
+ } else {
+ setFilter(createFilter(text));
+ root.setUserObject(root.getChildCount() > 0 ? getMatchingObjectsString()
+ : getNoMatchingObjectsString());
+ filterTreeModel.nodeChanged(root);
+ expandTreePaths();
+ }
+ tree.addTreeExpansionListener(treeExpandListener);
+ }
+ }
+
+ /**
+ * @return the availableObjectsString
+ */
+ public String getAvailableObjectsString() {
+ return availableObjectsString;
+ }
+
+ /**
+ * @param availableObjectsString the availableObjectsString to set
+ */
+ public void setAvailableObjectsString(String availableObjectsString) {
+ this.availableObjectsString = availableObjectsString;
+ }
+
+ /**
+ * @return the matchingObjectsString
+ */
+ public String getMatchingObjectsString() {
+ return matchingObjectsString;
+ }
+
+ /**
+ * @param matchingObjectsString the matchingObjectsString to set
+ */
+ public void setMatchingObjectsString(String matchingObjectsString) {
+ this.matchingObjectsString = matchingObjectsString;
+ }
+
+ /**
+ * @return the noMatchingObjectsString
+ */
+ public String getNoMatchingObjectsString() {
+ return noMatchingObjectsString;
+ }
+
+ /**
+ * @param noMatchingObjectsString the noMatchingObjectsString to set
+ */
+ public void setNoMatchingObjectsString(String noMatchingObjectsString) {
+ this.noMatchingObjectsString = noMatchingObjectsString;
+ }
+
+ public Filter createFilter(String text) {
+ return new MyFilter(text);
+ }
+
+ public void setFilter(Filter filter) {
+ if (tree.getCellRenderer() instanceof FilterTreeCellRenderer)
+ ((FilterTreeCellRenderer)tree.getCellRenderer()).setFilter(filter);
+ filterTreeModel.setFilter(filter);
+ }
+
+ protected class ExpandRowRunnable implements Runnable {
+ int rowNumber;
+
+ public ExpandRowRunnable(int rowNumber) {
+ this.rowNumber = rowNumber;
+ }
+
+ @Override
+ public void run() {
+ tree.expandRow(rowNumber);
+ }
+ }
+
+ protected class RunFilter implements Runnable {
+ @Override
+ public void run() {
+ Filter oldFilter = filterTreeModel.getCurrentFilter();
+ if (oldFilter != null)
+ oldFilter.setSuperseded(true);
+ try {
+ runFilter();
+ } catch (InterruptedException e) {
+ Thread.interrupted();
+ } catch (InvocationTargetException e) {
+ logger.error("", e);
+ }
+ }
+ }
+
+ protected class SearchFieldKeyAdapter extends KeyAdapter {
+ private final Runnable runFilterRunnable;
+ Timer timer = new Timer("Search field timer", true);
+
+ private SearchFieldKeyAdapter() {
+ this.runFilterRunnable = new RunFilter();
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ timer.cancel();
+ timer = new Timer();
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ invokeLater(runFilterRunnable);
+ }
+ }, 500);
+ }
+ }
+
+ private void noteExpansions() {
+ expandedPaths.clear();
+ TreePath rootPath = new TreePath(filterTreeModel.getRoot());
+ for (Enumeration<TreePath> e = tree.getExpandedDescendants(rootPath); e.hasMoreElements();) {
+ List<Object> userObjects = new ArrayList<>();
+ Object[] expandedPath = e.nextElement().getPath();
+ for (int i = 0; i < expandedPath.length; i++) {
+ FilterTreeNode node = (FilterTreeNode) expandedPath[i];
+// logger.info("The object in the path is a " + expandedPath[i].getClass());
+ userObjects.add(node.getUserObject());
+// logger.info("Added " + node.getUserObject() + " to path");
+ }
+ expandedPaths.add(userObjects);
+ }
+ }
+
+ protected class TreeExpandCollapseListener implements TreeExpansionListener {
+ @Override
+ public void treeCollapsed(TreeExpansionEvent event) {
+ noteExpansions();
+ }
+
+ @Override
+ public void treeExpanded(TreeExpansionEvent event) {
+ noteExpansions();
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..bb87331
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.servicepanel.ServicePanelComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/spring/activity-palette-ui-context-osgi.xml b/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/spring/activity-palette-ui-context-osgi.xml
new file mode 100644
index 0000000..2d96b28
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/spring/activity-palette-ui-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ServiceDescriptionConfigUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <service ref="ServicePanelComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="serviceDescriptionRegistry" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry"/>
+ <reference id="serviceDescriptionsConfiguration" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration"/>
+ <reference id="serviceRegistry" interface="uk.org.taverna.commons.services.ServiceRegistry" />
+
+</beans:beans>
diff --git a/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/spring/activity-palette-ui-context.xml b/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/spring/activity-palette-ui-context.xml
new file mode 100644
index 0000000..f0a11c1
--- /dev/null
+++ b/taverna-workbench-activity-palette-ui/src/main/resources/META-INF/spring/activity-palette-ui-context.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ServiceDescriptionConfigUIFactory"
+ class="net.sf.taverna.t2.workbench.ui.servicepanel.config.ServiceDescriptionConfigUIFactory">
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="serviceDescriptionsConfiguration" ref="serviceDescriptionsConfiguration" />
+ </bean>
+
+ <bean id="ServicePanelComponentFactory"
+ class="net.sf.taverna.t2.workbench.ui.servicepanel.ServicePanelComponentFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="serviceDescriptionRegistry" ref="serviceDescriptionRegistry" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-activity-tools/pom.xml b/taverna-workbench-activity-tools/pom.xml
new file mode 100644
index 0000000..c84a263
--- /dev/null
+++ b/taverna-workbench-activity-tools/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-tools</artifactId>
+ <packaging>bundle</packaging>
+ <name>Activity tools</name>
+ <description>Tools useful for ui-activitys</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-activity-tools/src/main/java/net/sf/taverna/t2/workbench/activitytools/AbstractConfigureActivityMenuAction.java b/taverna-workbench-activity-tools/src/main/java/net/sf/taverna/t2/workbench/activitytools/AbstractConfigureActivityMenuAction.java
new file mode 100644
index 0000000..4744774
--- /dev/null
+++ b/taverna-workbench-activity-tools/src/main/java/net/sf/taverna/t2/workbench/activitytools/AbstractConfigureActivityMenuAction.java
@@ -0,0 +1,64 @@
+package net.sf.taverna.t2.workbench.activitytools;
+
+import static javax.swing.Action.NAME;
+
+import java.awt.Frame;
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.ui.Utils;
+
+public abstract class AbstractConfigureActivityMenuAction extends AbstractContextualMenuAction {
+ private static final URI configureSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configure");
+
+ protected Scufl2Tools scufl2Tools = new Scufl2Tools();
+ protected final URI activityType;
+
+ public AbstractConfigureActivityMenuAction(URI activityType) {
+ super(configureSection, 50);
+ this.activityType = activityType;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && findActivity() != null;
+ }
+
+ protected Activity findActivity() {
+ if (getContextualSelection() == null)
+ return null;
+ Object selection = getContextualSelection().getSelection();
+ if (selection instanceof Activity) {
+ Activity activity = (Activity) selection;
+ if (activity.getType().equals(activityType))
+ return activity;
+ }
+ if (selection instanceof Processor) {
+ Processor processor = (Processor) selection;
+ Profile profile = processor.getParent().getParent().getMainProfile();
+ for (ProcessorBinding processorBinding : scufl2Tools.processorBindingsForProcessor(processor, profile))
+ if (processorBinding.getBoundActivity().getType().equals(activityType))
+ return processorBinding.getBoundActivity();
+ }
+ return null;
+ }
+
+ protected Frame getParentFrame() {
+ return Utils.getParentFrame(getContextualSelection()
+ .getRelativeToComponent());
+ }
+
+ protected void addMenuDots(Action configAction) {
+ String oldName = (String) configAction.getValue(NAME);
+ if (!oldName.endsWith(".."))
+ configAction.putValue(NAME, oldName + "...");
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-configuration-api/pom.xml b/taverna-workbench-configuration-api/pom.xml
new file mode 100644
index 0000000..81e819f
--- /dev/null
+++ b/taverna-workbench-configuration-api/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Configuration Management API</name>
+ <description>General configuration management</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/colour/ColourManager.java b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/colour/ColourManager.java
new file mode 100644
index 0000000..4d5356f
--- /dev/null
+++ b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/colour/ColourManager.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.configuration.colour;
+
+import java.awt.Color;
+
+import uk.org.taverna.configuration.Configurable;
+
+/**
+ * @author David Withers
+ */
+public interface ColourManager extends Configurable {
+ /**
+ * Builds a Color that has been configured and associated with the given
+ * String (usually an object type).
+ *
+ * @return the associated Color, or if nothing is associated returns
+ * {@link Color#WHITE}.
+ */
+ Color getPreferredColour(String itemKey);
+
+ void setPreferredColour(String itemKey, Color colour);
+}
\ No newline at end of file
diff --git a/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/mimetype/MimeTypeManager.java b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/mimetype/MimeTypeManager.java
new file mode 100644
index 0000000..f0ae0d3
--- /dev/null
+++ b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/mimetype/MimeTypeManager.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.configuration.mimetype;
+
+import java.util.Map;
+
+import uk.org.taverna.configuration.Configurable;
+
+public interface MimeTypeManager extends Configurable {
+ @Override
+ String getCategory();
+
+ @Override
+ Map<String, String> getDefaultPropertyMap();
+
+ @Override
+ String getUUID();
+
+ @Override
+ String getDisplayName();
+
+ @Override
+ String getFilePrefix();
+}
diff --git a/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/workbench/WorkbenchConfiguration.java b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/workbench/WorkbenchConfiguration.java
new file mode 100644
index 0000000..461ba5c
--- /dev/null
+++ b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/workbench/WorkbenchConfiguration.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.configuration.workbench;
+
+import uk.org.taverna.configuration.Configurable;
+
+/**
+ * @author David Withers
+ */
+public interface WorkbenchConfiguration extends Configurable {
+ boolean getCaptureConsole();
+
+ void setCaptureConsole(boolean captureConsole);
+
+ boolean getWarnInternalErrors();
+
+ void setWarnInternalErrors(boolean warnInternalErrors);
+
+ int getMaxMenuItems();
+
+ void setMaxMenuItems(int maxMenuItems);
+
+ String getDotLocation();
+
+ void setDotLocation(String dotLocation);
+}
diff --git a/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/workbench/ui/T2ConfigurationFrame.java b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/workbench/ui/T2ConfigurationFrame.java
new file mode 100644
index 0000000..577484f
--- /dev/null
+++ b/taverna-workbench-configuration-api/src/main/java/net/sf/taverna/t2/workbench/configuration/workbench/ui/T2ConfigurationFrame.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.configuration.workbench.ui;
+
+/**
+ * @author David Withers
+ */
+public interface T2ConfigurationFrame {
+ void showFrame();
+
+ void showConfiguration(String name);
+}
\ No newline at end of file
diff --git a/taverna-workbench-configuration-impl/pom.xml b/taverna-workbench-configuration-impl/pom.xml
new file mode 100644
index 0000000..19356bb
--- /dev/null
+++ b/taverna-workbench-configuration-impl/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>configuration-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Configuration Management Implementations</name>
+ <description>General configuration management</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-impl</artifactId>
+ <version>0.1.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-impl</artifactId>
+ <version>0.1.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java
new file mode 100644
index 0000000..0e63a4a
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * An implementation of Configurable for general Workbench configuration
+ * properties
+ *
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchConfigurationImpl extends AbstractConfigurable implements
+ WorkbenchConfiguration {
+ private static Logger logger = Logger
+ .getLogger(WorkbenchConfiguration.class);
+ private static final int DEFAULT_MAX_MENU_ITEMS = 20;
+ public static final String TAVERNA_DOTLOCATION = "taverna.dotlocation";
+ public static final String MAX_MENU_ITEMS = "taverna.maxmenuitems";
+ public static final String WARN_INTERNAL_ERRORS = "taverna.warninternal";
+ public static final String CAPTURE_CONSOLE = "taverna.captureconsole";
+ private static final String BIN = "bin";
+ private static final String BUNDLE_CONTENTS = "Contents";
+ private static final String BUNDLE_MAC_OS = "MacOS";
+ private static final String DOT_EXE = "dot.exe";
+ private static final String DOT_FALLBACK = "dot";
+ public static String uuid = "c14856f0-5967-11dd-ae16-0800200c9a66";
+ private static final String MAC_OS_X = "Mac OS X";
+ private static final String WIN32I386 = "win32i386";
+ private static final String WINDOWS = "Windows";
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ /**
+ * Constructs a new <code>WorkbenchConfigurationImpl</code>.
+ *
+ * @param configurationManager
+ */
+ public WorkbenchConfigurationImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ Map<String, String> defaultWorkbenchProperties = null;
+ Map<String, String> workbenchProperties = new HashMap<String, String>();
+
+ @Override
+ public String getCategory() {
+ return "general";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultWorkbenchProperties == null) {
+ defaultWorkbenchProperties = new HashMap<>();
+ String dotLocation = System.getProperty(TAVERNA_DOTLOCATION) != null ? System
+ .getProperty(TAVERNA_DOTLOCATION) : getDefaultDotLocation();
+ if (dotLocation != null)
+ defaultWorkbenchProperties
+ .put(TAVERNA_DOTLOCATION, dotLocation);
+ defaultWorkbenchProperties.put(MAX_MENU_ITEMS,
+ Integer.toString(DEFAULT_MAX_MENU_ITEMS));
+ defaultWorkbenchProperties.put(WARN_INTERNAL_ERRORS,
+ Boolean.FALSE.toString());
+ defaultWorkbenchProperties.put(CAPTURE_CONSOLE,
+ Boolean.TRUE.toString());
+ }
+ return defaultWorkbenchProperties;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Workbench";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "Workbench";
+ }
+
+ @Override
+ public String getUUID() {
+ return uuid;
+ }
+
+ @Override
+ public boolean getWarnInternalErrors() {
+ String property = getProperty(WARN_INTERNAL_ERRORS);
+ return Boolean.parseBoolean(property);
+ }
+
+ @Override
+ public boolean getCaptureConsole() {
+ String property = getProperty(CAPTURE_CONSOLE);
+ return Boolean.parseBoolean(property);
+ }
+
+ @Override
+ public void setWarnInternalErrors(boolean warnInternalErrors) {
+ setProperty(WARN_INTERNAL_ERRORS, Boolean.toString(warnInternalErrors));
+ }
+
+ @Override
+ public void setCaptureConsole(boolean captureConsole) {
+ setProperty(CAPTURE_CONSOLE, Boolean.toString(captureConsole));
+ }
+
+ @Override
+ public void setMaxMenuItems(int maxMenuItems) {
+ if (maxMenuItems < 2)
+ throw new IllegalArgumentException(
+ "Maximum menu items must be at least 2");
+ setProperty(MAX_MENU_ITEMS, Integer.toString(maxMenuItems));
+ }
+
+ @Override
+ public int getMaxMenuItems() {
+ String property = getProperty(MAX_MENU_ITEMS);
+ try {
+ int maxMenuItems = Integer.parseInt(property);
+ if (maxMenuItems >= 2)
+ return maxMenuItems;
+ logger.warn(MAX_MENU_ITEMS + " can't be less than 2");
+ } catch (NumberFormatException ex) {
+ logger.warn("Invalid number for " + MAX_MENU_ITEMS + ": "
+ + property);
+ }
+ // We'll return the default instead
+ return DEFAULT_MAX_MENU_ITEMS;
+ }
+
+ @Override
+ public String getDotLocation() {
+ return getProperty(TAVERNA_DOTLOCATION);
+ }
+
+ @Override
+ public void setDotLocation(String dotLocation) {
+ setProperty(TAVERNA_DOTLOCATION, dotLocation);
+ }
+
+ private String getDefaultDotLocation() {
+ if (applicationConfiguration == null)
+ return null;
+ File startupDir = applicationConfiguration.getStartupDir();
+ if (startupDir == null)
+ return DOT_FALLBACK;
+
+ String os = System.getProperty("os.name");
+ if (os.equals(MAC_OS_X))
+ if (startupDir.getParentFile() != null) {
+ File contentsDir = startupDir.getParentFile().getParentFile();
+ if (contentsDir != null
+ && contentsDir.getName().equalsIgnoreCase(
+ BUNDLE_CONTENTS)) {
+ File dot = new File(new File(contentsDir, BUNDLE_MAC_OS),
+ DOT_FALLBACK);
+ if (dot.exists())
+ return dot.getAbsolutePath();
+ }
+ } else if (os.startsWith(WINDOWS)) {
+ File binWin386Dir = new File(new File(startupDir, BIN),
+ WIN32I386);
+ File dot = new File(binWin386Dir, DOT_EXE);
+ if (dot.exists())
+ return dot.getAbsolutePath();
+ }
+ return DOT_FALLBACK;
+ }
+
+ /**
+ * Sets the applicationConfiguration.
+ *
+ * @param applicationConfiguration
+ * the new value of applicationConfiguration
+ */
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ defaultWorkbenchProperties = null;
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java
new file mode 100644
index 0000000..ecddc35
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java
@@ -0,0 +1,266 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration;
+
+import static java.awt.GridBagConstraints.*;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openIcon;
+
+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 javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkbenchConfigurationPanel extends JPanel {
+ private static final String RESTART_MSG = "For the new configuration to be fully applied, it is advised to restart Taverna.";
+ private static final String DOT_PATH_MSG = "Path to Graphviz executable <code>dot</code>:";
+ private static final String CONTEXT_MENU_SIZE_MSG = "Maximum number of services/ports in right-click menu:";
+ private static Logger logger = Logger
+ .getLogger(WorkbenchConfigurationUIFactory.class);
+
+ private JTextField dotLocation = new JTextField(25);
+ private JTextField menuItems = new JTextField(10);
+ private JCheckBox warnInternal = new JCheckBox("Warn on internal errors");
+ private JCheckBox captureConsole = new JCheckBox(
+ "Capture output on stdout/stderr to log file");
+
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ public WorkbenchConfigurationPanel(
+ WorkbenchConfiguration workbenchConfiguration) {
+ super();
+ this.workbenchConfiguration = workbenchConfiguration;
+ initComponents();
+ }
+
+ private static JLabel htmlLabel(String html) {
+ return new JLabel("<html><body>" + html + "</body></html>");
+ }
+
+ private void initComponents() {
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea(
+ "General Workbench configuration");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ gbc.anchor = WEST;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.fill = HORIZONTAL;
+ this.add(descriptionText, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(10, 5, 0, 0);
+ gbc.fill = NONE;
+ this.add(htmlLabel(DOT_PATH_MSG), gbc);
+
+ dotLocation.setText(workbenchConfiguration.getDotLocation());
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.weightx = 1.0;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ gbc.fill = HORIZONTAL;
+ this.add(dotLocation, gbc);
+
+ JButton browseButton = new JButton();
+ gbc.gridx = 1;
+ gbc.weightx = 0.0;
+ gbc.fill = NONE;
+ this.add(browseButton, gbc);
+ browseButton.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ System.setProperty("com.apple.macos.use-file-dialog-packages",
+ "false");
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.putClientProperty(
+ "JFileChooser.appBundleIsTraversable", "always");
+ fileChooser.putClientProperty(
+ "JFileChooser.packageIsTraversable", "always");
+
+ fileChooser.setDialogTitle("Browse for dot");
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+
+ fileChooser.setMultiSelectionEnabled(false);
+
+ int returnVal = fileChooser
+ .showOpenDialog(WorkbenchConfigurationPanel.this);
+ if (returnVal == APPROVE_OPTION)
+ dotLocation.setText(fileChooser.getSelectedFile()
+ .getAbsolutePath());
+ }
+ });
+ browseButton.setIcon(openIcon);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(10, 5, 0, 0);
+ gbc.fill = HORIZONTAL;
+ this.add(htmlLabel(CONTEXT_MENU_SIZE_MSG), gbc);
+
+ menuItems.setText(Integer.toString(workbenchConfiguration
+ .getMaxMenuItems()));
+ gbc.gridy++;
+ gbc.weightx = 1.0;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ gbc.fill = HORIZONTAL;
+ this.add(menuItems, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.fill = HORIZONTAL;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ warnInternal
+ .setSelected(workbenchConfiguration.getWarnInternalErrors());
+ this.add(warnInternal, gbc);
+
+ gbc.gridy++;
+ gbc.insets = new Insets(0, 0, 10, 0);
+ captureConsole.setSelected(workbenchConfiguration.getCaptureConsole());
+ this.add(captureConsole, gbc);
+
+ // Add the buttons panel
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 3;
+ gbc.weightx = 1.0;
+ gbc.weighty = 1.0;
+ gbc.fill = BOTH;
+ gbc.anchor = SOUTH;
+ this.add(getButtonsPanel(), gbc);
+ }
+
+ private Component getButtonsPanel() {
+ final JPanel panel = new JPanel();
+ panel.setLayout(new FlowLayout(FlowLayout.CENTER));
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ resetFields();
+ }
+ });
+ panel.add(resetButton);
+
+ JButton applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ String menus = menuItems.getText();
+ try {
+ workbenchConfiguration.setMaxMenuItems(Integer
+ .valueOf(menus));
+ } catch (IllegalArgumentException e) {
+ String message = "Invalid menu items number " + menus
+ + ":\n" + e.getLocalizedMessage();
+ showMessageDialog(panel, message, "Invalid menu items",
+ WARNING_MESSAGE);
+ return;
+ }
+
+ workbenchConfiguration.setCaptureConsole(captureConsole
+ .isSelected());
+ workbenchConfiguration.setWarnInternalErrors(warnInternal
+ .isSelected());
+ workbenchConfiguration.setDotLocation(dotLocation.getText());
+ try {
+ showMessageDialog(panel, RESTART_MSG, "Restart adviced",
+ INFORMATION_MESSAGE);
+ } catch (Exception e) {
+ logger.error("Error storing updated configuration", e);
+ }
+ }
+ });
+ panel.add(applyButton);
+ return panel;
+ }
+
+ /**
+ * Resets the shown field values to those currently set (last saved) in the
+ * configuration.
+ *
+ * @param configurable
+ */
+ private void resetFields() {
+ menuItems.setText(Integer.toString(workbenchConfiguration
+ .getMaxMenuItems()));
+ dotLocation.setText(workbenchConfiguration.getDotLocation());
+ warnInternal
+ .setSelected(workbenchConfiguration.getWarnInternalErrors());
+ captureConsole.setSelected(workbenchConfiguration.getCaptureConsole());
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java
new file mode 100644
index 0000000..263233f
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+public class WorkbenchConfigurationUIFactory implements ConfigurationUIFactory {
+ private WorkbenchConfiguration workbenchConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(workbenchConfiguration.getUUID());
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new WorkbenchConfigurationPanel(workbenchConfiguration);
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return workbenchConfiguration;
+ }
+
+ public void setWorkbenchConfiguration(
+ WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java
new file mode 100644
index 0000000..4c03dbe
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration.colour;
+
+import static java.awt.Color.WHITE;
+import static java.awt.Color.decode;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+/**
+ * A factory class that determines the colour that a Colourable UI component
+ * should be displayed as, according to a schema configured by the user.
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @see Colourable
+ */
+public class ColourManagerImpl extends AbstractConfigurable implements
+ ColourManager {
+ // Names of things that may be coloured
+ private static final String WORKFLOW_PORT_OBJECT = "uk.org.taverna.scufl2.api.port.WorkflowPort";
+ private static final String PROCESSOR_PORT_OBJECT = "uk.org.taverna.scufl2.api.port.ProcessorPort";
+ private static final String PROCESSOR_OBJECT = "uk.org.taverna.scufl2.api.core.Processor";
+ private static final String MERGE_OBJECT = "net.sf.taverna.t2.workflowmodel.Merge";
+ private static final String NONEXECUTABLE_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/nonExecutable";
+ private static final String XML_SPLITTER_OUT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/xml-splitter/out";
+ private static final String XML_SPLITTER_IN_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/xml-splitter/in";
+ private static final String LOCALWORKER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/localworker";
+ private static final String WSDL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/wsdl";
+ private static final String CONSTANT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/constant";
+ private static final String SOAPLAB_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/soaplab";
+ private static final String RSHELL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/rshell";
+ private static final String NESTED_WORKFLOW = "http://ns.taverna.org.uk/2010/activity/nested-workflow";
+ private static final String MOBY_PARSER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/parser";
+ private static final String MOBY_OBJECT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/object";
+ private static final String MOBY_SERVICE_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/service";
+ private static final String BIOMART_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomart";
+ private static final String BEANSHELL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/beanshell";
+ private static final String APICONSUMER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/apiconsumer";
+
+ // Names of colours used
+ private static final String burlywood2 = "#deb887";
+ private static final String darkgoldenrod1 = "#ffb90f";
+ private static final String darkolivegreen3 = "#a2cd5a";
+ private static final String gold = "#ffd700";
+ private static final String grey = "#777777";
+ private static final String lightcyan2 = "#d1eeee";
+ private static final String lightgoldenrodyellow = "#fafad2";
+ // light purple non standard
+ private static final String lightpurple = "#ab92ea";
+ private static final String lightsteelblue = "#b0c4de";
+ private static final String mediumorchid2 = "#d15fee";
+ private static final String palegreen = "#98fb98";
+ private static final String pink = "#ffc0cb";
+ private static final String purplish = "#8070ff";
+ // ShadedLabel.Orange
+ private static final String shadedorange = "#eece8f";
+ // ShadedLabel.Green
+ private static final String shadedgreen = "#a1c69d";
+ // slightly lighter than the real steelblue4
+ private static final String steelblue4 = "#648faa";
+ private static final String turquoise = "#77aadd";
+ private static final String white = "#ffffff";
+
+ private Map<String, String> defaultPropertyMap;
+ private Map<Object, Color> cachedColours;
+
+ public ColourManagerImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ initialiseDefaults();
+ }
+
+ @Override
+ public String getCategory() {
+ return "colour";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null)
+ initialiseDefaults();
+ return defaultPropertyMap;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Colour Management";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "ColourManagement";
+ }
+
+ /**
+ * Unique identifier for this ColourManager
+ */
+ @Override
+ public String getUUID() {
+ return "a2148420-5967-11dd-ae16-0800200c9a66";
+ }
+
+ private void initialiseDefaults() {
+ defaultPropertyMap = new HashMap<>();
+ cachedColours = new HashMap<>();
+
+ defaultPropertyMap.put(APICONSUMER_ACTIVITY, palegreen);
+ defaultPropertyMap.put(BEANSHELL_ACTIVITY, burlywood2);
+ defaultPropertyMap.put(BIOMART_ACTIVITY, lightcyan2);
+ defaultPropertyMap.put(CONSTANT_ACTIVITY, lightsteelblue);
+ defaultPropertyMap.put(LOCALWORKER_ACTIVITY, mediumorchid2);
+ defaultPropertyMap.put(MOBY_SERVICE_ACTIVITY, darkgoldenrod1);
+ defaultPropertyMap.put(MOBY_OBJECT_ACTIVITY, gold);
+ defaultPropertyMap.put(MOBY_PARSER_ACTIVITY, white);
+ defaultPropertyMap.put(NESTED_WORKFLOW, pink);
+ defaultPropertyMap.put(RSHELL_ACTIVITY, steelblue4);
+ defaultPropertyMap.put(SOAPLAB_ACTIVITY, lightgoldenrodyellow);
+ defaultPropertyMap.put(WSDL_ACTIVITY, darkolivegreen3);
+ defaultPropertyMap.put(XML_SPLITTER_IN_ACTIVITY, lightpurple);
+ defaultPropertyMap.put(XML_SPLITTER_OUT_ACTIVITY, lightpurple);
+
+ defaultPropertyMap.put(NONEXECUTABLE_ACTIVITY, grey);
+
+ defaultPropertyMap.put(MERGE_OBJECT, turquoise);
+ defaultPropertyMap.put(PROCESSOR_OBJECT, shadedgreen);
+ defaultPropertyMap.put(PROCESSOR_PORT_OBJECT, purplish);
+ defaultPropertyMap.put(WORKFLOW_PORT_OBJECT, shadedorange);
+ }
+
+ @Override
+ public Color getPreferredColour(String itemKey) {
+ Color colour = cachedColours.get(itemKey);
+ if (colour == null) {
+ String colourString = (String) getProperty(itemKey);
+ colour = colourString == null ? WHITE : decode(colourString);
+ cachedColours.put(itemKey, colour);
+ }
+ return colour;
+ }
+
+ @Override
+ public void setPreferredColour(String itemKey, Color colour) {
+ cachedColours.put(itemKey, colour);
+ }
+
+ @Override
+ public void restoreDefaults() {
+ super.restoreDefaults();
+ if (cachedColours == null)
+ cachedColours = new HashMap<>();
+ else
+ cachedColours.clear();
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java
new file mode 100644
index 0000000..0ff6c65
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration.mimetype;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.workbench.configuration.mimetype.MimeTypeManager;
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class MimeTypeManagerImpl extends AbstractConfigurable implements
+ MimeTypeManager {
+ /**
+ * Constructs a new <code>MimeTypeManagerImpl</code>.
+ *
+ * @param configurationManager
+ */
+ public MimeTypeManagerImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return "Mime Type";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ HashMap<String, String> map = new HashMap<>();
+ map.put("text/plain", "Plain Text");
+ map.put("text/xml", "XML Text");
+ map.put("text/html", "HTML Text");
+ map.put("text/rtf", "Rich Text Format");
+ map.put("text/x-graphviz", "Graphviz Dot File");
+ map.put("image/png", "PNG Image");
+ map.put("image/jpeg", "JPEG Image");
+ map.put("image/gif", "GIF Image");
+ map.put("application/octet-stream", "Binary Data");
+ map.put("application/zip", "Zip File");
+ map.put("chemical/x-swissprot", "SWISSPROT Flat File");
+ map.put("chemical/x-embl-dl-nucleotide", "EMBL Flat File");
+ map.put("chemical/x-ppd", "PPD File");
+ map.put("chemical/seq-aa-genpept", "Genpept Protein");
+ map.put("chemical/seq-na-genbank", "Genbank Nucleotide");
+ map.put("chemical/x-pdb", "PDB 3D Structure File");
+ return map;
+ }
+
+ @Override
+ public String getUUID() {
+ return "b9277fa0-5967-11dd-ae16-0800200c9a66";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Mime Type Manager";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "MimeTypeManagerImpl";
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java
new file mode 100644
index 0000000..4910f78
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration.ui;
+
+import static javax.swing.JSplitPane.HORIZONTAL_SPLIT;
+import static net.sf.taverna.t2.workbench.helper.HelpCollator.registerComponent;
+import static net.sf.taverna.t2.workbench.helper.Helper.setKeyCatcher;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.ListModel;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+public class T2ConfigurationFrameImpl implements T2ConfigurationFrame {
+ private static Logger logger = Logger.getLogger(T2ConfigurationFrameImpl.class);
+ private static final int FRAME_WIDTH = 700;
+ private static final int FRAME_HEIGHT = 450;
+
+ private List<ConfigurationUIFactory> configurationUIFactories = new ArrayList<>();
+
+ private JFrame frame;
+ private JSplitPane splitPane;
+ private JList<ConfigurableItem> list;
+
+ public T2ConfigurationFrameImpl() {
+ }
+
+ @Override
+ public void showFrame() {
+ getFrame().setVisible(true);
+ }
+
+ @Override
+ public void showConfiguration(String name) {
+ showFrame();
+ ListModel<ConfigurableItem> lm = list.getModel();
+ for (int i = 0; i < lm.getSize(); i++)
+ if (lm.getElementAt(i).toString().equals(name)) {
+ list.setSelectedIndex(i);
+ break;
+ }
+ }
+
+ private JFrame getFrame() {
+ if (frame != null)
+ return frame;
+
+ frame = new JFrame();
+ setKeyCatcher(frame);
+ registerComponent(frame);
+ frame.setLayout(new BorderLayout());
+
+ /*
+ * Split pane to hold list of properties (on the left) and their
+ * configurable options (on the right)
+ */
+ splitPane = new JSplitPane(HORIZONTAL_SPLIT);
+ splitPane.setBorder(null);
+
+ list = getConfigurationList();
+ JScrollPane jspList = new JScrollPane(list);
+ jspList.setBorder(new EmptyBorder(5, 5, 5, 5));
+ jspList.setMinimumSize(new Dimension(150,
+ jspList.getPreferredSize().height));
+
+ splitPane.setLeftComponent(jspList);
+ splitPane.setRightComponent(new JPanel());
+ splitPane.setDividerSize(1);
+
+ // select first item if one exists
+ if (list.getModel().getSize() > 0)
+ list.setSelectedValue(list.getModel().getElementAt(0), true);
+
+ frame.add(splitPane);
+ frame.setSize(new Dimension(FRAME_WIDTH, FRAME_HEIGHT));
+ return frame;
+ }
+
+ private JList<ConfigurableItem> getConfigurationList() {
+ if (list != null)
+ return list;
+
+ list = new JList<>();
+ list.addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (list.getSelectedValue() instanceof ConfigurableItem) {
+ ConfigurableItem item = (ConfigurableItem) list
+ .getSelectedValue();
+ setMainPanel(item.getPanel());
+ }
+
+ /*
+ * Keep the split pane's divider at its current position - but
+ * looks ugly. The problem with divider moving from its current
+ * position after selecting an item from the list on the left is
+ * that the right hand side panels are loaded dynamically and it
+ * seems there is nothing we can do about it - it's just the
+ * JSplitPane's behaviour
+ */
+ // splitPane.setDividerLocation(splitPane.getLastDividerLocation());
+ }
+ });
+ list.setListData(getListItems());
+ return list;
+ }
+
+ private void setMainPanel(JPanel panel) {
+ panel.setBorder(new EmptyBorder(15, 15, 15, 15));
+ splitPane.setRightComponent(panel);
+ }
+
+ public void setConfigurationUIFactories(
+ List<ConfigurationUIFactory> configurationUIFactories) {
+ this.configurationUIFactories = configurationUIFactories;
+ }
+
+ private ConfigurableItem[] getListItems() {
+ List<ConfigurableItem> arrayList = new ArrayList<>();
+ for (ConfigurationUIFactory fac : configurationUIFactories) {
+ String name = fac.getConfigurable().getDisplayName();
+ if (name != null) {
+ logger.info("Adding configurable for name: " + name);
+ arrayList.add(new ConfigurableItem(fac));
+ } else {
+ logger.warn("The configurable " + fac.getConfigurable().getClass()
+ + " has a null name");
+ }
+ }
+ // Sort the list alphabetically
+ ConfigurableItem[] array = arrayList.toArray(new ConfigurableItem[0]);
+ Arrays.sort(array, new Comparator<ConfigurableItem>() {
+ @Override
+ public int compare(ConfigurableItem item1, ConfigurableItem item2) {
+ return item1.toString().compareToIgnoreCase(item2.toString());
+ }
+ });
+ return array;
+ }
+
+ public void update(Object service, Map<?, ?> properties) {
+ getConfigurationList().setListData(getListItems());
+ if (frame != null) {
+ frame.revalidate();
+ frame.repaint();
+ // select first item if one exists
+ if (list.getModel().getSize() > 0)
+ list.setSelectedValue(list.getModel().getElementAt(0), true);
+ }
+ }
+
+ class ConfigurableItem {
+ private final ConfigurationUIFactory factory;
+
+ public ConfigurableItem(ConfigurationUIFactory factory) {
+ this.factory = factory;
+ }
+
+ public JPanel getPanel() {
+ return factory.getConfigurationPanel();
+ }
+
+ @Override
+ public String toString() {
+ return factory.getConfigurable().getDisplayName();
+ }
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java
new file mode 100644
index 0000000..453f0c0
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration.ui;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+
+public class WorkbenchConfigurationMenu extends AbstractMenuAction {
+ private static final String MAC_OS_X = "Mac OS X";
+
+ private T2ConfigurationFrame t2ConfigurationFrame;
+
+ public WorkbenchConfigurationMenu() {
+ super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#preferences"),
+ 100);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Preferences") {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ t2ConfigurationFrame.showFrame();
+ }
+ };
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return !MAC_OS_X.equalsIgnoreCase(System.getProperty("os.name"));
+ }
+
+ public void setT2ConfigurationFrame(T2ConfigurationFrame t2ConfigurationFrame) {
+ this.t2ConfigurationFrame = t2ConfigurationFrame;
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java
new file mode 100644
index 0000000..d131ac3
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration.ui;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class WorkbenchPreferencesSection extends AbstractMenuSection {
+ private static final URI FILE_MENU = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ private static final URI PREFERENCES_MENU_ITEM = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#preferences");
+
+ public WorkbenchPreferencesSection() {
+ super(FILE_MENU, 100, PREFERENCES_MENU_ITEM);
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..3b51dd4
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchPreferencesSection
+net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchConfigurationMenu
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..4af55ec
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml
new file mode 100644
index 0000000..29aea44
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="t2ConfigurationFrame" interface="net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame" />
+
+ <service ref="WorkbenchConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <service ref="WorkbenchPreferencesSection" auto-export="interfaces" />
+ <service ref="WorkbenchConfigurationMenu" auto-export="interfaces" />
+
+ <service ref="ColourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <service ref="WorkbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <service ref="MimeTypeManager" interface="net.sf.taverna.t2.workbench.configuration.mimetype.MimeTypeManager" />
+
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+ <list id="configurationUIFactories" interface="uk.org.taverna.configuration.ConfigurationUIFactory" cardinality="0..N">
+ <listener ref="t2ConfigurationFrame" bind-method="update" unbind-method="update"/>
+ </list>
+
+</beans:beans>
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml
new file mode 100644
index 0000000..40da7fd
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="t2ConfigurationFrame" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.T2ConfigurationFrameImpl">
+ <property name="configurationUIFactories" ref="configurationUIFactories" />
+ </bean>
+
+ <bean id="WorkbenchConfigurationUIFactory" class="net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationUIFactory">
+ <property name="workbenchConfiguration">
+ <ref local="WorkbenchConfiguration"/>
+ </property>
+ </bean>
+
+ <bean id="WorkbenchPreferencesSection" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchPreferencesSection" />
+ <bean id="WorkbenchConfigurationMenu" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchConfigurationMenu">
+ <property name="t2ConfigurationFrame">
+ <ref local="t2ConfigurationFrame"/>
+ </property>
+ </bean>
+
+ <bean id="ColourManager" class="net.sf.taverna.t2.workbench.ui.impl.configuration.colour.ColourManagerImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+ <bean id="WorkbenchConfiguration" class="net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationImpl">
+ <constructor-arg ref="configurationManager"/>
+ <property name="applicationConfiguration" ref="applicationConfiguration" />
+ </bean>
+ <bean id="MimeTypeManager" class="net.sf.taverna.t2.workbench.ui.impl.configuration.mimetype.MimeTypeManagerImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java
new file mode 100644
index 0000000..1202c11
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.UUID;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.ui.impl.configuration.colour.ColourManagerImpl;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ConfigurationManagerTest {
+
+ ConfigurationManagerImpl configurationManager;
+
+ @Before
+ public void setup() {
+ configurationManager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl());
+ }
+
+ @Test
+ public void createConfigManager() {
+ assertNotNull("Config Manager should not be null", configurationManager);
+ }
+
+ @Ignore("Hardcoded /Users/Ian") //FIXME: update test to work using File.createTempFile(...)
+ @Test
+ public void populateConfigOfColourmanager() {
+ ColourManager manager= new ColourManagerImpl(null);
+
+ manager.setProperty("colour.first", "25");
+ manager.setProperty("colour.second", "223");
+
+ configurationManager.setBaseConfigLocation(new File("/Users/Ian/scratch"));
+ try {
+ configurationManager.store(manager);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+
+ ColourManager manager2 = new ColourManagerImpl(configurationManager);
+
+ try {
+ configurationManager.populate(manager2);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ assertEquals("Properties do not match", manager2.getProperty("colour.first"), manager.getProperty("colour.first"));
+ assertEquals("Properties do not match", manager2.getProperty("colour.second"), manager.getProperty("colour.second"));
+
+
+ }
+
+ @Test
+ public void saveColoursForDummyColourable() {
+ String dummy = "";
+ ColourManager manager=new ColourManagerImpl(configurationManager);
+ manager.setProperty(dummy.getClass().getCanonicalName(), "#000000");
+
+ File f = new File(System.getProperty("java.io.tmpdir"));
+ File d = new File(f, UUID.randomUUID().toString());
+ d.mkdir();
+ configurationManager.setBaseConfigLocation(d);
+ try {
+ configurationManager.store(manager);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+
+ try {
+ configurationManager.populate(manager);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+
+ }
+
+}
diff --git a/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java
new file mode 100644
index 0000000..0239ea8
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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.ui.impl.configuration.colour;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.UUID;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ColourManagerTest {
+
+ private ConfigurationManagerImpl configurationManager;
+
+ @Before
+ public void setup() {
+ configurationManager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl());
+
+ File f = new File(System.getProperty("java.io.tmpdir"));
+ File d = new File(f, UUID.randomUUID().toString());
+ d.mkdir();
+ configurationManager.setBaseConfigLocation(d);
+ }
+
+ @Test
+ public void testGetPreferredColourEqualsWhite() throws Exception {
+ String dummy = new String();
+
+ Color c = new ColourManagerImpl(configurationManager).getPreferredColour(dummy);
+ assertEquals("The default colour should be WHITE", Color.WHITE, c);
+ }
+
+ @Test
+ public void testConfigurableness() throws Exception {
+ ColourManager manager = new ColourManagerImpl(configurationManager);
+ assertTrue(manager instanceof Configurable);
+
+ assertEquals("wrong category", "colour", manager.getCategory());
+ assertEquals("wrong name", "Colour Management", manager.getDisplayName());
+ assertEquals("wrong UUID", "a2148420-5967-11dd-ae16-0800200c9a66",
+ manager.getUUID());
+ assertNotNull("there is no default property map", manager
+ .getDefaultPropertyMap());
+ }
+
+ @Test
+ public void saveAsWrongArrayType() throws Exception {
+ String dummy = "";
+ ColourManager manager = new ColourManagerImpl(configurationManager);
+ manager.setProperty(dummy.getClass().getCanonicalName(), "#ffffff");
+
+ File baseLoc = File.createTempFile("test", "scratch");
+ baseLoc.delete();
+ assertTrue("Could not make directory " + baseLoc, baseLoc.mkdir());
+ configurationManager.setBaseConfigLocation(baseLoc);
+ configurationManager.store(manager);
+ configurationManager.populate(manager);
+ manager.getPreferredColour(dummy);
+ }
+
+}
diff --git a/taverna-workbench-contextual-views-api/pom.xml b/taverna-workbench-contextual-views-api/pom.xml
new file mode 100644
index 0000000..5b8608f
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Contextual Views API</name>
+ <description>Contextual views for the activities</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ </dependency>
+ </dependencies>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+</project>
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/ActivityConfigurationAction.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/ActivityConfigurationAction.java
new file mode 100644
index 0000000..4d2fcff
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/ActivityConfigurationAction.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * 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.ui.actions.activity;
+
+import java.util.List;
+import java.util.WeakHashMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.JDialog;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosingDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationDialog;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+@SuppressWarnings("serial")
+public abstract class ActivityConfigurationAction extends AbstractAction {
+ private static WeakHashMap<Activity, ActivityConfigurationDialog> configurationDialogs = new WeakHashMap<>();
+ private static DataflowCloseListener listener;
+
+ protected Activity activity;
+ private final ServiceDescriptionRegistry serviceDescriptionRegistry;
+
+ public ActivityConfigurationAction(Activity activity,
+ ActivityIconManager activityIconManager,
+ ServiceDescriptionRegistry serviceDescriptionRegistry) {
+ this.activity = activity;
+ this.serviceDescriptionRegistry = serviceDescriptionRegistry;
+ putValue(SMALL_ICON,
+ activityIconManager.iconForActivity(activity.getType()));
+ }
+
+ protected Activity getActivity() {
+ return activity;
+ }
+
+ protected ServiceDescription getServiceDescription() {
+ return serviceDescriptionRegistry.getServiceDescription(activity
+ .getType());
+ }
+
+ protected static void setDialog(Activity activity,
+ ActivityConfigurationDialog dialog, FileManager fileManager) {
+ if (listener == null) {
+ listener = new DataflowCloseListener();
+ /*
+ * Ensure that the DataflowCloseListener is the first notified
+ * listener. Otherwise you cannot save the configurations.
+ */
+ List<Observer<FileManagerEvent>> existingListeners = fileManager
+ .getObservers();
+ fileManager.addObserver(listener);
+ for (Observer<FileManagerEvent> observer : existingListeners)
+ if (!observer.equals(listener)) {
+ fileManager.removeObserver(observer);
+ fileManager.addObserver(observer);
+ }
+ }
+ if (configurationDialogs.containsKey(activity)) {
+ ActivityConfigurationDialog currentDialog = configurationDialogs
+ .get(activity);
+ if (!currentDialog.equals(dialog) && currentDialog.isVisible())
+ currentDialog.setVisible(false);
+ }
+ configurationDialogs.put(activity, dialog);
+ dialog.setVisible(true);
+ }
+
+ public static void clearDialog(Activity activity) {
+ if (configurationDialogs.containsKey(activity)) {
+ ActivityConfigurationDialog currentDialog = configurationDialogs
+ .get(activity);
+ if (currentDialog.isVisible())
+ currentDialog.setVisible(false);
+ configurationDialogs.remove(activity);
+ currentDialog.dispose();
+ }
+ }
+
+ protected static void clearDialog(JDialog dialog) {
+ if (configurationDialogs.containsValue(dialog)) {
+ if (dialog.isVisible())
+ dialog.setVisible(false);
+ for (Activity activity : configurationDialogs.keySet())
+ if (configurationDialogs.get(activity).equals(dialog))
+ configurationDialogs.remove(activity);
+ dialog.dispose();
+ }
+ }
+
+ public static boolean closeDialog(Activity activity) {
+ boolean closeIt = true;
+ if (configurationDialogs.containsKey(activity)) {
+ ActivityConfigurationDialog currentDialog = configurationDialogs
+ .get(activity);
+ if (currentDialog.isVisible())
+ closeIt = currentDialog.closeDialog();
+ if (closeIt)
+ configurationDialogs.remove(activity);
+ }
+ return closeIt;
+ }
+
+ public static ActivityConfigurationDialog getDialog(Activity activity) {
+ return configurationDialogs.get(activity);
+ }
+
+ private static class DataflowCloseListener implements
+ Observer<FileManagerEvent> {
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof ClosingDataflowEvent) {
+ ClosingDataflowEvent closingDataflowEvent = (ClosingDataflowEvent) message;
+ if (closingDataflowEvent.isAbortClose())
+ return;
+ closingDataflow(closingDataflowEvent,
+ ((ClosingDataflowEvent) message).getDataflow());
+ }
+ }
+
+ private void closingDataflow(ClosingDataflowEvent event,
+ WorkflowBundle bundle) {
+ Profile profile = bundle.getMainProfile();
+ for (Workflow workflow : bundle.getWorkflows())
+ for (Processor p : workflow.getProcessors()) {
+ ProcessorBinding processorBinding = scufl2Tools
+ .processorBindingForProcessor(p, profile);
+ Activity activity = processorBinding.getBoundActivity();
+ if (!closeDialog(activity))
+ event.setAbortClose(true);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/ActivityContextualView.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/ActivityContextualView.java
new file mode 100644
index 0000000..f063c02
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/ActivityContextualView.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.ui.actions.activity;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+/**
+ * A contextual view specific to an Activity. Concrete subclasses must
+ * initialise the view by calling {@link #initView()}.
+ * <p>
+ * The implementation provides a view based upon the properties set in the
+ * Configuration
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ *
+ * @see Activity
+ * @see ContextualView
+ */
+@SuppressWarnings("serial")
+public abstract class ActivityContextualView extends ContextualView {
+ private Activity activity;
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ /**
+ * Constructs an instance of the view.
+ * <p>
+ * The constructor parameter for the implementation of this class should
+ * define the specific Activity type itself.
+ *
+ * @param activity
+ */
+ protected ActivityContextualView(Activity activity) {
+ super();
+ this.activity = activity;
+ }
+
+ public Activity getActivity() {
+ return activity;
+ }
+
+ public Configuration getConfigBean() {
+ return scufl2Tools.configurationFor(activity, activity.getParent());
+ }
+
+ @Override
+ public abstract void refreshView();
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/HTMLBasedActivityContextualView.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/HTMLBasedActivityContextualView.java
new file mode 100644
index 0000000..de3df33
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/actions/activity/HTMLBasedActivityContextualView.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.ui.actions.activity;
+
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.panelForHtml;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+@SuppressWarnings("serial")
+public abstract class HTMLBasedActivityContextualView extends ActivityContextualView {
+ private static final String BEANSHELL_URI = "http://ns.taverna.org.uk/2010/activity/beanshell";
+ private static final String LOCALWORKER_URI = "http://ns.taverna.org.uk/2010/activity/localworker";
+ private JEditorPane editorPane;
+ private final ColourManager colourManager;
+
+ public HTMLBasedActivityContextualView(Activity activity, ColourManager colourManager) {
+ super(activity);
+ this.colourManager = colourManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ editorPane = createEditorPane(buildHtml());
+ return panelForHtml(editorPane);
+ }
+
+ private String buildHtml() {
+ StringBuilder html = new StringBuilder(getHtmlHead(getBackgroundColour()));
+ html.append(buildTableOpeningTag());
+ html.append("<tr><th colspan=\"2\">").append(getViewTitle()).append("</th></tr>");
+ html.append(getRawTableRowsHtml()).append("</table>");
+ html.append("</body></html>");
+ return html.toString();
+ }
+
+ protected abstract String getRawTableRowsHtml();
+
+ public String getBackgroundColour() {
+ String activityType = getActivity().getType().toString();
+ if (LOCALWORKER_URI.equals(activityType))
+ if (getConfigBean().getJson().get("isAltered").booleanValue())
+ return (String) colourManager.getProperty(BEANSHELL_URI);
+ String colour = (String) colourManager.getProperty(activityType);
+ return colour == null ? "#ffffff" : colour;
+ }
+
+ /**
+ * Update the html view with the latest information in the configuration
+ * bean
+ */
+ @Override
+ public void refreshView() {
+ editorPane.setText(buildHtml());
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/AddLayerFactorySPI.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/AddLayerFactorySPI.java
new file mode 100644
index 0000000..6b0245c
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/AddLayerFactorySPI.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+/**
+ * SPI for adding dispatch stack layers to a processor, such as
+ * {@link net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Loop}.
+ * <p>
+ * Buttons or similar will be added in the processor contextual view.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public interface AddLayerFactorySPI {
+ boolean canAddLayerFor(Processor proc);
+
+ Action getAddLayerActionFor(Processor proc);
+
+ boolean canCreateLayerClass(URI dispatchLayerType);
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/ContextualView.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/ContextualView.java
new file mode 100644
index 0000000..45efaab
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/ContextualView.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews;
+
+import static java.awt.BorderLayout.CENTER;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/**
+ * An abstract class defining the base container to hold a contextual view over
+ * Dataflow element.
+ * <p>
+ * The specific implementation of this class to support a given dataflow element
+ * needs to implement the {@link #getMainFrame()} and {@link #getViewTitle()}.
+ * <p>
+ * If a view is associated with an action handler to configure this component,
+ * then the {@link #getConfigureAction(Frame) getConfigureAction} handler must
+ * be over-ridden. If this returns null then the configure button is left
+ * disabled and it is not possible to configure the element.
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public abstract class ContextualView extends JPanel {
+ /**
+ * When implemented, this method should define the main frame that is placed
+ * in this container, and provides a static view of the Dataflow element.
+ *
+ * @return a JComponent that represents the dataflow element.
+ */
+ public abstract JComponent getMainFrame();
+
+ /**
+ * @return a String providing a title for the view
+ */
+ public abstract String getViewTitle();
+
+ /**
+ * Allows the item to be configured, but returning an action handler that
+ * will be invoked when selecting to configure. By default this is provided
+ * by a button.
+ * <p>
+ * If there is no ability to configure the given item, then this should
+ * return null.
+ *
+ * @param owner
+ * the owning dialog to be used when displaying dialogues for
+ * configuration options
+ * @return an action that allows the element being viewed to be configured.
+ */
+ public Action getConfigureAction(Frame owner) {
+ return null;
+ }
+
+ /**
+ * This <i>must</i> be called by any sub-classes after they have initialised
+ * their own view since it gets their main panel and adds it to the main
+ * contextual view. If you don't do this you will get a very empty frame
+ * popping up!
+ */
+ public void initView() {
+ setLayout(new BorderLayout());
+ add(getMainFrame(), CENTER);
+ setName(getViewTitle());
+ }
+
+ public abstract void refreshView();
+
+ public abstract int getPreferredPosition();
+
+ public static String getTextFromDepth(String kind, Integer depth) {
+ String labelText = "The last prediction said the " + kind;
+ if (depth == null) {
+ labelText += " would not transmit a value";
+ } else if (depth == -1) {
+ labelText += " was invalid/unpredicted";
+ } else if (depth == 0) {
+ labelText += " would carry a single value";
+ } else {
+ labelText += " would carry a list of depth " + depth;
+ }
+ return labelText;
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityConfigurationDialog.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityConfigurationDialog.java
new file mode 100644
index 0000000..c4c77b7
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityConfigurationDialog.java
@@ -0,0 +1,474 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.activity;
+
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Cursor.DEFAULT_CURSOR;
+import static java.awt.Cursor.WAIT_CURSOR;
+import static java.awt.Cursor.getPredefinedCursor;
+import static java.lang.Math.max;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction.clearDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.ui.DeselectingButton;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.AddProcessorInputPortEdit;
+import net.sf.taverna.t2.workflow.edits.AddProcessorOutputPortEdit;
+import net.sf.taverna.t2.workflow.edits.ChangeDepthEdit;
+import net.sf.taverna.t2.workflow.edits.ChangeGranularDepthEdit;
+import net.sf.taverna.t2.workflow.edits.ChangeJsonEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveChildEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveProcessorInputPortEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveProcessorOutputPortEdit;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.apache.log4j.Logger;
+
+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.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.ActivityPort;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+@SuppressWarnings("serial")
+public class ActivityConfigurationDialog extends HelpEnabledDialog {
+ private enum PortType {
+ INPUT, OUTPUT
+ }
+
+ protected static Logger logger = Logger.getLogger(ActivityConfigurationDialog.class);
+ private static final Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ private final EditManager editManager;
+
+ private Activity activity;
+ private ActivityConfigurationPanel panel;
+ protected WorkflowBundle owningWorkflowBundle;
+ protected Processor owningProcessor;
+ private Observer<EditManagerEvent> observer;
+ Dimension minimalSize = null;
+ Dimension buttonPanelSize = null;
+ JPanel buttonPanel;
+ protected JButton applyButton;
+
+ public ActivityConfigurationDialog(Activity a, ActivityConfigurationPanel p,
+ EditManager editManager) {
+ super(getMainWindow(), "Configuring " + a.getClass().getSimpleName(),
+ false, null);
+ this.activity = a;
+ this.panel = p;
+ this.editManager = editManager;
+
+ owningWorkflowBundle = activity.getParent().getParent();
+ owningProcessor = findProcessor(a);
+
+ setTitle(getRelativeName(owningWorkflowBundle, activity));
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ setLayout(new BorderLayout());
+
+ add(panel, BorderLayout.CENTER);
+
+ buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ buttonPanel.setBorder(new EmptyBorder(5, 20, 5, 5));
+
+ JButton helpButton = new DeselectingButton("Help", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ showHelp(panel);
+ }
+ });
+ buttonPanel.add(helpButton);
+
+ applyButton = new DeselectingButton("Apply", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ /*
+ * For the moment it always does an apply as what should be
+ * happening is that the apply button only becomes available
+ * when the configuration has changed. However, many
+ * configuration panels are not set up to detected changes
+ */
+ // if (panel.isConfigurationChanged()) {
+ if (checkPanelValues())
+ applyConfiguration();
+ // } else {
+ // logger.info("Ignoring apply");
+ // }
+ }
+ });
+ buttonPanel.add(applyButton);
+
+ JButton closeButton = new DeselectingButton("Close", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ closeDialog();
+ }
+ });
+ buttonPanel.add(closeButton);
+
+ add(buttonPanel, SOUTH);
+
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent e) {
+ requestFocusInWindow();
+ panel.whenOpened();
+ }
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ closeDialog();
+ }
+ });
+ pack();
+ minimalSize = getSize();
+ setLocationRelativeTo(null);
+ setResizable(true);
+ addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ int newWidth = max(getWidth(), minimalSize.width);
+ int newHeight = max(getHeight(), minimalSize.height);
+ setSize(new Dimension(newWidth, newHeight));
+ }
+ });
+
+ observer = new Observer<EditManagerEvent>() {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+ throws Exception {
+ logger.info("sender is a " + sender.getClass().getCanonicalName());
+ logger.info("message is a " + message.getClass().getCanonicalName());
+ Edit<?> edit = message.getEdit();
+ logger.info(edit.getClass().getCanonicalName());
+ considerEdit(message, edit);
+ }
+ };
+ editManager.addObserver(observer);
+ }
+
+ private boolean checkPanelValues() {
+ boolean result = false;
+ try {
+ setCursor(getPredefinedCursor(WAIT_CURSOR));
+ result = panel.checkValues();
+ } finally {
+ setCursor(getPredefinedCursor(DEFAULT_CURSOR));
+ }
+ return result;
+ }
+
+ private void considerEdit(EditManagerEvent message, Edit<?> edit) {
+ // boolean result = false;
+ if (edit instanceof CompoundEdit) {
+ for (Edit<?> subEdit : ((CompoundEdit) edit).getChildEdits())
+ considerEdit(message, subEdit);
+ return;
+ }
+
+ Object subject = edit.getSubject();
+ if (subject == owningProcessor) {
+ // panel.reevaluate();
+ setTitle(getRelativeName(owningWorkflowBundle, activity));
+ } else if (subject == owningWorkflowBundle) {
+ for (Workflow workflow : owningWorkflowBundle.getWorkflows())
+ if (!workflow.getProcessors().contains(owningProcessor))
+ clearDialog(activity);
+ } else if (subject == activity) {
+ if (message instanceof DataFlowUndoEvent) {
+ logger.info("undo of activity edit found");
+ panel.refreshConfiguration();
+ } else if (message instanceof DataFlowRedoEvent) {
+ logger.info("redo of activity edit found");
+ panel.refreshConfiguration();
+ }
+ }
+ }
+
+ protected void configureActivity(ObjectNode json, List<ActivityPortConfiguration> inputPorts,
+ List<ActivityPortConfiguration> outputPorts) {
+ configureActivity(owningWorkflowBundle, activity, json, inputPorts, outputPorts);
+ }
+
+ public void configureActivity(WorkflowBundle workflowBundle, Activity activity,
+ ObjectNode json, List<ActivityPortConfiguration> inputPorts,
+ List<ActivityPortConfiguration> outputPorts) {
+ try {
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ Profile profile = activity.getParent();
+ List<ProcessorBinding> processorBindings = scufl2Tools
+ .processorBindingsToActivity(activity);
+ Configuration configuration = scufl2Tools.configurationFor(activity, profile);
+ editList.add(new ChangeJsonEdit(configuration, json));
+
+ configurePorts(activity, editList, processorBindings, inputPorts, PortType.INPUT);
+ configurePorts(activity, editList, processorBindings, outputPorts, PortType.OUTPUT);
+ editManager.doDataflowEdit(workflowBundle, new CompoundEdit(editList));
+ } catch (IllegalStateException | EditException e) {
+ logger.error(e);
+ }
+ }
+
+ private void configurePorts(Activity activity, List<Edit<?>> editList,
+ List<ProcessorBinding> processorBindings,
+ List<ActivityPortConfiguration> portDefinitions, PortType portType) {
+ Set<ActivityPort> ports = new HashSet<>();
+ for (ActivityPort activityPort : portType == PortType.INPUT ? activity
+ .getInputPorts() : activity.getOutputPorts())
+ ports.add(activityPort);
+ for (ActivityPortConfiguration portDefinition : portDefinitions) {
+ String portName = portDefinition.getName();
+ int portDepth = portDefinition.getDepth();
+ int granularPortDepth = portDefinition.getGranularDepth();
+ ActivityPort activityPort = portDefinition.getActivityPort();
+ if (activityPort == null) {
+ // no activity port so add a new one
+ if (portType == PortType.INPUT)
+ createInputPort(activity, editList, processorBindings, portDefinition);
+ else
+ createOutputPort(activity, editList, processorBindings, portDefinition);
+ } else {
+ ports.remove(activityPort);
+ // check if port has changed
+ for (ProcessorBinding processorBinding : processorBindings)
+ if (portType == PortType.INPUT)
+ for (ProcessorInputPortBinding portBinding : processorBinding
+ .getInputPortBindings()) {
+ if (!portBinding.getBoundActivityPort().equals(
+ activityPort))
+ continue;
+ InputProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ if (!activityPort.getName().equals(portName))
+ // port name changed
+ if (processorPort.getName().equals(activityPort.getName()))
+ // default mapping so change processor port
+ editList.add(new RenameEdit<>(processorPort, portName));
+ if (!processorPort.getDepth().equals(portDepth))
+ // port depth changed
+ editList.add(new ChangeDepthEdit<>(
+ processorPort, portDepth));
+ }
+ else
+ for (ProcessorOutputPortBinding portBinding : processorBinding
+ .getOutputPortBindings()) {
+ if (!portBinding.getBoundActivityPort().equals(
+ activityPort))
+ continue;
+ OutputProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ if (!activityPort.getName().equals(portName))
+ // port name changed
+ if (processorPort.getName().equals(
+ activityPort.getName()))
+ // default mapping so change processor port
+ editList.add(new RenameEdit<>(
+ processorPort, portName));
+ if (!processorPort.getDepth().equals(portDepth))
+ // port depth changed
+ editList.add(new ChangeDepthEdit<>(
+ processorPort, portDepth));
+ if (!processorPort.getGranularDepth().equals(
+ granularPortDepth))
+ // port granular depth changed
+ editList.add(new ChangeGranularDepthEdit<>(
+ processorPort, granularPortDepth));
+ }
+ if (!activityPort.getName().equals(portName))
+ // port name changed
+ editList.add(new RenameEdit<>(activityPort, portName));
+ if (!activityPort.getDepth().equals(portDepth))
+ // port depth changed
+ editList.add(new ChangeDepthEdit<>(activityPort, portDepth));
+ if (activityPort instanceof OutputActivityPort) {
+ OutputActivityPort outputActivityPort = (OutputActivityPort) activityPort;
+ Integer granularDepth = outputActivityPort
+ .getGranularDepth();
+ if (granularDepth == null
+ || !granularDepth.equals(granularPortDepth))
+ // granular port depth changed
+ editList.add(new ChangeGranularDepthEdit<>(
+ outputActivityPort, granularPortDepth));
+ }
+ }
+ }
+
+ // remove any unconfigured ports
+ for (ActivityPort activityPort : ports) {
+ // remove processor ports and bindings
+ for (ProcessorBinding processorBinding : processorBindings)
+ if (portType.equals(PortType.INPUT))
+ for (ProcessorInputPortBinding portBinding : processorBinding
+ .getInputPortBindings()) {
+ if (portBinding.getBoundActivityPort().equals(activityPort)) {
+ editList.add(new RemoveProcessorInputPortEdit(processorBinding
+ .getBoundProcessor(), portBinding.getBoundProcessorPort()));
+ editList.add(new RemoveChildEdit<>(processorBinding,
+ portBinding));
+ }
+ }
+ else
+ for (ProcessorOutputPortBinding portBinding : processorBinding
+ .getOutputPortBindings())
+ if (portBinding.getBoundActivityPort().equals(activityPort)) {
+ editList.add(new RemoveProcessorOutputPortEdit(processorBinding
+ .getBoundProcessor(), portBinding.getBoundProcessorPort()));
+ editList.add(new RemoveChildEdit<>(processorBinding,
+ portBinding));
+ }
+ // remove activity port
+ editList.add(new RemoveChildEdit<Activity>(activity, activityPort));
+ }
+ }
+
+ private void createInputPort(Activity activity, List<Edit<?>> editList,
+ List<ProcessorBinding> processorBindings,
+ ActivityPortConfiguration portDefinition) {
+ InputActivityPort actPort = new InputActivityPort(null,
+ portDefinition.getName());
+ actPort.setDepth(portDefinition.getDepth());
+ // add port to activity
+ editList.add(new AddChildEdit<>(activity, actPort));
+ for (ProcessorBinding processorBinding : processorBindings) {
+ Processor processor = processorBinding.getBoundProcessor();
+ // add a new processor port
+ InputProcessorPort procPort = new InputProcessorPort();
+ procPort.setName(portDefinition.getName());
+ procPort.setDepth(portDefinition.getDepth());
+ editList.add(new AddProcessorInputPortEdit(processor, procPort));
+ // add a new port binding
+ ProcessorInputPortBinding binding = new ProcessorInputPortBinding();
+ binding.setBoundProcessorPort(procPort);
+ binding.setBoundActivityPort(actPort);
+ editList.add(new AddChildEdit<>(processorBinding, binding));
+ }
+ }
+
+ private void createOutputPort(Activity activity, List<Edit<?>> editList,
+ List<ProcessorBinding> processorBindings,
+ ActivityPortConfiguration portDefinition) {
+ OutputActivityPort actPort = new OutputActivityPort(null,
+ portDefinition.getName());
+ actPort.setDepth(portDefinition.getDepth());
+ actPort.setGranularDepth(portDefinition.getGranularDepth());
+ // add port to activity
+ editList.add(new AddChildEdit<Activity>(activity, actPort));
+ for (ProcessorBinding processorBinding : processorBindings) {
+ Processor processor = processorBinding.getBoundProcessor();
+ // add a new processor port
+ OutputProcessorPort procPort = new OutputProcessorPort();
+ procPort.setName(portDefinition.getName());
+ procPort.setDepth(portDefinition.getDepth());
+ procPort.setGranularDepth(portDefinition.getGranularDepth());
+ editList.add(new AddProcessorOutputPortEdit(processor, procPort));
+ // add a new port binding
+ ProcessorOutputPortBinding binding = new ProcessorOutputPortBinding();
+ binding.setBoundProcessorPort(procPort);
+ binding.setBoundActivityPort(actPort);
+ editList.add(new AddChildEdit<>(processorBinding, binding));
+ }
+ }
+
+ protected static Processor findProcessor(Activity activity) {
+ for (ProcessorBinding processorBinding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ return processorBinding.getBoundProcessor();
+ return null;
+ }
+
+ public static String getRelativeName(WorkflowBundle workflowBundle, Activity activity) {
+ StringBuilder relativeName = new StringBuilder("");
+ if (workflowBundle != null) {
+ Workflow workflow = workflowBundle.getMainWorkflow();
+ if (workflow != null) {
+ relativeName.append(workflow.getName());
+ relativeName.append(":");
+ }
+ }
+ Processor processor = findProcessor(activity);
+ if (processor != null)
+ relativeName.append(processor.getName());
+ return relativeName.toString();
+ }
+
+ public boolean closeDialog() {
+ if (panel.isConfigurationChanged()) {
+ String relativeName = getRelativeName(owningWorkflowBundle, activity);
+ if (checkPanelValues()) {
+ int answer = showConfirmDialog(this,
+ "Do you want to save the configuration of " + relativeName + "?",
+ relativeName, YES_NO_CANCEL_OPTION);
+ if (answer == YES_OPTION) {
+ applyConfiguration();
+ } else if (answer == CANCEL_OPTION) {
+ return false;
+ }
+ } else if (showConfirmDialog(
+ this,
+ "New configuration could not be saved. Do you still want to close?",
+ relativeName, YES_NO_OPTION) == NO_OPTION)
+ return false;
+ }
+ panel.whenClosed();
+ clearDialog(activity);
+ return true;
+ }
+
+ private void applyConfiguration() {
+ panel.noteConfiguration();
+ configureActivity(panel.getJson(), panel.getInputPorts(),
+ panel.getOutputPorts());
+ panel.refreshConfiguration();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ editManager.removeObserver(observer);
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityConfigurationPanel.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityConfigurationPanel.java
new file mode 100644
index 0000000..cf7f42a
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityConfigurationPanel.java
@@ -0,0 +1,214 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.activity;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ActivityTypeNotFoundException;
+import uk.org.taverna.commons.services.InvalidConfigurationException;
+import uk.org.taverna.commons.services.ServiceRegistry;
+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.port.ActivityPort;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public abstract class ActivityConfigurationPanel extends JPanel {
+ private static final Logger logger = Logger.getLogger(ActivityConfigurationPanel.class);
+ private final static Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ // protected final URITools uriTools = new URITools();
+ private final Activity activity;
+ private final Configuration configuration;
+ private final List<ActivityPortConfiguration> inputPorts;
+ private final List<ActivityPortConfiguration> outputPorts;
+ protected ObjectNode json;
+
+ public ActivityConfigurationPanel(Activity activity) {
+ this(activity, scufl2Tools.configurationFor(activity,
+ activity.getParent()));
+ }
+
+ public ActivityConfigurationPanel(Activity activity,
+ Configuration configuration) {
+ this.activity = activity;
+ this.configuration = configuration;
+ inputPorts = new ArrayList<>();
+ outputPorts = new ArrayList<>();
+ }
+
+ /**
+ * Initializes the configuration panel. This method is also used to discard
+ * any changes and reset the panel to its initial state. Subclasses should
+ * implement this method to set up the panel and must call
+ * <tt>super.initialise()</tt> first.
+ */
+ protected void initialise() {
+ json = configuration.getJson().deepCopy();
+ inputPorts.clear();
+ for (InputActivityPort activityPort : activity.getInputPorts())
+ inputPorts.add(new ActivityPortConfiguration(activityPort));
+ outputPorts.clear();
+ for (OutputActivityPort activityPort : activity.getOutputPorts())
+ outputPorts.add(new ActivityPortConfiguration(activityPort));
+ }
+
+ public abstract boolean checkValues();
+
+ public abstract void noteConfiguration();
+
+ public boolean isConfigurationChanged() {
+ noteConfiguration();
+ if (portsChanged(inputPorts, activity.getInputPorts().size()))
+ return true;
+ if (portsChanged(outputPorts, activity.getOutputPorts().size()))
+ return true;
+ return !json.equals(configuration.getJson());
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ public ObjectNode getJson() {
+ return json;
+ }
+
+ protected void setJson(ObjectNode json) {
+ this.json = json;
+ }
+
+ public void refreshConfiguration() {
+ initialise();
+ }
+
+ public void whenOpened() {
+ }
+
+ public void whenClosed() {
+ }
+
+ /**
+ * Convenience method for getting simple String property values.
+ *
+ * @param name
+ * the property name
+ * @return the property value
+ */
+ protected String getProperty(String name) {
+ JsonNode jsonNode = json.get(name);
+ if (jsonNode == null)
+ return null;
+ return json.get(name).asText();
+ }
+
+ /**
+ * Convenience method for setting simple String property values.
+ *
+ * @param name
+ * the property name
+ * @param value
+ * the property value
+ */
+ protected void setProperty(String name, String value) {
+ json.put(name, value);
+ }
+
+ public List<ActivityPortConfiguration> getInputPorts() {
+ return inputPorts;
+ }
+
+ public List<ActivityPortConfiguration> getOutputPorts() {
+ return outputPorts;
+ }
+
+ protected void configureInputPorts(ServiceRegistry serviceRegistry) {
+ try {
+ Map<String, InputActivityPort> newInputPorts = new HashMap<>();
+ for (InputActivityPort port : serviceRegistry
+ .getActivityInputPorts(getActivity().getType(), getJson()))
+ newInputPorts.put(port.getName(), port);
+ List<ActivityPortConfiguration> inputPorts = getInputPorts();
+ for (ActivityPortConfiguration portConfig : new ArrayList<>(
+ inputPorts))
+ if (newInputPorts.containsKey(portConfig.getName())) {
+ InputActivityPort port = newInputPorts.remove(portConfig
+ .getName());
+ portConfig.setDepth(port.getDepth());
+ } else
+ inputPorts.remove(portConfig);
+ for (InputActivityPort newPort : newInputPorts.values())
+ inputPorts.add(new ActivityPortConfiguration(newPort.getName(),
+ newPort.getDepth()));
+ } catch (InvalidConfigurationException | ActivityTypeNotFoundException e) {
+ logger.warn("Error configuring input ports", e);
+ }
+ }
+
+ protected void configureOutputPorts(ServiceRegistry serviceRegistry) {
+ try {
+ Map<String, OutputActivityPort> newOutputPorts = new HashMap<>();
+ for (OutputActivityPort port : serviceRegistry
+ .getActivityOutputPorts(getActivity().getType(), getJson()))
+ newOutputPorts.put(port.getName(), port);
+ List<ActivityPortConfiguration> outputPorts = getOutputPorts();
+ for (ActivityPortConfiguration portConfig : new ArrayList<>(
+ outputPorts))
+ if (newOutputPorts.containsKey(portConfig.getName())) {
+ OutputActivityPort port = newOutputPorts.remove(portConfig
+ .getName());
+ portConfig.setDepth(port.getDepth());
+ portConfig.setGranularDepth(port.getGranularDepth());
+ } else
+ outputPorts.remove(portConfig);
+ for (OutputActivityPort newPort : newOutputPorts.values())
+ outputPorts.add(new ActivityPortConfiguration(
+ newPort.getName(), newPort.getDepth()));
+ } catch (InvalidConfigurationException | ActivityTypeNotFoundException e) {
+ logger.warn("Error configuring output ports", e);
+ }
+ }
+
+ private boolean portsChanged(List<ActivityPortConfiguration> portDefinitions, int ports) {
+ int checkedPorts = 0;
+ for (ActivityPortConfiguration portDefinition : portDefinitions) {
+ String portName = portDefinition.getName();
+ int portDepth = portDefinition.getDepth();
+ ActivityPort activityPort = portDefinition.getActivityPort();
+ if (activityPort == null)
+ // new port added
+ return true;
+ if (!activityPort.getName().equals(portName))
+ // port name changed
+ return true;
+ if (!activityPort.getDepth().equals(portDepth))
+ // port depth changed
+ return true;
+ checkedPorts++;
+ }
+ if (checkedPorts < ports)
+ // ports deleted
+ return true;
+ return false;
+ }
+
+ public Activity getActivity() {
+ return activity;
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityPortConfiguration.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityPortConfiguration.java
new file mode 100644
index 0000000..6b23fd5
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ActivityPortConfiguration.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.views.contextualviews.activity;
+
+import uk.org.taverna.scufl2.api.port.ActivityPort;
+
+/**
+ *
+ *
+ * @author David Withers
+ */
+public class ActivityPortConfiguration {
+
+ private ActivityPort activityPort;
+
+ private String name;
+
+ private int depth;
+
+ private int granularDepth;
+
+ public ActivityPortConfiguration(ActivityPort activityPort) {
+ this.activityPort = activityPort;
+ name = activityPort.getName();
+ depth = activityPort.getDepth();
+ }
+
+ public ActivityPortConfiguration(String name, int depth) {
+ this(name, depth, depth);
+ }
+
+ public ActivityPortConfiguration(String name, int depth, int granularDepth) {
+ this.name = name;
+ this.depth = depth;
+ this.granularDepth = granularDepth;
+ }
+
+ public ActivityPort getActivityPort() {
+ return activityPort;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getDepth() {
+ return depth;
+ }
+
+ public void setDepth(int depth) {
+ this.depth = depth;
+ }
+
+ public int getGranularDepth() {
+ return granularDepth;
+ }
+
+ public void setGranularDepth(int granularDepth) {
+ this.granularDepth = granularDepth;
+ }
+
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ContextualViewFactory.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ContextualViewFactory.java
new file mode 100644
index 0000000..b5d29d7
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ContextualViewFactory.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.activity;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Defines a factory class that when associated with a selected object creates a
+ * {@link ContextualView} for that selection.
+ * <p>
+ * This factory acts as an SPI to find {@link ContextualView}s for a given
+ * Activity and other workflow components.
+ * </p>
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @author Stian Soiland-Reyes
+ *
+ *
+ * @param <SelectionType>
+ * - the selection type this factory is associated with
+ *
+ * @see ContextualView
+ * @see ContextualViewFactoryRegistry
+ */
+public interface ContextualViewFactory<SelectionType> {
+ /**
+ * @param selection
+ * - the object for which ContextualViews needs to be generated
+ * @return instance of {@link ContextualView}
+ */
+ public List<ContextualView> getViews(SelectionType selection);
+
+ /**
+ * Used by the SPI system to find the correct factory that can handle the
+ * given object type.
+ *
+ * @param selection
+ * @return true if this factory relates to the given selection type
+ * @see ContextualViewFactoryRegistry
+ */
+ public boolean canHandle(Object selection);
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ContextualViewFactoryRegistry.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ContextualViewFactoryRegistry.java
new file mode 100644
index 0000000..305f3c0
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ContextualViewFactoryRegistry.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.ui.views.contextualviews.activity;
+
+import java.util.List;
+
+/**
+ * A registry for discovering ActivityViewFactories for a given object,
+ * like an {@link net.sf.taverna.t2.workflowmodel.processor.activity.Activity}.
+ *
+ * @author David Withers
+ */
+public interface ContextualViewFactoryRegistry {
+ /**
+ * Discover and return the ContextualViewFactory associated to the provided
+ * object. This is accomplished by returning the discovered
+ * {@link ContextualViewFactory#canHandle(Object)} that returns true for
+ * that Object.
+ *
+ * @param object
+ * @return
+ * @see ContextualViewFactory#canHandle(Object)
+ */
+ public <T> List<ContextualViewFactory<? super T>> getViewFactoriesForObject(T object);
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/DependencyConfigurationPanel.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/DependencyConfigurationPanel.java
new file mode 100644
index 0000000..c28cb55
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/DependencyConfigurationPanel.java
@@ -0,0 +1,293 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.views.contextualviews.activity;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Color.RED;
+import static java.awt.GridBagConstraints.FIRST_LINE_START;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.event.ItemEvent.DESELECTED;
+import static java.awt.event.ItemEvent.SELECTED;
+import static java.util.Arrays.asList;
+import static javax.swing.Box.createRigidArea;
+import static javax.swing.BoxLayout.PAGE_AXIS;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+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.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * Component for configuring activities that require dependencies.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class DependencyConfigurationPanel extends JPanel {
+ private String classLoaderSharing;
+ private List<String> localDependencies;
+ private File libDir;
+
+ public DependencyConfigurationPanel(String classLoaderSharing,
+ List<String> localDependencies, File libDir) {
+ this.classLoaderSharing = classLoaderSharing;
+ this.localDependencies = localDependencies;
+ this.libDir = libDir;
+ setLayout(new BoxLayout(this, PAGE_AXIS));
+
+ // Create panel with classloading options
+ JPanel classloadingPanel = new ClassloadingPanel();
+ // Create panel for selecting jar files
+ JPanel jarFilesPanel = new JarFilesPanel();
+
+ add(classloadingPanel);
+ add(createRigidArea(new Dimension(0,10)));
+ add(jarFilesPanel);
+ add(createRigidArea(new Dimension(0,10)));
+
+ }
+
+ public String getClassLoaderSharing() {
+ return classLoaderSharing;
+ }
+
+ public List<String> getLocalDependencies() {
+ return localDependencies;
+ }
+
+ // Classloading option 'workflow'
+ private static final String WORKFLOW = "Shared for whole workflow";
+ // Classloading option 'system'
+ private static final String SYSTEM = "System classloader";
+
+ // Panel containing classloading options
+ private class ClassloadingPanel extends JPanel {
+ // Combobox with classloading options
+ private JComboBox<String> jcbClassloadingOption;
+ // Classloading option descriptions
+ private HashMap<String, String> classloadingDescriptions;
+ // JLabel with classloading option description
+ private JLabel jlClassloadingDescription;
+
+ /*
+ * Panel containing a list of possible classloading options which users
+ * can select from
+ */
+ private ClassloadingPanel() {
+ super(new GridBagLayout());
+ jcbClassloadingOption = new JComboBox<>(new String[] { WORKFLOW,
+ SYSTEM });
+ // Set the current classlaoding option based on the configuration bean
+ if ("workflow".equals(classLoaderSharing)) {
+ jcbClassloadingOption.setSelectedItem(WORKFLOW);
+ } else if ("system".equals(classLoaderSharing)) {
+ jcbClassloadingOption.setSelectedItem(SYSTEM);
+ }
+
+ jcbClassloadingOption.addActionListener(new ActionListener(){
+ // Fires up when combobox selection changes
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Object selectedItem = jcbClassloadingOption.getSelectedItem();
+ jlClassloadingDescription.setText(classloadingDescriptions
+ .get(selectedItem));
+ if (selectedItem.equals(WORKFLOW))
+ classLoaderSharing = "workflow";
+ else if (selectedItem.equals(SYSTEM))
+ classLoaderSharing = "system";
+ }
+ });
+ //jcbClassloadingOption.setEnabled(false);
+
+ classloadingDescriptions = new HashMap<>();
+ classloadingDescriptions.put(WORKFLOW, "<html><small>"
+ + "Classes are shared across the whole workflow (with any service<br>"
+ + "also selecting this option), but are reinitialised for each workflow run.<br>"
+ + "This might be needed if a service passes objects to another, or <br>"
+ + "state is shared within static members of loaded classes."
+ + "</small></html>");
+ classloadingDescriptions.put(SYSTEM, "<html><small><p>"
+ + "The (global) system classloader is used, any dependencies defined here are<br>"
+ + "made available globally on the first run. Note that if you are NOT using<br>"
+ + "the defaulf Taverna BootstrapClassLoader, any settings here will be disregarded."
+ + "</p><p>"
+ + "This is mainly useful if you are using JNI-based libraries. Note that <br>"
+ + "for JNI you also have to specify <code>-Djava.library.path</code> and <br>"
+ + "probably your operating system's dynamic library search path<br>"
+ + "<code>LD_LIBRARY_PATH</code> / <code>DYLD_LIBRARY_PATH</code> / <code>PATH</code> </p>"
+ + "</small></html>");
+
+ /*
+ * Set the current classlaoding description based on the item
+ * selected in the combobox.
+ */
+ jlClassloadingDescription = new JLabel(classloadingDescriptions
+ .get(jcbClassloadingOption.getSelectedItem()));
+
+ // Add components to the ClassloadingPanel
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = FIRST_LINE_START;
+ c.fill = HORIZONTAL;
+ c.gridx = 0;
+ c.insets = new Insets(10,0,0,0);
+ add(new JLabel("Classloader persistence"), c);
+ c.insets = new Insets(0,0,0,0);
+ add(jcbClassloadingOption, c);
+ c.insets = new Insets(0,30,0,0);
+ add(jlClassloadingDescription, c);
+ }
+ }
+
+ // Panel for users to add local JAR dependencies (contains a list of jar files which users can select from)
+ private class JarFilesPanel extends JPanel {
+ private JLabel warning = new JLabel(
+ "<html>"
+ + "<center<font color='red'>"
+ + "Warning: Depending on local libraries makes this workflow<br>"
+ + "difficult or impossible to run for other users. Try depending<br>"
+ + "on artifacts from a public repository if possible.</font></center>"
+ + "</html>");
+
+ private JarFilesPanel() {
+ super();
+ setMinimumSize(new Dimension(400, 150));
+ setLayout(new BorderLayout());
+ setBorder(new EmptyBorder(0,10,0,10));
+
+ JPanel labelPanel = new JPanel();
+ labelPanel.setLayout(new BoxLayout(labelPanel, PAGE_AXIS));
+ JLabel label = new JLabel("Local JAR files");
+ JLabel libLabel = new JLabel("<html><small>" + libDir.getAbsolutePath()
+ + "</small></html>");
+ labelPanel.add(label);
+ labelPanel.add(libLabel);
+
+ add(labelPanel, NORTH);
+ add(new JScrollPane(jarFiles(), VERTICAL_SCROLLBAR_AS_NEEDED,
+ HORIZONTAL_SCROLLBAR_NEVER), CENTER);
+
+ warning.setVisible(false);
+ /*
+ * We'll skip the warning until we actually have support for
+ * artifacts
+ */
+ //add(warning);
+ updateWarning();
+ }
+
+ private void updateWarning() {
+ // Show warning if there is any local dependencies
+ warning.setVisible(!localDependencies.isEmpty());
+ }
+
+ public JPanel jarFiles() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, PAGE_AXIS));
+
+ // List of all jar files in the lib directory
+ List<String> jarFiles = asList(libDir
+ .list(new FileExtFilter(".jar")));
+ /*
+ * We also add the list of jars that may have been configured
+ * sometime before but are now not present in the lib directory for
+ * some reason
+ */
+ Set<String> missingLocalDeps = new HashSet<>(localDependencies);
+ missingLocalDeps.removeAll(jarFiles);
+ /*
+ * jarFiles and missingLocalDeps now contain two sets of files that
+ * do not intersect
+ */
+ List<String> jarFilesList = new ArrayList<>();
+ // Put them all together
+ jarFilesList.addAll(jarFiles);
+ jarFilesList.addAll(missingLocalDeps);
+ Collections.sort(jarFilesList);
+
+ if (jarFilesList.isEmpty()) {
+ panel.add(new JLabel("<html><small>To depend on a JAR file, "
+ + "copy it to the above-mentioned folder.</small></html>"));
+ return panel;
+ }
+
+ for (String jarFile : jarFilesList) {
+ JCheckBox checkBox = new JCheckBox(jarFile);
+ // Has it already been selected in some previous configuring?
+ checkBox.setSelected(localDependencies.contains(jarFile));
+ checkBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ JCheckBox box = (JCheckBox) e.getSource();
+ if (e.getStateChange() == SELECTED)
+ localDependencies.add(box.getText());
+ else if (e.getStateChange() == DESELECTED)
+ localDependencies.remove(box.getText());
+ updateWarning();
+ }
+ });
+ panel.add(checkBox);
+ // The jar may not be in the lib directory, so warn the user
+ if (!new File(libDir, jarFile).exists()) {
+ checkBox.setForeground(RED);
+ checkBox.setText(checkBox.getText() + " (missing file!)");
+ }
+ }
+ return panel;
+ }
+ }
+
+ public static class FileExtFilter implements FilenameFilter {
+ final String ext;
+
+ public FileExtFilter(String ext) {
+ this.ext = ext;
+ }
+
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(ext);
+ }
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ListConfigurationComponent.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ListConfigurationComponent.java
new file mode 100644
index 0000000..c0e3aab
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ListConfigurationComponent.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.views.contextualviews.activity;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.RIGHT;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class ListConfigurationComponent<T> extends JPanel {
+ private static final String REMOVE = "Remove";
+ private static final String ADD = "Add";
+
+ private String name;
+ private List<T> items;
+ private JPanel listPanel;
+
+ public ListConfigurationComponent(String name, List<T> items) {
+ this.name = name;
+ setLayout(new BorderLayout());
+
+ listPanel = new JPanel(new ListLayout());
+ JPanel buttonPanel = new JPanel(new FlowLayout(RIGHT));
+ buttonPanel.add(new JButton(createAddAction()));
+
+ add(new JScrollPane(listPanel), CENTER);
+ add(buttonPanel, SOUTH);
+
+ setItems(items);
+ }
+
+ protected void setItems(List<T> items) {
+ this.items = items;
+ listPanel.removeAll();
+ for (T item : items)
+ addItemComponent(item);
+ }
+
+ protected void addItem(T item) {
+ items.add(item);
+ addItemComponent(item);
+ }
+
+ protected void addItemComponent(T item) {
+ JComponent itemPanel = new JPanel(new BorderLayout());
+ itemPanel.add(createItemComponent(item), CENTER);
+ itemPanel.add(new JButton(createRemoveAction(item)), EAST);
+ listPanel.add(itemPanel);
+ listPanel.revalidate();
+ listPanel.repaint();
+ }
+
+ protected void removeItem(T item) {
+ int index = items.indexOf(item);
+ if (index >= 0) {
+ items.remove(index);
+ listPanel.remove(index);
+ listPanel.revalidate();
+ listPanel.repaint();
+ }
+ }
+
+ private Action createRemoveAction(final T item) {
+ return new AbstractAction(REMOVE) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ removeItem(item);
+ }
+ };
+ }
+
+ private Action createAddAction() {
+ return new AbstractAction(ADD + " " + name) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ addItem(createDefaultItem());
+ }
+ };
+ }
+
+ protected abstract Component createItemComponent(T item);
+
+ protected abstract T createDefaultItem();
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ListLayout.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ListLayout.java
new file mode 100644
index 0000000..0ce35b5
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ListLayout.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.views.contextualviews.activity;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+
+/**
+ * Lays out components vertically using their preferred height and the available
+ * width.
+ *
+ * @author David Withers
+ */
+public class ListLayout implements LayoutManager {
+ private static final int DEFAULT_GAP = 5;
+ private final int gap;
+
+ public ListLayout() {
+ this(DEFAULT_GAP);
+ }
+
+ public ListLayout(int gap) {
+ this.gap = gap;
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets insets = parent.getInsets();
+ int x = insets.left;
+ int y = insets.top;
+ int width = parent.getWidth() - insets.left - insets.right;
+ Component[] components = parent.getComponents();
+ for (int i = 0; i < components.length; i++) {
+ components[i].setLocation(x, y);
+ components[i].setSize(width,
+ components[i].getPreferredSize().height);
+ y = y + gap + components[i].getHeight();
+ }
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ Insets insets = parent.getInsets();
+ int minimumWidth = 0;
+ int minimumHeight = 0;
+ Component[] components = parent.getComponents();
+ for (int i = 0; i < components.length; i++) {
+ Dimension size = components[i].getPreferredSize();
+ if (size.width > minimumWidth)
+ minimumWidth = size.width;
+ minimumHeight = minimumHeight + size.height + gap;
+ }
+ minimumWidth = minimumWidth + insets.left + insets.right;
+ minimumHeight = minimumHeight + insets.top + insets.bottom;
+
+ return new Dimension(minimumWidth, minimumHeight);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return minimumLayoutSize(parent);
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/MultiPageActivityConfigurationPanel.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/MultiPageActivityConfigurationPanel.java
new file mode 100644
index 0000000..19cd180
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/MultiPageActivityConfigurationPanel.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.views.contextualviews.activity;
+
+import static java.awt.BorderLayout.CENTER;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+
+import javax.swing.JTabbedPane;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+/**
+ * Component for configuring activities that have multiple configuration pages.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class MultiPageActivityConfigurationPanel extends
+ ActivityConfigurationPanel {
+ private JTabbedPane tabbedPane;
+
+ /**
+ * Constructs a new <code>MultiPageActivityConfigurationPanel</code>.
+ *
+ * @param activity
+ */
+ public MultiPageActivityConfigurationPanel(Activity activity) {
+ super(activity);
+ setLayout(new BorderLayout());
+ tabbedPane = new JTabbedPane();
+ add(tabbedPane, CENTER);
+ }
+
+ public void addPage(String name, Component component) {
+ tabbedPane.addTab(name, component);
+ }
+
+ public void removePage(String name) {
+ tabbedPane.removeTabAt(tabbedPane.indexOfTab(name));
+ }
+
+ public void removeAllPages() {
+ tabbedPane.removeAll();
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ScriptConfigurationComponent.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ScriptConfigurationComponent.java
new file mode 100644
index 0000000..8cb7652
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ScriptConfigurationComponent.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.views.contextualviews.activity;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Color.WHITE;
+import static java.awt.Font.PLAIN;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.lang.ui.FileTools.readStringFromFile;
+import static net.sf.taverna.t2.lang.ui.FileTools.saveStringToFile;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+
+import net.sf.taverna.t2.lang.ui.KeywordDocument;
+import net.sf.taverna.t2.lang.ui.LineEnabledTextPanel;
+import net.sf.taverna.t2.lang.ui.LinePainter;
+import net.sf.taverna.t2.lang.ui.NoWrapEditorKit;
+
+/**
+ * Component for configuring activities that have scripts.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ScriptConfigurationComponent extends JPanel {
+ private JTextPane scriptTextArea;
+
+ public ScriptConfigurationComponent(String script, Set<String> keywords,
+ Set<String> ports, final String scriptType,
+ final String fileExtension) {
+ this(script, keywords, ports, scriptType, fileExtension, "");
+ }
+
+ public ScriptConfigurationComponent(String script, Set<String> keywords,
+ Set<String> ports, final String scriptType,
+ final String fileExtension, final String resetScript) {
+ super(new BorderLayout());
+ scriptTextArea = new JTextPane();
+ new LinePainter(scriptTextArea, WHITE);
+
+ final KeywordDocument doc = new KeywordDocument(keywords, ports);
+
+ // NOTE: Due to T2-1145 - always set editor kit BEFORE setDocument
+ scriptTextArea.setEditorKit(new NoWrapEditorKit());
+ scriptTextArea.setFont(new Font("Monospaced", PLAIN, 14));
+ scriptTextArea.setDocument(doc);
+ scriptTextArea.setText(script);
+ scriptTextArea.setCaretPosition(0);
+ scriptTextArea.setPreferredSize(new Dimension(200, 100));
+
+ add(new LineEnabledTextPanel(scriptTextArea), CENTER);
+
+ final JButton checkScriptButton = new JButton("Check script");
+ checkScriptButton.setToolTipText("Check the " + scriptType + " script");
+ checkScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ex) {
+ showMessageDialog(ScriptConfigurationComponent.this, scriptType
+ + " script check not implemented", scriptType
+ + " script check", INFORMATION_MESSAGE);
+ }
+ });
+
+ JButton loadScriptButton = new JButton("Load script");
+ loadScriptButton.setToolTipText("Load a " + scriptType
+ + " script from a file");
+ loadScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String newScript = readStringFromFile(
+ ScriptConfigurationComponent.this, "Load " + scriptType
+ + " script", fileExtension);
+ if (newScript != null) {
+ scriptTextArea.setText(newScript);
+ scriptTextArea.setCaretPosition(0);
+ }
+ }
+ });
+
+ JButton saveRScriptButton = new JButton("Save script");
+ saveRScriptButton.setToolTipText("Save the " + scriptType
+ + " script to a file");
+ saveRScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveStringToFile(ScriptConfigurationComponent.this, "Save "
+ + scriptType + " script", fileExtension,
+ scriptTextArea.getText());
+ }
+ });
+
+ JButton clearScriptButton = new JButton("Clear script");
+ clearScriptButton.setToolTipText("Clear current script from the edit area");
+ clearScriptButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (showConfirmDialog(ScriptConfigurationComponent.this,
+ "Do you really want to clear the script?",
+ "Clearing the script", YES_NO_OPTION) == YES_OPTION)
+ scriptTextArea.setText(resetScript);
+ }
+ });
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout());
+ buttonPanel.add(checkScriptButton);
+ buttonPanel.add(loadScriptButton);
+ buttonPanel.add(saveRScriptButton);
+ buttonPanel.add(clearScriptButton);
+
+ add(buttonPanel, SOUTH);
+ }
+
+ public String getScript() {
+ return scriptTextArea.getText();
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ValidatingTextField.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ValidatingTextField.java
new file mode 100644
index 0000000..cf1ff96
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ValidatingTextField.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.views.contextualviews.activity;
+
+import javax.swing.JTextField;
+
+/**
+ * Adds a "<tt>valid</tt>" property to a JTextField.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ValidatingTextField extends JTextField {
+ private boolean valid = true;
+
+ public ValidatingTextField() {
+ }
+
+ public ValidatingTextField(String text) {
+ super(text);
+ }
+
+ @Override
+ public boolean isValid() {
+ return valid;
+ }
+
+ public void setValid(boolean valid) {
+ if (this.valid != valid) {
+ boolean old = this.valid;
+ this.valid = valid;
+ firePropertyChange("valid", old, valid);
+ }
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ValidatingTextGroup.java b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ValidatingTextGroup.java
new file mode 100644
index 0000000..7597f7c
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/ValidatingTextGroup.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.views.contextualviews.activity;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+/**
+ *
+ *
+ * @author David Withers
+ */
+public class ValidatingTextGroup {
+ private Map<ValidatingTextField, DocumentListener> textComponents;
+
+ public ValidatingTextGroup() {
+ textComponents = new HashMap<>();
+ }
+
+ public void addValidTextComponent(ValidatingTextField textComponent) {
+ setUniqueText(textComponent);
+ DocumentListener documentListener = new ValidatorDocumentListener();
+ textComponent.getDocument().addDocumentListener(documentListener);
+ textComponents.put(textComponent, documentListener);
+ }
+
+ public void addTextComponent(ValidatingTextField textComponent) {
+ DocumentListener documentListener = new ValidatorDocumentListener();
+ textComponent.getDocument().addDocumentListener(documentListener);
+ textComponents.put(textComponent, documentListener);
+ validate();
+ }
+
+ public void removeTextComponent(ValidatingTextField textComponent) {
+ textComponent.getDocument().removeDocumentListener(
+ textComponents.remove(textComponent));
+ validate();
+ }
+
+ private void setUniqueText(ValidatingTextField textComponent) {
+ String text = textComponent.getText();
+ if (textExists(text)) {
+ // Remove any existing number suffix
+ String nameTemplate = text.replaceAll("_\\d+$", "_");
+ long i = 1;
+ do {
+ text = nameTemplate + i++;
+ } while (textExists(text));
+
+ textComponent.setText(text);
+ }
+ }
+
+ private void validate() {
+ Map<String, ValidatingTextField> textValues = new HashMap<>();
+ Set<ValidatingTextField> maybeValid = new HashSet<>();
+ for (ValidatingTextField textComponent : textComponents.keySet()) {
+ ValidatingTextField duplicate = textValues.get(textComponent
+ .getText());
+ if (duplicate != null) {
+ duplicate.setValid(false);
+ maybeValid.remove(duplicate);
+ textComponent.setValid(false);
+ } else {
+ textValues.put(textComponent.getText(), textComponent);
+ maybeValid.add(textComponent);
+ }
+ }
+ for (ValidatingTextField textComponent : maybeValid)
+ textComponent.setValid(true);
+ }
+
+ private boolean textExists(String text) {
+ for (ValidatingTextField currentTextComponent : textComponents.keySet())
+ if (text.equals(currentTextComponent.getText()))
+ return true;
+ return false;
+ }
+
+ class ValidatorDocumentListener implements DocumentListener {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ validate();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ validate();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ validate();
+ }
+ }
+}
diff --git a/taverna-workbench-contextual-views-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..312f95b
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1,2 @@
+#net.sf.taverna.t2.workbench.ui.actions.activity.draggable.ActivityDraggerPaletteComponentFactory
+#net.sf.taverna.t2.workbench.ui.views.contextualviews.DragActivitiesToHereComponentFactory
diff --git a/taverna-workbench-contextual-views-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..1448a49
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1,3 @@
+#net.sf.taverna.t2.workbench.ui.actions.activity.draggable.ActivityDraggerPaletteComponent
+#net.sf.taverna.t2.workbench.ui.views.contextualviews.DragActivitiesToHereComponent
+net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewComponent
diff --git a/taverna-workbench-contextual-views-api/src/main/resources/META-INF/spring/contextual-views-api-context-osgi.xml b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/spring/contextual-views-api-context-osgi.xml
new file mode 100644
index 0000000..ab22b97
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/spring/contextual-views-api-context-osgi.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+</beans:beans>
diff --git a/taverna-workbench-contextual-views-api/src/main/resources/META-INF/spring/contextual-views-api-context.xml b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/spring/contextual-views-api-context.xml
new file mode 100644
index 0000000..d662d87
--- /dev/null
+++ b/taverna-workbench-contextual-views-api/src/main/resources/META-INF/spring/contextual-views-api-context.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+</beans>
diff --git a/taverna-workbench-contextual-views-impl/pom.xml b/taverna-workbench-contextual-views-impl/pom.xml
new file mode 100644
index 0000000..1cafa80
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Contextual Views Implementation</name>
+ <description>Contextual views for the activities</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java
new file mode 100644
index 0000000..8bac354
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+
+/**
+ * @author Alan R Williams
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+
+/**
+ * An SPI registry for discovering ActivityViewFactories for a given object,
+ * like an {@link net.sf.taverna.t2.workflowmodel.processor.activity.Activity}.
+ * <p>
+ * For {@link ContextualViewFactory factories} to be found, its full qualified
+ * name needs to be defined as a resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewFactory</code>
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @author Stian Soiland-Reyes
+ *
+ * @see ContextualViewFactory
+ */
+public class ContextualViewFactoryRegistryImpl implements
+ ContextualViewFactoryRegistry {
+ private List<ContextualViewFactory<?>> contextualViewFactories;
+
+ /**
+ * Discover and return the ContextualViewFactory associated to the provided
+ * object. This is accomplished by returning the discovered
+ * {@link ContextualViewFactory#canHandle(Object)} that returns true for
+ * that Object.
+ *
+ * @param object
+ * @return
+ * @see ContextualViewFactory#canHandle(Object)
+ */
+ @Override
+ public List<ContextualViewFactory<?>> getViewFactoriesForObject(
+ Object object) {
+ List<ContextualViewFactory<?>> result = new ArrayList<>();
+ for (ContextualViewFactory<?> factory : contextualViewFactories)
+ if (factory.canHandle(object))
+ result.add(factory);
+ return result;
+ }
+
+ public void setContextualViewFactories(
+ List<ContextualViewFactory<?>> contextualViewFactories) {
+ this.contextualViewFactories = contextualViewFactories;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java
new file mode 100644
index 0000000..018a121
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.annotated;
+
+import static javax.swing.BoxLayout.Y_AXIS;
+
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.regex.Pattern;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * This is a ContextualView that should be able to display and allow editing of
+ * Annotation information for any Annotated. At the moment it is only used for
+ * Dataflow.
+ *
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+class AnnotatedContextualView extends ContextualView {
+ private static final int WORKFLOW_NAME_LENGTH = 20;
+ public static final String VIEW_TITLE = "Annotations";
+ private final static String MISSING_VALUE = "Type here to give details";
+ private final static int DEFAULT_AREA_WIDTH = 60;
+ private final static int DEFAULT_AREA_ROWS = 8;
+
+ private static Logger logger = Logger
+ .getLogger(AnnotatedContextualView.class);
+ private static PropertyResourceBundle prb = (PropertyResourceBundle) ResourceBundle
+ .getBundle("annotatedcontextualview");
+
+ // TODO convert to scufl2
+ // private static AnnotationTools annotationTools = new AnnotationTools();
+
+ /**
+ * The object to which the Annotations apply
+ */
+ private Annotated<?> annotated;
+ private SelectionManager selectionManager;
+ private EditManager editManager;
+ private boolean isStandalone = false;
+ private JPanel panel;
+ @SuppressWarnings("unused")
+ private final List<AnnotationBeanSPI> annotationBeans;
+
+ public AnnotatedContextualView(Annotated<?> annotated,
+ EditManager editManager, SelectionManager selectionManager,
+ List<AnnotationBeanSPI> annotationBeans) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ this.annotationBeans = annotationBeans;
+ this.annotated = annotated;
+
+ initialise();
+ initView();
+ }
+
+ @Override
+ public void refreshView() {
+ initialise();
+ }
+
+ private void initialise() {
+ if (panel == null) {
+ panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, Y_AXIS));
+ } else
+ panel.removeAll();
+ populatePanel();
+ revalidate();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ return panel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return VIEW_TITLE;
+ }
+
+ private Map<String,String> getAnnotations() {
+ // TODO convert to scufl2
+ Map<String, String> result = new HashMap<>();
+ //for (Class<?> c : annotationTools.getAnnotatingClasses(annotated)) {
+ // String name = "";
+ // try {
+ // name = prb.getString(c.getCanonicalName());
+ // } catch (MissingResourceException e) {
+ // name = c.getCanonicalName();
+ // }
+ // String value = annotationTools.getAnnotationString(annotated, c,
+ // MISSING_VALUE);
+ // result.put(name,value);
+ //}
+ return result;
+ }
+ public void populatePanel() {
+ JPanel scrollPanel = new JPanel();
+ scrollPanel.setLayout(new BoxLayout(scrollPanel, Y_AXIS));
+ panel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ Map<String,String>annotations = getAnnotations();
+ for (String name : annotations.keySet()) {
+ JPanel subPanel = new JPanel();
+ subPanel.setBorder(new TitledBorder(name));
+ subPanel.add(createTextArea(String.class, annotations.get(name)));
+ scrollPanel.add(subPanel);
+ }
+ JScrollPane scrollPane = new JScrollPane(scrollPanel);
+ panel.add(scrollPane);
+ }
+
+ private JScrollPane createTextArea(Class<?> c, String value) {
+ DialogTextArea area = new DialogTextArea(value);
+ area.setFocusable(true);
+ area.addFocusListener(new TextAreaFocusListener(area, c));
+ area.setColumns(DEFAULT_AREA_WIDTH);
+ area.setRows(DEFAULT_AREA_ROWS);
+ area.setLineWrap(true);
+ area.setWrapStyleWord(true);
+
+ return new JScrollPane(area);
+ }
+
+ private class TextAreaFocusListener implements FocusListener {
+ String oldValue = null;
+ Class<?> annotationClass;
+ DialogTextArea area = null;
+
+ public TextAreaFocusListener(DialogTextArea area, Class<?> c) {
+ annotationClass = c;
+ oldValue = area.getText();
+ this.area = area;
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ if (area.getText().equals(MISSING_VALUE))
+ area.setText("");
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ String currentValue = area.getText();
+ if (currentValue.isEmpty() || currentValue.equals(MISSING_VALUE)) {
+ currentValue = MISSING_VALUE;
+ area.setText(currentValue);
+ }
+ if (!currentValue.equals(oldValue)) {
+ if (currentValue == MISSING_VALUE)
+ currentValue = "";
+ try {
+ WorkflowBundle currentDataflow = selectionManager
+ .getSelectedWorkflowBundle();
+ List<Edit<?>> editList = new ArrayList<>();
+ addWorkflowNameEdits(currentValue, currentDataflow,
+ editList);
+ if (!isStandalone)
+ ContextualViewComponent.selfGenerated = true;
+ editManager.doDataflowEdit(currentDataflow,
+ new CompoundEdit(editList));
+ ContextualViewComponent.selfGenerated = false;
+ } catch (EditException e1) {
+ logger.warn("Can't set annotation", e1);
+ }
+ oldValue = area.getText();
+ }
+ }
+
+ private boolean isTitleAnnotation() {
+ // TODO convert to scufl2
+ return prb.getString(annotationClass.getCanonicalName()).equals(
+ "Title");
+ }
+
+ // TODO convert to scufl2
+ private void addWorkflowNameEdits(String currentValue,
+ WorkflowBundle currentDataflow, List<Edit<?>> editList) {
+ //editList.add(annotationTools.setAnnotationString(annotated,
+ // annotationClass, currentValue, edits));
+ if (annotated == currentDataflow && isTitleAnnotation()
+ && !currentValue.isEmpty()) {
+ @SuppressWarnings("unused")
+ String sanitised = sanitiseName(currentValue);
+ //editList.add(edits.getUpdateDataflowNameEdit(currentDataflow,
+ // sanitised));
+ }
+ }
+ }
+
+ /**
+ * Checks that the name does not have any characters that are invalid for a
+ * processor name.
+ * <p>
+ * The resulting name must contain only the chars [A-Za-z_0-9].
+ *
+ * @param name
+ * the original name
+ * @return the sanitised name
+ */
+ private static String sanitiseName(String name) {
+ if (name.length() > WORKFLOW_NAME_LENGTH)
+ name = name.substring(0, WORKFLOW_NAME_LENGTH);
+ if (Pattern.matches("\\w++", name))
+ return name;
+ StringBuilder temp = new StringBuilder();
+ for (char c : name.toCharArray())
+ temp.append(Character.isLetterOrDigit(c) || c == '_' ? c : '_');
+ return temp.toString();
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 500;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java
new file mode 100644
index 0000000..eb18803
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java
@@ -0,0 +1,43 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated;
+
+import static java.util.Collections.singletonList;
+
+import java.util.List;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+
+public class AnnotatedContextualViewFactory implements
+ ContextualViewFactory<Annotated<?>> {
+ private EditManager editManager;
+ private List<AnnotationBeanSPI> annotationBeans;
+ private SelectionManager selectionManager;
+
+ @Override
+ public boolean canHandle(Object selection) {
+ return ((selection instanceof Annotated) && !(selection instanceof Activity));
+ }
+
+ @Override
+ public List<ContextualView> getViews(Annotated<?> selection) {
+ return singletonList((ContextualView) new AnnotatedContextualView(
+ selection, editManager, selectionManager, annotationBeans));
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setAnnotationBeans(List<AnnotationBeanSPI> annotationBeans) {
+ this.annotationBeans = annotationBeans;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java
new file mode 100644
index 0000000..f9308b5
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.condition;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+
+/**
+ * Contextual view for dataflow's control (condition) links.
+ *
+ * @author David Withers
+ */
+class ConditionContextualView extends ContextualView {
+ private static final long serialVersionUID = -894521200616176439L;
+
+ private final BlockingControlLink condition;
+ private JPanel contitionView;
+
+ public ConditionContextualView(BlockingControlLink condition) {
+ this.condition = condition;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return contitionView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Control link: " + condition.getBlock().getName()
+ + " runs after " + condition.getUntilFinished().getName();
+ }
+
+ @Override
+ public void refreshView() {
+ contitionView = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ contitionView.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel label = new JLabel(
+ "<html><body><i>No details available.</i></body><html>");
+ contitionView.add(label);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java
new file mode 100644
index 0000000..ea69f1a
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.condition;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Condition;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+
+/**
+ * A factory of contextual views for dataflow's condition links.
+ *
+ * @author David Withers
+ *
+ */
+public class ConditionContextualViewFactory implements
+ ContextualViewFactory<BlockingControlLink> {
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof Condition;
+ }
+
+ @Override
+ public List<ContextualView> getViews(BlockingControlLink condition) {
+ return asList(new ContextualView[] { new ConditionContextualView(
+ condition) });
+ }
+
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java
new file mode 100644
index 0000000..4a63868
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java
@@ -0,0 +1,108 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow;
+
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.panelForHtml;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+class DataflowContextualView extends ContextualView {
+ private static int MAX_LENGTH = 50;
+ private static final String ELLIPSIS = "...";
+
+ private Workflow dataflow;
+ private JEditorPane editorPane;
+ private final FileManager fileManager;
+ private final ColourManager colourManager;
+
+ public DataflowContextualView(Workflow dataflow, FileManager fileManager,
+ ColourManager colourManager) {
+ this.dataflow = dataflow;
+ this.fileManager = fileManager;
+ this.colourManager = colourManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ editorPane = createEditorPane(buildHtml());
+ return panelForHtml(editorPane);
+ }
+
+ private String buildHtml() {
+ StringBuilder html = new StringBuilder(getHtmlHead(getBackgroundColour()));
+ html.append(buildTableOpeningTag());
+
+ html.append("<tr><td colspan=\"2\" align=\"center\"><b>Source</b></td></tr>");
+ String source = "Newly created";
+ if (fileManager.getDataflowSource(dataflow.getParent()) != null)
+ source = fileManager.getDataflowName(dataflow.getParent());
+
+ html.append("<tr><td colspan=\"2\" align=\"center\">").append(source)
+ .append("</td></tr>");
+ if (!dataflow.getInputPorts().isEmpty()) {
+ html.append("<tr><th>Input Port Name</th><th>Depth</th></tr>");
+ for (InputWorkflowPort dip : dataflow.getInputPorts())
+ html.append("<tr><td>")
+ .append(dip.getName())
+ .append("</td><td>")
+ .append(dip.getDepth() < 0 ? "invalid/unpredicted"
+ : dip.getDepth()).append("</td></tr>");
+ }
+ if (!dataflow.getOutputPorts().isEmpty()) {
+ html.append("<tr><th>Output Port Name</th><th>Depth</th></tr>");
+ for (OutputWorkflowPort dop : dataflow.getOutputPorts())
+ html.append("<tr><td>")
+ .append(dop.getName())
+ .append("</td><td>")
+ .append(/*(dop.getDepth() < 0 ?*/ "invalid/unpredicted" /*: dop.getDepth())*/)
+ .append("</td>" + "</tr>");
+ }
+
+ return html.append("</table>").append("</body></html>").toString();
+ }
+
+ public String getBackgroundColour() {
+ return colourManager.getDefaultPropertyMap().get(
+ Dataflow.class.toString());
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+
+ private String limitName(String fullName) {
+ if (fullName.length() <= MAX_LENGTH)
+ return fullName;
+ return fullName.substring(0, MAX_LENGTH - ELLIPSIS.length()) + ELLIPSIS;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Workflow " + limitName(dataflow.getName());
+ }
+
+ @Override
+ public void refreshView() {
+ editorPane.setText(buildHtml());
+ repaint();
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java
new file mode 100644
index 0000000..0d7f3c0
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java
@@ -0,0 +1,41 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowContextualViewFactory implements
+ ContextualViewFactory<Workflow> {
+ private FileManager fileManager;
+ private ColourManager colourManager;
+
+ @Override
+ public boolean canHandle(Object selection) {
+ return selection instanceof Workflow;
+ }
+
+ @Override
+ public List<ContextualView> getViews(Workflow selection) {
+ return Arrays.asList(new ContextualView[] {
+ new DataflowContextualView(selection, fileManager, colourManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java
new file mode 100644
index 0000000..3f17a65
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.dataflowinputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's input ports.
+ *
+ * @author Alex Nenadic
+ */
+class DataflowInputPortContextualView extends ContextualView{
+ private static final long serialVersionUID = -8746856072335775933L;
+
+ private InputWorkflowPort dataflowInputPort;
+ private JPanel dataflowInputPortView;
+ @SuppressWarnings("unused")
+ private FileManager fileManager;
+
+ public DataflowInputPortContextualView(InputWorkflowPort inputport,
+ FileManager fileManager) {
+ this.dataflowInputPort = inputport;
+ this.fileManager = fileManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return dataflowInputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Workflow input port: " + dataflowInputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ dataflowInputPortView = new JPanel(new FlowLayout(LEFT));
+ dataflowInputPortView.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel label = new JLabel(getTextFromDepth("port",
+ dataflowInputPort.getDepth()));
+ dataflowInputPortView.add(label);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new AbstractAction("Update prediction") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // fileManager.getCurrentDataflow().checkValidity();
+ refreshView();
+ }
+ };
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java
new file mode 100644
index 0000000..5dc5434
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.dataflowinputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+/**
+ * A factory of contextual views for dataflow's input ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowInputPortContextualViewFactory implements
+ ContextualViewFactory<InputWorkflowPort> {
+ private FileManager fileManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof InputWorkflowPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(InputWorkflowPort inputport) {
+ return Arrays.asList(new ContextualView[] {
+ new DataflowInputPortContextualView(inputport, fileManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java
new file mode 100644
index 0000000..9ba55fe
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.dataflowoutputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowOutputPortContextualView extends ContextualView {
+ private static final long serialVersionUID = 5496014085110553051L;
+
+ private OutputWorkflowPort dataflowOutputPort;
+ private JPanel dataflowOutputPortView;
+ @SuppressWarnings("unused")
+ private FileManager fileManager;
+
+ public DataflowOutputPortContextualView(OutputWorkflowPort outputport,
+ FileManager fileManager) {
+ this.dataflowOutputPort = outputport;
+ this.fileManager = fileManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return dataflowOutputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Workflow output port: " + dataflowOutputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ dataflowOutputPortView = new JPanel(new FlowLayout(LEFT));
+ dataflowOutputPortView.setBorder(new EmptyBorder(5,5,5,5));
+ JLabel label = new JLabel(getTextForLabel());
+ dataflowOutputPortView.add(label);
+ }
+
+ private String getTextForLabel() {
+ //FIXME
+ //return getTextFromDepth("port", dataflowOutputPort.getDepth());
+ return "Fix depth for OutputWorkflowPort";
+ }
+
+ private void updatePrediction() {
+ //FIXME
+ // fileManager.getCurrentDataflow().checkValidity();
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Action getConfigureAction(Frame owner) {
+ return new AbstractAction("Update prediction") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updatePrediction();
+ refreshView();
+ }
+ };
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java
new file mode 100644
index 0000000..20ac960
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.dataflowoutputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * A factory of contextual views for dataflow's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowOutputPortContextualViewFactory implements
+ ContextualViewFactory<OutputWorkflowPort> {
+ private FileManager fileManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof DataflowOutputPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(OutputWorkflowPort outputport) {
+ return Arrays.asList(new ContextualView[] {
+ new DataflowOutputPortContextualView(outputport, fileManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java
new file mode 100644
index 0000000..daa3414
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.datalink;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's datalinks.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+class DatalinkContextualView extends ContextualView {
+ private static final long serialVersionUID = -5031256519235454876L;
+
+ private DataLink datalink;
+ private JPanel datalinkView;
+ @SuppressWarnings("unused")
+ private final FileManager fileManager;
+
+ public DatalinkContextualView(DataLink datalink, FileManager fileManager) {
+ this.datalink = datalink;
+ this.fileManager = fileManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return datalinkView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Data link: " + datalink.getReceivesFrom().getName() + " -> " + datalink.getSendsTo().getName();
+ }
+
+ @Override
+ public void refreshView() {
+ datalinkView = new JPanel(new FlowLayout(LEFT));
+ datalinkView.setBorder(new EmptyBorder(5,5,5,5));
+ JLabel label = new JLabel (getTextForLabel());
+ datalinkView.add(label);
+ }
+
+ private String getTextForLabel() {
+ //FIXME
+ // return getTextFromDepth("link", datalink.getResolvedDepth());
+ return "Fix DataLink resolved depth";
+ }
+
+ private void updatePrediction() {
+ //FIXME
+ // fileManager.getCurrentDataflow().checkValidity();
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Action getConfigureAction(Frame owner) {
+ return new AbstractAction("Update prediction") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updatePrediction();
+ refreshView();
+ }
+ };
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java
new file mode 100644
index 0000000..fa8bf96
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.datalink;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Datalink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * A factory of contextual views for dataflow's datalinks.
+ *
+ * @author Alex Nenadic
+ */
+public class DatalinkContextualViewFactory implements
+ ContextualViewFactory<DataLink> {
+ private FileManager fileManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof Datalink;
+ }
+
+ @Override
+ public List<ContextualView> getViews(DataLink datalink) {
+ return Arrays.asList(new ContextualView[] {
+ new DatalinkContextualView(datalink, fileManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java
new file mode 100644
index 0000000..11306d0
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java
@@ -0,0 +1,389 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.BLUE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.ORANGE;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.minusIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.plusIcon;
+
+import java.awt.Color;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.Utils;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class ContextualViewComponent extends JScrollPane implements UIComponentSPI {
+ /** delay before contextual view is redrawn */
+ private static final int DELAY = 250;
+ private static final Color[] colors = new Color[] { BLUE, GREEN, ORANGE };
+ // HACK ALERT!
+ public static boolean selfGenerated = false;
+
+ private Observer<DataflowSelectionMessage> dataflowSelectionListener = new DataflowSelectionListener();
+ private SelectionManager selectionManager;
+ private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+ GridBagConstraints gbc;
+ protected Map<JPanel, SectionLabel> panelToLabelMap = new HashMap<>();
+ private String lastOpenedSectionName = "";
+ private JPanel mainPanel;
+ private List<JPanel> shownComponents = null;
+ int colorIndex = 0;
+ private Timer updateSelectionTimer = null;
+ private Object lastSelectedObject = null;
+
+ private static final Comparator<ContextualView> viewComparator = new Comparator<ContextualView>() {
+ @Override
+ public int compare(ContextualView o1, ContextualView o2) {
+ return o1.getPreferredPosition() - o2.getPreferredPosition();
+ }
+ };
+
+ public ContextualViewComponent(EditManager editManager,
+ SelectionManager selectionManager,
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+ this.selectionManager = selectionManager;
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ updateSelectionTimer = new Timer(DELAY, updateSelectionListener);
+ updateSelectionTimer.setRepeats(false);
+
+ initialise();
+
+ editManager.addObserver(new EditManagerObserver());
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Details";
+ }
+
+ private void initialise() {
+ mainPanel = new JPanel(new GridBagLayout());
+ this.setViewportView(mainPanel);
+ }
+
+ @Override
+ public void onDisplay() {
+ }
+
+ @Override
+ public void onDispose() {
+ updateSelectionTimer.stop();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void updateContextualView(List<ContextualViewFactory<?>> viewFactories,
+ Object selection) {
+ if (selection == lastSelectedObject)
+ return;
+ lastSelectedObject = selection;
+ mainPanel = new JPanel(new GridBagLayout());
+ panelToLabelMap.clear();
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.weightx = 0.1;
+ gbc.fill = HORIZONTAL;
+
+ gbc.gridy = 0;
+ shownComponents = new ArrayList<>();
+ List<ContextualView> views = new ArrayList<>();
+ for (ContextualViewFactory<?> cvf : viewFactories)
+ views.addAll(((ContextualViewFactory<Object>) cvf)
+ .getViews(selection));
+ Collections.sort(views, viewComparator);
+ colorIndex = 0;
+ if (views.isEmpty())
+ mainPanel.add(new JLabel("No details available"));
+ else
+ populateContextualView(viewFactories, gbc, views);
+ gbc.weighty = 0.1;
+ gbc.fill = BOTH;
+ mainPanel.add(new JPanel(), gbc);
+ // mainPanel.revalidate();
+ // mainPanel.repaint();
+ this.setViewportView(mainPanel);
+ // this.revalidate();
+ // this.repaint();
+ }
+
+ private void populateContextualView(
+ List<ContextualViewFactory<?>> viewFactories,
+ GridBagConstraints gbc, List<ContextualView> views) {
+ JPanel firstPanel = null;
+ JPanel lastOpenedSection = null;
+ for (ContextualView view : views) {
+ SectionLabel label = new SectionLabel(view.getViewTitle(), nextColor());
+ mainPanel.add(label, gbc);
+ gbc.gridy++;
+ JPanel subPanel = new JPanel();
+ if (view.getViewTitle().equals(lastOpenedSectionName))
+ lastOpenedSection = subPanel;
+ subPanel.setLayout(new GridBagLayout());
+
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.weightx = 0.1;
+ constraints.weighty = 0;
+ constraints.anchor = CENTER;
+ constraints.fill = HORIZONTAL;
+
+ subPanel.add(view, constraints);
+ Frame frame = Utils.getParentFrame(this);
+ Action configureAction = view.getConfigureAction(frame);
+ if (configureAction != null) {
+ JButton configButton = new JButton(configureAction);
+ if (configButton.getText() == null
+ || configButton.getText().isEmpty())
+ configButton.setText("Configure");
+ constraints.gridy++;
+ constraints.fill = NONE;
+ constraints.anchor = LINE_START;
+ subPanel.add(configButton, constraints);
+ }
+ if (firstPanel == null)
+ firstPanel = subPanel;
+ mainPanel.add(subPanel, gbc);
+ shownComponents.add(subPanel);
+ gbc.gridy++;
+ if (viewFactories.size() != 1)
+ makeCloseable(subPanel, label);
+ else {
+ lastOpenedSectionName = label.getText();
+ lastOpenedSection = subPanel;
+ panelToLabelMap.put(subPanel, label);
+ subPanel.setVisible(false);
+ }
+ }
+ if (lastOpenedSection != null)
+ openSection(lastOpenedSection);
+ else if (firstPanel != null)
+ openSection(firstPanel);
+ }
+
+ private void clearContextualView() {
+ lastSelectedObject = null;
+ mainPanel = new JPanel(new GridBagLayout());
+ mainPanel.add(new JLabel("No details available"));
+ this.setViewportView(mainPanel);
+ this.revalidate();
+ }
+
+ public void updateSelection(Object selectedItem) {
+ findContextualView(selectedItem);
+ }
+
+ private Runnable updateSelectionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Object selection = getSelection();
+ if (selection == null)
+ clearContextualView();
+ else
+ updateSelection(selection);
+ }
+ };
+
+ private ActionListener updateSelectionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(updateSelectionRunnable);
+ }
+ };
+
+ public void updateSelection() {
+ updateSelectionTimer.restart();
+ }
+
+ private Object getSelection() {
+ WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+
+ /*
+ * If there is no currently opened dataflow, clear the contextual view
+ * panel
+ */
+ if (workflowBundle == null) {
+ return null;
+ }
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+ Set<Object> selection = selectionModel.getSelection();
+
+ /*
+ * If the dataflow is opened but no component of the dataflow is
+ * selected, clear the contextual view panel
+ */
+ if (selection.isEmpty())
+ return null;
+ return selection.iterator().next();
+ }
+
+ private void findContextualView(Object selection) {
+ List<ContextualViewFactory<?>> viewFactoriesForBeanType = contextualViewFactoryRegistry
+ .getViewFactoriesForObject(selection);
+ updateContextualView(viewFactoriesForBeanType, selection);
+ }
+
+ private final class SelectionManagerObserver extends SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender, SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent)
+ bundleSelected((WorkflowBundleSelectionEvent) message);
+ }
+
+ private void bundleSelected(WorkflowBundleSelectionEvent event) {
+ WorkflowBundle oldBundle = event
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newBundle = event.getSelectedWorkflowBundle();
+
+ if (oldBundle != null)
+ selectionManager.getDataflowSelectionModel(oldBundle)
+ .removeObserver(dataflowSelectionListener);
+ if (newBundle != null)
+ selectionManager.getDataflowSelectionModel(newBundle)
+ .addObserver(dataflowSelectionListener);
+ lastSelectedObject = null;
+ updateSelection();
+ }
+ }
+
+ private final class DataflowSelectionListener extends SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ updateSelection();
+ }
+ }
+
+ private final class EditManagerObserver extends SwingAwareObserver<EditManagerEvent> {
+ @Override
+ public void notifySwing(Observable<EditManagerEvent> sender, EditManagerEvent message) {
+ Object selection = getSelection();
+ if ((selection != lastSelectedObject) && !selfGenerated) {
+ lastSelectedObject = null;
+ refreshView();
+ }
+ }
+ }
+
+ public void refreshView() {
+ if (mainPanel != null)
+ updateSelection();
+ }
+
+ private final class SectionLabel extends ShadedLabel {
+ private JLabel expand;
+
+ private SectionLabel(String text, Color colour) {
+ super(text, colour);
+ expand = new JLabel(minusIcon);
+ add(expand, 0);
+ setExpanded(true);
+ }
+
+ public void setExpanded(boolean expanded) {
+ if (expanded)
+ expand.setIcon(minusIcon);
+ else
+ expand.setIcon(plusIcon);
+ }
+ }
+
+ private void makeCloseable(JPanel panel, SectionLabel label) {
+ panel.setVisible(false);
+ if (panelToLabelMap.get(panel) != label) {
+ panelToLabelMap.put(panel, label);
+ // Only add mouse listener once
+ label.addMouseListener(new SectionOpener(panel));
+ }
+ }
+
+ protected class SectionOpener extends MouseAdapter {
+ private final JPanel sectionToOpen;
+
+ public SectionOpener(JPanel sectionToOpen) {
+ this.sectionToOpen = sectionToOpen;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ openSection(sectionToOpen);
+ }
+ }
+
+ public synchronized void openSection(JPanel sectionToOpen) {
+ lastOpenedSectionName = "";
+ for (Entry<JPanel, SectionLabel> entry : panelToLabelMap.entrySet()) {
+ JPanel section = entry.getKey();
+ SectionLabel sectionLabel = entry.getValue();
+
+ if (section != sectionToOpen)
+ section.setVisible(false);
+ else {
+ section.setVisible(!section.isVisible());
+ if (section.isVisible())
+ lastOpenedSectionName = sectionLabel.getText();
+ }
+ sectionLabel.setExpanded(section.isVisible());
+ }
+ this.revalidate();
+ this.repaint();
+ }
+
+ private Color nextColor() {
+ if (colorIndex >= colors.length)
+ colorIndex = 0;
+ return colors[colorIndex++];
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java
new file mode 100644
index 0000000..db43a0d
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (C) 2007-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.ui.views.contextualviews.impl;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+public class ContextualViewComponentFactory implements UIComponentFactorySPI {
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+
+ @Override
+ public UIComponentSPI getComponent() {
+ return new ContextualViewComponent(editManager, selectionManager,
+ contextualViewFactoryRegistry);
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Details";
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setContextualViewFactoryRegistry(
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java
new file mode 100644
index 0000000..c1b3d06
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.inputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow procerssor's input ports.
+ *
+ * @author Alex Nenadic
+ */
+class InputPortContextualView extends ContextualView {
+ private static final String NO_DETAILS_AVAILABLE_HTML = "<html><body>"
+ + "<i>No details available.</i>" + "</body><html>";
+ private static final long serialVersionUID = -7743029534480678624L;
+
+ private InputActivityPort inputPort;
+ private JPanel inputPortView;
+
+ public InputPortContextualView(InputActivityPort inputport) {
+ this.inputPort = inputport;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return inputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service input port: " + inputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ inputPortView = new JPanel(new FlowLayout(LEFT));
+ inputPortView.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel label = new JLabel(NO_DETAILS_AVAILABLE_HTML);
+ inputPortView.add(label);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java
new file mode 100644
index 0000000..490e5b7
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.inputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+
+/**
+ * A factory of contextual views for dataflow proessor's (i.e. its associated
+ * activity's) input ports.
+ *
+ * @author Alex Nenadic
+ */
+public class InputPortContextualViewFactory implements
+ ContextualViewFactory<InputActivityPort> {
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof InputActivityPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(InputActivityPort inputport) {
+ return Arrays.asList(new ContextualView[] {
+ new InputPortContextualView(inputport)});
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java
new file mode 100644
index 0000000..567cc4b
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.merge;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.ReorderMergePositionsEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * Configuration action for a Merge. This action changes the order of
+ * merge's incoming ports.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+class MergeConfigurationAction extends AbstractAction {
+ private static Logger logger = Logger
+ .getLogger(MergeConfigurationAction.class);
+
+ private final List<DataLink> reorderedDataLinksList;
+ private final List<DataLink> datalinks;
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ MergeConfigurationAction(List<DataLink> datalinks,
+ List<DataLink> reorderedDataLinksList, EditManager editManager,
+ SelectionManager selectionManager) {
+ this.datalinks = datalinks;
+ this.reorderedDataLinksList = reorderedDataLinksList;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ReorderMergePositionsEdit edit = new ReorderMergePositionsEdit(
+ datalinks, reorderedDataLinksList);
+
+ WorkflowBundle bundle = selectionManager.getSelectedWorkflowBundle();
+
+ try {
+ editManager.doDataflowEdit(bundle, edit);
+ } catch (IllegalStateException ex1) {
+ logger.error("Could not configure merge", ex1);
+ } catch (EditException ex2) {
+ logger.error("Could not configure merge", ex2);
+ }
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java
new file mode 100644
index 0000000..66eeb3e
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java
@@ -0,0 +1,233 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.lang.Math.max;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.ListSelectionModel.SINGLE_SELECTION;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
+import static javax.swing.SwingConstants.CENTER;
+import static javax.swing.SwingConstants.LEFT;
+import static javax.swing.SwingConstants.RIGHT;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.downArrowIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.upArrowIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+@SuppressWarnings("serial")
+public class MergeConfigurationView extends HelpEnabledDialog {
+ private static final String TITLE = "<html><body><b>Order of incoming links</b></body></html>";
+
+ private List<DataLink> dataLinks;
+ private List<DataLink> reorderedDataLinks;
+ /** Ordered list of labels for dataLinks to be displayed to the user */
+ private DefaultListModel<String> labelListModel;
+ /** JList that displays the labelListModel */
+ JList<String> list;
+ /** Button to push the dataLink up the list */
+ private JButton upButton;
+ /** Button to push the dataLink down the list */
+ private JButton downButton;
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public MergeConfigurationView(List<DataLink> dataLinks, EditManager editManager,
+ SelectionManager selectionManager) {
+ super((Frame)null, "Merge Configuration", true);
+
+ this.dataLinks = new ArrayList<>(dataLinks);
+ reorderedDataLinks = new ArrayList<>(dataLinks);
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ labelListModel = new DefaultListModel<>();
+ for (DataLink dataLink : dataLinks)
+ labelListModel.addElement(dataLink.toString());
+
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JPanel listPanel = new JPanel();
+ listPanel.setLayout(new BorderLayout());
+ listPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10, 10),
+ new EtchedBorder()));
+
+ JLabel title = new JLabel(TITLE);
+ title.setBorder(new EmptyBorder(5, 5, 5, 5));
+ listPanel.add(title, NORTH);
+
+ list = new JList<>(labelListModel);
+ list.setSelectionMode(SINGLE_SELECTION);
+ list.setVisibleRowCount(-1);
+ list.addListSelectionListener(new ListSelectionListener() {
+ /**
+ * Enable and disable up and down buttons based on which item in the
+ * list is selected
+ */
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ int index = list.getSelectedIndex();
+ if ((index == -1) || (index == 0 && labelListModel.size() == 0)) {
+ // nothing selected or only one item in the list
+ upButton.setEnabled(false);
+ downButton.setEnabled(false);
+ } else {
+ upButton.setEnabled(index > 0);
+ downButton.setEnabled(index < labelListModel.size() - 1);
+ }
+ }
+ });
+
+ final JScrollPane listScroller = new JScrollPane(list);
+ listScroller.setBorder(new EmptyBorder(5, 5, 5, 5));
+ listScroller.setBackground(listPanel.getBackground());
+ listScroller.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_ALWAYS);
+ listScroller.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
+ // Set the size of scroll pane to make all list items visible
+ FontMetrics fm = listScroller.getFontMetrics(this.getFont());
+ int listScrollerHeight = fm.getHeight() * labelListModel.size() + 75; //+75 just in case
+ listScroller.setPreferredSize(new Dimension(listScroller
+ .getPreferredSize().width, max(listScrollerHeight,
+ listScroller.getPreferredSize().height)));
+ listPanel.add(listScroller, BorderLayout.CENTER);
+
+ JPanel upDownButtonPanel = new JPanel();
+ upDownButtonPanel.setLayout(new BoxLayout(upDownButtonPanel, Y_AXIS));
+ upDownButtonPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+
+ upButton = new JButton(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int index = list.getSelectedIndex();
+ if (index != -1) {
+ // Swap the labels
+ String label = (String) labelListModel.elementAt(index);
+ labelListModel.set(index, labelListModel.get(index - 1));
+ labelListModel.set(index - 1, label);
+ // Swap the dataLinks
+ DataLink dataLink = reorderedDataLinks.get(index);
+ reorderedDataLinks.set(index,
+ reorderedDataLinks.get(index - 1));
+ reorderedDataLinks.set(index - 1, dataLink);
+ // Make the pushed item selected
+ list.setSelectedIndex(index - 1);
+ // Refresh the list
+ listScroller.repaint();
+ listScroller.revalidate();
+ }
+ }
+ });
+ upButton.setIcon(upArrowIcon);
+ upButton.setText("Up");
+ // Place text to the right of icon, vertically centered
+ upButton.setVerticalTextPosition(CENTER);
+ upButton.setHorizontalTextPosition(RIGHT);
+ // Set the horizontal alignment of the icon and text
+ upButton.setHorizontalAlignment(LEFT);
+ upButton.setEnabled(false);
+ upDownButtonPanel.add(upButton);
+
+ downButton = new JButton(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int index = list.getSelectedIndex();
+ if (index != -1) {
+ // Swap the labels
+ String label = (String) labelListModel.elementAt(index);
+ labelListModel.set(index, labelListModel.get(index + 1));
+ labelListModel.set(index + 1, label);
+ // Swap the dataLinks
+ DataLink dataLink = reorderedDataLinks.get(index);
+ reorderedDataLinks.set(index,
+ reorderedDataLinks.get(index + 1));
+ reorderedDataLinks.set(index + 1, dataLink);
+ // Make the pushed item selected
+ list.setSelectedIndex(index + 1);
+ // Refresh the list
+ list.repaint();
+ listScroller.revalidate();
+ }
+ }
+ });
+ downButton.setIcon(downArrowIcon);
+ downButton.setText("Down");
+ // Place text to the right of icon, vertically centered
+ downButton.setVerticalTextPosition(CENTER);
+ downButton.setHorizontalTextPosition(RIGHT);
+ // Set the horizontal alignment of the icon and text
+ downButton.setHorizontalAlignment(LEFT);
+ downButton.setEnabled(false);
+ // set the up button to be of the same size as down button
+ upButton.setPreferredSize(downButton.getPreferredSize());
+ upButton.setMaximumSize(downButton.getPreferredSize());
+ upButton.setMinimumSize(downButton.getPreferredSize());
+ upDownButtonPanel.add(downButton);
+
+ listPanel.add(upDownButtonPanel, EAST);
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+
+ JButton jbOK = new JButton("OK");
+ jbOK.addActionListener(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ new MergeConfigurationAction(dataLinks, reorderedDataLinks,
+ editManager, selectionManager).actionPerformed(e);
+ closeDialog();
+ }
+ });
+
+ JButton jbCancel = new JButton("Cancel");
+ jbCancel.addActionListener(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ closeDialog();
+ }
+ });
+
+ buttonPanel.add(jbOK);
+ buttonPanel.add(jbCancel);
+
+ getContentPane().add(listPanel, BorderLayout.CENTER);
+ getContentPane().add(buttonPanel, SOUTH);
+ pack();
+ }
+
+ /**
+ * Close the dialog.
+ */
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java
new file mode 100644
index 0000000..deb09fb
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.merge;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.LEFT;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Merge;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * Contextual view for a {@link Merge}.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+class MergeContextualView extends ContextualView {
+ @SuppressWarnings("unused")
+ private DataLink dataLink;
+ private List<DataLink> datalinks;
+ @SuppressWarnings("unused")
+ private WorkflowBundle workflow;
+ private JEditorPane editorPane;
+ private final EditManager editManager;
+ private final ColourManager colourManager;
+ private final SelectionManager selectionManager;
+
+ // TODO inject from Spring via factory?
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public MergeContextualView(DataLink dataLink, EditManager editManager,
+ SelectionManager selectionManager, ColourManager colourManager) {
+ this.dataLink = dataLink;
+ this.selectionManager = selectionManager;
+ datalinks = scufl2Tools.datalinksTo(dataLink.getSendsTo());
+ this.editManager = editManager;
+ this.colourManager = colourManager;
+ workflow = selectionManager.getSelectedWorkflowBundle();
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ editorPane = createEditorPane(buildHtml());
+ return panelForHtml(editorPane);
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Merge Position";
+ }
+
+ /**
+ * Update the view with the latest information from the configuration bean.
+ */
+ @Override
+ public void refreshView() {
+ editorPane.setText(buildHtml());
+ repaint();
+ }
+
+ private String buildHtml() {
+ StringBuilder html = new StringBuilder(
+ getHtmlHead(getBackgroundColour()));
+ html.append(buildTableOpeningTag())
+ .append("<tr><td colspan=\"2\"><b>")
+ .append(getViewTitle())
+ .append("</b></td></tr>")
+ .append("<tr><td colspan=\"2\"><b>Ordered incoming links</b></td></tr>");
+
+ int counter = 1;
+ for (DataLink datalink : datalinks)
+ html.append("<tr><td>").append(counter++).append(".</td><td>")
+ .append(datalink).append("</td></tr>");
+
+ return html.append("</table>").append("</body></html>").toString();
+ }
+
+ protected JPanel panelForHtml(JEditorPane editorPane) {
+ final JPanel panel = new JPanel();
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(LEFT));
+
+ JButton configureButton = new JButton(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ MergeConfigurationView mergeConfigurationView = new MergeConfigurationView(
+ datalinks, editManager, selectionManager);
+ mergeConfigurationView.setLocationRelativeTo(panel);
+ mergeConfigurationView.setVisible(true);
+ }
+ });
+ configureButton.setText("Configure");
+ buttonPanel.add(configureButton);
+
+ panel.setLayout(new BorderLayout());
+ panel.add(editorPane, CENTER);
+ panel.add(buttonPanel, SOUTH);
+ return panel;
+ }
+
+ public String getBackgroundColour() {
+ return colourManager.getDefaultPropertyMap().get(
+ "net.sf.taverna.t2.workflowmodel.Merge");
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java
new file mode 100644
index 0000000..712b183
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.merge;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * A factory of contextual views for dataflow's merges.
+ *
+ * @author Alex Nenadic
+ */
+public class MergeContextualViewFactory implements ContextualViewFactory<DataLink> {
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private ColourManager colourManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof DataLink
+ && ((DataLink) object).getMergePosition() != null;
+ }
+
+ @Override
+ public List<ContextualView> getViews(DataLink merge) {
+ return Arrays.asList(new ContextualView[] {
+ new MergeContextualView(merge, editManager, selectionManager, colourManager)});
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java
new file mode 100644
index 0000000..f2c7861
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.outputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+/**
+ * Contextual view for dataflow procerssor's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class OutputPortContextualView extends ContextualView {
+ private static final String NO_DETAILS_AVAILABLE_HTML = "<html><body>"
+ + "<i>No details available.</i>" + "</body><html>";
+ private static final long serialVersionUID = -7743029534480678624L;
+
+ private ActivityOutputPort outputPort;
+ private JPanel outputPortView;
+
+ public OutputPortContextualView(ActivityOutputPort outputport) {
+ this.outputPort = outputport;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return outputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service output port: " + outputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ outputPortView = new JPanel(new FlowLayout(LEFT));
+ outputPortView.setBorder(new EmptyBorder(5,5,5,5));
+ JLabel label = new JLabel(NO_DETAILS_AVAILABLE_HTML);
+ outputPortView.add(label);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java
new file mode 100644
index 0000000..71e2cd4
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.ui.views.contextualviews.outputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+/**
+ * A factory of contextual views for dataflow proessor's (i.e. its associated
+ * activity's) output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class OutputPortContextualViewFactory implements
+ ContextualViewFactory<ActivityOutputPort> {
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof ActivityOutputPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(ActivityOutputPort outputport) {
+ return Arrays.asList(new ContextualView[] {
+ new OutputPortContextualView(outputport)});
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..7744cb3
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1,9 @@
+net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport.OutputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport.InputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport.DataflowOutputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport.DataflowInputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink.DatalinkContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.condition.ConditionContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow.DataflowContextualViewFactory
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..a564691
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml
new file mode 100644
index 0000000..767943d
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ContextualViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <service ref="OutputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="InputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DataflowOutputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DataflowInputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DatalinkContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="ConditionContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="MergeContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="AnnotatedContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DataflowContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <service ref="ContextualViewFactoryRegistry" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+
+ <list id="annotationBeans" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
+ <list id="contextualViewFactories" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml
new file mode 100644
index 0000000..18bbd36
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ContextualViewComponentFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponentFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="contextualViewFactoryRegistry" ref="ContextualViewFactoryRegistry"/>
+ </bean>
+
+ <bean id="OutputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport.OutputPortContextualViewFactory" />
+ <bean id="InputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport.InputPortContextualViewFactory" />
+ <bean id="DataflowOutputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport.DataflowOutputPortContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="DataflowInputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport.DataflowInputPortContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="DatalinkContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink.DatalinkContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="ConditionContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.condition.ConditionContextualViewFactory" />
+ <bean id="MergeContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+ <bean id="AnnotatedContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="annotationBeans" ref ="annotationBeans"/>
+ </bean>
+ <bean id="DataflowContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow.DataflowContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+
+ <bean id="ContextualViewFactoryRegistry" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl.ContextualViewFactoryRegistryImpl">
+ <property name="contextualViewFactories" ref="contextualViewFactories" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties b/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties
new file mode 100644
index 0000000..e3196f6
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties
@@ -0,0 +1,4 @@
+net.sf.taverna.t2.annotation.annotationbeans.FreeTextDescription: Description
+net.sf.taverna.t2.annotation.annotationbeans.Author: Author
+net.sf.taverna.t2.annotation.annotationbeans.DescriptiveTitle: Title
+net.sf.taverna.t2.annotation.annotationbeans.ExampleValue: Example
\ No newline at end of file
diff --git a/taverna-workbench-contextual-views/pom.xml b/taverna-workbench-contextual-views/pom.xml
new file mode 100644
index 0000000..2a07c7e
--- /dev/null
+++ b/taverna-workbench-contextual-views/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>contextual-views</artifactId>
+ <packaging>bundle</packaging>
+ <name>Contextual views</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorActivitiesContextualView.java b/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorActivitiesContextualView.java
new file mode 100644
index 0000000..61f6dd6
--- /dev/null
+++ b/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorActivitiesContextualView.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (C) 2007-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.ui.views.contextualviews.processor;
+
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static net.sf.taverna.t2.workbench.ui.Utils.getParentFrame;
+
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * View of a processor, including it's iteration stack, activities, etc.
+ *
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class ProcessorActivitiesContextualView extends ContextualView {
+ private static final String ABSTRACT_PROCESSOR_MSG = "<strong>Abstract processor</strong><br>"
+ + "<i>No services. This will not execute.</i>";
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+ protected JPanel mainPanel = new JPanel();
+ protected Processor processor;
+ private final ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+ private final SelectionManager selectionManager;
+
+ public ProcessorActivitiesContextualView(Processor processor,
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry,
+ SelectionManager selectionManager) {
+ super();
+ this.processor = processor;
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ this.selectionManager = selectionManager;
+ initialise();
+ initView();
+ }
+
+ @Override
+ public void refreshView() {
+ initialise();
+ this.revalidate();
+ }
+
+ private synchronized void initialise() {
+ mainPanel.removeAll();
+ mainPanel.setLayout(new GridBagLayout());
+
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.weightx = 0.1;
+ constraints.weighty = 0;
+
+ List<ProcessorBinding> processorBindings = scufl2Tools
+ .processorBindingsForProcessor(processor,
+ selectionManager.getSelectedProfile());
+ if (processorBindings.isEmpty()) {
+ JLabel noActivitiesLabel = new JLabel("<html>"
+ + ABSTRACT_PROCESSOR_MSG + "</html>");
+ constraints.fill = NONE;
+ constraints.anchor = LINE_START;
+ mainPanel.add(noActivitiesLabel, constraints);
+ } else
+ for (ProcessorBinding processorBinding : processorBindings)
+ addViewForBinding(constraints, processorBinding);
+ mainPanel.revalidate();
+ mainPanel.repaint();
+ this.revalidate();
+ this.repaint();
+ }
+
+ private void addViewForBinding(GridBagConstraints constraints,
+ ProcessorBinding processorBinding) {
+ Activity activity = processorBinding.getBoundActivity();
+ List<ContextualViewFactory<? super Activity>> viewFactoryForBeanType = contextualViewFactoryRegistry
+ .getViewFactoriesForObject(activity);
+ if (viewFactoryForBeanType.isEmpty())
+ return;
+ // TODO why a list when we only use the first, twice, and assume non-empty too?
+ ContextualView view = (ContextualView) viewFactoryForBeanType.get(0)
+ .getViews(activity).get(0);
+
+ constraints.anchor = CENTER;
+ constraints.fill = HORIZONTAL;
+ mainPanel.add(view, constraints);
+ Frame frame = getParentFrame(this);
+ Action configureAction = view.getConfigureAction(frame);
+ if (configureAction != null) {
+ constraints.gridy++;
+ constraints.fill = NONE;
+ constraints.anchor = LINE_START;
+ JButton configureButton = new JButton(configureAction);
+ if (configureButton.getText() == null
+ || configureButton.getText().isEmpty())
+ configureButton.setText("Configure");
+ mainPanel.add(configureButton, constraints);
+ }
+ constraints.gridy++;
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ return mainPanel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service";
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorActivitiesContextualViewFactory.java b/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorActivitiesContextualViewFactory.java
new file mode 100644
index 0000000..8e28d4a
--- /dev/null
+++ b/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorActivitiesContextualViewFactory.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (C) 2007-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.ui.views.contextualviews.processor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * SPI factory for creating a {@link ProcessorContextualView}.
+ *
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ */
+public class ProcessorActivitiesContextualViewFactory implements
+ ContextualViewFactory<Processor> {
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+ private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+ private SelectionManager selectionManager;
+
+ @Override
+ public boolean canHandle(Object selection) {
+ return selection instanceof Processor;
+ }
+
+ @Override
+ public List<ContextualView> getViews(Processor selection) {
+ List<ContextualView> result = new ArrayList<>();
+ List<ProcessorBinding> processorBindings = scufl2Tools
+ .processorBindingsForProcessor(selection,
+ selectionManager.getSelectedProfile());
+ for (ProcessorBinding processorBinding : processorBindings) {
+ Activity activity = processorBinding.getBoundActivity();
+ for (ContextualViewFactory<? super Activity> cvf : contextualViewFactoryRegistry
+ .getViewFactoriesForObject(activity))
+ result.addAll(cvf.getViews(activity));
+ }
+ return result;
+ }
+
+ public void setContextualViewFactoryRegistry(
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorPredictedBehaviorContextualViewFactory.java b/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorPredictedBehaviorContextualViewFactory.java
new file mode 100644
index 0000000..0ef4f79
--- /dev/null
+++ b/taverna-workbench-contextual-views/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/processor/ProcessorPredictedBehaviorContextualViewFactory.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (C) 2007-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.ui.views.contextualviews.processor;
+
+import static java.util.Collections.singletonList;
+import static javax.swing.BoxLayout.Y_AXIS;
+
+import java.util.List;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+
+/**
+ * How to get a panel describing what Taverna predicts the depth of the ports of
+ * a processor to be.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class ProcessorPredictedBehaviorContextualViewFactory implements
+ ContextualViewFactory<Processor> {
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ @Override
+ public boolean canHandle(Object selection) {
+ return selection instanceof Processor;
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public List<ContextualView> getViews(final Processor selection) {
+ class ProcessorPredictedBehaviorContextualView extends ContextualView {
+ protected JPanel mainPanel = new JPanel();
+ protected Processor processor;
+
+ public ProcessorPredictedBehaviorContextualView() {
+ super();
+ refreshView();
+ initView();
+ }
+
+ @Override
+ public void refreshView() {
+ initialise();
+ this.revalidate();
+ }
+
+ private synchronized void initialise() {
+ mainPanel.removeAll();
+ mainPanel.setLayout(new BoxLayout(mainPanel, Y_AXIS));
+
+ StringBuilder html = new StringBuilder("<html><head>");
+ addStyle(html);
+ html.append("</head><body>");
+
+ NamedSet<InputProcessorPort> inputs = processor.getInputPorts();
+ if (!inputs.isEmpty()) {
+ html.append("<table border=1><tr><th>Input Port Name</th>")
+ .append("<th>Size of data</th>").append("</tr>");
+ for (InputProcessorPort ip : inputs) {
+ html.append("<tr><td>").append(ip.getName())
+ .append("</td><td>");
+ List<DataLink> incomingDataLinks = scufl2Tools
+ .datalinksTo(ip);
+ if (incomingDataLinks.isEmpty())
+ html.append("No value");
+ else {
+ int depth = getDepth(incomingDataLinks.get(0));
+ if (depth == -1)
+ html.append("Invalid");
+ else if (depth == 0)
+ html.append("Single value");
+ else
+ html.append("List of depth ").append(depth);
+ }
+ html.append("</td></tr>");
+ }
+ html.append("</table>");
+ }
+ NamedSet<OutputProcessorPort> outputs = processor
+ .getOutputPorts();
+ if (!outputs.isEmpty()) {
+ html.append("<table border=1><tr><th>Output Port Name</th>")
+ .append("<th>Size of data</th>").append("</tr>");
+ for (OutputProcessorPort op : outputs) {
+ html.append("<tr><td>").append(op.getName())
+ .append("</td><td>");
+ List<DataLink> outgoingDataLinks = scufl2Tools
+ .datalinksFrom(op);
+ if (outgoingDataLinks.isEmpty())
+ html.append("No value");
+ else {
+ int depth = getDepth(outgoingDataLinks.get(0));
+ if (depth == -1)
+ html.append("Invalid/unpredicted");
+ else if (depth == 0)
+ html.append("Single value");
+ else
+ html.append("List of depth ").append(depth);
+ }
+ html.append("</td></tr>");
+ }
+ html.append("</table>");
+ }
+ if (inputs.isEmpty() && outputs.isEmpty())
+ html.append("<p>No port behavior predicted</p>");
+ html.append("</body></html>");
+ JEditorPane editorPane = new JEditorPane("text/html",
+ html.toString());
+ editorPane.setEditable(false);
+ mainPanel.add(editorPane);
+
+ mainPanel.revalidate();
+ mainPanel.repaint();
+ this.revalidate();
+ this.repaint();
+ }
+
+ protected void addStyle(StringBuilder html) {
+ html.append("<style type='text/css'>")
+ .append("table {align:center; border:solid black 1px;")
+ .append("width:100%; height:100%; overflow:auto;}")
+ .append("</style>");
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ return mainPanel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Predicted behavior";
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 300;
+ }
+ }
+
+ return singletonList((ContextualView) new ProcessorPredictedBehaviorContextualView());
+ }
+
+ private int getDepth(DataLink datalink) {
+ // TODO calculate actual depth
+ return -1;
+ }
+}
diff --git a/taverna-workbench-contextual-views/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-contextual-views/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..3aa7ee0
--- /dev/null
+++ b/taverna-workbench-contextual-views/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1,4 @@
+#net.sf.taverna.t2.workbench.ui.views.contextualviews.processor.ProcessorContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.processor.ProcessorDispatchStackContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.processor.ProcessorPredictedBehaviorContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.processor.ProcessorActivitiesContextualViewFactory
\ No newline at end of file
diff --git a/taverna-workbench-contextual-views/src/main/resources/META-INF/spring/contextual-views-context-osgi.xml b/taverna-workbench-contextual-views/src/main/resources/META-INF/spring/contextual-views-context-osgi.xml
new file mode 100644
index 0000000..932b541
--- /dev/null
+++ b/taverna-workbench-contextual-views/src/main/resources/META-INF/spring/contextual-views-context-osgi.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ProcessorPredictedBehaviorContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="ProcessorActivitiesContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="contextualViewFactoryRegistry" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-contextual-views/src/main/resources/META-INF/spring/contextual-views-context.xml b/taverna-workbench-contextual-views/src/main/resources/META-INF/spring/contextual-views-context.xml
new file mode 100644
index 0000000..7f53cb8
--- /dev/null
+++ b/taverna-workbench-contextual-views/src/main/resources/META-INF/spring/contextual-views-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ProcessorPredictedBehaviorContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.processor.ProcessorPredictedBehaviorContextualViewFactory" />
+ <bean id="ProcessorActivitiesContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.processor.ProcessorActivitiesContextualViewFactory">
+ <property name="contextualViewFactoryRegistry" ref="contextualViewFactoryRegistry" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-credential-manager-ui/pom.xml b/taverna-workbench-credential-manager-ui/pom.xml
new file mode 100644
index 0000000..601c546
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>credential-manager-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Credential Manager UI</name>
+ <description>
+ Integrates the Credential Manager into the Workbench
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.security</groupId>
+ <artifactId>credential-manager</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <!-- <dependency>
+ <groupId>BrowserLauncher2</groupId>
+ <artifactId>BrowserLauncher2</artifactId>
+ <version>1.3</version>
+ </dependency> -->
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons.io.version}</version>
+ </dependency>
+ </dependencies>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository</url>
+ </repository>
+ </repositories>
+</project>
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CMStrings.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CMStrings.java
new file mode 100644
index 0000000..3f6664c
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CMStrings.java
@@ -0,0 +1,7 @@
+package net.sf.taverna.t2.workbench.ui.credentialmanager;
+
+interface CMStrings {
+ String ALERT_TITLE = "Credential Manager Alert";
+ String ERROR_TITLE = "Credential Manager Error";
+ String WARN_TITLE = "Credential Manager Warning";
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ChangeMasterPasswordDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ChangeMasterPasswordDialog.java
new file mode 100644
index 0000000..26086bc
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ChangeMasterPasswordDialog.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Font.PLAIN;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.WARN_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Dialog used by users to change their master password for the Credential
+ * Manager.
+ */
+@SuppressWarnings("serial")
+public class ChangeMasterPasswordDialog extends NonBlockedHelpEnabledDialog {
+ /** Old password entry field */
+ private JPasswordField oldPasswordField;
+ /** New password entry field */
+ private JPasswordField newPasswordField;
+ /** New password confirmation entry field */
+ private JPasswordField newPasswordConfirmField;
+ /** The entered new password */
+ private String password = null;
+ /** Instructions to the users as to what to do in the dialog */
+ private String instructions;
+ private final CredentialManager credentialManager;
+
+ public ChangeMasterPasswordDialog(JFrame parent, String title,
+ boolean modal, String instructions,
+ CredentialManager credentialManager) {
+ super(parent, title, modal, null);
+ this.instructions = instructions;
+ this.credentialManager = credentialManager;
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel instructionsLabel = new JLabel(instructions);
+ instructionsLabel.setFont(new Font(null, PLAIN, 11));
+
+ JPanel instructionsPanel = new JPanel();
+ instructionsPanel.setLayout(new BoxLayout(instructionsPanel, Y_AXIS));
+ instructionsPanel.add(instructionsLabel);
+ instructionsPanel.setBorder(new EmptyBorder(10, 5, 10, 0));
+
+ JLabel oldPasswordLabel = new JLabel("Old master password");
+ oldPasswordLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ JLabel newPasswordLabel = new JLabel("New master password");
+ newPasswordLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ JLabel newPasswordConfirmLabel = new JLabel(
+ "Confirm new master password");
+ newPasswordConfirmLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ oldPasswordField = new JPasswordField(15);
+ newPasswordField = new JPasswordField(15);
+ newPasswordConfirmField = new JPasswordField(15);
+
+ JPanel jpPassword = new JPanel(new GridLayout(0, 2, 5, 5));
+ jpPassword.add(oldPasswordLabel);
+ jpPassword.add(oldPasswordField);
+ jpPassword.add(newPasswordLabel);
+ jpPassword.add(newPasswordField);
+ jpPassword.add(newPasswordConfirmLabel);
+ jpPassword.add(newPasswordConfirmField);
+
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10, 10),
+ new EtchedBorder()));
+ mainPanel.add(instructionsPanel, NORTH);
+ mainPanel.add(jpPassword, CENTER);
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(okButton);
+ buttonsPanel.add(cancelButton);
+
+ getContentPane().add(mainPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ /**
+ * Get the password set in the dialog or null if none was set.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Check that the user has provided the correct old master password, that
+ * the user has supplied the new password and confirmed it and that it is
+ * not empty. If all is OK, stores the new password in the password field.
+ *
+ */
+ private boolean checkPassword() {
+ String oldPassword = new String(oldPasswordField.getPassword());
+
+ if (oldPassword.length() == 0) {
+ // old password must not be empty
+ showMessageDialog(this,
+ "You must provide your current master password",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+
+ try {
+ if (!credentialManager.confirmMasterPassword(oldPassword)) {
+ showMessageDialog(this,
+ "You have provided an incorrect master password",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+ } catch (Exception e) {
+ showMessageDialog(
+ this,
+ "Credential Manager could not verify your current master password",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+
+ String newPassword = new String(newPasswordField.getPassword());
+ String newPasswordConfirm = new String(
+ newPasswordConfirmField.getPassword());
+
+ if (!newPassword.equals(newPasswordConfirm)) {
+ // passwords do not match
+ showMessageDialog(this, "Passwords do not match", WARN_TITLE,
+ WARNING_MESSAGE);
+ return false;
+ }
+
+ if (newPassword.isEmpty()) {
+ // passwords match but are empty
+ showMessageDialog(this, "The new master password cannot be empty",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+
+ // passwords match and not empty
+ password = newPassword;
+ return true;
+ }
+
+ private void okPressed() {
+ if (checkPassword())
+ closeDialog();
+ }
+
+ private void cancelPressed() {
+ /*
+ * Set the password to null as it might have changed in the meantime if
+ * user entered something then cancelled.
+ */
+ password = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ConfirmTrustedCertificateDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ConfirmTrustedCertificateDialog.java
new file mode 100644
index 0000000..6558562
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ConfirmTrustedCertificateDialog.java
@@ -0,0 +1,520 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Color.WHITE;
+import static java.awt.Font.BOLD;
+import static java.awt.Font.PLAIN;
+import static java.awt.GridBagConstraints.LINE_START;
+import static javax.security.auth.x500.X500Principal.RFC2253;
+
+import java.awt.BorderLayout;
+import java.awt.Dialog;
+import java.awt.FlowLayout;
+import java.awt.Font;
+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.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.math.BigInteger;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.security.credentialmanager.ParsedDistinguishedName;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Displays the details of a X.509 certificate and asks user if they want to
+ * trust it. This is normally invoked by the Taverna's TrustManager when trying
+ * to confirm the trust in the remote server during SSL handshake.
+ */
+@SuppressWarnings("serial")
+public class ConfirmTrustedCertificateDialog extends NonBlockedHelpEnabledDialog {
+ private static Logger logger = Logger.getLogger(ConfirmTrustedCertificateDialog.class);
+
+ /** The certificate to display */
+ private X509Certificate cert;
+ /** User's decision as whether to trust this service's certificate or not */
+ private boolean shouldTrust;
+ /**
+ * Should the decision also be saved in Credential Manager? Actually - it is
+ * always saved now as it was really hard to implement trusting for one
+ * connection only - so we can either "trust" or "not" trust but not
+ * "trust once".
+ */
+ private boolean shouldSave = false;
+ private final DistinguishedNameParser dnParser;
+
+ public ConfirmTrustedCertificateDialog(Frame parent, String title,
+ boolean modal, X509Certificate crt, DistinguishedNameParser dnParser) {
+ super(parent, title, modal);
+ this.cert = crt;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ public ConfirmTrustedCertificateDialog(Dialog parent, String title,
+ boolean modal, X509Certificate crt, DistinguishedNameParser dnParser)
+ throws CMException {
+ super(parent, title, modal);
+ this.cert = crt;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ private void initComponents(){
+ // title panel
+ JPanel titlePanel = new JPanel(new BorderLayout());
+ titlePanel.setBackground(WHITE);
+ JLabel titleLabel = new JLabel("View service's certificate");
+ titleLabel.setFont(titleLabel.getFont().deriveFont(BOLD, 13.5f));
+ titleLabel.setBorder(new EmptyBorder(10, 10, 0, 10));
+
+ DialogTextArea titleMessage = new DialogTextArea();
+ titleMessage.setMargin(new Insets(5, 20, 10, 10));
+ titleMessage.setFont(titleMessage.getFont().deriveFont(11f));
+ titleMessage.setEditable(false);
+ titleMessage.setFocusable(false);
+ titlePanel.setBorder( new EmptyBorder(10, 10, 0, 10));
+ titlePanel.add(titleLabel, NORTH);
+ titlePanel.add(titleMessage, CENTER);
+
+ // Certificate details:
+
+ ParsedDistinguishedName subjectDN = dnParser.parseDN(cert
+ .getSubjectX500Principal().getName(RFC2253));
+ ParsedDistinguishedName issuerDN = dnParser.parseDN(cert
+ .getIssuerX500Principal().getName(RFC2253));
+ JPanel certificatePanel = createCertificateDetailsPanel(subjectDN, issuerDN);
+ titleMessage.setText("The service host " + subjectDN.getCN() + " requires HTTPS connection and has identified itself with the certificate below.\n" +
+ "Do you want to trust this service? (Refusing to trust means you will not be able to invoke services on this host from a workflow.)");
+
+ // OK button
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+
+// final JButton trustButton = new JButton("Trust once");
+// trustButton.addActionListener(new ActionListener() {
+// public void actionPerformed(ActionEvent evt) {
+// trustPressed();
+// }
+// });
+
+ //final JButton trustAlwaysButton = new JButton("Trust always");
+ final JButton trustAlwaysButton = new JButton("Trust");
+ trustAlwaysButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ trustAlwaysPressed();
+ }
+ });
+
+ final JButton dontTrustButton = new JButton("Do not trust");
+ dontTrustButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ dontTrustPressed();
+ }
+ });
+
+ //jpButtons.add(trustButton);
+ buttonsPanel.add(trustAlwaysButton);
+ buttonsPanel.add(dontTrustButton);
+
+ getContentPane().add(titlePanel, NORTH);
+ getContentPane().add(certificatePanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ setResizable(false);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ getRootPane().setDefaultButton(trustAlwaysButton);
+ pack();
+ }
+
+ private JPanel createCertificateDetailsPanel(ParsedDistinguishedName subjectDN, ParsedDistinguishedName issuerDN) {
+ /*
+ * Grid Bag Constraints templates for labels (column 1) and values
+ * (column 2) of certificate details
+ */
+ GridBagConstraints gbc_labels = new GridBagConstraints();
+ gbc_labels.gridx = 0;
+ gbc_labels.ipadx = 20;
+ gbc_labels.gridwidth = 1;
+ gbc_labels.gridheight = 1;
+ gbc_labels.insets = new Insets(2, 15, 2, 2);
+ gbc_labels.anchor = LINE_START;
+
+ GridBagConstraints gbc_values = new GridBagConstraints();
+ gbc_values.gridx = 1;
+ gbc_values.gridwidth = 1;
+ gbc_values.gridheight = 1;
+ gbc_values.insets = new Insets(2, 5, 2, 2);
+ gbc_values.anchor = LINE_START;
+
+ /*
+ * Netscape Certificate Type non-critical extension (if any) defines the
+ * intended uses of the certificate - to make it look like Firefox's
+ * view certificate dialog
+ *
+ * From openssl's documentation: "The [above] extension is non standard,
+ * Netscape specific and largely obsolete. Their use in new applications
+ * is discouraged."
+ *
+ * TODO replace with "basicConstraints, keyUsage and extended key usage
+ * extensions which are now used instead."
+ */
+// byte[] intendedUses = cert.getExtensionValue("2.16.840.1.113730.1.1"); // Netscape Certificate Type OID
+// JLabel intendedUsesLabel = null;
+// JTextField intendedUsesTextField = null;
+// JPanel intendedUsesPanel = null;
+// GridBagConstraints gbc_intendedUsesLabel = null;
+// if (intendedUses != null) {
+// intendedUsesLabel = new JLabel(
+// "This certificate has been approved for the following uses:");
+// intendedUsesLabel.setFont(new Font(null, Font.BOLD, 11));
+// intendedUsesLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+//
+// intendedUsesTextField = new JTextField(45);
+// intendedUsesTextField.setText(CMUtils.getIntendedCertificateUses(intendedUses));
+// intendedUsesTextField.setEditable(false);
+// intendedUsesTextField.setFont(new Font(null, Font.PLAIN, 11));
+//
+// intendedUsesPanel = new JPanel(new BorderLayout());
+// intendedUsesPanel.add(intendedUsesLabel, BorderLayout.NORTH);
+// intendedUsesPanel.add(intendedUsesTextField, BorderLayout.CENTER);
+// JSeparator separator = new JSeparator(JSeparator.HORIZONTAL);
+// intendedUsesPanel.add(separator, BorderLayout.SOUTH);
+//
+// gbc_intendedUsesLabel = (GridBagConstraints) gbc_labels.clone();
+// gbc_intendedUsesLabel.gridy = 0;
+// gbc_intendedUsesLabel.gridwidth = 2; // takes two columns
+// gbc_intendedUsesLabel.insets = new Insets(5, 5, 5, 5);// has slightly bigger insets
+// }
+
+ // Issued To
+ JLabel issuedToLabel = new JLabel("Issued To");
+ issuedToLabel.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_issuedTo = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_issuedTo.gridy = 1;
+ gbc_issuedTo.gridwidth = 2; // takes two columns
+ gbc_issuedTo.insets = new Insets(5, 5, 5, 5);// has slightly bigger insets
+ // Subject's Distinguished Name (DN)
+ // Extract the CN, O, OU and EMAILADDRESS fields
+ String subjectCN = subjectDN.getCN();
+ String subjectOrg = subjectDN.getO();
+ String subjectOU = subjectDN.getOU();
+ // String sEMAILADDRESS = CMUtils.getEmilAddress();
+ // Subject's Common Name (CN)
+ JLabel subjectCNLabel = new JLabel("Common Name (CN)");
+ subjectCNLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_subjectCNLabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_subjectCNLabel.gridy = 2;
+ JLabel subjectCNValue = new JLabel(subjectCN);
+ subjectCNValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_subjectCNValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_subjectCNValue.gridy = 2;
+ // Subject's Organisation (O)
+ JLabel subjectOrgLabel = new JLabel("Organisation (O)");
+ subjectOrgLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_subjectOrgLabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_subjectOrgLabel.gridy = 3;
+ JLabel subjectOrgValue = new JLabel(subjectOrg);
+ subjectOrgValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_subjectOrgValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_subjectOrgValue.gridy = 3;
+ // Subject's Organisation Unit (OU)
+ JLabel subjectOULabel = new JLabel("Organisation Unit (OU)");
+ subjectOULabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_subjectOULabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_subjectOULabel.gridy = 4;
+ JLabel subjectOUValue = new JLabel(subjectOU);
+ subjectOUValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_subjectOUValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_subjectOUValue.gridy = 4;
+ // E-mail Address
+ // JLabel jlEmail = new JLabel("E-mail Address");
+ // jlEmail.setFont(new Font(null, Font.PLAIN, 11));
+ // GridBagConstraints gbc_jlEmail = (GridBagConstraints)
+ // gbcLabel.clone();
+ // gbc_jlEmail.gridy = 5;
+ // JLabel jlEmailValue = new JLabel(sEMAILADDRESS);
+ // jlEmailValue.setFont(new Font(null, Font.PLAIN, 11));
+ // GridBagConstraints gbc_jlEmailValue = (GridBagConstraints)
+ // gbcValue.clone();
+ // gbc_jlEmailValue.gridy = 5;
+ // Serial Number
+ JLabel snLabel = new JLabel("Serial Number");
+ snLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_snLabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_snLabel.gridy = 6;
+ JLabel snValue = new JLabel();
+ // Get the hexadecimal serial number
+ StringBuilder strBuff = new StringBuilder(new BigInteger(1, cert
+ .getSerialNumber().toByteArray()).toString(16).toUpperCase());
+ // Place colons at every two hexadecimal characters
+ if (strBuff.length() > 2)
+ for (int iCnt = 2; iCnt < strBuff.length(); iCnt += 3)
+ strBuff.insert(iCnt, ':');
+ snValue.setText(strBuff.toString());
+ snValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_snValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_snValue.gridy = 6;
+ // Certificate version number
+ JLabel versionLabel = new JLabel("Version");
+ versionLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_versionLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_versionLabel.gridy = 7;
+ JLabel versionValue = new JLabel(Integer.toString(cert.getVersion()));
+ versionValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_versionValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_versionValue.gridy = 7;
+
+ // Issued By
+ JLabel issuedByLabel = new JLabel("Issued By");
+ issuedByLabel.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_issuedByLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_issuedByLabel.gridy = 8;
+ gbc_issuedByLabel.gridwidth = 2; // takes two columns
+ gbc_issuedByLabel.insets = new Insets(5, 5, 5, 5);// has slightly bigger insets
+ // Issuer's Distinguished Name (DN)
+ // Extract the CN, O and OU fields for the issuer
+ String issuerCN = issuerDN.getCN();
+ String issuerOrg = issuerDN.getO();
+ String issuerOU = issuerDN.getOU();
+ // Issuer's Common Name (CN)
+ JLabel issuerCNLabel = new JLabel("Common Name (CN)");
+ issuerCNLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuerCNLabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_issuerCNLabel.gridy = 9;
+ JLabel issuerCNValue = new JLabel(issuerCN);
+ issuerCNValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuerCNValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_issuerCNValue.gridy = 9;
+ // Issuer's Organisation (O)
+ JLabel issuerOrgLabel = new JLabel("Organisation (O)");
+ issuerOrgLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuerOrgLabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_issuerOrgLabel.gridy = 10;
+ JLabel issuerOrgValue = new JLabel(issuerOrg);
+ issuerOrgValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuerOrgValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_issuerOrgValue.gridy = 10;
+ // Issuer's Organisation Unit (OU)
+ JLabel issuerOULabel = new JLabel("Organisation Unit (OU)");
+ issuerOULabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuerOULabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_issuerOULabel.gridy = 11;
+ JLabel issuerOUValue = new JLabel(issuerOU);
+ issuerOUValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuerOUValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_issuerOUValue.gridy = 11;
+
+ // Validity
+ JLabel validityLabel = new JLabel("Validity");
+ validityLabel.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_validityLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_validityLabel.gridy = 12;
+ gbc_validityLabel.gridwidth = 2; // takes two columns
+ gbc_validityLabel.insets = new Insets(5, 5, 5, 5);// has slightly bigger insets
+ // Issued On
+ JLabel issuedOnLabel = new JLabel("Issued On");
+ issuedOnLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuedOnLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_issuedOnLabel.gridy = 13;
+ JLabel issuedOnValue = new JLabel(cert.getNotBefore().toString());
+ issuedOnValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_issuedOnValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_issuedOnValue.gridy = 13;
+ // Expires On
+ JLabel expiresOnLabel = new JLabel("Expires On");
+ expiresOnLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_expiresOnLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_expiresOnLabel.gridy = 14;
+ JLabel expiresOnValue = new JLabel(cert.getNotAfter().toString());
+ expiresOnValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_expiresOnValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_expiresOnValue.gridy = 14;
+
+ // Fingerprints
+ byte[] binaryCertificateEncoding = new byte[0];
+ try {
+ // each certificate has one binary encoding; for X.509 certs it is DER
+ binaryCertificateEncoding = cert.getEncoded();
+ } catch (CertificateEncodingException ex) {
+ logger.error("Could not get the encoded form of the certificate.", ex);
+ }
+ JLabel fingerprintsLabel = new JLabel("Fingerprints");
+ fingerprintsLabel.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_fingerprintsLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_fingerprintsLabel.gridy = 15;
+ gbc_fingerprintsLabel.gridwidth = 2; // takes two columns
+ gbc_fingerprintsLabel.insets = new Insets(5, 5, 5, 5);// has slightly bigger insets
+ // SHA-1 Fingerprint
+ JLabel sha1FingerprintLabel = new JLabel("SHA1 Fingerprint");
+ sha1FingerprintLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_sha1FingerprintLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_sha1FingerprintLabel.gridy = 16;
+ JLabel sha1FingerprintValue = new JLabel(
+ dnParser.getMessageDigestAsFormattedString(
+ binaryCertificateEncoding, "SHA1"));
+ sha1FingerprintValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_sha1FingerprintValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_sha1FingerprintValue.gridy = 16;
+ // MD5 Fingerprint
+ JLabel md5FingerprintLabel = new JLabel("MD5 Fingerprint");
+ md5FingerprintLabel.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_md5FingerprinLabel = (GridBagConstraints) gbc_labels
+ .clone();
+ gbc_md5FingerprinLabel.gridy = 17;
+ JLabel md5FingerprintValue = new JLabel(
+ dnParser.getMessageDigestAsFormattedString(
+ binaryCertificateEncoding, "MD5"));
+ md5FingerprintValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_md5FingerprintValue = (GridBagConstraints) gbc_values
+ .clone();
+ gbc_md5FingerprintValue.gridy = 17;
+
+ /*
+ * Empty label to add a bit space at the bottom of the panel to make it
+ * look like Firefox's view certificate dialog
+ */
+ JLabel emptyLabel = new JLabel("");
+ GridBagConstraints gbc_emptyLabel = (GridBagConstraints) gbc_labels.clone();
+ gbc_emptyLabel.gridy = 18;
+ gbc_emptyLabel.gridwidth = 2; // takes two columns
+ gbc_emptyLabel.ipady = 40;
+
+ JPanel certificatePanel = new JPanel(new GridBagLayout());
+ certificatePanel.setBorder(new CompoundBorder(new EmptyBorder(15, 15, 15,
+ 15), new EtchedBorder()));
+
+// if (intendedUses != null)
+// certificatePanel.add(intendedUsesPanel, gbc_intendedUsesLabel);
+ certificatePanel.add(issuedToLabel, gbc_issuedTo); // Issued To
+ certificatePanel.add(subjectCNLabel, gbc_subjectCNLabel);
+ certificatePanel.add(subjectCNValue, gbc_subjectCNValue);
+ certificatePanel.add(subjectOrgLabel, gbc_subjectOrgLabel);
+ certificatePanel.add(subjectOrgValue, gbc_subjectOrgValue);
+ certificatePanel.add(subjectOULabel, gbc_subjectOULabel);
+ certificatePanel.add(subjectOUValue, gbc_subjectOUValue);
+ // jpCertificate.add(jlEmail, gbc_jlEmail);
+ // jpCertificate.add(jlEmailValue, gbc_jlEmailValue);
+ certificatePanel.add(snLabel, gbc_snLabel);
+ certificatePanel.add(snValue, gbc_snValue);
+ certificatePanel.add(versionLabel, gbc_versionLabel);
+ certificatePanel.add(versionValue, gbc_versionValue);
+ certificatePanel.add(issuedByLabel, gbc_issuedByLabel); // Issued By
+ certificatePanel.add(issuerCNLabel, gbc_issuerCNLabel);
+ certificatePanel.add(issuerCNValue, gbc_issuerCNValue);
+ certificatePanel.add(issuerOrgLabel, gbc_issuerOrgLabel);
+ certificatePanel.add(issuerOrgValue, gbc_issuerOrgValue);
+ certificatePanel.add(issuerOULabel, gbc_issuerOULabel);
+ certificatePanel.add(issuerOUValue, gbc_issuerOUValue);
+ certificatePanel.add(validityLabel, gbc_validityLabel); // Validity
+ certificatePanel.add(issuedOnLabel, gbc_issuedOnLabel);
+ certificatePanel.add(issuedOnValue, gbc_issuedOnValue);
+ certificatePanel.add(expiresOnLabel, gbc_expiresOnLabel);
+ certificatePanel.add(expiresOnValue, gbc_expiresOnValue);
+ certificatePanel.add(fingerprintsLabel, gbc_fingerprintsLabel); // Fingerprints
+ certificatePanel.add(sha1FingerprintLabel, gbc_sha1FingerprintLabel);
+ certificatePanel.add(sha1FingerprintValue, gbc_sha1FingerprintValue);
+ certificatePanel.add(md5FingerprintLabel, gbc_md5FingerprinLabel);
+ certificatePanel.add(md5FingerprintValue, gbc_md5FingerprintValue);
+ // Empty label to get some vertical space on the frame
+ certificatePanel.add(emptyLabel, gbc_emptyLabel);
+ return certificatePanel;
+ }
+
+// private void trustPressed() {
+// shouldTrust = true;
+// shouldSave = false;
+// closeDialog();
+// }
+
+ private void trustAlwaysPressed() {
+ shouldTrust = true;
+ shouldSave = true;
+ closeDialog();
+ }
+
+ private void dontTrustPressed() {
+ shouldTrust = false;
+ shouldSave = false;
+ closeDialog();
+ }
+
+ public void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+ public boolean shouldTrust() {
+ return shouldTrust;
+ }
+
+ public boolean shouldSave() {
+ return shouldSave;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ConfirmTrustedCertificateUI.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ConfirmTrustedCertificateUI.java
new file mode 100644
index 0000000..0845543
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ConfirmTrustedCertificateUI.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.Frame;
+import java.security.cert.X509Certificate;
+
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.security.credentialmanager.TrustConfirmationProvider;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Stian Soiland-Reyes
+ */
+public class ConfirmTrustedCertificateUI implements TrustConfirmationProvider {
+ private static Logger logger = Logger
+ .getLogger(ConfirmTrustedCertificateUI.class);
+
+ private DistinguishedNameParser dnParser;
+
+ @Override
+ public Boolean shouldTrustCertificate(X509Certificate[] chain) {
+ boolean trustConfirm = false;
+ logger.info("Asking the user if they want to trust a certificate.");
+ // Ask user if they want to trust this service
+ ConfirmTrustedCertificateDialog confirmCertTrustDialog = new ConfirmTrustedCertificateDialog(
+ (Frame) null, "Untrusted HTTPS connection", true,
+ (X509Certificate) chain[0], dnParser);
+ confirmCertTrustDialog.setLocationRelativeTo(null);
+ confirmCertTrustDialog.setVisible(true);
+ trustConfirm = confirmCertTrustDialog.shouldTrust();
+// trustConfirm.setShouldSave(confirmCertTrustDialog.shouldSave());
+ if (!confirmCertTrustDialog.shouldTrust())
+ showMessageDialog(
+ null,
+ "As you refused to trust this host, you will not be able to use its services from a workflow.",
+ "Untrusted HTTPS connection", INFORMATION_MESSAGE);
+
+ return trustConfirm;
+ }
+
+ /**
+ * @param dnParser
+ * the dnParser to set
+ */
+ public void setDistinguishedNameParser(DistinguishedNameParser dnParser) {
+ this.dnParser = dnParser;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CredentialManagerUI.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CredentialManagerUI.java
new file mode 100644
index 0000000..41d7a15
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CredentialManagerUI.java
@@ -0,0 +1,1512 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.PAGE_END;
+import static java.awt.Dialog.ModalExclusionType.APPLICATION_EXCLUDE;
+import static java.awt.Toolkit.getDefaultToolkit;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.KEYSTORE;
+import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.TRUSTSTORE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ALERT_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ERROR_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableColumn;
+
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+
+import org.apache.log4j.Logger;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.openssl.PEMWriter;
+
+/**
+ * Provides a UI for the Credential Manager for users to manage their
+ * credentials saved by the Credential Manager in Taverna's Keystore and
+ * Trustore. Credentials include username and passwords pairs, key pairs, proxy
+ * key pairs and trusted certificates of CA's and s. Credentials are stored in
+ * two Bouncy Castle "UBER"-type keystores: the Keystore (containing passwords
+ * and (normal and proxy) key pairs) and the Truststore (containing trusted
+ * certificates).
+ *
+ * Inspired by the Portlecle tool (http://portecle.sourceforge.net/)
+ * and Firefox's Certificate Manager.
+ *
+ * @author Alex Nenadic
+ */
+
+@SuppressWarnings("serial")
+public class CredentialManagerUI extends JFrame {
+ private static Logger logger = Logger.getLogger(CredentialManagerUI.class);
+ /** Default tabbed pane width */
+ private static final int DEFAULT_FRAME_WIDTH = 650;
+ /** Default tabbed pane height */
+ private static final int DEFAULT_FRAME_HEIGHT = 400;
+ /** Credential Manager icon (when frame is minimised)*/
+ private static final Image credManagerIconImage = getDefaultToolkit()
+ .createImage(
+ CredentialManagerUI.class
+ .getResource("/images/cred_manager_transparent.png"));
+
+ /**
+ * Credential Manager to manage all operations on the Keystore and
+ * Truststore
+ */
+ public final CredentialManager credManager;
+ private final DistinguishedNameParser dnParser;
+
+ ////////////// Tabs //////////////
+
+ /**
+ * Tabbed pane to hold tables containing various entries in the Keystore and
+ * Truststore
+ */
+ private JTabbedPane keyStoreTabbedPane;
+ /** Tab 1: holds passwords table */
+ private JPanel passwordsTab = new JPanel(new BorderLayout(10, 10));
+ /** Tab 1: name */
+ public static final String PASSWORDS = "Passwords";
+ /** Tab 2: holds key pairs (user certificates) table */
+ private JPanel keyPairsTab = new JPanel(new BorderLayout(10, 10));
+ /** Tab 2: name */
+ public static final String KEYPAIRS = "Your Certificates";
+ /** Tab 3: holds trusted certificates table */
+ private JPanel trustedCertificatesTab = new JPanel(new BorderLayout(10, 10));
+ /** Tab 3: name */
+ public static final String TRUSTED_CERTIFICATES = "Trusted Certificates";
+
+ ////////////// Tables //////////////
+
+ /** Password entries' table */
+ private JTable passwordsTable;
+ /** Key pair entries' table */
+ private JTable keyPairsTable;
+ /** Trusted certificate entries' table */
+ private JTable trustedCertsTable;
+ /** Password entry column type */
+ public static final String PASSWORD_ENTRY_TYPE = "Password";
+ /** Key pair entry column type */
+ public static final String KEY_PAIR_ENTRY_TYPE = "Key Pair";
+ /** Trusted cert entry column type */
+ public static final String TRUST_CERT_ENTRY_TYPE = "Trusted Certificate";
+
+ /**
+ * Overrides the Object's clone method to prevent the singleton object to be
+ * cloned.
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+
+ /**
+ * Creates a new Credential Manager UI's frame.
+ */
+ public CredentialManagerUI(CredentialManager credentialManager,
+ DistinguishedNameParser dnParser) {
+ credManager = credentialManager;
+ this.dnParser = dnParser;
+ setModalExclusionType(APPLICATION_EXCLUDE);
+ // Initialise the UI components
+ initComponents();
+ }
+
+ private void initComponents() {
+ /*
+ * Initialise the tabbed pane that contains the tabs with tabular
+ * representations of the Keystore's content.
+ */
+ keyStoreTabbedPane = new JTabbedPane();
+ /*
+ * Initialise the tab containing the table for username/password entries
+ * from the Keystore
+ */
+ passwordsTable = initTable(PASSWORDS, passwordsTab);
+ /*
+ * Initialise the tab containing the table for key pair entries from the
+ * Keystore
+ */
+ keyPairsTable = initTable(KEYPAIRS, keyPairsTab);
+ /*
+ * Initialise the tab containing the table for proxy entries from the
+ * Keystore
+ */
+ //proxiesTable = initTable(PROXIES, proxiesTab);
+ /*
+ * Initialise the tab containing the table for trusted certificate
+ * entries from the Truststore
+ */
+ trustedCertsTable = initTable(TRUSTED_CERTIFICATES,
+ trustedCertificatesTab);
+ /*
+ * Set the size of the tabbed pane to the preferred size - the size of
+ * the main application frame depends on it.
+ */
+ keyStoreTabbedPane.setPreferredSize(new Dimension(DEFAULT_FRAME_WIDTH,
+ DEFAULT_FRAME_HEIGHT));
+
+ JPanel globalButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ JButton resetJavaAuthCache = new JButton("Clear HTTP authentication");
+ resetJavaAuthCache.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ clearAuthenticationCache();
+ }
+ });
+ globalButtons.add(resetJavaAuthCache);
+
+ // Button for changing Credential Manager's master password
+ JButton changeMasterPasswordButton = new JButton(
+ "Change master password");
+ changeMasterPasswordButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ changeMasterPassword();
+ }
+ });
+ globalButtons.add(changeMasterPasswordButton);
+
+ // Add change master password to the main application frame
+ getContentPane().add(globalButtons, NORTH);
+ // Add tabbed pane to the main application frame
+ getContentPane().add(keyStoreTabbedPane, CENTER);
+
+ // Handle application close
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeFrame();
+ }
+ });
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ pack();
+
+ // Centre the frame in the centre of the screen
+ setLocationRelativeTo(null);
+
+ // Set the frame's icon
+ setIconImage(credManagerIconImage);
+
+ // Set the frame's title
+ setTitle("Credential Manager");
+
+ // setModal(true);
+ // setVisible(true);
+ }
+
+ protected void clearAuthenticationCache() {
+ if (!credManager.resetAuthCache())
+ showMessageDialog(
+ this,
+ "Java's internal HTTP authentication cache could not be cleared. \n\n"
+ + "Taverna can only clear the cache using an undocumented Java API \n"
+ + "that might not work if you are using a Java VM other than \n"
+ + "Java 6 from Sun. You can restarting Taverna to clear the cache.",
+ "Could not clear authentication cache", ERROR_MESSAGE);
+ else
+ showMessageDialog(
+ this,
+ "Java's internal HTTP authentication cache has been cleared. \n\n"
+ + "You might also need to edit or delete individual \n"
+ + "password entries in the credential manager \n"
+ + "if a relevant password has previously been saved.",
+ "Cleared authentication cache", INFORMATION_MESSAGE);
+ }
+
+ protected void changeMasterPassword() {
+ ChangeMasterPasswordDialog changePasswordDialog = new ChangeMasterPasswordDialog(
+ this, "Change master password", true,
+ "Change master password for Credential Manager", credManager);
+ changePasswordDialog.setLocationRelativeTo(null);
+ changePasswordDialog.setVisible(true);
+ String password = changePasswordDialog.getPassword();
+ if (password == null) // user cancelled
+ return; // do nothing
+
+ try {
+ credManager.changeMasterPassword(password);
+ showMessageDialog(this, "Master password changed sucessfully",
+ ALERT_TITLE, INFORMATION_MESSAGE);
+ } catch (CMException cme) {
+ /*
+ * Failed to change the master password for Credential Manager -
+ * warn the user
+ */
+ String exMessage = "Failed to change master password for Credential Manager";
+ logger.error(exMessage);
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * Initialise the tabs and tables with the content from the Keystore and Truststore.
+ */
+ private JTable initTable(String tableType, JPanel tab) {
+ JTable table = null;
+
+ if (tableType.equals(PASSWORDS)) { // Passwords table
+ // The Passwords table's data model
+ PasswordsTableModel passwordsTableModel = new PasswordsTableModel(credManager);
+ // The table itself
+ table = new JTable(passwordsTableModel);
+
+ /*
+ * Set the password and alias columns of the Passwords table to be
+ * invisible by removing them from the column model (they will still
+ * present in the table model)
+ *
+ * Remove the last column first
+ */
+ TableColumn aliasColumn = table.getColumnModel().getColumn(5);
+ table.getColumnModel().removeColumn(aliasColumn);
+ TableColumn passwordColumn = table.getColumnModel().getColumn(4);
+ table.getColumnModel().removeColumn(passwordColumn);
+ TableColumn lastModifiedDateColumn = table.getColumnModel().getColumn(3);
+ table.getColumnModel().removeColumn(lastModifiedDateColumn);
+
+ // Buttons
+ JButton newPasswordButton = new JButton("New");
+ newPasswordButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ newPassword();
+ }
+ });
+
+ final JButton viewPasswordButton = new JButton("Details");
+ viewPasswordButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ viewPassword();
+ }
+ });
+ viewPasswordButton.setEnabled(false);
+
+ final JButton editPasswordButton = new JButton("Edit");
+ editPasswordButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ editPassword();
+ }
+ });
+ editPasswordButton.setEnabled(false);
+
+ final JButton deletePasswordButton = new JButton("Delete");
+ deletePasswordButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ deletePassword();
+ }
+ });
+ deletePasswordButton.setEnabled(false);
+
+ /*
+ * Selection listener for passwords table to enable/disable action
+ * buttons accordingly
+ */
+ class PasswordsTableSelectionListner implements
+ ListSelectionListener {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getSource() != passwordsTable.getSelectionModel())
+ return;
+ if (passwordsTable.getSelectedRow() == -1) {
+ // nothing is selected
+ viewPasswordButton.setEnabled(false);
+ editPasswordButton.setEnabled(false);
+ deletePasswordButton.setEnabled(false);
+ } else {
+ if (!viewPasswordButton.isEnabled())
+ viewPasswordButton.setEnabled(true);
+ if (!editPasswordButton.isEnabled())
+ editPasswordButton.setEnabled(true);
+ if (!deletePasswordButton.isEnabled())
+ deletePasswordButton.setEnabled(true);
+ }
+ }
+ }
+ table.getSelectionModel().addListSelectionListener(new PasswordsTableSelectionListner());
+
+ // Panel to hold the buttons
+ JPanel bp = new JPanel();
+ bp.add(viewPasswordButton);
+ bp.add(editPasswordButton);
+ bp.add(newPasswordButton);
+ bp.add(deletePasswordButton);
+
+ // Add button panel to the tab
+ tab.add(bp, PAGE_END);
+
+ } else if (tableType.equals(KEYPAIRS)) { // Key Pairs tab
+ // The Key Pairs table's data model
+ KeyPairsTableModel keyPairsTableModel = new KeyPairsTableModel(credManager);
+ // The table itself
+ table = new JTable(keyPairsTableModel);
+
+ /*
+ * Set the alias and service URIs columns of the KayPairs table to
+ * be invisible by removing them from the column model (they will
+ * still present in the table model)
+ *
+ * Remove the last column first
+ */
+ TableColumn aliasColumn = table.getColumnModel().getColumn(6);
+ table.getColumnModel().removeColumn(aliasColumn);
+ TableColumn serviceURIsColumn = table.getColumnModel().getColumn(5);
+ table.getColumnModel().removeColumn(serviceURIsColumn);
+ TableColumn lastModifiedDateColumn = table.getColumnModel().getColumn(4);
+ table.getColumnModel().removeColumn(lastModifiedDateColumn);
+
+ // Buttons
+ final JButton viewKeyPairButton = new JButton("Details");
+ viewKeyPairButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ viewCertificate();
+ }
+ });
+ viewKeyPairButton.setEnabled(false);
+
+ JButton importKeyPairButton = new JButton("Import");
+ importKeyPairButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importKeyPair();
+ }
+ });
+
+ final JButton exportKeyPairButton = new JButton("Export");
+ exportKeyPairButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ exportKeyPair();
+ }
+ });
+ exportKeyPairButton.setEnabled(false);
+
+ final JButton deleteKeyPairButton = new JButton("Delete");
+ deleteKeyPairButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ deleteKeyPair();
+ }
+ });
+ deleteKeyPairButton.setEnabled(false);
+
+ /*
+ * Selection listener for key pairs table to enable/disable action
+ * buttons accordingly
+ */
+ class KeyPairsTableSelectionListner implements
+ ListSelectionListener {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getSource() != keyPairsTable.getSelectionModel())
+ return;
+ if (keyPairsTable.getSelectedRow() == -1) {
+ // nothing is selected
+ viewKeyPairButton.setEnabled(false);
+ exportKeyPairButton.setEnabled(false);
+ deleteKeyPairButton.setEnabled(false);
+ } else {
+ if (!viewKeyPairButton.isEnabled())
+ viewKeyPairButton.setEnabled(true);
+ if (!exportKeyPairButton.isEnabled())
+ exportKeyPairButton.setEnabled(true);
+ if (!deleteKeyPairButton.isEnabled())
+ deleteKeyPairButton.setEnabled(true);
+ }
+ }
+ }
+ table.getSelectionModel().addListSelectionListener(
+ new KeyPairsTableSelectionListner());
+
+ // Panel to hold the buttons
+ JPanel bp = new JPanel();
+ bp.add(viewKeyPairButton);
+ bp.add(importKeyPairButton);
+ bp.add(exportKeyPairButton);
+ bp.add(deleteKeyPairButton);
+
+ // Add button panel to the tab
+ tab.add(bp, PAGE_END);
+ } else if (tableType.equals(TRUSTED_CERTIFICATES)) { // Certificates tab
+
+ // The Trusted Certificate table's data model
+ TrustedCertsTableModel trustedCertificatesTableModel = new TrustedCertsTableModel(credManager);
+ // The table itself
+ table = new JTable(trustedCertificatesTableModel);
+
+ /*
+ * Set the alias columns of the Trusted Certs table to be invisible
+ * by removing them from the column model (they will still be
+ * present in the table model)
+ *
+ * Remove the last column first
+ */
+ TableColumn aliasColumn = table.getColumnModel().getColumn(5);
+ table.getColumnModel().removeColumn(aliasColumn);
+ TableColumn lastModifiedDateColumn = table.getColumnModel().getColumn(4);
+ table.getColumnModel().removeColumn(lastModifiedDateColumn);
+
+ // Buttons
+ final JButton viewTrustedCertificateButton = new JButton("Details");
+ viewTrustedCertificateButton
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ viewCertificate();
+ }
+ });
+ viewTrustedCertificateButton.setEnabled(false);
+
+ JButton importTrustedCertificateButton = new JButton("Import");
+ importTrustedCertificateButton
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importTrustedCertificate();
+ }
+ });
+
+ final JButton exportTrustedCertificateButton = new JButton("Export");
+ exportTrustedCertificateButton
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ exportTrustedCertificate();
+ }
+ });
+ exportTrustedCertificateButton.setEnabled(false);
+
+ final JButton deleteTrustedCertificateButton = new JButton("Delete");
+ deleteTrustedCertificateButton
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ deleteTrustedCertificate();
+ }
+ });
+ deleteTrustedCertificateButton.setEnabled(false);
+
+ // Selection listener for trusted certs table to enable/disable action buttons accordingly
+ class TrustedCertsTableSelectionListener implements
+ ListSelectionListener {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getSource() != trustedCertsTable.getSelectionModel())
+ return;
+ if (trustedCertsTable.getSelectedRow() == -1) {
+ // nothing is selected
+ viewTrustedCertificateButton.setEnabled(false);
+ exportTrustedCertificateButton.setEnabled(false);
+ deleteTrustedCertificateButton.setEnabled(false);
+ } else {
+ if (!viewTrustedCertificateButton.isEnabled())
+ viewTrustedCertificateButton.setEnabled(true);
+ if (!exportTrustedCertificateButton.isEnabled())
+ exportTrustedCertificateButton.setEnabled(true);
+ if (!deleteTrustedCertificateButton.isEnabled())
+ deleteTrustedCertificateButton.setEnabled(true);
+ }
+ }
+ }
+ table.getSelectionModel().addListSelectionListener(
+ new TrustedCertsTableSelectionListener());
+
+ // Panel to hold the buttons
+ JPanel bp = new JPanel();
+ bp.add(viewTrustedCertificateButton);
+ bp.add(importTrustedCertificateButton);
+ bp.add(exportTrustedCertificateButton);
+ bp.add(deleteTrustedCertificateButton);
+
+ // Add button panel to the tab
+ tab.add(bp, PAGE_END);
+ } else {
+ throw new RuntimeException("Unknown table type " + tableType);
+ }
+
+ table.setShowGrid(false);
+ table.setRowMargin(0);
+ table.getColumnModel().setColumnMargin(0);
+ table.getTableHeader().setReorderingAllowed(false);
+ table.setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
+ // Top accommodates entry icons with 2 pixels spare space (images are
+ // 16x16 pixels)
+ table.setRowHeight(18);
+
+ // Add custom renderrers for the table headers and cells
+ for (int iCnt = 0; iCnt < table.getColumnCount(); iCnt++) {
+ TableColumn column = table.getColumnModel().getColumn(iCnt);
+ column.setHeaderRenderer(new TableHeaderRenderer());
+ column.setCellRenderer(new TableCellRenderer());
+ }
+
+ // Make the first column small and not resizable (it holds icons to
+ // represent different entry types)
+ TableColumn typeCol = table.getColumnModel().getColumn(0);
+ typeCol.setResizable(false);
+ typeCol.setMinWidth(20);
+ typeCol.setMaxWidth(20);
+ typeCol.setPreferredWidth(20);
+
+ // Set the size for the second column
+ // (i.e. Service URI column of Passwords table, and
+ // Certificate Name column of the Kay Pairs and Trusted Certificates tables)
+ // We do not care about the size of other columns.
+ TableColumn secondCol = table.getColumnModel().getColumn(1);
+ secondCol.setMinWidth(20);
+ secondCol.setMaxWidth(10000);
+ secondCol.setPreferredWidth(300);
+
+ // Put the table into a scroll pane
+ JScrollPane jspTableScrollPane = new JScrollPane(table,
+ VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ jspTableScrollPane.getViewport().setBackground(table.getBackground());
+
+ // Put the scroll pane on the tab panel
+ tab.add(jspTableScrollPane, CENTER);
+ jspTableScrollPane.setBorder(new EmptyBorder(3, 3, 3, 3));
+
+ /*
+ * Add mouse listeners to show an entry's details if it is
+ * double-clicked
+ */
+ table.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent evt) {
+ tableDoubleClick(evt);
+ }
+ });
+
+ // Add the tab to the tabbed pane
+ keyStoreTabbedPane.addTab(tableType, tab);
+
+ return table;
+ }
+
+ /**
+ * Displays the details of the username/password pair entry - this includes
+ * showing the plaintext password and service URI for this entry.
+ */
+ private void viewPassword() {
+ // Which username/password pair entry has been selected, if any?
+ int iRow = passwordsTable.getSelectedRow();
+ if (iRow == -1) // no row currently selected
+ return;
+
+ // Get current values for service URI, username and password
+ String serviceURI = (String) passwordsTable.getValueAt(iRow, 1); // current entry's service URI
+
+ String username = (String) passwordsTable.getValueAt(iRow, 2); // current entry's username
+
+ /*
+ * Because the password column is not visible we call the getValueAt
+ * method on the table model rather than at the JTable
+ */
+ String password = (String) passwordsTable.getModel()
+ .getValueAt(iRow, 4); // current entry's password value
+
+ // Let the user view service URI, username and password of the entry
+ ViewUsernamePasswordEntryDialog viewServicePassDialog = new ViewUsernamePasswordEntryDialog(
+ this, serviceURI, username, password);
+
+ viewServicePassDialog.setLocationRelativeTo(this);
+ viewServicePassDialog.setVisible(true);
+ }
+
+ /**
+ * Lets a user insert a new username/password/service URI tuple to the
+ * Keystore.
+ */
+ private void newPassword() {
+ URI serviceURI = null; // service URI
+ String username = null; // username
+ String password = null; // password
+
+ // Loop until the user cancels or enters everything correctly
+ while (true) {
+ /*
+ * Let the user insert a new password entry (by specifying service
+ * URI, username and password)
+ */
+ NewEditPasswordEntryDialog newPasswordDialog = new NewEditPasswordEntryDialog(
+ this, "New username and password for a service", true,
+ serviceURI, username, password, credManager);
+ newPasswordDialog.setLocationRelativeTo(this);
+ newPasswordDialog.setVisible(true);
+
+ serviceURI = newPasswordDialog.getServiceURI(); // get service URI
+ username = newPasswordDialog.getUsername(); // get username
+ password = newPasswordDialog.getPassword(); // get password
+
+ if (password == null) { // user cancelled - any of the above three
+ // fields is null
+ // do nothing
+ return;
+ }
+
+ /*
+ * Check if a password entry with the given service URI already
+ * exists in the Keystore. We ask this here as the user may wish to
+ * overwrite the existing password entry. Checking for key pair
+ * entries' URIs is done in the NewEditPasswordEntry dialog.
+ */
+
+ /*
+ * Get list of service URIs for all the password entries in the
+ * Keystore
+ */
+ List<URI> serviceURIs = null;
+ try {
+ serviceURIs = credManager
+ .getServiceURIsForAllUsernameAndPasswordPairs();
+ } catch (CMException cme) {
+ showMessageDialog(this, "Failed to get service URIs for all username and password pairs "
+ + "to check if the entered service URI already exists",
+ ERROR_TITLE, ERROR_MESSAGE);
+ return;
+ }
+ if (serviceURIs.contains(serviceURI)) { // if such a URI already
+ // exists
+ // Ask if the user wants to overwrite it
+ int answer = showConfirmDialog(
+ this,
+ "Credential Manager already contains a password entry with the same service URI.\n"
+ + "Do you want to overwrite it?",
+ ALERT_TITLE,
+ YES_NO_OPTION);
+
+ // Add the new password entry in the Keystore
+ try {
+ if (answer == YES_OPTION) {
+ credManager.addUsernameAndPasswordForService(
+ new UsernamePassword(username, password),
+ serviceURI);
+ break;
+ }
+ } catch (CMException cme) {
+ showMessageDialog(
+ this,
+ "Credential Manager failed to insert a new username and password pair",
+ ERROR_TITLE, ERROR_MESSAGE);
+ }
+ /*
+ * Otherwise show the same window with the entered service URI,
+ * username and password values
+ */
+ } else
+ // Add the new password entry in the Keystore
+ try {
+ credManager.addUsernameAndPasswordForService(new UsernamePassword(username,
+ password), serviceURI);
+ break;
+ } catch (CMException cme) {
+ showMessageDialog(
+ this,
+ "Credential Manager failed to insert a new username and password pair",
+ ERROR_TITLE, ERROR_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Lets a user insert a new username/password pair for a given service URI
+ * to the Keystore.
+ */
+ public void newPasswordForService(URI serviceURI) {
+ /*
+ * As this method can be called from outside of Credential Manager UI,
+ * e.g. from wsdl-activity-ui or rshell-activity-ui to pop up a dialog
+ * to ask the user for username and password, we also want to make sure
+ * the main Credential Manager UI Dialog is visible as it may be clearer
+ * to the user what is going on
+ */
+ if (!isVisible() || getState() == ICONIFIED)
+ setVisible(true);
+
+ // Make sure password tab is selected as this method may
+ // be called from outside of Credential Manager UI.
+ keyStoreTabbedPane.setSelectedComponent(passwordsTab);
+
+ String username = null; // username
+ String password = null; // password
+
+ // Loop until the user cancels or enters everything correctly
+ while (true) {
+
+// if(!this.isVisible()){ // if Cred Man UI is already showing but e.g. obscured by another window or minimised
+// // Do not bring it up!
+// } // actually we now want to show it as it makes it clearer to the user what is going on
+
+ // Let the user insert a new password entry for the given service
+ // URI (by specifying username and password)
+ NewEditPasswordEntryDialog newPasswordDialog = new NewEditPasswordEntryDialog(
+ this, "New username and password for a service", true,
+ serviceURI, username, password, credManager);
+ newPasswordDialog.setLocationRelativeTo(this);
+ newPasswordDialog.setVisible(true);
+
+ serviceURI = newPasswordDialog.getServiceURI(); // get service URI
+ username = newPasswordDialog.getUsername(); // get username
+ password = newPasswordDialog.getPassword(); // get password
+
+ if (password == null) // user cancelled - any of the above three
+ // fields is null
+ // do nothing
+ return;
+
+ /*
+ * Check if a password entry with the given service URI already
+ * exists in the Keystore. We ask this here as the user may wish to
+ * overwrite the existing password entry. Checking for key pair
+ * entries' URIs is done in the NewEditPasswordEntry dialog.
+ */
+
+ // Get list of service URIs for all the password entries in the
+ // Keystore
+ List<URI> serviceURIs = null;
+ try {
+ serviceURIs = credManager
+ .getServiceURIsForAllUsernameAndPasswordPairs();
+ } catch (CMException cme) {
+ showMessageDialog(this, "Failed to get service URIs for all username and password pairs "
+ + "to check if the entered service URI already exists",
+ ERROR_TITLE, ERROR_MESSAGE);
+ return;
+ }
+ if (serviceURIs.contains(serviceURI)) { // if such a URI already
+ // exists
+ // Ask if the user wants to overwrite it
+ int answer = showConfirmDialog(
+ this,
+ "Credential Manager already contains a password entry with the same service URI.\n"
+ + "Do you want to overwrite it?", ALERT_TITLE,
+ YES_NO_OPTION);
+
+ // Add the new password entry in the Keystore
+ try {
+ if (answer == YES_OPTION) {
+ credManager.addUsernameAndPasswordForService(
+ new UsernamePassword(username, password),
+ serviceURI);
+ break;
+ }
+ } catch (CMException cme) {
+ String exMessage = "Credential Manager failed to insert a new username and password pair";
+ showMessageDialog(this, exMessage, ERROR_TITLE,
+ ERROR_MESSAGE);
+ }
+ // Otherwise show the same window with the entered service
+ // URI, username and password values
+ } else
+ // Add the new password entry in the Keystore
+ try {
+ credManager.addUsernameAndPasswordForService(new UsernamePassword(username,
+ password), serviceURI);
+ break;
+ } catch (CMException cme) {
+ showMessageDialog(this, "Credential Manager failed to insert a new username and password pair",
+ ERROR_TITLE,
+ ERROR_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Lets a user edit a username and password entry or their related service
+ * URI to the Keystore.
+ */
+ private void editPassword() {
+ // Which password entry has been selected?
+ int iRow = passwordsTable.getSelectedRow();
+ if (iRow == -1) { // no row currently selected
+ return;
+ }
+
+ // Get current values for service URI, username and password
+ URI serviceURI = URI.create((String) passwordsTable.getValueAt(iRow, 1)); // current entry's service URI
+
+ String username = (String) passwordsTable.getValueAt(iRow, 2); // current entry's username
+
+ /*
+ * Because the password column is not visible we call the getValueAt
+ * method on the table model rather than at the JTable
+ */
+ String password = (String) passwordsTable.getModel()
+ .getValueAt(iRow, 4); // current entry's password value
+
+ while (true) { // loop until user cancels or enters everything correctly
+ // Let the user edit service URI, username or password of a password entry
+ NewEditPasswordEntryDialog editPasswordDialog = new NewEditPasswordEntryDialog(
+ this, "Edit username and password for a service", true,
+ serviceURI, username, password, credManager);
+
+ editPasswordDialog.setLocationRelativeTo(this);
+ editPasswordDialog.setVisible(true);
+
+ // New values
+ URI newServiceURI = editPasswordDialog.getServiceURI(); // get new service URI
+ String newUsername = editPasswordDialog.getUsername(); // get new username
+ String newPassword = editPasswordDialog.getPassword(); // get new password
+
+ if (newPassword == null) // user cancelled - any of the above three
+ // fields is null
+ // do nothing
+ return;
+
+ // Is anything actually modified?
+ boolean isModified = !serviceURI.equals(newServiceURI)
+ || !username.equals(newUsername)
+ || !password.equals(newPassword);
+
+ if (isModified) {
+ /*
+ * Check if a different password entry with the new URI (i.e.
+ * alias) already exists in the Keystore We ask this here as the
+ * user may wish to overwrite that other password entry.
+ */
+
+ // Get list of URIs for all passwords in the Keystore
+ List<URI> serviceURIs = null;
+ try {
+ serviceURIs = credManager
+ .getServiceURIsForAllUsernameAndPasswordPairs();
+ } catch (CMException cme) {
+ showMessageDialog(this, "Failed to get service URIs for all username and password pairs "
+ + "to check if the modified entry already exists",
+ ERROR_TITLE,
+ ERROR_MESSAGE);
+ return;
+ }
+
+ // If the modified service URI already exists and is not the
+ // currently selected one
+ if (!newServiceURI.equals(serviceURI)
+ && serviceURIs.contains(newServiceURI)) {
+ int answer = showConfirmDialog(
+ this,
+ "The Keystore already contains username and password pair for the entered service URI.\n"
+ + "Do you want to overwrite it?",
+ ALERT_TITLE, YES_NO_OPTION);
+
+ try {
+ if (answer == YES_OPTION) {
+ /*
+ * Overwrite that other entry entry and save the new
+ * one in its place. Also remove the current one
+ * that we are editing - as it is replacing the
+ * other entry.
+ */
+ credManager
+ .deleteUsernameAndPasswordForService(serviceURI);
+ credManager.addUsernameAndPasswordForService(
+ new UsernamePassword(newUsername,
+ newPassword), newServiceURI);
+ break;
+ }
+ } catch (CMException cme) {
+ showMessageDialog(
+ this,
+ "Failed to update the username and password pair in the Keystore",
+ ERROR_TITLE, ERROR_MESSAGE);
+ }
+ // Otherwise show the same window with the entered
+ // service URI, username and password values
+ } else
+ try {
+ if (!newServiceURI.equals(serviceURI))
+ credManager
+ .deleteUsernameAndPasswordForService(serviceURI);
+ credManager.addUsernameAndPasswordForService(
+ new UsernamePassword(newUsername, newPassword), newServiceURI);
+ break;
+ } catch (CMException cme) {
+ showMessageDialog(
+ this,
+ "Failed to update the username and password pair in the Keystore",
+ ERROR_TITLE, ERROR_MESSAGE);
+ }
+ } else // nothing actually modified
+ break;
+ }
+ }
+
+ /**
+ * Lets the user delete the selected username and password entries from the
+ * Keystore.
+ */
+ private void deletePassword() {
+ // Which entries have been selected?
+ int[] selectedRows = passwordsTable.getSelectedRows();
+ if (selectedRows.length == 0) // no password entry selected
+ return;
+
+ // Ask user to confirm the deletion
+ if (showConfirmDialog(
+ null,
+ "Are you sure you want to delete the selected username and password entries?",
+ ALERT_TITLE, YES_NO_OPTION) != YES_OPTION)
+ return;
+
+ String exMessage = null;
+ for (int i = selectedRows.length - 1; i >= 0; i--) { // delete from backwards
+ // Get service URI for the current entry
+ URI serviceURI = URI.create((String) passwordsTable.getValueAt(selectedRows[i], 1));
+ // current entry's service URI
+ try {
+ // Delete the password entry from the Keystore
+ credManager.deleteUsernameAndPasswordForService(serviceURI);
+ } catch (CMException cme) {
+ exMessage = "Failed to delete the username and password pair from the Keystore";
+ }
+ }
+ if (exMessage != null)
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+
+ /**
+ * Shows the contents of a (user or trusted) certificate.
+ */
+ private void viewCertificate() {
+ int selectedRow = -1;
+ String alias = null;
+ X509Certificate certToView = null;
+ ArrayList<String> serviceURIs = null;
+ KeystoreType keystoreType = null;
+
+ // Are we showing user's public key certificate?
+ if (keyPairsTab.isShowing()) {
+ keystoreType = KEYSTORE;
+ selectedRow = keyPairsTable.getSelectedRow();
+
+ if (selectedRow != -1)
+ /*
+ * Because the alias column is not visible we call the
+ * getValueAt method on the table model rather than at the
+ * JTable
+ */
+ alias = (String) keyPairsTable.getModel().getValueAt(selectedRow, 6); // current entry's Keystore alias
+ }
+ // Are we showing trusted certificate?
+ else if (trustedCertificatesTab.isShowing()) {
+ keystoreType = TRUSTSTORE;
+ selectedRow = trustedCertsTable.getSelectedRow();
+
+ if (selectedRow != -1)
+ /*
+ * Get the selected trusted certificate entry's Truststore alias
+ * Alias column is invisible so we get the value from the table
+ * model
+ */
+ alias = (String) trustedCertsTable.getModel().getValueAt(
+ selectedRow, 5);
+ }
+
+ try {
+ if (selectedRow != -1) { // something has been selected
+ // Get the entry's certificate
+ certToView = dnParser.convertCertificate(credManager
+ .getCertificate(keystoreType, alias));
+
+ // Show the certificate's contents to the user
+ ViewCertDetailsDialog viewCertDetailsDialog = new ViewCertDetailsDialog(
+ this, "Certificate details", true, certToView,
+ serviceURIs, dnParser);
+ viewCertDetailsDialog.setLocationRelativeTo(this);
+ viewCertDetailsDialog.setVisible(true);
+ }
+ } catch (CMException cme) {
+ String exMessage = "Failed to get certificate details to display to the user";
+ logger.error(exMessage, cme);
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * Lets a user import a key pair from a PKCS #12 keystore file to the
+ * Keystore.
+ */
+ private void importKeyPair() {
+ /*
+ * Let the user choose a PKCS #12 file (keystore) containing a public
+ * and private key pair to import
+ */
+ File importFile = selectImportExportFile(
+ "PKCS #12 file to import from", // title
+ new String[] { ".p12", ".pfx" }, // array of file extensions
+ // for the file filter
+ "PKCS#12 Files (*.p12, *.pfx)", // description of the filter
+ "Import", // text for the file chooser's approve button
+ "keyPairDir"); // preference string for saving the last chosen directory
+
+ if (importFile == null)
+ return;
+
+ // The PKCS #12 keystore is not a file
+ if (!importFile.isFile()) {
+ showMessageDialog(this, "Your selection is not a file",
+ ALERT_TITLE, WARNING_MESSAGE);
+ return;
+ }
+
+ // Get the user to enter the password that was used to encrypt the
+ // private key contained in the PKCS #12 file
+ GetPasswordDialog getPasswordDialog = new GetPasswordDialog(this,
+ "Import key pair entry", true,
+ "Enter the password that was used to encrypt the PKCS #12 file");
+ getPasswordDialog.setLocationRelativeTo(this);
+ getPasswordDialog.setVisible(true);
+
+ String pkcs12Password = getPasswordDialog.getPassword();
+
+ if (pkcs12Password == null) // user cancelled
+ return;
+ else if (pkcs12Password.isEmpty()) // empty password
+ // FIXME: Maybe user did not have the password set for the private key???
+ return;
+
+ try {
+ // Load the PKCS #12 keystore from the file
+ // (this is using the BouncyCastle provider !!!)
+ KeyStore pkcs12Keystore = credManager.loadPKCS12Keystore(importFile,
+ pkcs12Password);
+
+ /*
+ * Display the import key pair dialog supplying all the private keys
+ * stored in the PKCS #12 file (normally there will be only one
+ * private key inside, but could be more as this is a keystore after
+ * all).
+ */
+ NewKeyPairEntryDialog importKeyPairDialog = new NewKeyPairEntryDialog(
+ this, "Credential Manager", true, pkcs12Keystore, dnParser);
+ importKeyPairDialog.setLocationRelativeTo(this);
+ importKeyPairDialog.setVisible(true);
+
+ // Get the private key and certificate chain of the key pair
+ Key privateKey = importKeyPairDialog.getPrivateKey();
+ Certificate[] certChain = importKeyPairDialog.getCertificateChain();
+
+ if (privateKey == null || certChain == null)
+ // User did not select a key pair for import or cancelled
+ return;
+
+ /*
+ * Check if a key pair entry with the same alias already exists in
+ * the Keystore
+ */
+ if (credManager.hasKeyPair(privateKey, certChain)
+ && showConfirmDialog(this,
+ "The keystore already contains the key pair entry with the same private key.\n"
+ + "Do you want to overwrite it?",
+ ALERT_TITLE, YES_NO_OPTION) != YES_OPTION)
+ return;
+
+ // Place the private key and certificate chain into the Keystore
+ credManager.addKeyPair(privateKey, certChain);
+
+ // Display success message
+ showMessageDialog(this, "Key pair import successful", ALERT_TITLE,
+ INFORMATION_MESSAGE);
+ } catch (Exception ex) { // too many exceptions to catch separately
+ String exMessage = "Failed to import the key pair entry to the Keystore. "
+ + ex.getMessage();
+ logger.error(exMessage, ex);
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * Lets a user export user's private and public key pair to a PKCS #12
+ * keystore file.
+ */
+ private void exportKeyPair() {
+ // Which key pair entry has been selected?
+ int selectedRow = keyPairsTable.getSelectedRow();
+ if (selectedRow == -1) // no row currently selected
+ return;
+
+ // Get the key pair entry's Keystore alias
+ String alias = (String) keyPairsTable.getModel().getValueAt(selectedRow, 6);
+
+ // Let the user choose a PKCS #12 file (keystore) to export public and
+ // private key pair to
+ File exportFile = selectImportExportFile("Select a file to export to", // title
+ new String[] { ".p12", ".pfx" }, // array of file extensions
+ // for the file filter
+ "PKCS#12 Files (*.p12, *.pfx)", // description of the filter
+ "Export", // text for the file chooser's approve button
+ "keyPairDir"); // preference string for saving the last chosen directory
+
+ if (exportFile == null)
+ return;
+
+ // If file already exist - ask the user if he wants to overwrite it
+ if (exportFile.isFile()
+ && showConfirmDialog(this,
+ "The file with the given name already exists.\n"
+ + "Do you want to overwrite it?", ALERT_TITLE,
+ YES_NO_OPTION) == NO_OPTION)
+ return;
+
+ // Get the user to enter the password for the PKCS #12 keystore file
+ GetPasswordDialog getPasswordDialog = new GetPasswordDialog(this,
+ "Credential Manager", true,
+ "Enter the password for protecting the exported key pair");
+ getPasswordDialog.setLocationRelativeTo(this);
+ getPasswordDialog.setVisible(true);
+
+ String pkcs12Password = getPasswordDialog.getPassword();
+
+ if (pkcs12Password == null) { // user cancelled or empty password
+ // Warn the user
+ showMessageDialog(
+ this,
+ "You must supply a password for protecting the exported key pair.",
+ ALERT_TITLE, INFORMATION_MESSAGE);
+ return;
+ }
+
+ // Export the key pair
+ try {
+ credManager.exportKeyPair(alias, exportFile, pkcs12Password);
+ showMessageDialog(this, "Key pair export successful", ALERT_TITLE,
+ INFORMATION_MESSAGE);
+ } catch (CMException cme) {
+ showMessageDialog(this, cme.getMessage(), ERROR_TITLE,
+ ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * Lets a user delete selected key pair entries from the Keystore.
+ */
+ private void deleteKeyPair() {
+ // Which entries have been selected?
+ int[] selectedRows = keyPairsTable.getSelectedRows();
+ if (selectedRows.length == 0) // no key pair entry selected
+ return;
+
+ // Ask user to confirm the deletion
+ if (showConfirmDialog(null,
+ "Are you sure you want to delete the selected key pairs?",
+ ALERT_TITLE, YES_NO_OPTION) != YES_OPTION)
+ return;
+
+ String exMessage = null;
+ for (int i = selectedRows.length - 1; i >= 0; i--) { // delete from backwards
+ // Get the alias for the current entry
+ String alias = (String) keyPairsTable.getModel().getValueAt(
+ selectedRows[i], 6);
+ try {
+ // Delete the key pair entry from the Keystore
+ credManager.deleteKeyPair(alias);
+ } catch (CMException cme) {
+ logger.warn("failed to delete " + alias, cme);
+ exMessage = "Failed to delete the key pair(s) from the Keystore";
+ }
+ }
+ if (exMessage != null)
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+
+ /**
+ * Lets a user import a trusted certificate from a PEM or DER encoded file
+ * into the Truststore.
+ */
+ private void importTrustedCertificate() {
+ // Let the user choose a file containing trusted certificate(s) to
+ // import
+ File certFile = selectImportExportFile(
+ "Certificate file to import from", // title
+ new String[] { ".pem", ".crt", ".cer", ".der", "p7", ".p7c" }, // file extensions filters
+ "Certificate Files (*.pem, *.crt, , *.cer, *.der, *.p7, *.p7c)", // filter descriptions
+ "Import", // text for the file chooser's approve button
+ "trustedCertDir"); // preference string for saving the last chosen directory
+ if (certFile == null)
+ return;
+
+ // Load the certificate(s) from the file
+ ArrayList<X509Certificate> trustCertsList = new ArrayList<>();
+ CertificateFactory cf;
+ try {
+ cf = CertificateFactory.getInstance("X.509");
+ } catch (Exception e) {
+ // Nothing we can do! Things are badly misconfigured
+ cf = null;
+ }
+
+ if (cf != null) {
+ try (FileInputStream fis = new FileInputStream(certFile)) {
+ for (Certificate cert : cf.generateCertificates(fis))
+ trustCertsList.add((X509Certificate) cert);
+ } catch (Exception cex) {
+ // Do nothing
+ }
+
+ if (trustCertsList.size() == 0) {
+ // Could not load certificates as any of the above types
+ try (FileInputStream fis = new FileInputStream(certFile);
+ PEMReader pr = new PEMReader(
+ new InputStreamReader(fis), null, cf
+ .getProvider().getName())) {
+ /*
+ * Try as openssl PEM format - which sligtly differs from
+ * the one supported by JCE
+ */
+ Object cert;
+ while ((cert = pr.readObject()) != null)
+ if (cert instanceof X509Certificate)
+ trustCertsList.add((X509Certificate) cert);
+ } catch (Exception cex) {
+ // do nothing
+ }
+ }
+ }
+
+ if (trustCertsList.size() == 0) {
+ /* Failed to load certifcate(s) using any of the known encodings */
+ showMessageDialog(this,
+ "Failed to load certificate(s) using any of the known encodings -\n"
+ + "file format not recognised.", ERROR_TITLE,
+ ERROR_MESSAGE);
+ return;
+ }
+
+ // Show the list of certificates contained in the file for the user to
+ // select the ones to import
+ NewTrustCertsDialog importTrustCertsDialog = new NewTrustCertsDialog(this,
+ "Credential Manager", true, trustCertsList, dnParser);
+
+ importTrustCertsDialog.setLocationRelativeTo(this);
+ importTrustCertsDialog.setVisible(true);
+ List<X509Certificate> selectedTrustCerts = importTrustCertsDialog
+ .getTrustedCertificates(); // user-selected trusted certs to import
+
+ // If user cancelled or did not select any cert to import
+ if (selectedTrustCerts == null || selectedTrustCerts.isEmpty())
+ return;
+
+ try {
+ for (X509Certificate cert : selectedTrustCerts)
+ // Import the selected trusted certificates
+ credManager.addTrustedCertificate(cert);
+
+ // Display success message
+ showMessageDialog(this, "Trusted certificate(s) import successful",
+ ALERT_TITLE, INFORMATION_MESSAGE);
+ } catch (CMException cme) {
+ String exMessage = "Failed to import trusted certificate(s) to the Truststore";
+ logger.error(exMessage, cme);
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * Lets the user export one (at the moment) or more (in future) trusted
+ * certificate entries to a PEM-encoded file.
+ */
+ private boolean exportTrustedCertificate() {
+ // Which trusted certificate has been selected?
+ int selectedRow = trustedCertsTable.getSelectedRow();
+ if (selectedRow == -1) // no row currently selected
+ return false;
+
+ // Get the trusted certificate entry's Keystore alias
+ String alias = (String) trustedCertsTable.getModel()
+ .getValueAt(selectedRow, 3);
+ // the alias column is invisible so we get the value from the table
+ // model
+
+ // Let the user choose a file to export to
+ File exportFile = selectImportExportFile("Select a file to export to", // title
+ new String[] { ".pem" }, // array of file extensions for the
+ // file filter
+ "Certificate Files (*.pem)", // description of the filter
+ "Export", // text for the file chooser's approve button
+ "trustedCertDir"); // preference string for saving the last chosen directory
+ if (exportFile == null)
+ return false;
+
+ // If file already exist - ask the user if he wants to overwrite it
+ if (exportFile.isFile()
+ && showConfirmDialog(this,
+ "The file with the given name already exists.\n"
+ + "Do you want to overwrite it?", ALERT_TITLE,
+ YES_NO_OPTION) == NO_OPTION)
+ return false;
+
+ // Export the trusted certificate
+ try (PEMWriter pw = new PEMWriter(new FileWriter(exportFile))) {
+ // Get the trusted certificate
+ pw.writeObject(credManager.getCertificate(TRUSTSTORE, alias));
+ } catch (Exception ex) {
+ String exMessage = "Failed to export the trusted certificate from the Truststore.";
+ logger.error(exMessage, ex);
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ return false;
+ }
+ showMessageDialog(this, "Trusted certificate export successful",
+ ALERT_TITLE, INFORMATION_MESSAGE);
+ return true;
+ }
+
+ /**
+ * Lets a user delete the selected trusted certificate entries from the
+ * Truststore.
+ */
+ private void deleteTrustedCertificate() {
+ // Which entries have been selected?
+ int[] selectedRows = trustedCertsTable.getSelectedRows();
+ if (selectedRows.length == 0) // no trusted cert entry selected
+ return;
+
+ // Ask user to confirm the deletion
+ if (showConfirmDialog(
+ null,
+ "Are you sure you want to delete the selected trusted certificate(s)?",
+ ALERT_TITLE, YES_NO_OPTION) != YES_OPTION)
+ return;
+
+ String exMessage = null;
+ for (int i = selectedRows.length - 1; i >= 0; i--) { // delete from backwards
+ // Get the alias for the current entry
+ String alias = (String) trustedCertsTable.getModel().getValueAt(
+ selectedRows[i], 5);
+ try {
+ // Delete the trusted certificate entry from the Truststore
+ credManager.deleteTrustedCertificate(alias);
+ } catch (CMException cme) {
+ exMessage = "Failed to delete the trusted certificate(s) from the Truststore";
+ logger.error(exMessage, cme);
+ }
+ }
+ if (exMessage != null)
+ showMessageDialog(this, exMessage, ERROR_TITLE, ERROR_MESSAGE);
+ }
+
+ /**
+ * If double click on a table occured - show the
+ * details of the table entry.
+ */
+ private void tableDoubleClick(MouseEvent evt) {
+ if (evt.getClickCount() > 1) { // is it a double click?
+ // Which row was clicked on (if any)?
+ Point point = new Point(evt.getX(), evt.getY());
+ int row = ((JTable) evt.getSource()).rowAtPoint(point);
+ if (row == -1)
+ return;
+ // Which table the click occured on?
+ if (((JTable) evt.getSource()).getModel() instanceof PasswordsTableModel)
+ // Passwords table
+ viewPassword();
+ else if (((JTable) evt.getSource()).getModel() instanceof KeyPairsTableModel)
+ // Key pairs table
+ viewCertificate();
+ else
+ // Trusted certificates table
+ viewCertificate();
+ }
+ }
+
+ /**
+ * Lets the user select a file to export to or import from a key pair or a
+ * certificate.
+ */
+ private File selectImportExportFile(String title, String[] filter,
+ String description, String approveButtonText, String prefString) {
+ Preferences prefs = Preferences
+ .userNodeForPackage(CredentialManagerUI.class);
+ String keyPairDir = prefs.get(prefString,
+ System.getProperty("user.home"));
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.addChoosableFileFilter(new CryptoFileFilter(filter,
+ description));
+ fileChooser.setDialogTitle(title);
+ fileChooser.setMultiSelectionEnabled(false);
+ fileChooser.setCurrentDirectory(new File(keyPairDir));
+
+ if (fileChooser.showDialog(this, approveButtonText) != APPROVE_OPTION)
+ return null;
+
+ File selectedFile = fileChooser.getSelectedFile();
+ prefs.put(prefString, fileChooser.getCurrentDirectory().toString());
+ return selectedFile;
+ }
+
+ private void closeFrame() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CredentialManagerUILauncher.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CredentialManagerUILauncher.java
new file mode 100644
index 0000000..cdcabb7
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CredentialManagerUILauncher.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * Test launcher for Credential Manager GUI (so it does not have to be
+ * launched from Taverna).
+ *
+ * @author Alexandra Nenadic
+ */
+public class CredentialManagerUILauncher extends JFrame {
+ private static final long serialVersionUID = 2079805060170251148L;
+
+ private final ImageIcon launchCMIcon = new ImageIcon(
+ CredentialManagerUILauncher.class
+ .getResource("/images/cred_manager.png"));
+
+ public CredentialManagerUILauncher() {
+ JPanel jpLaunch = new JPanel();
+ jpLaunch.setPreferredSize(new Dimension(300, 120));
+
+ JLabel jlLaunch = new JLabel("T2: Launch Credential Manager GUI");
+
+ JButton jbLaunch = new JButton();
+ jbLaunch.setIcon(launchCMIcon);
+ jbLaunch.setToolTipText("Launches Credential Manager");
+ jbLaunch.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ CredentialManagerUI cmGUI = new CredentialManagerUI(null, null);
+ if (cmGUI != null)
+ cmGUI.setVisible(true);
+ }
+ });
+
+ jpLaunch.add(jlLaunch);
+ jpLaunch.add(jbLaunch);
+
+ getContentPane().add(jpLaunch, CENTER);
+
+ // Handle application close
+ setDefaultCloseOperation(EXIT_ON_CLOSE);
+
+ pack();
+
+ // Centre the frame in the centre of the desktop
+ setLocationRelativeTo(null);
+ // Set the frame's title
+ setTitle("Credential Manager GUI Launcher");
+ setVisible(true);
+ }
+
+ /**
+ * Launcher for the Credential Manager GUI.
+ */
+ public static void main(String[] args) {
+ // Create and show GUI on the event handler thread
+ invokeLater(new Runnable(){
+ @Override
+ public void run() {
+ new CredentialManagerUILauncher();
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CryptoFileFilter.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CryptoFileFilter.java
new file mode 100644
index 0000000..d58aa8a
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/CryptoFileFilter.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * File filter for filtering against various file extensions. Crypto files
+ * normally contain a private key (and optionally its certificate chain) or a
+ * public key certificate (and optionally its certificate chain).
+ *
+ * .p12 or .pfx are PKCS #12 keystore files containing private key and its
+ * public key (+cert chain); .pem are ASN.1 PEM-encoded files containing one (or
+ * more concatenated) public key certificate(s); .der are ASN.1 DER-encoded
+ * files containing one public key certificate; .cer are CER-encoded files
+ * containing one ore more DER-encoded certificates; .crt files are either
+ * encoded as binary DER or as ASCII PEM. .p7 and .p7c are PKCS #7 certificate
+ * chain files (i.e. SignedData structure without data, just certificate(s)).
+ */
+public class CryptoFileFilter extends FileFilter {
+ // Description of the filter
+ private String description;
+
+ // Array of file extensions to filter against
+ private List<String> exts;
+
+ public CryptoFileFilter(String[] extList, String desc) {
+ exts = Arrays.asList(extList);
+ this.description = desc;
+ }
+
+ @Override
+ public boolean accept(File file) {
+ if (file.isDirectory())
+ return true;
+ if (file.isFile())
+ for (String ext : exts)
+ if (file.getName().toLowerCase().endsWith(ext))
+ return true;
+ return false;
+ }
+
+ public void setDescription(String desc) {
+ this.description = desc;
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/GetMasterPasswordDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/GetMasterPasswordDialog.java
new file mode 100644
index 0000000..b6f623e
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/GetMasterPasswordDialog.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.WARN_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Dialog used for getting a master password for Credential Manager from the
+ * users.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class GetMasterPasswordDialog extends NonBlockedHelpEnabledDialog {
+ /** Password entry field */
+ private JPasswordField passwordField;
+ /** The entered password */
+ private String password = null;
+ /** Text giving user the instructions what to do in the dialog */
+ private String instructions;
+
+ public GetMasterPasswordDialog(String instructions) {
+ super((Frame) null, "Enter master password", true);
+ this.instructions = instructions;
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel instructionsLabel = new JLabel(instructions);
+ // instructionsLabel.setFont(new Font(null, Font.PLAIN, 11));
+
+ JPanel instructionsPanel = new JPanel();
+ instructionsPanel.setLayout(new BoxLayout(instructionsPanel, Y_AXIS));
+ instructionsPanel.add(instructionsLabel);
+ instructionsPanel.setBorder(new EmptyBorder(10, 5, 10, 0));
+
+ JLabel passwordLabel = new JLabel("Password");
+ passwordLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ passwordField = new JPasswordField(15);
+ JPanel passwordPanel = new JPanel(new GridLayout(1, 1, 5, 5));
+ passwordPanel.add(passwordLabel);
+ passwordPanel.add(passwordField);
+
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+ mainPanel.add(instructionsPanel, NORTH);
+ mainPanel.add(passwordPanel, CENTER);
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(okButton);
+ buttonsPanel.add(cancelButton);
+
+ getContentPane().add(mainPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ /**
+ * Get the password entered in the dialog.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Check that the entered password is not empty and store the entered
+ * password.
+ */
+ private boolean checkPassword() {
+ password = new String(passwordField.getPassword());
+
+ if (password.isEmpty()) {
+ showMessageDialog(this, "The password cannot be empty",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void okPressed() {
+ if (checkPassword())
+ closeDialog();
+ }
+
+ private void cancelPressed() {
+ /*
+ * Set the password to null as it might have changed in the meantime if
+ * user entered something then cancelled.
+ */
+ password = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/GetPasswordDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/GetPasswordDialog.java
new file mode 100644
index 0000000..45a0f88
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/GetPasswordDialog.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Font.PLAIN;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.WARN_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * A general dialog for entering a password.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class GetPasswordDialog extends NonBlockedHelpEnabledDialog {
+ /** Instructions for user explaining the purpose of the password */
+ private String instructions = null;
+ /* Password entry password field */
+ private JPasswordField passwordField;
+ /* Stores the password entered */
+ private String password = null;
+
+ public GetPasswordDialog(JFrame parent, String title, boolean modal,
+ String instr) {
+ super(parent, title, modal);
+ instructions = instr;
+ initComponents();
+ }
+
+ public GetPasswordDialog(JDialog parent, String title, boolean modal,
+ String instr) {
+ super(parent, title, modal);
+ instructions = instr;
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel passwordLabel = new JLabel("Password");
+ passwordField = new JPasswordField(15);
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+
+ JLabel instructionsLabel; // Instructions
+ if (instructions != null) {
+ instructionsLabel = new JLabel(instructions);
+ instructionsLabel.setFont(new Font(null, PLAIN, 11));
+ instructionsLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ getContentPane().add(instructionsLabel, NORTH);
+ }
+
+ JPanel passwordPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ passwordPanel.add(passwordLabel);
+ passwordPanel.add(passwordField);
+ passwordPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(okButton);
+ buttonsPanel.add(cancelButton);
+
+ getContentPane().add(passwordPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ /**
+ * Get the password entered in the dialog.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Check that the password entered is not empty and store the entered
+ * password.
+ */
+ private boolean checkPassword() {
+ password = new String(passwordField.getPassword());
+
+ if (password.isEmpty()) {
+ showMessageDialog(this, "The password cannot be empty",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void okPressed() {
+ if (checkPassword())
+ closeDialog();
+ }
+
+ private void cancelPressed() {
+ password = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/KeyPairsTableModel.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/KeyPairsTableModel.java
new file mode 100644
index 0000000..712cdf6
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/KeyPairsTableModel.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.KEYSTORE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ERROR_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI.KEY_PAIR_ENTRY_TYPE;
+
+import java.util.TreeMap;
+
+import javax.swing.JFrame;
+import javax.swing.table.AbstractTableModel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.KeystoreChangedEvent;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The table model used to display the Keystore's key pair entries.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class KeyPairsTableModel extends AbstractTableModel implements Observer<KeystoreChangedEvent> {
+ private static final Logger logger = Logger.getLogger(KeyPairsTableModel.class);
+
+ /** Column names*/
+ private String[] columnNames;
+ /** Table data*/
+ private Object[][] data;
+ private CredentialManager credManager;
+
+ public KeyPairsTableModel(CredentialManager credentialManager) {
+ credManager = credentialManager;
+
+ if (credManager == null) {
+ /* Failed to instantiate Credential Manager - warn the user and exit */
+ String sMessage = "Failed to instantiate Credential Manager. ";
+ logger.error("CM GUI: " + sMessage);
+ showMessageDialog(new JFrame(), sMessage,
+ ERROR_TITLE, ERROR_MESSAGE);
+ return;
+ }
+
+ data = new Object[0][0];
+ columnNames = new String[] {
+ "Entry Type", // type of the Keystore entry
+ "Owner", // owner's common name
+ "Issuer", // issuer's common name
+ "Serial Number", // public key certificate's serial number
+ "Last Modified", // last modified date of the entry
+ "URLs", // the invisible column holding the list of URLs associated with this entry
+ "Alias" // the invisible column holding the actual alias in the Keystore
+ };
+
+ try {
+ load();
+ } catch (CMException cme) {
+ String sMessage = "Failed to load key pairs";
+ logger.error(sMessage, cme);
+ showMessageDialog(new JFrame(), sMessage,
+ ERROR_TITLE, ERROR_MESSAGE);
+ return;
+ }
+
+ // Start observing changes to the Keystore
+ credManager.addObserver(this);
+ }
+
+ /**
+ * Load the table model with the key pair entries from the Keystore.
+ */
+ public void load() throws CMException {
+ // Place key pair entries' aliases in a tree map to sort them
+ TreeMap<String, String> sortedAliases = new TreeMap<>();
+
+ for (String alias: credManager.getAliases(KEYSTORE))
+ /*
+ * We are only interested in key pair entries here.
+ *
+ * Alias for such entries is constructed as
+ * "keypair#<CERT_SERIAL_NUMBER>#<CERT_COMMON_NAME>" where
+ */
+ if (alias.startsWith("keypair#"))
+ sortedAliases.put(alias, alias);
+
+ // Create one table row for each key pair entry
+ data = new Object[sortedAliases.size()][7];
+
+ /*
+ * Iterate through the sorted aliases (if any), retrieving the key pair
+ * entries and populating the table model
+ */
+ int iCnt = 0;
+ for (String alias : sortedAliases.values()) {
+ /*
+ * Populate the type column - it is set with an integer but a custom
+ * cell renderer will cause a suitable icon to be displayed
+ */
+ data[iCnt][0] = KEY_PAIR_ENTRY_TYPE;
+
+ /*
+ * Split the alias string to extract owner, issuer and serial number
+ * alias =
+ * "keypair#"<SUBJECT_COMMON_NAME>"#"<ISSUER_COMMON_NAME>"#"<SERIAL_NUMBER>
+ */
+ String[] aliasComponents = alias.split("#");
+
+ // Populate the owner column extracted from the alias
+ data[iCnt][1] = aliasComponents[1];
+
+ // Populate the issuer column extracted from the alias
+ data[iCnt][2] = aliasComponents[2];
+
+ // Populate the serial number column extracted from the alias
+ data[iCnt][3] = aliasComponents[3];
+
+ // Populate the modified date column ("UBER" keystore type supports creation date)
+ //data[iCnt][4] = credManager.getEntryCreationDate(CredentialManager.KEYSTORE, alias);
+
+ // Populate the invisible URLs list column
+ //data[iCnt][5] = credManager.getServiceURLsForKeyPair(alias);
+
+ // Populate the invisible alias column
+ data[iCnt][6] = alias;
+
+ iCnt++;
+ }
+
+ fireTableDataChanged();
+ }
+
+ /**
+ * Get the number of columns in the table.
+ */
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ /**
+ * Get the number of rows in the table.
+ */
+ @Override
+ public int getRowCount() {
+ return data.length;
+ }
+
+ /**
+ * Get the name of the column at the given position.
+ */
+ @Override
+ public String getColumnName(int iCol) {
+ return columnNames[iCol];
+ }
+
+ /**
+ * Get the cell value at the given row and column position.
+ */
+ @Override
+ public Object getValueAt(int iRow, int iCol) {
+ return data[iRow][iCol];
+ }
+
+ /**
+ * Get the class at of the cells at the given column position.
+ */
+ @Override
+ public Class<? extends Object> getColumnClass(int iCol) {
+ return getValueAt(0, iCol).getClass();
+ }
+
+ /**
+ * Is the cell at the given row and column position editable?
+ */
+ @Override
+ public boolean isCellEditable(int iRow, int iCol) {
+ // The table is always read-only
+ return false;
+ }
+
+ @Override
+ public void notify(Observable<KeystoreChangedEvent> sender,
+ KeystoreChangedEvent message) throws Exception {
+ // reload the table
+ if (message.keystoreType.equals(KEYSTORE))
+ load();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewEditPasswordEntryDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewEditPasswordEntryDialog.java
new file mode 100644
index 0000000..3e80f8f
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewEditPasswordEntryDialog.java
@@ -0,0 +1,397 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ALERT_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ERROR_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.WARN_TITLE;
+
+import java.awt.BorderLayout;
+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.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import javax.swing.JPanel;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Dialog used for editing or entering new service URI, username or password for
+ * a password entry.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class NewEditPasswordEntryDialog extends NonBlockedHelpEnabledDialog
+{
+ private static final Logger logger = Logger
+ .getLogger(NewEditPasswordEntryDialog.class);
+ /** 'Edit' mode constant - the dialog is in the 'edit' entry mode */
+ private static final String EDIT_MODE = "EDIT";
+ /** 'New' mode constant - the dialog is in the 'new' entry mode */
+ private static final String NEW_MODE = "NEW";
+
+ /**
+ * Mode of this dialog - {@link #NEW_MODE} for entering new password entry
+ * and {@link #EDIT_MODE} for editting an existing password entry
+ */
+ String mode;
+ /** Service URI field */
+ private JTextField serviceURIField;
+ /** Username field */
+ private JTextField usernameField;
+ /** First password entry field */
+ private JPasswordField passwordField;
+ /** Password confirmation entry field */
+ private JPasswordField passwordConfirmField;
+ /** Stores service URI entered */
+ private URI serviceURI;
+ /** Stores previous service URI for {@link #EDIT_MODE} */
+ private URI serviceURIOld;
+ /** Stores username entered */
+ private String username;
+ /** Stores password entered*/
+ private String password;
+ private CredentialManager credentialManager;
+
+ public NewEditPasswordEntryDialog(JFrame parent, String title,
+ boolean modal, URI currentURI, String currentUsername,
+ String currentPassword, CredentialManager credentialManager) {
+ super(parent, title, modal);
+ serviceURI = currentURI;
+ username = currentUsername;
+ password = currentPassword;
+ this.credentialManager = credentialManager;
+ if (serviceURI == null && username == null && password == null) {
+ // if passed values are all null
+ mode = NEW_MODE; // dialog is for entering a new password entry
+ } else {
+ mode = EDIT_MODE; // dialog is for editing an existing entry
+ serviceURIOld = currentURI;
+ }
+ initComponents();
+ }
+
+ public NewEditPasswordEntryDialog(JDialog parent, String title,
+ boolean modal, URI currentURI, String currentUsername,
+ String currentPassword, CredentialManager credentialManager) {
+ super(parent, title, modal);
+ serviceURI = currentURI;
+ username = currentUsername;
+ password = currentPassword;
+ this.credentialManager = credentialManager;
+ if (serviceURI == null && username == null && password == null) {
+ // if passed values are all null
+ mode = NEW_MODE; // dialog is for entering new password entry
+ } else {
+ mode = EDIT_MODE; // dialog is for editing existing entry
+ serviceURIOld = currentURI;
+ }
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel serviceURILabel = new JLabel("Service URI");
+ serviceURILabel.setBorder(new EmptyBorder(0,5,0,0));
+
+ JLabel usernameLabel = new JLabel("Username");
+ usernameLabel.setBorder(new EmptyBorder(0,5,0,0));
+
+ JLabel passwordLabel = new JLabel("Password");
+ passwordLabel.setBorder(new EmptyBorder(0,5,0,0));
+
+ JLabel passwordConfirmLabel = new JLabel("Confirm password");
+ passwordConfirmLabel.setBorder(new EmptyBorder(0,5,0,0));
+
+ serviceURIField = new JTextField();
+ //jtfServiceURI.setBorder(new EmptyBorder(0,0,0,5));
+
+ usernameField = new JTextField(15);
+ //jtfUsername.setBorder(new EmptyBorder(0,0,0,5));
+
+ passwordField = new JPasswordField(15);
+ //jpfFirstPassword.setBorder(new EmptyBorder(0,0,0,5));
+
+ passwordConfirmField = new JPasswordField(15);
+ //jpfConfirmPassword.setBorder(new EmptyBorder(0,0,0,5));
+
+ //If in EDIT_MODE - populate the fields with current values
+ if (mode.equals(EDIT_MODE)) {
+ serviceURIField.setText(serviceURI.toASCIIString());
+ usernameField.setText(username);
+ passwordField.setText(password);
+ passwordConfirmField.setText(password);
+ }
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+
+ JPanel passwordPanel = new JPanel(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.weighty = 0.0;
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ passwordPanel.add(serviceURILabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ passwordPanel.add(serviceURIField, gbc);
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ passwordPanel.add(usernameLabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 1;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ passwordPanel.add(usernameField, gbc);
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ passwordPanel.add(passwordLabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 2;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ passwordPanel.add(passwordField, gbc);
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 3;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ passwordPanel.add(passwordConfirmLabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 3;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ passwordPanel.add(passwordConfirmField, gbc);
+
+ passwordPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10,
+ 10), new EtchedBorder()));
+
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(okButton);
+ buttonsPanel.add(cancelButton);
+
+ getContentPane().add(passwordPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ //setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ /**
+ * Get the username entered in the dialog.
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Get the service URI entered in the dialog.
+ */
+ public URI getServiceURI() {
+ return serviceURI;
+ }
+
+ /**
+ * Get the password entered in the dialog.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Checks that the user has entered a non-empty service URI, a non-empty
+ * username, a non-empty password and that an entry with the same URI
+ * already does not already exist in the Keystore. Store the new password.
+ */
+ private boolean checkControls() {
+ String serviceURIString = new String(serviceURIField.getText());
+ if (serviceURIString.isEmpty()) {
+ showMessageDialog(this, "Service URI cannot be empty",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+ try {
+ serviceURI = new URI(serviceURIString);
+ } catch (URISyntaxException e) {
+ showMessageDialog(this, "Service URI is not a valid URI",
+ WARN_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+
+ username = new String(usernameField.getText());
+ if (username.isEmpty()) {
+ showMessageDialog(this, "Username cannot be empty", WARN_TITLE,
+ WARNING_MESSAGE);
+ return false;
+ }
+
+ String firstPassword = new String(passwordField.getPassword());
+ String confirmPassword = new String(passwordConfirmField.getPassword());
+
+ if (!firstPassword.equals(confirmPassword)) {
+ // passwords do not match
+ showMessageDialog(this, "Passwords do not match", WARN_TITLE,
+ WARNING_MESSAGE);
+ return false;
+ }
+ if (firstPassword.isEmpty()) {
+ // passwords match but are empty
+ showMessageDialog(this, "Password cannot be empty", WARN_TITLE,
+ WARNING_MESSAGE);
+ return false;
+ }
+
+ // passwords the same and non-empty
+ password = firstPassword;
+
+ // Check if the entered service URL is already associated with another password entry in the Keystore
+ List<URI> uriList = null;
+ try {
+ uriList = credentialManager.getServiceURIsForAllUsernameAndPasswordPairs();
+ } catch (CMException cme) {
+ // Failed to instantiate Credential Manager - warn the user and exit
+ String exMessage = "Failed to instantiate Credential Manager to check for duplicate service URIs.";
+ logger.error(exMessage, cme);
+ showMessageDialog(new JFrame(), exMessage, ERROR_TITLE,
+ ERROR_MESSAGE);
+ return false;
+ }
+
+ if (uriList != null) { // should not be null really (although can be empty). Check anyway.
+ if (mode.equals(EDIT_MODE)) // edit mode
+ // Remove the current entry's service URI from the list
+ uriList.remove(serviceURIOld);
+
+ if (uriList.contains(serviceURI)) { // found another entry for this service URI
+ // Warn the user and exit
+ showMessageDialog(
+ this,
+ "The entered service URI is already associated with another password entry",
+ ALERT_TITLE, WARNING_MESSAGE);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void okPressed() {
+ if (checkControls())
+ closeDialog();
+ }
+
+ private void cancelPressed() {
+ // Set all fields to null to indicate that cancel button was pressed
+ serviceURI = null;
+ username = null;
+ password = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewKeyPairEntryDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewKeyPairEntryDialog.java
new file mode 100644
index 0000000..36e3015
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewKeyPairEntryDialog.java
@@ -0,0 +1,304 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.BorderLayout.WEST;
+import static java.awt.Font.PLAIN;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.ListSelectionModel.SINGLE_SELECTION;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ALERT_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ERROR_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Allows the user import a key pair from a PKCS #12 file (keystore).
+ */
+@SuppressWarnings("serial")
+class NewKeyPairEntryDialog extends NonBlockedHelpEnabledDialog {
+ //private static final Logger logger = Logger.getLogger(NewKeyPairEntryDialog.class);
+
+ /** List of key pairs available for import */
+ private JList<String> keyPairsJList;
+ /** PKCS #12 keystore */
+ private KeyStore pkcs12KeyStore;
+ /** Private key part of the key pair chosen by the user for import */
+ private Key privateKey;
+ /** Certificate chain part of the key pair chosen by the user for import */
+ private Certificate[] certificateChain;
+ /** Key pair alias to be used for this entry in the Keystore */
+ private String alias;
+ private final DistinguishedNameParser dnParser;
+
+ public NewKeyPairEntryDialog(JFrame parent, String title, boolean modal,
+ KeyStore pkcs12KeyStore, DistinguishedNameParser dnParser)
+ throws CMException {
+ super(parent, title, modal);
+ this.pkcs12KeyStore = pkcs12KeyStore;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ public NewKeyPairEntryDialog(JDialog parent, String title, boolean modal,
+ KeyStore pkcs12KeyStore, DistinguishedNameParser dnParser)
+ throws CMException {
+ super(parent, title, modal);
+ this.pkcs12KeyStore = pkcs12KeyStore;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ /**
+ * Get the private part of the key pair.
+ */
+ public Key getPrivateKey() {
+ return privateKey;
+ }
+
+ /**
+ * Get the certificate chain part of the key pair.
+ */
+ public Certificate[] getCertificateChain() {
+ return certificateChain;
+ }
+
+ /**
+ * Get the keystore alias of the key pair.
+ */
+ public String getAlias() {
+ return alias;
+ }
+
+ private void initComponents() throws CMException {
+ // Instructions
+ JLabel instructionsLabel = new JLabel("Select a key pair to import:");
+ instructionsLabel.setFont(new Font(null, PLAIN, 11));
+ instructionsLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JPanel instructionsPanel = new JPanel(new BorderLayout());
+ instructionsPanel.add(instructionsLabel, WEST);
+
+ // Import button
+ final JButton importButton = new JButton("Import");
+ importButton.setEnabled(false);
+ importButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ importPressed();
+ }
+ });
+
+ // Certificate details button
+ final JButton certificateDetailsButton = new JButton("Details");
+ certificateDetailsButton.setEnabled(false);
+ certificateDetailsButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ certificateDetailsPressed();
+ }
+ });
+
+ // List to hold keystore's key pairs
+ keyPairsJList = new JList<>();
+ keyPairsJList.setSelectionMode(SINGLE_SELECTION);
+ keyPairsJList.addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent evt) {
+ boolean enabled = keyPairsJList.getSelectedIndex() >= 0;
+ importButton.setEnabled(enabled);
+ certificateDetailsButton.setEnabled(enabled);
+ }
+ });
+
+ // Put the key list into a scroll pane
+ JScrollPane keyPairsScrollPane = new JScrollPane(keyPairsJList,
+ VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ keyPairsScrollPane.getViewport().setBackground(
+ keyPairsJList.getBackground());
+
+ JPanel keyPairsPanel = new JPanel();
+ keyPairsPanel.setLayout(new BoxLayout(keyPairsPanel, Y_AXIS));
+ keyPairsPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ instructionsPanel.setAlignmentY(LEFT_ALIGNMENT);
+ keyPairsPanel.add(instructionsPanel);
+ keyPairsScrollPane.setAlignmentY(LEFT_ALIGNMENT);
+ keyPairsPanel.add(keyPairsScrollPane);
+
+ // Cancel button
+ final JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(certificateDetailsButton);
+ buttonsPanel.add(importButton);
+ buttonsPanel.add(cancelButton);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(keyPairsPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ // Populate the list
+ populateKeyPairList();
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(importButton);
+ pack();
+ }
+
+ /**
+ * Populate the key pair list with the PKCS #12 keystore's key pair aliases.
+ */
+ private void populateKeyPairList() throws CMException {
+ try {
+ List<String> keyPairAliases = new ArrayList<>();
+
+ Enumeration<String> aliases = pkcs12KeyStore.aliases();
+ while (aliases.hasMoreElements()) {
+ String alias = aliases.nextElement();
+
+ if (pkcs12KeyStore.isKeyEntry(alias)) {
+ pkcs12KeyStore.getKey(alias, new char[] {});
+ Certificate[] certs = pkcs12KeyStore
+ .getCertificateChain(alias);
+ if (certs != null && certs.length != 0)
+ keyPairAliases.add(alias);
+ }
+ }
+
+ if (!keyPairAliases.isEmpty()) {
+ keyPairsJList.setListData(keyPairAliases.toArray(new String[0]));
+ keyPairsJList.setSelectedIndex(0);
+ } else
+ // No key pairs were found - warn the user
+ showMessageDialog(this,
+ "No private key pairs were found in the file",
+ ALERT_TITLE, WARNING_MESSAGE);
+ } catch (GeneralSecurityException ex) {
+ throw new CMException("Problem occured while reading the PKCS #12 file.",
+ ex);
+ }
+ }
+
+ /**
+ * Display the selected key pair's certificate.
+ */
+ private void certificateDetailsPressed() {
+ try {
+ String alias = (String) keyPairsJList.getSelectedValue();
+
+ // Convert the certificate object into an X509Certificate object.
+ X509Certificate cert = dnParser.convertCertificate(pkcs12KeyStore
+ .getCertificate(alias));
+
+ ViewCertDetailsDialog viewCertificateDialog = new ViewCertDetailsDialog(
+ this, "Certificate details", true, (X509Certificate) cert,
+ null, dnParser);
+ viewCertificateDialog.setLocationRelativeTo(this);
+ viewCertificateDialog.setVisible(true);
+ } catch (Exception ex) {
+ showMessageDialog(this,
+ "Failed to obtain certificate details to show",
+ ALERT_TITLE, WARNING_MESSAGE);
+ closeDialog();
+ }
+ }
+
+ public void importPressed() {
+ String alias = (String) keyPairsJList.getSelectedValue();
+ try {
+ privateKey = pkcs12KeyStore.getKey(alias, new char[] {});
+ certificateChain = pkcs12KeyStore.getCertificateChain(alias);
+ this.alias = alias;
+ } catch (Exception ex) {
+ showMessageDialog(
+ this,
+ "Failed to load the private key and certificate chain from the PKCS #12 file.",
+ ERROR_TITLE, ERROR_MESSAGE);
+ }
+
+ closeDialog();
+ }
+
+ public void cancelPressed() {
+ /*
+ * Set everything to null, just in case some of the values have been set
+ * previously and the user pressed 'cancel' after that.
+ */
+ privateKey = null;
+ certificateChain = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewTrustCertsDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewTrustCertsDialog.java
new file mode 100644
index 0000000..26854ec
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/NewTrustCertsDialog.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.BorderLayout.WEST;
+import static java.awt.Font.PLAIN;
+import static javax.security.auth.x500.X500Principal.RFC2253;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ALERT_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.security.credentialmanager.ParsedDistinguishedName;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Allows the user to import one or more trusted certificates from a file.
+ */
+@SuppressWarnings("serial")
+public class NewTrustCertsDialog extends NonBlockedHelpEnabledDialog {
+ private JList<String> trustedCertsJList;
+ /** List of trusted certs read from the file and available for import */
+ private ArrayList<X509Certificate> availableTrustedCerts = new ArrayList<>();
+ /** List of trusted certs selected for import */
+ private ArrayList<X509Certificate> selectedTrustedCerts;
+ private final DistinguishedNameParser dnParser;
+
+ public NewTrustCertsDialog(JFrame parent, String title, boolean modal,
+ ArrayList<X509Certificate> lCerts, DistinguishedNameParser dnParser) {
+ super(parent, title, modal);
+ availableTrustedCerts = lCerts;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ public NewTrustCertsDialog(JDialog parent, String title, boolean modal,
+ ArrayList<X509Certificate> lCerts, DistinguishedNameParser dnParser) {
+ super(parent, title, modal);
+ availableTrustedCerts = lCerts;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ private void initComponents() {
+ // Instructions
+ JLabel instructionsLabel = new JLabel(
+ "Select one or more certificates for import:");
+ instructionsLabel.setFont(new Font(null, PLAIN, 11));
+ instructionsLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JPanel instructionsPanel = new JPanel(new BorderLayout());
+ instructionsPanel.add(instructionsLabel, WEST);
+
+ // Import button
+ final JButton importButton = new JButton("Import");
+ importButton.setEnabled(false);
+ importButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ importPressed();
+ }
+ });
+
+ // Certificate details button
+ final JButton certificateDetailsButton = new JButton(
+ "Certificate Details");
+ certificateDetailsButton.setEnabled(false);
+ certificateDetailsButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ certificateDetailsPressed();
+ }
+ });
+
+ // List with trusted certs' aliases
+ trustedCertsJList = new JList<>();
+ trustedCertsJList.setSelectionMode(MULTIPLE_INTERVAL_SELECTION);
+ trustedCertsJList.addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent evt) {
+ boolean enabled = trustedCertsJList.getSelectedIndex() >= 0;
+ importButton.setEnabled(enabled);
+ certificateDetailsButton.setEnabled(enabled);
+ }
+ });
+ // Populate the list - get the certificate subjects' CNs
+ ArrayList<String> cns = new ArrayList<>();
+ for (int i = 0; i < availableTrustedCerts.size(); i++) {
+ String subjectDN = ((X509Certificate) availableTrustedCerts.get(i))
+ .getSubjectX500Principal().getName(RFC2253);
+ ParsedDistinguishedName parsedDN = dnParser.parseDN(subjectDN);
+ String subjectCN = parsedDN.getCN();
+ cns.add(i, subjectCN);
+ }
+ trustedCertsJList.setListData(cns.toArray(new String[0]));
+ trustedCertsJList.setSelectedIndex(0);
+
+ // Put the list into a scroll pane
+ JScrollPane trustedCertsScrollPanel = new JScrollPane(
+ trustedCertsJList, VERTICAL_SCROLLBAR_AS_NEEDED,
+ HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ trustedCertsScrollPanel.getViewport().setBackground(
+ trustedCertsJList.getBackground());
+
+ JPanel trustedCertsPanel = new JPanel();
+ trustedCertsPanel.setLayout(new BoxLayout(trustedCertsPanel, Y_AXIS));
+ trustedCertsPanel.setBorder(new CompoundBorder(new CompoundBorder(
+ new EmptyBorder(5, 5, 5, 5), new EtchedBorder()),
+ new EmptyBorder(5, 5, 5, 5)));
+
+ instructionsPanel.setAlignmentY(LEFT_ALIGNMENT);
+ trustedCertsPanel.add(instructionsPanel);
+ trustedCertsScrollPanel.setAlignmentY(LEFT_ALIGNMENT);
+ trustedCertsPanel.add(trustedCertsScrollPanel);
+ certificateDetailsButton.setAlignmentY(RIGHT_ALIGNMENT);
+ trustedCertsPanel.add(certificateDetailsButton);
+
+ // Cancel button
+ final JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+
+ JPanel jpButtons = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ jpButtons.add(importButton);
+ jpButtons.add(cancelButton);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(trustedCertsPanel, CENTER);
+ getContentPane().add(jpButtons, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(importButton);
+ pack();
+ }
+
+ /**
+ * Shows the selected key pair's certificate.
+ */
+ private void certificateDetailsPressed() {
+ try {
+ int i = trustedCertsJList.getSelectedIndex();
+
+ X509Certificate cert = (X509Certificate) availableTrustedCerts
+ .get(i);
+
+ ViewCertDetailsDialog viewCertificateDialog = new ViewCertDetailsDialog(
+ this, "Certificate details", true, cert, null, dnParser);
+ viewCertificateDialog.setLocationRelativeTo(this);
+ viewCertificateDialog.setVisible(true);
+ } catch (Exception ex) {
+ showMessageDialog(this,
+ "Failed to obtain certificate details to show",
+ ALERT_TITLE, WARNING_MESSAGE);
+ closeDialog();
+ }
+ }
+
+ /**
+ * Get the trusted certificates selected for import.
+ */
+ public ArrayList<X509Certificate> getTrustedCertificates() {
+ return selectedTrustedCerts;
+ }
+
+ /**
+ * Store the selected trusted certs.
+ */
+ public void importPressed() {
+ int[] selectedValues = trustedCertsJList.getSelectedIndices();
+ selectedTrustedCerts = new ArrayList<>();
+ for (int i = 0; i < selectedValues.length; i++)
+ selectedTrustedCerts.add(availableTrustedCerts
+ .get(selectedValues[i]));
+ closeDialog();
+ }
+
+ public void cancelPressed() {
+ /*
+ * Set selectedTrustCerts to null to indicate that user has cancelled
+ * the import
+ */
+ selectedTrustedCerts = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/PasswordsTableModel.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/PasswordsTableModel.java
new file mode 100644
index 0000000..9715fc6
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/PasswordsTableModel.java
@@ -0,0 +1,227 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.KEYSTORE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ERROR_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI.PASSWORD_ENTRY_TYPE;
+
+import java.net.URI;
+import java.util.TreeMap;
+
+import javax.swing.JFrame;
+import javax.swing.table.AbstractTableModel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.KeystoreChangedEvent;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The table model used to display the Keystore's username/password pair
+ * entries.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class PasswordsTableModel extends AbstractTableModel implements
+ Observer<KeystoreChangedEvent> {
+ private static final Logger logger = Logger
+ .getLogger(PasswordsTableModel.class);
+
+ // Column names
+ private String[] columnNames;
+ // Table data
+ private Object[][] data;
+ private CredentialManager credManager;
+
+ public PasswordsTableModel(CredentialManager credentialManager) {
+ credManager = credentialManager;
+ if (credentialManager == null) {
+ // Failed to instantiate Credential Manager - warn the user and exit
+ String sMessage = "Failed to instantiate Credential Manager. ";
+ logger.error("CM GUI: " + sMessage);
+ showMessageDialog(new JFrame(), sMessage, ERROR_TITLE,
+ ERROR_MESSAGE);
+ return;
+ }
+
+ data = new Object[0][0];
+ columnNames = new String[] { "Entry Type", // type of the Keystore entry
+ "Service URL", // the service url, part of the actual alias in
+ // the Keystore
+ "Username", // username for the service, part of the password
+ // entry in the Keystore
+ "Last Modified", // last modified date of the entry
+ "Password", // the invisible column holding the password value
+ // of the password entry in the Keystore
+ "Alias" // the invisible column holding the Keystore alias of
+ // the entry
+ };
+
+ try {
+ load();
+ } catch (CMException cme) {
+ String sMessage = "Failed to load username and password pairs";
+ logger.error(sMessage);
+ showMessageDialog(new JFrame(), sMessage, ERROR_TITLE,
+ ERROR_MESSAGE);
+ return;
+ }
+
+ // Start observing changes to the Keystore
+ credManager.addObserver(this);
+ }
+
+ /**
+ * Load the PasswordsTableModel with the password entries from the Keystore.
+ */
+ public void load() throws CMException {
+ // Place password entries' aliases in a tree map to sort them
+ TreeMap<String, String> aliases = new TreeMap<>();
+
+ for (String alias : credManager.getAliases(KEYSTORE))
+ /*
+ * We are only interested in username/password entries here. Alias
+ * for such entries is constructed as "password#"<SERVICE_URL> where
+ * service URL is the service this username/password pair is to be
+ * used for.
+ */
+ if (alias.startsWith("password#"))
+ aliases.put(alias, alias);
+
+ // Create one table row for each password entry
+ data = new Object[aliases.size()][6];
+
+ /*
+ * Iterate through the sorted aliases, retrieving the password entries
+ * and populating the table model
+ */
+ int iCnt = 0;
+ for (String alias : aliases.values()) {
+ /*
+ * Populate the type column - it is set with an integer but a custom
+ * cell renderer will cause a suitable icon to be displayed
+ */
+ data[iCnt][0] = PASSWORD_ENTRY_TYPE;
+
+ /*
+ * Populate the service URL column as a substring of alias from the
+ * first occurrence of '#' till the end of the string
+ */
+ String serviceURL = alias.substring(alias.indexOf('#') + 1);
+ data[iCnt][1] = serviceURL;
+
+ /*
+ * Get the username and password pair from the Keystore. They are
+ * returned in a single string in format
+ * <USERNAME><SEPARATOR_CHARACTER><PASSWORD>
+ */
+ UsernamePassword usernamePassword = credManager
+ .getUsernameAndPasswordForService(URI.create(serviceURL),
+ false, "");
+ String username = usernamePassword.getUsername();
+ String password = usernamePassword.getPasswordAsString();
+
+ // Populate the username column
+ data[iCnt][2] = username;
+
+ // Populate the last modified date column ("UBER" keystore type
+ // supports creation date)
+ // data[iCnt][3] =
+ // credManager.getEntryCreationDate(CredentialManager.KEYSTORE,
+ // alias);
+
+ // Populate the invisible password column
+ data[iCnt][4] = password;
+
+ // Populate the invisible alias column
+ data[iCnt][5] = alias;
+
+ iCnt++;
+ }
+
+ fireTableDataChanged();
+ }
+
+ /**
+ * Get the number of columns in the table.
+ */
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ /**
+ * Get the number of rows in the table.
+ */
+ @Override
+ public int getRowCount() {
+ return data.length;
+ }
+
+ /**
+ * Get the name of the column at the given position.
+ */
+ @Override
+ public String getColumnName(int iCol) {
+ return columnNames[iCol];
+ }
+
+ /**
+ * Get the cell value at the given row and column position.
+ */
+ @Override
+ public Object getValueAt(int iRow, int iCol) {
+ return data[iRow][iCol];
+ }
+
+ /**
+ * Get the class at of the cells at the given column position.
+ */
+ @Override
+ public Class<? extends Object> getColumnClass(int iCol) {
+ return getValueAt(0, iCol).getClass();
+ }
+
+ /**
+ * Is the cell at the given row and column position editable?
+ */
+ @Override
+ public boolean isCellEditable(int iRow, int iCol) {
+ // The table is always read-only
+ return false;
+ }
+
+ @Override
+ public void notify(Observable<KeystoreChangedEvent> sender,
+ KeystoreChangedEvent message) throws Exception {
+ // reload the table
+ if (message.keystoreType.equals(KEYSTORE))
+ load();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/SetMasterPasswordDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/SetMasterPasswordDialog.java
new file mode 100644
index 0000000..bae6068
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/SetMasterPasswordDialog.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Font.PLAIN;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.WARN_TITLE;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Dialog used for user to set a master password for Credential Manager.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class SetMasterPasswordDialog extends NonBlockedHelpEnabledDialog {
+ /** Password entry field */
+ private JPasswordField passwordField;
+ /** Password confirmation entry field */
+ private JPasswordField passwordConfirmField;
+ /** The entered password */
+ private String password = null;
+ /** Instructions for the user */
+ private String instructions;
+
+ public SetMasterPasswordDialog(JFrame parent, String title, boolean modal,
+ String instructions) {
+ super(parent, title, modal);
+ this.instructions = instructions;
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel instructionsLabel = new JLabel(instructions);
+ instructionsLabel.setFont(new Font(null, PLAIN, 11));
+
+ JPanel instructionsPanel = new JPanel();
+ instructionsPanel.setLayout(new BoxLayout(instructionsPanel, Y_AXIS));
+ instructionsPanel.add(instructionsLabel);
+ instructionsPanel.setBorder(new EmptyBorder(10, 5, 10, 0));
+
+ JLabel passwordLabel = new JLabel("Master password");
+ passwordLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ JLabel passwordConfirmLabel = new JLabel("Confirm master password");
+ passwordConfirmLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ passwordField = new JPasswordField(15);
+ passwordConfirmField = new JPasswordField(15);
+
+ JPanel passwordPanel = new JPanel(new GridLayout(2, 2, 5, 5));
+ passwordPanel.add(passwordLabel);
+ passwordPanel.add(passwordField);
+ passwordPanel.add(passwordConfirmLabel);
+ passwordPanel.add(passwordConfirmField);
+
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10, 10),
+ new EtchedBorder()));
+ mainPanel.add(instructionsPanel, NORTH);
+ mainPanel.add(passwordPanel, CENTER);
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(okButton);
+ buttonsPanel.add(cancelButton);
+
+ getContentPane().add(mainPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Check that the user has entered a non-empty password and store the new
+ * password.
+ */
+ private boolean checkPassword() {
+ String firstPassword = new String(passwordField.getPassword());
+ String confirmPassword = new String(passwordConfirmField.getPassword());
+
+ if (!firstPassword.equals(confirmPassword)) {
+ showMessageDialog(this, "The passwords do not match", WARN_TITLE,
+ WARNING_MESSAGE);
+ return false;
+ }
+ if (firstPassword.isEmpty()) {
+ // passwords match but are empty
+ showMessageDialog(this, "The password cannot be empty", WARN_TITLE,
+ WARNING_MESSAGE);
+ return false;
+ }
+
+ // passwords match and not empty
+ password = firstPassword;
+ return true;
+ }
+
+ private void okPressed() {
+ if (checkPassword())
+ closeDialog();
+ }
+
+ private void cancelPressed() {
+ /*
+ * Set the password to null as it might have changed in the meantime if
+ * user entered something then cancelled.
+ */
+ password = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TableCellRenderer.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TableCellRenderer.java
new file mode 100644
index 0000000..0eaae99
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TableCellRenderer.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI.KEY_PAIR_ENTRY_TYPE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI.PASSWORD_ENTRY_TYPE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI.TRUST_CERT_ENTRY_TYPE;
+
+import java.awt.Component;
+//import java.text.DateFormat;
+//import java.util.Date;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.EmptyBorder;
+import javax.swing.table.DefaultTableCellRenderer;
+//import net.sf.taverna.t2.workbench.ui.credentialmanager.KeyPairsTableModel;
+//import net.sf.taverna.t2.workbench.ui.credentialmanager.PasswordsTableModel;
+//import net.sf.taverna.t2.workbench.ui.credentialmanager.TrustedCertsTableModel;
+
+/**
+ * Custom cell renderer for the cells of the tables displaying
+ * Keystore/Truststore contents.
+ *
+ * @author Alex Nenadic
+ */
+public class TableCellRenderer extends DefaultTableCellRenderer {
+ private static final long serialVersionUID = -3983986682794010259L;
+
+ private final ImageIcon passwordEntryIcon = new ImageIcon(
+ TableCellRenderer.class.getResource("/images/table/key_entry.png"));
+ private final ImageIcon keypairEntryIcon = new ImageIcon(
+ TableCellRenderer.class
+ .getResource("/images/table/keypair_entry.png"));
+ private final ImageIcon trustcertEntryIcon = new ImageIcon(
+ TableCellRenderer.class
+ .getResource("/images/table/trustcert_entry.png"));
+
+ /**
+ * Get the rendered cell for the supplied value and column.
+ */
+ @Override
+ public Component getTableCellRendererComponent(JTable keyStoreTable,
+ Object value, boolean bIsSelected, boolean bHasFocus, int iRow,
+ int iCol) {
+ JLabel cell = (JLabel) super.getTableCellRendererComponent(
+ keyStoreTable, value, bIsSelected, bHasFocus, iRow, iCol);
+
+ if (value != null) {
+ // Type column - display an icon representing the type
+ if (iCol == 0)
+ configureTypeColumn(value, cell);
+ // Last Modified column - format date (if date supplied)
+ /*else if (((keyStoreTable.getModel() instanceof PasswordsTableModel) && (iCol == 3)) ||
+ ((keyStoreTable.getModel() instanceof KeyPairsTableModel) && (iCol == 4))||
+ ((keyStoreTable.getModel() instanceof TrustedCertsTableModel) && (iCol == 4))){
+ if (value instanceof Date) {
+ // Include timezone
+ cell.setText(DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+ DateFormat.LONG).format((Date) value));
+ } else {
+ cell.setText(value.toString());
+ }
+ }*/
+ // Other columns - just use their text values
+ else
+ cell.setText(value.toString());
+ }
+
+ cell.setBorder(new EmptyBorder(0, 5, 0, 5));
+ return cell;
+ }
+
+ private void configureTypeColumn(Object value, JLabel cell) {
+ ImageIcon icon = null;
+ // The cell is in the first column of Passwords table
+ if (PASSWORD_ENTRY_TYPE.equals(value)) {
+ icon = passwordEntryIcon; // key (i.e. password) entry image
+ }
+ // The cell is in the first column of Key Pairs table
+ else if (KEY_PAIR_ENTRY_TYPE.equals(value)) {
+ icon = keypairEntryIcon; // key pair entry image
+ }
+ // The cell is in the first column of Trusted Certificates table
+ else if (TRUST_CERT_ENTRY_TYPE.equals(value)) {
+ icon = trustcertEntryIcon; // trust. certificate entry image
+ }
+
+ cell.setIcon(icon);
+ cell.setText("");
+ cell.setVerticalAlignment(CENTER);
+ cell.setHorizontalAlignment(CENTER);
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TableHeaderRenderer.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TableHeaderRenderer.java
new file mode 100644
index 0000000..8070b98
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TableHeaderRenderer.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static javax.swing.border.BevelBorder.RAISED;
+
+import java.awt.Component;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import net.sf.taverna.t2.workbench.ui.credentialmanager.KeyPairsTableModel;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.PasswordsTableModel;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.TableHeaderRenderer;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.TrustedCertsTableModel;
+
+/**
+ * Custom cell renderer for the headers of the tables displaying
+ * the Keystore/Truststore contents.
+ */
+@SuppressWarnings("serial")
+public class TableHeaderRenderer extends DefaultTableCellRenderer {
+ private final ImageIcon entryTypeIcon = new ImageIcon(
+ TableHeaderRenderer.class
+ .getResource("/images/table/entry_heading.png"));
+
+ @Override
+ public Component getTableCellRendererComponent(JTable jtKeyStoreTable,
+ Object value, boolean bIsSelected, boolean bHasFocus, int iRow,
+ int iCol) {
+ // Get header renderer
+ JLabel header = (JLabel) jtKeyStoreTable.getColumnModel().getColumn(iCol).getHeaderRenderer();
+
+ // The entry type header contains an icon for every table
+ if (iCol == 0) {
+ header.setText("");
+ header.setIcon(entryTypeIcon); // entry type icon (header for the first column of the table)
+ header.setHorizontalAlignment(CENTER);
+ header.setVerticalAlignment(CENTER);
+ header.setToolTipText("Entry type");
+ }
+ // All other headers contain text
+ else {
+ header.setText((String) value);
+ header.setHorizontalAlignment(LEFT);
+
+ // Passwords table
+ if (jtKeyStoreTable.getModel() instanceof PasswordsTableModel){
+ if (iCol == 1) //Service URL column
+ header.setToolTipText("URL of the service username and password will be used for");
+ else if (iCol == 2) // Username column
+ header.setToolTipText("Username for the service");
+ }
+ // Key pairs table
+ else if (jtKeyStoreTable.getModel() instanceof KeyPairsTableModel) {
+ if (iCol == 1) // Owner
+ header.setToolTipText("Certificate's owner");
+ else if (iCol == 2) // Issuer
+ header.setToolTipText("Certificate's issuer");
+ else if (iCol == 3) // Serial number
+ header.setToolTipText("Certificate's serial number");
+ }
+ // Trusted certs table
+ else if (jtKeyStoreTable.getModel() instanceof TrustedCertsTableModel) {
+ if (iCol == 1) // Owner
+ header.setToolTipText("Certificate's owner");
+ else if (iCol == 2) // Issuer
+ header.setToolTipText("Certificate's issuer");
+ else if (iCol == 3) // Serial number
+ header.setToolTipText("Certificate's serial number");
+ }
+ }
+ header.setBorder(new CompoundBorder(new BevelBorder(RAISED),
+ new EmptyBorder(0, 5, 0, 5)));
+ return header;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TrustedCertsTableModel.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TrustedCertsTableModel.java
new file mode 100644
index 0000000..5189eb2
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/TrustedCertsTableModel.java
@@ -0,0 +1,216 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.TRUSTSTORE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CMStrings.ERROR_TITLE;
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI.TRUST_CERT_ENTRY_TYPE;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.swing.JFrame;
+import javax.swing.table.AbstractTableModel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.KeystoreChangedEvent;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The table model used to display the Keystore's trusted certificate entries.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class TrustedCertsTableModel extends AbstractTableModel implements
+ Observer<KeystoreChangedEvent> {
+ private static final Logger logger = Logger
+ .getLogger(TrustedCertsTableModel.class);
+
+ // Column names
+ private String[] columnNames;
+ // Table data
+ private Object[][] data;
+ private CredentialManager credManager;
+
+ public TrustedCertsTableModel(CredentialManager credentialManager) {
+ credManager = credentialManager;
+ if (credentialManager == null) {
+ // Failed to instantiate Credential Manager - warn the user and exit
+ String sMessage = "Failed to instantiate Credential Manager. ";
+ logger.error("CM GUI: "+ sMessage);
+ showMessageDialog(new JFrame(), sMessage, ERROR_TITLE,
+ ERROR_MESSAGE);
+ return;
+ }
+
+ data = new Object[0][0];
+ columnNames = new String[] {
+ "Entry Type", // type of the Keystore entry
+ "Owner", // owner's common name
+ "Issuer", // issuer's common name
+ "Serial Number", // public key certificate's serial number
+ "Last Modified", // last modified date of the entry
+ "Alias" // the invisible column holding the actual alias in the Keystore
+ };
+
+ try {
+ load();
+ } catch (CMException cme) {
+ String sMessage = "Failed to load trusted certificates";
+ logger.error(sMessage);
+ showMessageDialog(new JFrame(), sMessage, ERROR_TITLE,
+ ERROR_MESSAGE);
+ return;
+ }
+
+ // Start observing changes to the Keystore
+ credManager.addObserver(this);
+ }
+
+ /**
+ * Load the TrustCertsTableModel with trusted certificate entries from the Keystore.
+ */
+ public void load() throws CMException {
+ /*
+ * Place trusted certificate entries' aliases in a tree map to sort them
+ */
+ Set<String> aliases = new TreeSet<>();
+ for (String alias : credManager.getAliases(TRUSTSTORE))
+ /*
+ * We are only interested in trusted certificate entries here. Alias
+ * for such entries is constructed as
+ * "trustedcert#<CERT_SERIAL_NUMBER>#<CERT_COMMON_NAME>"
+ */
+ if (alias.startsWith("trustedcert#"))
+ aliases.add(alias);
+
+ /*
+ * Create one table row for each trusted certificate entry Each row has
+ * 4 fields - type, owner name, last modified data and the invisible
+ * alias
+ */
+ data = new Object[aliases.size()][6];
+
+ /*
+ * Iterate through the sorted aliases, retrieving the trusted
+ * certificate entries and populating the table model
+ */
+ int i = 0;
+ for (String alias : aliases) {
+ /*
+ * Populate the type column - it is set with an integer but a custom
+ * cell renderer will cause a suitable icon to be displayed
+ */
+ data[i][0] = TRUST_CERT_ENTRY_TYPE;
+
+ /*
+ * Split the alias string to extract owner, issuer and serial number
+ * alias =
+ * "trustedcert#<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"
+ * #"<CERT_SERIAL_NUMBER>
+ */
+ String[] aliasComponents = alias.split("#");
+
+ // Populate the owner column extracted from the alias
+ data[i][1] = aliasComponents[1];
+
+ // Populate the issuer column extracted from the alias
+ data[i][2] = aliasComponents[2];
+
+ // Populate the serial number column extracted from the alias
+ data[i][3] = aliasComponents[3];
+
+ // Populate the modified date column
+ //data[iCnt][4] = credManager.getEntryCreationDate(CredentialManager.TRUSTSTORE, alias);
+
+ // Populate the invisible alias column
+ data[i][5] = alias;
+
+ i++;
+ }
+
+ fireTableDataChanged();
+ }
+
+ /**
+ * Get the number of columns in the table.
+ */
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ /**
+ * Get the number of rows in the table.
+ */
+ @Override
+ public int getRowCount() {
+ return data.length;
+ }
+
+ /**
+ * Get the name of the column at the given position.
+ */
+ @Override
+ public String getColumnName(int iCol) {
+ return columnNames[iCol];
+ }
+
+ /**
+ * Get the cell value at the given row and column position.
+ */
+ @Override
+ public Object getValueAt(int iRow, int iCol) {
+ return data[iRow][iCol];
+ }
+
+ /**
+ * Get the class at of the cells at the given column position.
+ */
+ @Override
+ public Class<? extends Object> getColumnClass(int iCol) {
+ return getValueAt(0, iCol).getClass();
+ }
+
+ /**
+ * Is the cell at the given row and column position editable?
+ */
+ @Override
+ public boolean isCellEditable(int iRow, int iCol) {
+ // The table is always read-only
+ return false;
+ }
+
+ @Override
+ public void notify(Observable<KeystoreChangedEvent> sender,
+ KeystoreChangedEvent message) throws Exception {
+ // reload the table
+ if (message.keystoreType.equals(TRUSTSTORE))
+ load();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ViewCertDetailsDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ViewCertDetailsDialog.java
new file mode 100644
index 0000000..953ed2d
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ViewCertDetailsDialog.java
@@ -0,0 +1,509 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Font.BOLD;
+import static java.awt.Font.PLAIN;
+import static java.awt.GridBagConstraints.LINE_START;
+import static javax.security.auth.x500.X500Principal.RFC2253;
+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 java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+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.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.math.BigInteger;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.security.credentialmanager.ParsedDistinguishedName;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Displays the details of a X.509 certificate.
+ *
+ * Inspired by the Portlecle tool (http://portecle.sourceforge.net/). and the
+ * view certificate dialog from Firefox's Certificate Manager.
+ */
+@SuppressWarnings("serial")
+public class ViewCertDetailsDialog extends NonBlockedHelpEnabledDialog {
+ // Logger
+ //private static Logger logger = Logger.getLogger(ViewCertDetailsDialog.class);
+
+ /** Stores certificate to display*/
+ private X509Certificate cert;
+ /** Stores list of serviceURLs to display*/
+ private ArrayList<String> serviceURLs;
+ private final DistinguishedNameParser dnParser;
+
+ /**
+ * Creates new ViewCertDetailsDialog dialog where the parent is a frame.
+ */
+ public ViewCertDetailsDialog(JFrame parent, String title, boolean modal,
+ X509Certificate crt, ArrayList<String> serviceURLs,
+ DistinguishedNameParser dnParser) throws CMException {
+ super(parent, title, modal);
+ this.cert = crt;
+ this.serviceURLs = serviceURLs;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ /**
+ * Creates new ViewCertDetailsDialog dialog where the parent is a dialog.
+ */
+ public ViewCertDetailsDialog(JDialog parent, String title, boolean modal,
+ X509Certificate crt, ArrayList<String> urlList,
+ DistinguishedNameParser dnParser) throws CMException {
+ super(parent, title, modal);
+ cert = crt;
+ serviceURLs = urlList;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ /**
+ * Initialise the dialog's GUI components.
+ *
+ * @throws CMException
+ * A problem was encountered getting the certificates' details
+ */
+ private void initComponents() throws CMException {
+ // Certificate details:
+
+ // Grid Bag Constraints templates for labels (column 1) and
+ // values (column 2) of certificate details
+ GridBagConstraints gbcLabel = new GridBagConstraints();
+ gbcLabel.gridx = 0;
+ gbcLabel.ipadx = 20;
+ gbcLabel.gridwidth = 1;
+ gbcLabel.gridheight = 1;
+ gbcLabel.insets = new Insets(2, 15, 2, 2);
+ gbcLabel.anchor = LINE_START;
+
+ GridBagConstraints gbcValue = new GridBagConstraints();
+ gbcValue.gridx = 1;
+ gbcValue.gridwidth = 1;
+ gbcValue.gridheight = 1;
+ gbcValue.insets = new Insets(2, 5, 2, 2);
+ gbcValue.anchor = LINE_START;
+
+ /*
+ * Netscape Certificate Type non-critical extension (if any) defines the
+ * intended uses of the certificate - to make it look like firefox's
+ * view certificate dialog. From openssl's documentation: "The [above]
+ * extension is non standard, Netscape specific and largely obsolete.
+ * Their use in new applications is discouraged."
+ *
+ * TODO replace with "basicConstraints, keyUsage and extended key usage
+ * extensions which are now used instead."
+ */
+// byte[] intendedUses = cert.getExtensionValue("2.16.840.1.113730.1.1"); //Netscape Certificate Type OID/*
+// JLabel jlIntendedUses = null;
+// JTextField jtfIntendedUsesValue = null;
+// JPanel jpUses = null;
+// GridBagConstraints gbc_jpUses = null;
+// if (intendedUses != null)
+// {
+// jlIntendedUses = new JLabel("This certificate has been approved for the following uses:");
+// jlIntendedUses.setFont(new Font(null, Font.BOLD, 11));
+// jlIntendedUses.setBorder(new EmptyBorder(5,5,5,5));
+//
+// jtfIntendedUsesValue = new JTextField(45);
+// jtfIntendedUsesValue.setText(CMUtils.getIntendedCertificateUses(intendedUses));
+// jtfIntendedUsesValue.setEditable(false);
+// jtfIntendedUsesValue.setFont(new Font(null, Font.PLAIN, 11));
+//
+// jpUses = new JPanel(new BorderLayout());
+// jpUses.add(jlIntendedUses, BorderLayout.NORTH);
+// jpUses.add(jtfIntendedUsesValue, BorderLayout.CENTER);
+// JSeparator jsp = new JSeparator(JSeparator.HORIZONTAL);
+// jpUses.add(jsp, BorderLayout.SOUTH);
+//
+// gbc_jpUses = (GridBagConstraints) gbcLabel.clone();
+// gbc_jpUses.gridy = 0;
+// gbc_jpUses.gridwidth = 2; //takes two columns
+// gbc_jpUses.insets = new Insets(5, 5, 5, 5);//has slightly bigger insets
+//
+// }
+
+ //Issued To
+ JLabel jlIssuedTo = new JLabel("Issued To");
+ jlIssuedTo.setFont(new Font(null, Font.BOLD, 11));
+ GridBagConstraints gbc_jlIssuedTo = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlIssuedTo.gridy = 1;
+ gbc_jlIssuedTo.gridwidth = 2; //takes two columns
+ gbc_jlIssuedTo.insets = new Insets(5, 5, 5, 5);//has slightly bigger insets
+
+ // Distinguished Name (DN)
+ String sDN = cert.getSubjectX500Principal().getName(RFC2253);
+ ParsedDistinguishedName parsedDN = dnParser.parseDN(sDN);
+ // Extract the CN, O, OU and EMAILADDRESS fields
+ String sCN = parsedDN.getCN();
+ String sOrg = parsedDN.getO();
+ String sOU = parsedDN.getOU();
+ //String sEMAILADDRESS = CMX509Util.getEmilAddress();
+
+ // Common Name (CN)
+ JLabel jlCN = new JLabel("Common Name (CN)");
+ jlCN.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlCN = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlCN.gridy = 2;
+ JLabel jlCNValue = new JLabel(sCN);
+ jlCNValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlCNValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlCNValue.gridy = 2;
+
+ // Organisation (O)
+ JLabel jlOrg = new JLabel("Organisation (O)");
+ jlOrg.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlOrg = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlOrg.gridy = 3;
+ JLabel jlOrgValue = new JLabel(sOrg);
+ jlOrgValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlOrgValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlOrgValue.gridy = 3;
+
+ // Organisation Unit (OU)
+ JLabel jlOU = new JLabel("Organisation Unit (OU)");
+ jlOU.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlOU = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlOU.gridy = 4;
+ JLabel jlOUValue = new JLabel(sOU);
+ jlOUValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlOUValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlOUValue.gridy = 4;
+
+ // E-mail Address
+ //JLabel jlEmail = new JLabel("E-mail Address");
+ //jlEmail.setFont(new Font(null, PLAIN, 11));
+ //GridBagConstraints gbc_jlEmail = (GridBagConstraints) gbcLabel.clone();
+ //gbc_jlEmail.gridy = 5;
+ //JLabel jlEmailValue = new JLabel(sEMAILADDRESS);
+ //jlEmailValue.setFont(new Font(null, PLAIN, 11));
+ //GridBagConstraints gbc_jlEmailValue = (GridBagConstraints) gbcValue.clone();
+ //gbc_jlEmailValue.gridy = 5;
+
+ // Serial Number
+ JLabel jlSN = new JLabel("Serial Number");
+ jlSN.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlSN = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlSN.gridy = 6;
+ JLabel jlSNValue = new JLabel();
+ // Get the hexadecimal serial number
+ StringBuilder strBuff = new StringBuilder(new BigInteger(1,
+ cert.getSerialNumber().toByteArray()).toString(16).toUpperCase());
+ // Place colons at every two hexadecimal characters
+ if (strBuff.length() > 2)
+ for (int iCnt = 2; iCnt < strBuff.length(); iCnt += 3)
+ strBuff.insert(iCnt, ':');
+ jlSNValue.setText(strBuff.toString());
+ jlSNValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlSNValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlSNValue.gridy = 6;
+
+ // Version
+ JLabel jlVersion = new JLabel("Version");
+ jlVersion.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlVersion = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlVersion.gridy = 7;
+ JLabel jlVersionValue = new JLabel(Integer.toString(cert.getVersion()));
+ jlVersionValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlVersionValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlVersionValue.gridy = 7;
+
+ // Issued By
+ JLabel jlIssuedBy = new JLabel("Issued By");
+ jlIssuedBy.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_jlIssuedBy = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlIssuedBy.gridy = 8;
+ gbc_jlIssuedBy.gridwidth = 2; //takes two columns
+ gbc_jlIssuedBy.insets = new Insets(5, 5, 5, 5);//has slightly bigger insets
+
+ // Distinguished Name (DN)
+ String iDN = cert.getIssuerX500Principal().getName(RFC2253);
+ parsedDN = dnParser.parseDN(iDN);
+ // Extract the CN, O and OU fields
+ String iCN = parsedDN.getCN();
+ String iOrg = parsedDN.getO();
+ String iOU = parsedDN.getOU();
+
+ // Common Name (CN)
+ JLabel jlICN = new JLabel("Common Name (CN)");
+ jlICN.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlICN = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlICN.gridy = 9;
+ JLabel jlICNValue = new JLabel(iCN);
+ jlICNValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlICNValue = (GridBagConstraints) gbcValue
+ .clone();
+ gbc_jlICNValue.gridy = 9;
+
+ // Organisation (O)
+ JLabel jlIOrg = new JLabel("Organisation (O)");
+ jlIOrg.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlIOrg = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlIOrg.gridy = 10;
+ JLabel jlIOrgValue = new JLabel(iOrg);
+ jlIOrgValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlIOrgValue = (GridBagConstraints) gbcValue
+ .clone();
+ gbc_jlIOrgValue.gridy = 10;
+
+ // Organisation Unit (OU)
+ JLabel jlIOU = new JLabel("Organisation Unit (OU)");
+ jlIOU.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlIOU = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlIOU.gridy = 11;
+ JLabel jlIOUValue = new JLabel(iOU);
+ jlIOUValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlIOUValue = (GridBagConstraints) gbcValue
+ .clone();
+ gbc_jlIOUValue.gridy = 11;
+
+ // Validity
+ JLabel jlValidity = new JLabel("Validity");
+ jlValidity.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_jlValidity = (GridBagConstraints) gbcLabel
+ .clone();
+ gbc_jlValidity.gridy = 12;
+ gbc_jlValidity.gridwidth = 2; // takes two columns
+ gbc_jlValidity.insets = new Insets(5, 5, 5, 5);// has slightly bigger insets
+
+ // Issued On
+ JLabel jlIssuedOn = new JLabel("Issued On");
+ jlIssuedOn.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlIssuedOn = (GridBagConstraints) gbcLabel
+ .clone();
+ gbc_jlIssuedOn.gridy = 13;
+ JLabel jlIssuedOnValue = new JLabel(cert.getNotBefore().toString());
+ jlIssuedOnValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlIssuedOnValue = (GridBagConstraints) gbcValue
+ .clone();
+ gbc_jlIssuedOnValue.gridy = 13;
+
+ // Expires On
+ JLabel jlExpiresOn = new JLabel("Expires On");
+ jlExpiresOn.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlExpiresOn = (GridBagConstraints) gbcLabel
+ .clone();
+ gbc_jlExpiresOn.gridy = 14;
+ JLabel jlExpiresOnValue = new JLabel(cert.getNotAfter().toString());
+ jlExpiresOnValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlExpiresOnValue = (GridBagConstraints) gbcValue
+ .clone();
+ gbc_jlExpiresOnValue.gridy = 14;
+
+ // Fingerprints
+ byte[] certBinaryEncoding;
+ try {
+ certBinaryEncoding = cert.getEncoded();
+ } catch (CertificateEncodingException ex) {
+ throw new CMException(
+ "Could not get the encoded form of the certificate.", ex);
+ }
+ JLabel jlFingerprints = new JLabel("Fingerprints");
+ jlFingerprints.setFont(new Font(null, BOLD, 11));
+ GridBagConstraints gbc_jlFingerprints = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlFingerprints.gridy = 15;
+ gbc_jlFingerprints.gridwidth = 2; //takes two columns
+ gbc_jlFingerprints.insets = new Insets(5, 5, 5, 5);//has slightly bigger insets
+
+ // SHA-1 Fingerprint
+ JLabel jlSHA1Fingerprint = new JLabel("SHA1 Fingerprint");
+ jlSHA1Fingerprint.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlSHA1Fingerprint = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlSHA1Fingerprint.gridy = 16;
+ JLabel jlSHA1FingerprintValue = new JLabel(dnParser.getMessageDigestAsFormattedString(certBinaryEncoding, "SHA1"));
+ jlSHA1FingerprintValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlSHA1FingerprintValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlSHA1FingerprintValue.gridy = 16;
+
+ // MD5 Fingerprint
+ JLabel jlMD5Fingerprint = new JLabel("MD5 Fingerprint");
+ jlMD5Fingerprint.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlMD5Fingerprint = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlMD5Fingerprint.gridy = 17;
+ JLabel jlMD5FingerprintValue = new JLabel(dnParser.getMessageDigestAsFormattedString(certBinaryEncoding, "MD5"));
+ jlMD5FingerprintValue.setFont(new Font(null, PLAIN, 11));
+ GridBagConstraints gbc_jlMD5FingerprintValue = (GridBagConstraints) gbcValue.clone();
+ gbc_jlMD5FingerprintValue.gridy = 17;
+
+ /*
+ * Empty label to add a bit space at the bottom of the panel to make it
+ * look like firefox's view certificate dialog
+ */
+ JLabel jlEmpty = new JLabel("");
+ GridBagConstraints gbc_jlEmpty = (GridBagConstraints) gbcLabel.clone();
+ gbc_jlEmpty.gridy = 18;
+ gbc_jlEmpty.gridwidth = 2; // takes two columns
+ gbc_jlEmpty.ipady = 40;
+
+ JPanel jpCertificate = new JPanel(new GridBagLayout());
+ jpCertificate.setBorder(new CompoundBorder(new EmptyBorder(15, 15, 15,
+ 15), new EtchedBorder()));
+
+// if (intendedUses != null){
+// jpCertificate.add(jpUses, gbc_jpUses);
+// }
+ jpCertificate.add(jlIssuedTo, gbc_jlIssuedTo); // Issued To
+ jpCertificate.add(jlCN, gbc_jlCN);
+ jpCertificate.add(jlCNValue, gbc_jlCNValue);
+ jpCertificate.add(jlOrg, gbc_jlOrg);
+ jpCertificate.add(jlOrgValue, gbc_jlOrgValue);
+ jpCertificate.add(jlOU, gbc_jlOU);
+ jpCertificate.add(jlOUValue, gbc_jlOUValue);
+ //jpCertificate.add(jlEmail, gbc_jlEmail);
+ //jpCertificate.add(jlEmailValue, gbc_jlEmailValue);
+ jpCertificate.add(jlSN, gbc_jlSN);
+ jpCertificate.add(jlSNValue, gbc_jlSNValue);
+ jpCertificate.add(jlVersion, gbc_jlVersion);
+ jpCertificate.add(jlVersionValue, gbc_jlVersionValue);
+ jpCertificate.add(jlIssuedBy, gbc_jlIssuedBy); //Issued By
+ jpCertificate.add(jlICN, gbc_jlICN);
+ jpCertificate.add(jlICNValue, gbc_jlICNValue);
+ jpCertificate.add(jlIOrg, gbc_jlIOrg);
+ jpCertificate.add(jlIOrgValue, gbc_jlIOrgValue);
+ jpCertificate.add(jlIOU, gbc_jlIOU);
+ jpCertificate.add(jlIOUValue, gbc_jlIOUValue);
+ jpCertificate.add(jlValidity, gbc_jlValidity); //Validity
+ jpCertificate.add(jlIssuedOn, gbc_jlIssuedOn);
+ jpCertificate.add(jlIssuedOnValue, gbc_jlIssuedOnValue);
+ jpCertificate.add(jlExpiresOn, gbc_jlExpiresOn);
+ jpCertificate.add(jlExpiresOnValue, gbc_jlExpiresOnValue);
+ jpCertificate.add(jlFingerprints, gbc_jlFingerprints); //Fingerprints
+ jpCertificate.add(jlSHA1Fingerprint, gbc_jlSHA1Fingerprint);
+ jpCertificate.add(jlSHA1FingerprintValue, gbc_jlSHA1FingerprintValue);
+ jpCertificate.add(jlMD5Fingerprint, gbc_jlMD5Fingerprint);
+ jpCertificate.add(jlMD5FingerprintValue, gbc_jlMD5FingerprintValue);
+ jpCertificate.add(jlEmpty, gbc_jlEmpty); //Empty label to get some vertical space on the frame
+
+ // List of serviceURLs
+ JPanel jpURLs = null; // Panel to hold the URL list
+ if (serviceURLs != null) { //if service serviceURLs are not null (even if empty - show empty list)
+
+ jpURLs = new JPanel(new BorderLayout());
+ jpURLs.setBorder(new CompoundBorder(
+ new EmptyBorder(0, 15, 0, 15), new EtchedBorder()));
+ // Label
+ JLabel jlServiceURLs = new JLabel ("Service URLs this key pair will be used for:");
+ jlServiceURLs.setFont(new Font(null, Font.BOLD, 11));
+ jlServiceURLs.setBorder(new EmptyBorder(5,5,5,5));
+
+ // New empty service serviceURLs list
+ DefaultListModel<String> jltModel = new DefaultListModel<>();
+ JList<String> jltServiceURLs = new JList<>(jltModel);
+ for (String url : serviceURLs)
+ jltModel.addElement(url);
+ // don't show more than 5 otherwise the window is too big
+ jltServiceURLs.setVisibleRowCount(5);
+
+ // Scroll pane for service serviceURLs
+ JScrollPane jspServiceURLs = new JScrollPane(jltServiceURLs,
+ VERTICAL_SCROLLBAR_AS_NEEDED,
+ HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ jspServiceURLs.getViewport().setBackground(
+ jltServiceURLs.getBackground());
+
+ jpURLs.add(jlServiceURLs, NORTH);
+ jpURLs.add(jspServiceURLs, CENTER);
+
+ // Put it on the main content pane
+ getContentPane().add(jpURLs, CENTER);
+ }
+
+ // OK button
+ JPanel jpOK = new JPanel(new FlowLayout(FlowLayout.CENTER));
+
+ final JButton jbOK = new JButton("OK");
+ jbOK.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ jpOK.add(jbOK);
+
+ /*
+ * Put it all together (panel with URL list is already added, if it was
+ * not null)
+ */
+ getContentPane().add(jpCertificate, NORTH);
+ getContentPane().add(jpOK, SOUTH);
+
+ // Resizing wreaks havoc
+ setResizable(false);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ getRootPane().setDefaultButton(jbOK);
+
+ pack();
+
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ jbOK.requestFocus();
+ }
+ });
+ }
+
+ private void okPressed() {
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ViewUsernamePasswordEntryDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ViewUsernamePasswordEntryDialog.java
new file mode 100644
index 0000000..7c92842
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/ViewUsernamePasswordEntryDialog.java
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+
+import java.awt.BorderLayout;
+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.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Dialog used for viewing service URL, username and password.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class ViewUsernamePasswordEntryDialog extends
+ NonBlockedHelpEnabledDialog {
+ /** Service URL field */
+ private JTextField serviceURLField;
+ /** Username field */
+ private JTextField usernameField;
+ /** Password field */
+ private JTextField passwordField;
+ /** Service URL value */
+ private String serviceURL;
+ /** Service username value */
+ private String username;
+ /** Service password value */
+ private String password;
+
+ public ViewUsernamePasswordEntryDialog(JFrame parent, String currentURL,
+ String currentUsername, String currentPassword) {
+ super(parent, "View username and password for a service", true);
+ serviceURL = currentURL;
+ username = currentUsername;
+ password = currentPassword;
+ initComponents();
+ }
+
+ public ViewUsernamePasswordEntryDialog(JDialog parent, String currentURL,
+ String currentUsername, String currentPassword) {
+ super(parent, "View username and password for a service", true);
+ serviceURL = currentURL;
+ username = currentUsername;
+ password = currentPassword;
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel serviceURLLabel = new JLabel("Service URL");
+ serviceURLLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+ JLabel usernameLabel = new JLabel("Username");
+ usernameLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+ JLabel passwordLabel = new JLabel("Password");
+ passwordLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
+
+ // Populate the fields with values and disable user input
+ serviceURLField = new JTextField();
+ serviceURLField.setText(serviceURL);
+ serviceURLField.setEditable(false);
+
+ usernameField = new JTextField(15);
+ usernameField.setText(username);
+ usernameField.setEditable(false);
+
+ passwordField = new JTextField(15);
+ passwordField.setText(password);
+ passwordField.setEditable(false);
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ closeDialog();
+ }
+ });
+
+ JPanel fieldsPanel = new JPanel(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.weighty = 0.0;
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ fieldsPanel.add(serviceURLLabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ fieldsPanel.add(serviceURLField, gbc);
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ fieldsPanel.add(usernameLabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 1;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ fieldsPanel.add(usernameField, gbc);
+
+ gbc.weightx = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 0);
+ fieldsPanel.add(passwordLabel, gbc);
+
+ gbc.weightx = 1.0;
+ gbc.gridx = 1;
+ gbc.gridy = 2;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.insets = new Insets(5, 10, 0, 5);
+ fieldsPanel.add(passwordField, gbc);
+
+ fieldsPanel.setBorder(new CompoundBorder(
+ new EmptyBorder(10, 10, 10, 10), new EtchedBorder()));
+
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ buttonsPanel.add(okButton);
+
+ getContentPane().add(fieldsPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ // setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/WarnUserAboutJCEPolicyDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/WarnUserAboutJCEPolicyDialog.java
new file mode 100644
index 0000000..c826c8f
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/WarnUserAboutJCEPolicyDialog.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Desktop.getDesktop;
+import static javax.swing.border.EtchedBorder.LOWERED;
+import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
+import static org.apache.commons.io.FileUtils.touch;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * Dialog that warns user that they need to install unlimited cryptography
+ * strength policy for Java.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class WarnUserAboutJCEPolicyDialog extends NonBlockedHelpEnabledDialog {
+ private static final Logger logger = Logger
+ .getLogger(WarnUserAboutJCEPolicyDialog.class);
+
+ private JCheckBox doNotWarnMeAgainCheckBox;
+ private final ApplicationConfiguration applicationConfiguration;
+ private final DistinguishedNameParser dnParser;
+
+ public WarnUserAboutJCEPolicyDialog(
+ ApplicationConfiguration applicationConfiguration,
+ DistinguishedNameParser dnParser) {
+ super((Frame) null,
+ "Java Unlimited Strength Cryptography Policy Warning", true);
+ this.applicationConfiguration = applicationConfiguration;
+ this.dnParser = dnParser;
+ initComponents();
+ }
+
+ // For testing
+ public static void main(String[] args) {
+ WarnUserAboutJCEPolicyDialog dialog = new WarnUserAboutJCEPolicyDialog(
+ null, null);
+ dialog.setVisible(true);
+ }
+
+ private void initComponents() {
+ // Base font for all components on the form
+ Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+
+ // Message saying that updates are available
+ JPanel messagePanel = new JPanel(new BorderLayout());
+ messagePanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10,
+ 10), new EtchedBorder(LOWERED)));
+
+ JEditorPane message = new JEditorPane();
+ message.setEditable(false);
+ message.setBackground(this.getBackground());
+ message.setFocusable(false);
+ HTMLEditorKit kit = new HTMLEditorKit();
+ message.setEditorKit(kit);
+ StyleSheet styleSheet = kit.getStyleSheet();
+ //styleSheet.addRule("body {font-family:"+baseFont.getFamily()+"; font-size:"+baseFont.getSize()+";}"); // base font looks bigger when rendered as HTML
+ styleSheet.addRule("body {font-family:" + baseFont.getFamily()
+ + "; font-size:10px;}");
+ Document doc = kit.createDefaultDocument();
+ message.setDocument(doc);
+ message.setText("<html><body>In order for Taverna's security features to function properly - you need to install<br>"
+ + "'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy'. <br><br>"
+ + "If you do not already have it, for <b>Java 6</b> you can get it from:<br>"
+ + "<a href=\"http://www.oracle.com/technetwork/java/javase/downloads/index.html\">http://www.oracle.com/technetwork/java/javase/downloads/index.html</a><br<br>"
+ + "Installation instructions are contained in the bundle you download."
+ + "</body><html>");
+ message.addHyperlinkListener(new HyperlinkListener() {
+ @Override
+ public void hyperlinkUpdate(HyperlinkEvent he) {
+ HyperlinkEvent.EventType type = he.getEventType();
+ if (type == ACTIVATED)
+ // Open a Web browser
+ try {
+ getDesktop().browse(he.getURL().toURI());
+// BrowserLauncher launcher = new BrowserLauncher();
+// launcher.openURLinBrowser(he.getURL().toString());
+ } catch (Exception ex) {
+ logger.error("Failed to launch browser to fetch JCE "
+ + he.getURL());
+ }
+ }
+ });
+ message.setBorder(new EmptyBorder(5, 5, 5, 5));
+ messagePanel.add(message, CENTER);
+
+ doNotWarnMeAgainCheckBox = new JCheckBox("Do not warn me again");
+ doNotWarnMeAgainCheckBox.setFont(baseFont.deriveFont(12f));
+ messagePanel.add(doNotWarnMeAgainCheckBox, SOUTH);
+
+ // Buttons
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ JButton okButton = new JButton("OK");
+ okButton.setFont(baseFont);
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ okPressed();
+ }
+ });
+
+ buttonsPanel.add(okButton);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(messagePanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ pack();
+ setResizable(false);
+ // Center the dialog on the screen (we do not have the parent)
+ Dimension dimension = getToolkit().getScreenSize();
+ Rectangle abounds = getBounds();
+ setLocation((dimension.width - abounds.width) / 2,
+ (dimension.height - abounds.height) / 2);
+ setSize(getPreferredSize());
+ }
+
+ private static final String DO_NOT_WARN_ABOUT_JCE_POLICY = "do_not_warn_about_JCE_policy";
+ public static boolean warnedUser = false; // have we already warned user for
+ // this run
+
+ /**
+ * Warn user that they need to install Java Cryptography Extension (JCE)
+ * Unlimited Strength Jurisdiction Policy if they want Credential Manager to
+ * function properly.
+ */
+ public static void warnUserAboutJCEPolicy(
+ ApplicationConfiguration applicationConfiguration,
+ DistinguishedNameParser dnParser) {
+ /*
+ * Do not pop up a dialog if we are running headlessly. If we have
+ * warned the user and they do not want us to remind them again - exit.
+ */
+ if (warnedUser || GraphicsEnvironment.isHeadless()
+ || doNotWarnFile(applicationConfiguration, dnParser).exists())
+ return;
+
+ WarnUserAboutJCEPolicyDialog warnDialog = new WarnUserAboutJCEPolicyDialog(
+ applicationConfiguration, dnParser);
+ warnDialog.setVisible(true);
+ warnedUser = true;
+ }
+
+ private static File doNotWarnFile(
+ ApplicationConfiguration applicationConfiguration,
+ DistinguishedNameParser dnParser) {
+ return new File(
+ dnParser.getCredentialManagerDefaultDirectory(applicationConfiguration),
+ DO_NOT_WARN_ABOUT_JCE_POLICY);
+ }
+
+ protected void okPressed() {
+ try {
+ if (doNotWarnMeAgainCheckBox.isSelected())
+ touch(doNotWarnFile(applicationConfiguration, dnParser));
+ } catch (IOException e) {
+ logger.error(
+ "Failed to touch the 'Do not want me about JCE unilimited security policy' file.",
+ e);
+ }
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/action/CredentialManagerAction.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/action/CredentialManagerAction.java
new file mode 100644
index 0000000..c86ad27
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/action/CredentialManagerAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager.action;
+
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.CredentialManagerUI;
+
+//import javax.swing.SwingUtilities;
+
+@SuppressWarnings("serial")
+public class CredentialManagerAction extends AbstractAction {
+ private static ImageIcon ICON = new ImageIcon(
+ CredentialManagerAction.class
+ .getResource("/images/cred_manager16x16.png"));
+
+ private CredentialManagerUI cmUI;
+ private final CredentialManager credentialManager;
+ private final DistinguishedNameParser dnParser;
+
+ public CredentialManagerAction(CredentialManager credentialManager,
+ DistinguishedNameParser dnParser) {
+ super("Credential Manager", ICON);
+ this.credentialManager = credentialManager;
+ this.dnParser = dnParser;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (cmUI != null) {
+ cmUI.setVisible(true);
+ return;
+ }
+
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ cmUI = new CredentialManagerUI(credentialManager, dnParser);
+ cmUI.setVisible(true);
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/menu/CredentialManagerMenu.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/menu/CredentialManagerMenu.java
new file mode 100644
index 0000000..1eaf82b
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/menu/CredentialManagerMenu.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+//import org.apache.log4j.Logger;
+
+//import net.sf.taverna.t2.security.credentialmanager.CMException;
+//import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.action.CredentialManagerAction;
+
+public class CredentialManagerMenu extends AbstractMenuAction {
+ private static final String MENU_URI = "http://taverna.sf.net/2008/t2workbench/menu#advanced";
+
+ private CredentialManager credentialManager;
+ private DistinguishedNameParser dnParser;
+
+ // private static Logger logger = Logger.getLogger(CredentialManagerMenu.class);
+
+ public CredentialManagerMenu() {
+ super(URI.create(MENU_URI), 60);
+ /* This is now done in the initialise SSL startup hook - no need to do it here.
+ // Force initialisation at startup
+ try {
+ CredentialManager.getInstance();
+ } catch (CMException e) {
+ logger.error("Could not initialise SSL properties for SSL connections from Taverna.", e);
+ }
+ */
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CredentialManagerAction(credentialManager, dnParser);
+ }
+
+ public void setCredentialManager(CredentialManager credentialManager) {
+ this.credentialManager = credentialManager;
+ }
+
+ /**
+ * @param dnParser
+ * the dnParser to set
+ */
+ public void setDistinguishedNameParser(DistinguishedNameParser dnParser) {
+ this.dnParser = dnParser;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserJavaTruststorePasswordProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserJavaTruststorePasswordProvider.java
new file mode 100644
index 0000000..9ddd9a7
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserJavaTruststorePasswordProvider.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.credentialmanager.password;
+
+import net.sf.taverna.t2.security.credentialmanager.JavaTruststorePasswordProvider;
+
+/**
+ * An implementation of the {@link JavaTruststorePasswordProvider} that pops up a
+ * dialog and asks the user to provide the password.
+ *
+ * @author Alex Nenadic
+ *
+ */
+public class AskUserJavaTruststorePasswordProvider implements JavaTruststorePasswordProvider{
+
+ @Override
+ public String getJavaTruststorePassword() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setJavaTruststorePassword(String password) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserMasterPasswordProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserMasterPasswordProvider.java
new file mode 100644
index 0000000..55b7d5b
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserMasterPasswordProvider.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.credentialmanager.password;
+
+import net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider;
+
+public class AskUserMasterPasswordProvider implements MasterPasswordProvider{
+
+// @Override
+// public boolean canProvideMasterPassword() {
+// // TODO Auto-generated method stub
+// return false;
+// }
+ private int priority = 100;
+
+ @Override
+ public String getMasterPassword(boolean firstTime) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getProviderPriority() {
+ return priority;
+ }
+
+ @Override
+ public void setMasterPassword(String password) {
+ // TODO Auto-generated method stub
+ }
+
+// @Override
+// public void setProviderPriority(int priority) {
+// this.priority = priority;
+// }
+
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserServiceUsernameAndPasswordProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserServiceUsernameAndPasswordProvider.java
new file mode 100644
index 0000000..b43d184
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserServiceUsernameAndPasswordProvider.java
@@ -0,0 +1,23 @@
+package net.sf.taverna.t2.workbench.ui.credentialmanager.password;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.security.credentialmanager.ServiceUsernameAndPasswordProvider;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+
+public class AskUserServiceUsernameAndPasswordProvider implements ServiceUsernameAndPasswordProvider{
+
+ @Override
+ public UsernamePassword getServiceUsernameAndPassword(URI serviceURI, String requestMessage) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setServiceUsernameAndPassword(URI serviceURI,
+ UsernamePassword usernamePassword) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserTrustConfirmationProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserTrustConfirmationProvider.java
new file mode 100644
index 0000000..824764d
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/AskUserTrustConfirmationProvider.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.credentialmanager.password;
+
+import java.security.cert.X509Certificate;
+
+import net.sf.taverna.t2.security.credentialmanager.TrustConfirmationProvider;
+
+public class AskUserTrustConfirmationProvider implements TrustConfirmationProvider {
+
+ @Override
+ public Boolean shouldTrustCertificate(X509Certificate[] chain) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/GetPasswordDialog.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/GetPasswordDialog.java
new file mode 100644
index 0000000..851e900
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/GetPasswordDialog.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager.password;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.LEFT;
+import static java.awt.FlowLayout.RIGHT;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.workbench.helper.NonBlockedHelpEnabledDialog;
+
+/**
+ * Dialog for entering user's username and password.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class GetPasswordDialog extends NonBlockedHelpEnabledDialog {
+ /**
+ * Whether we should ask user to save their username and password using
+ * Credential Manager
+ */
+ private boolean shouldAskUserToSave;
+ /** Username field */
+ private JTextField usernameField;
+ /** Password field */
+ private JPasswordField passwordField;
+ /**
+ * Whether user wished to save the username and password using Credential
+ * Manager
+ */
+ private JCheckBox saveCheckBox;
+ /** The entered username */
+ private String username;
+ /** The entered password */
+ private String password;
+ /** Instructions to the user */
+ private String instructions;
+
+ public GetPasswordDialog(String instructions, boolean shouldAskUserToSave) {
+ super((Frame) null, "Enter username and password", true);
+ this.instructions = instructions;
+ this.shouldAskUserToSave = shouldAskUserToSave;
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JLabel instructionsLabel = new JLabel(instructions);
+ instructionsLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JPanel jpInstructions = new JPanel(new FlowLayout(LEFT));
+ jpInstructions.add(instructionsLabel);
+
+ JLabel usernameLabel = new JLabel("Username");
+ usernameLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel passwordLabel = new JLabel("Password");
+ passwordLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
+
+ usernameField = new JTextField(15);
+ passwordField = new JPasswordField(15);
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okPressed();
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelPressed();
+ }
+ });
+
+ // Central panel with username/password fields and a "Do you want to Save?" checkbox
+ JPanel mainPanel = new JPanel(new BorderLayout());
+
+ JPanel passwordPanel = new JPanel(new GridLayout(2, 2, 5, 5));
+ passwordPanel.add(usernameLabel);
+ passwordPanel.add(usernameField);
+ passwordPanel.add(passwordLabel);
+ passwordPanel.add(passwordField);
+ mainPanel.add(passwordPanel, CENTER);
+
+ // If user wants to save this username and password
+ saveCheckBox = new JCheckBox();
+ saveCheckBox.setBorder(new EmptyBorder(5, 5, 5, 5));
+ saveCheckBox.setSelected(true);
+ saveCheckBox
+ .setText("Use Credential Manager to save this username and password");
+ if (shouldAskUserToSave) {
+ JPanel jpSaveCheckBox = new JPanel(new FlowLayout(LEFT));
+ jpSaveCheckBox.add(saveCheckBox);
+ mainPanel.add(jpSaveCheckBox, SOUTH);
+ }
+
+ passwordPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10,
+ 10), new EtchedBorder()));
+
+ JPanel buttonsPanel = new JPanel(new FlowLayout(RIGHT));
+ buttonsPanel.add(okButton);
+ buttonsPanel.add(cancelButton);
+
+ passwordPanel.setMinimumSize(new Dimension(300, 100));
+
+ getContentPane().add(jpInstructions, NORTH);
+ getContentPane().add(mainPanel, CENTER);
+ getContentPane().add(buttonsPanel, SOUTH);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent evt) {
+ closeDialog();
+ }
+ });
+
+ setResizable(false);
+ getRootPane().setDefaultButton(okButton);
+ pack();
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Check if user wishes to save username and pasword using the Credential
+ * Manager.
+ */
+ public boolean shouldSaveUsernameAndPassword() {
+ return saveCheckBox.isSelected();
+ }
+
+ private boolean checkControls() {
+ username = usernameField.getText();
+ if (username.length() == 0) {
+ showMessageDialog(this, "Username cannot be empty", "Warning",
+ WARNING_MESSAGE);
+ return false;
+ }
+
+ password = new String(passwordField.getPassword());
+ if (password.length() == 0) { // password empty
+ showMessageDialog(this, "Password cannot be empty", "Warning",
+ WARNING_MESSAGE);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private void okPressed() {
+ if (checkControls())
+ closeDialog();
+ }
+
+ private void cancelPressed() {
+ // Set all fields to null to indicate that cancel button was pressed
+ username = null;
+ password = null;
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ usernameField.setText(username);
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ passwordField.setText(password);
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/SimpleMasterPasswordProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/SimpleMasterPasswordProvider.java
new file mode 100644
index 0000000..4b9950e
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/SimpleMasterPasswordProvider.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.credentialmanager.password;
+
+import net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider;
+
+/**
+ * A simple implementation of {@link MasterPasswordProvider} that just provides
+ * a master password that can be obtained and set from outside the provider.
+ *
+ * @author Alex Nenadic
+ */
+public class SimpleMasterPasswordProvider implements MasterPasswordProvider {
+ private String masterPassword;
+ private int priority = 200;
+
+ @Override
+ public String getMasterPassword(boolean firstTime) {
+ return masterPassword;
+ }
+
+ @Override
+ public void setMasterPassword(String masterPassword){
+ this.masterPassword = masterPassword;
+ }
+
+ @Override
+ public int getProviderPriority() {
+ return priority;
+ }
+
+// @Override
+// public void setProviderPriority(int priority) {
+// this.priority = priority;
+// }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/UIMasterPasswordProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/UIMasterPasswordProvider.java
new file mode 100644
index 0000000..81b2e5c
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/UIMasterPasswordProvider.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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.ui.credentialmanager.password;
+
+import java.awt.GraphicsEnvironment;
+
+import javax.swing.JFrame;
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+import net.sf.taverna.t2.security.credentialmanager.JavaTruststorePasswordProvider;
+import net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.GetMasterPasswordDialog;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.SetMasterPasswordDialog;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.WarnUserAboutJCEPolicyDialog;
+
+/**
+ * A UI pop-up that asks user for a master password for Credential Manager.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class UIMasterPasswordProvider implements MasterPasswordProvider, JavaTruststorePasswordProvider {
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ private DistinguishedNameParser dnParser;
+
+ @Override
+ public String getJavaTruststorePassword() {
+ if (GraphicsEnvironment.isHeadless()) {
+ return null;
+ }
+
+ GetMasterPasswordDialog getPasswordDialog = new GetMasterPasswordDialog(
+ "Credential Manager needs to copy certificates from Java truststore. "
+ + "Please enter your password.");
+ getPasswordDialog.setLocationRelativeTo(null);
+ getPasswordDialog.setVisible(true);
+ String javaTruststorePassword = getPasswordDialog.getPassword();
+ return javaTruststorePassword;
+ }
+
+ @Override
+ public void setJavaTruststorePassword(String password) {
+ }
+
+ @Override
+ public String getMasterPassword(boolean firstTime) {
+
+ // Check if this Taverna run is headless (i.e. Taverna Server or Taverna
+ // from command line) - do not do anything here if it is as we do not
+ // want
+ // any windows popping up even if they could
+ if (GraphicsEnvironment.isHeadless()) {
+ return null;
+ }
+
+ // Pop up a warning about Java Cryptography Extension (JCE)
+ // Unlimited Strength Jurisdiction Policy
+ WarnUserAboutJCEPolicyDialog.warnUserAboutJCEPolicy(applicationConfiguration, dnParser);
+
+ if (firstTime) {
+ // Ask user to set the master password for Credential Manager (only
+ // the first time)
+ SetMasterPasswordDialog setPasswordDialog = new SetMasterPasswordDialog(
+ (JFrame) null, "Set master password", true,
+ "Set master password for Credential Manager");
+ setPasswordDialog.setLocationRelativeTo(null);
+ setPasswordDialog.setVisible(true);
+ return setPasswordDialog.getPassword();
+ } else {
+ // Ask user to provide a master password for Credential Manager
+ GetMasterPasswordDialog getPasswordDialog = new GetMasterPasswordDialog(
+ "Enter master password for Credential Manager");
+ getPasswordDialog.setLocationRelativeTo(null);
+ getPasswordDialog.setVisible(true);
+ return getPasswordDialog.getPassword();
+ }
+ }
+
+ @Override
+ public void setMasterPassword(String password) {
+ }
+
+ @Override
+ public int getProviderPriority() {
+ return 100;
+ }
+
+ /**
+ * Sets the applicationConfiguration.
+ *
+ * @param applicationConfiguration the new value of applicationConfiguration
+ */
+ public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+
+ /**
+ * @param dnParser the dnParser to set
+ */
+ public void setDistinguishedNameParser(DistinguishedNameParser dnParser) {
+ this.dnParser = dnParser;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/UIUsernamePasswordProvider.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/UIUsernamePasswordProvider.java
new file mode 100644
index 0000000..60318d0
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/password/UIUsernamePasswordProvider.java
@@ -0,0 +1,92 @@
+package net.sf.taverna.t2.workbench.ui.credentialmanager.password;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
+import net.sf.taverna.t2.security.credentialmanager.ServiceUsernameAndPasswordProvider;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+
+import org.apache.log4j.Logger;
+
+public class UIUsernamePasswordProvider implements
+ ServiceUsernameAndPasswordProvider {
+ private static final Logger logger = Logger
+ .getLogger(UIUsernamePasswordProvider.class);
+
+ private DistinguishedNameParser dnParser;
+
+ public boolean canProvideUsernamePassword(URI serviceURI) {
+ return !isHeadless();
+ }
+
+ @Override
+ public UsernamePassword getServiceUsernameAndPassword(URI serviceURI,
+ String requestingPrompt) {
+ URI displayURI = serviceURI;
+
+ try {
+ displayURI = dnParser.setFragmentForURI(displayURI, null);
+ displayURI = dnParser.setUserInfoForURI(displayURI, null);
+ } catch (URISyntaxException e) {
+ logger.warn("Could not strip fragment/userinfo from " + serviceURI,
+ e);
+ }
+
+ StringBuilder message = new StringBuilder();
+ message.append("<html><body>The Taverna Credential Manager could not find a ");
+ message.append("username and password for the service at:");
+ message.append("<br><br><code>");
+ message.append(displayURI);
+ message.append("</code>");
+ if (requestingPrompt != null && !requestingPrompt.isEmpty()) {
+ message.append("<p><i>");
+ message.append(requestingPrompt);
+ message.append("</i>");
+ }
+ message.append("<br><br>Please provide the username and password.</body></html>");
+
+ GetPasswordDialog getPasswordDialog = new GetPasswordDialog(
+ message.toString(), true);
+ getPasswordDialog.setLocationRelativeTo(null);
+ if (serviceURI.getRawUserInfo() != null
+ && serviceURI.getRawUserInfo().length() > 1) {
+ String userInfo = serviceURI.getRawUserInfo();
+ String[] userPassword = userInfo.split(":", 2);
+ if (userPassword.length == 2) {
+ getPasswordDialog.setUsername(userPassword[0]);
+ getPasswordDialog.setPassword(userPassword[1]);
+ }
+ }
+ getPasswordDialog.setVisible(true);
+
+ String username = getPasswordDialog.getUsername(); // get username
+ String password = getPasswordDialog.getPassword(); // get password
+ boolean shouldSaveUsernameAndPassword = getPasswordDialog
+ .shouldSaveUsernameAndPassword();
+ if (username == null || password == null)
+ // user cancelled - any of the above two variables is null
+ return null;
+
+ UsernamePassword credential = new UsernamePassword();
+ credential.setUsername(username);
+ credential.setPassword(password.toCharArray());
+ credential.setShouldSave(shouldSaveUsernameAndPassword);
+ return credential;
+ }
+
+ @Override
+ public void setServiceUsernameAndPassword(URI serviceURI,
+ UsernamePassword usernamePassword) {
+ }
+
+ /**
+ * @param dnParser
+ * the dnParser to set
+ */
+ public void setDistinguishedNameParser(DistinguishedNameParser dnParser) {
+ this.dnParser = dnParser;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/startup/InitialiseSSLStartupHook.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/startup/InitialiseSSLStartupHook.java
new file mode 100644
index 0000000..1e4073c
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/startup/InitialiseSSLStartupHook.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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.ui.credentialmanager.startup;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.workbench.StartupSPI;
+
+/**
+ *
+ * Startup hook to initialise SSL socket factory used by Taverna for creating
+ * HTTPS connections.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ */
+public class InitialiseSSLStartupHook implements StartupSPI {
+ private static final Logger logger = Logger
+ .getLogger(InitialiseSSLStartupHook.class);
+
+ private CredentialManager credManager;
+
+ @Override
+ public int positionHint() {
+ return 25;
+ }
+
+ @Override
+ public boolean startup() {
+ logger.info("Initialising SSL socket factory for SSL connections from Taverna.");
+ try {
+ credManager.initializeSSL();
+ } catch (CMException e) {
+ logger.error(
+ "Could not initialise the SSL socket factory (for creating SSL connections)"
+ + " using Taverna's keystores.", e);
+ }
+ return true;
+ }
+
+ public void setCredentialManager(CredentialManager credManager) {
+ this.credManager = credManager;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/startup/SetCredManAuthenticatorStartupHook.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/startup/SetCredManAuthenticatorStartupHook.java
new file mode 100644
index 0000000..29ec9b6
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/startup/SetCredManAuthenticatorStartupHook.java
@@ -0,0 +1,24 @@
+package net.sf.taverna.t2.workbench.ui.credentialmanager.startup;
+
+import java.net.Authenticator;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.workbench.StartupSPI;
+
+public class SetCredManAuthenticatorStartupHook implements StartupSPI {
+ private CredentialManager credManager;
+
+ @Override
+ public int positionHint() {
+ return 50;
+ }
+
+ @Override
+ public boolean startup() {
+ Authenticator.setDefault(credManager.getAuthenticator());
+ return true;
+ }
+
+ public void setCredentialManager(CredentialManager credManager) {
+ this.credManager = credManager;
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/toolbar/CredentialManagerToolbarAction.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/toolbar/CredentialManagerToolbarAction.java
new file mode 100644
index 0000000..d515809
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/toolbar/CredentialManagerToolbarAction.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager.toolbar;
+
+import static net.sf.taverna.t2.workbench.ui.credentialmanager.toolbar.CredentialManagerToolbarSection.CREDENTIAL_MANAGER_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.ui.credentialmanager.action.CredentialManagerAction;
+
+public class CredentialManagerToolbarAction extends AbstractMenuAction {
+ private static final String ENTRY_URI = "http://taverna.sf.net/2008/t2workbench/toolbar#credentialManagerAction";
+
+ public CredentialManagerToolbarAction() {
+ super(CREDENTIAL_MANAGER_TOOLBAR_SECTION, 100, URI.create(ENTRY_URI));
+ }
+
+ @Override
+ protected Action createAction() {
+ // need to add CredentialManager if toolbar is ever used
+ return new CredentialManagerAction(null, null);
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/toolbar/CredentialManagerToolbarSection.java b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/toolbar/CredentialManagerToolbarSection.java
new file mode 100644
index 0000000..e5367be
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/java/net/sf/taverna/t2/workbench/ui/credentialmanager/toolbar/CredentialManagerToolbarSection.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * 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.ui.credentialmanager.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class CredentialManagerToolbarSection extends AbstractMenuSection {
+ private static final String ENTRY_URI = "http://taverna.sf.net/2008/t2workbench/toolbar#credentialManagerSection";
+ /** {@value #ENTRY_URI} */
+ public static URI CREDENTIAL_MANAGER_TOOLBAR_SECTION = URI
+ .create(ENTRY_URI);
+
+ public CredentialManagerToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 300, CREDENTIAL_MANAGER_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.security.credentialmanager.CredentialProviderSPI b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.security.credentialmanager.CredentialProviderSPI
new file mode 100644
index 0000000..725aa11
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.security.credentialmanager.CredentialProviderSPI
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.workbench.ui.credentialmanager.password.UIUsernamePasswordProvider
+net.sf.taverna.t2.workbench.ui.credentialmanager.password.UIMasterPasswordProvider
+net.sf.taverna.t2.workbench.ui.credentialmanager.ConfirmTrustedCertificateUI
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..3743c2f
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.workbench.ui.credentialmanager.menu.CredentialManagerMenu
+#net.sf.taverna.t2.workbench.ui.credentialmanager.toolbar.CredentialManagerToolbarAction
+#net.sf.taverna.t2.workbench.ui.credentialmanager.toolbar.CredentialManagerToolbarSection
\ No newline at end of file
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..b43772c
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.credentialmanager.startup.InitialiseSSLStartupHook
+net.sf.taverna.t2.workbench.ui.credentialmanager.startup.SetCredManAuthenticatorStartupHook
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/spring/credential-manager-ui-context-osgi.xml b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/spring/credential-manager-ui-context-osgi.xml
new file mode 100644
index 0000000..f595eda
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/spring/credential-manager-ui-context-osgi.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="askUserMasterPasswordProvider" interface="net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider" />
+
+ <service ref="simpleMasterPasswordProvider" interface="net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider" />
+
+ <service ref="askUserJavaTruststorePasswordProvider" interface="net.sf.taverna.t2.security.credentialmanager.JavaTruststorePasswordProvider" />
+
+ <service ref="askUserServiceUsernameAndPasswordProvider" interface="net.sf.taverna.t2.security.credentialmanager.ServiceUsernameAndPasswordProvider" />
+
+ <service ref="askUserTrustConfirmationProvider" interface="net.sf.taverna.t2.security.credentialmanager.TrustConfirmationProvider" />
+
+ <service ref="UIUsernamePasswordProvider" auto-export="interfaces" />
+
+ <service ref="UIMasterPasswordProvider" auto-export="interfaces" />
+
+ <service ref="ConfirmTrustedCertificateUI" auto-export="interfaces" />
+
+ <service ref="InitialiseSSLStartupHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+
+ <service ref="SetCredManAuthenticatorStartupHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+
+ <service ref="CredentialManagerMenu" auto-export="interfaces" />
+
+ <reference id="CredentialManager" interface="net.sf.taverna.t2.security.credentialmanager.CredentialManager" />
+
+ <reference id="distinguishedNameParser" interface="net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser" />
+
+ <reference id="ApplicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+</beans:beans>
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/spring/credential-manager-ui-context.xml b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/spring/credential-manager-ui-context.xml
new file mode 100644
index 0000000..0e54c93
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/META-INF/spring/credential-manager-ui-context.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="askUserMasterPasswordProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.AskUserMasterPasswordProvider" />
+
+ <bean id="simpleMasterPasswordProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.SimpleMasterPasswordProvider" />
+
+ <bean id="askUserJavaTruststorePasswordProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.AskUserJavaTruststorePasswordProvider" />
+
+ <bean id="askUserServiceUsernameAndPasswordProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.AskUserServiceUsernameAndPasswordProvider" />
+
+ <bean id="askUserTrustConfirmationProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.AskUserTrustConfirmationProvider" />
+
+ <bean id="MasterPasswordProviderComparator" class="net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider$ProviderComparator" />
+
+ <bean id="UIUsernamePasswordProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.UIUsernamePasswordProvider" >
+ <property name="distinguishedNameParser" ref="distinguishedNameParser" />
+ </bean>
+
+ <bean id="UIMasterPasswordProvider" class="net.sf.taverna.t2.workbench.ui.credentialmanager.password.UIMasterPasswordProvider">
+ <property name="applicationConfiguration" ref="ApplicationConfiguration" />
+ <property name="distinguishedNameParser" ref="distinguishedNameParser" />
+ </bean>
+
+ <bean id="ConfirmTrustedCertificateUI" class="net.sf.taverna.t2.workbench.ui.credentialmanager.ConfirmTrustedCertificateUI">
+ <property name="distinguishedNameParser" ref="distinguishedNameParser" />
+ </bean>
+
+ <bean id="InitialiseSSLStartupHook" class="net.sf.taverna.t2.workbench.ui.credentialmanager.startup.InitialiseSSLStartupHook">
+ <property name="credentialManager" ref="CredentialManager" />
+ </bean>
+
+ <bean id="SetCredManAuthenticatorStartupHook" class="net.sf.taverna.t2.workbench.ui.credentialmanager.startup.SetCredManAuthenticatorStartupHook" >
+ <property name="credentialManager" ref="CredentialManager" />
+ </bean>
+
+ <bean id="CredentialManagerMenu" class="net.sf.taverna.t2.workbench.ui.credentialmanager.menu.CredentialManagerMenu" >
+ <property name="credentialManager" ref="CredentialManager" />
+ <property name="distinguishedNameParser" ref="distinguishedNameParser" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager.png
new file mode 100644
index 0000000..48cd63c
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager.png
Binary files differ
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager16x16.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager16x16.png
new file mode 100644
index 0000000..c6e73b5
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager16x16.png
Binary files differ
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager_transparent.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager_transparent.png
new file mode 100644
index 0000000..1e89bde
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/cred_manager_transparent.png
Binary files differ
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/table/entry_heading.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/entry_heading.png
new file mode 100644
index 0000000..8b59845
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/entry_heading.png
Binary files differ
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/table/key_entry.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/key_entry.png
new file mode 100644
index 0000000..1fd18c6
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/key_entry.png
Binary files differ
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/table/keypair_entry.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/keypair_entry.png
new file mode 100644
index 0000000..8fd3e8b
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/keypair_entry.png
Binary files differ
diff --git a/taverna-workbench-credential-manager-ui/src/main/resources/images/table/trustcert_entry.png b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/trustcert_entry.png
new file mode 100644
index 0000000..0f110e1
--- /dev/null
+++ b/taverna-workbench-credential-manager-ui/src/main/resources/images/table/trustcert_entry.png
Binary files differ
diff --git a/taverna-workbench-data-management-config-ui/pom.xml b/taverna-workbench-data-management-config-ui/pom.xml
new file mode 100644
index 0000000..4afb4ad
--- /dev/null
+++ b/taverna-workbench-data-management-config-ui/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>data-management-config-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Data management configuration UI components</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <!--<dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>run-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-database-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <!-- <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aop</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.aspectj</groupId>
+ <artifactId>aspectjrt</artifactId>
+ <version>${aspectj.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.aspectj</groupId>
+ <artifactId>aspectjweaver</artifactId>
+ <version>${aspectj.version}</version>
+ </dependency> -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-data-management-config-ui/src/main/java/net/sf/taverna/t2/workbench/reference/config/DataManagementConfigurationPanel.java b/taverna-workbench-data-management-config-ui/src/main/java/net/sf/taverna/t2/workbench/reference/config/DataManagementConfigurationPanel.java
new file mode 100644
index 0000000..b705362
--- /dev/null
+++ b/taverna-workbench-data-management-config-ui/src/main/java/net/sf/taverna/t2/workbench/reference/config/DataManagementConfigurationPanel.java
@@ -0,0 +1,304 @@
+/*******************************************************************************
+ * 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.reference.config;
+
+import static java.awt.Color.RED;
+import static java.awt.Font.PLAIN;
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.RELATIVE;
+import static java.awt.GridBagConstraints.WEST;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.sql.Connection;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import uk.org.taverna.configuration.database.DatabaseConfiguration;
+import uk.org.taverna.configuration.database.DatabaseManager;
+
+@SuppressWarnings("serial")
+public class DataManagementConfigurationPanel extends JPanel {
+ private DatabaseConfiguration configuration;
+ private DatabaseManager databaseManager;
+
+ private JCheckBox enableProvenance;
+ private JCheckBox enableInMemory;
+ private JButton helpButton;
+ private JButton resetButton;
+ private JButton applyButton;
+ private JTextArea storageText;
+ private JTextArea exposeDatanatureText;
+ private JCheckBox exposeDatanatureBox;
+ private DialogTextArea enableInMemoryTextDisabled;
+
+ public DataManagementConfigurationPanel(DatabaseConfiguration configuration, DatabaseManager databaseManager) {
+ this.configuration = configuration;
+ this.databaseManager = databaseManager;
+
+ setLayout(generateLayout());
+ resetFields();
+ }
+
+ private static final boolean ADD_WARNING_LISTENERS = false;
+
+ private GridBagLayout generateLayout() {
+ GridBagLayout gridbag = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+
+ enableProvenance = new JCheckBox("Enable provenance capture");
+ DialogTextArea enableProvenanceText = new DialogTextArea(
+ "Disabling provenance will prevent you from being able to view intermediate results, but does give a performance benefit.");
+ Font plain = enableProvenanceText.getFont().deriveFont(PLAIN, 11);
+ enableProvenanceText.setLineWrap(true);
+ enableProvenanceText.setWrapStyleWord(true);
+ enableProvenanceText.setEditable(false);
+ enableProvenanceText.setFocusable(false);
+ enableProvenanceText.setOpaque(false);
+ enableProvenanceText.setFont(plain);
+
+ enableInMemory = new JCheckBox("In-memory storage");
+ DialogTextArea enableInMemoryText = new DialogTextArea(
+ "Data will not be stored between workbench sessions. If you run workflows passing larger amounts of data, try disabling in-memory storage, which can reduce execution performance, but also Taverna's memory consumption. ");
+ enableInMemoryText.setLineWrap(true);
+ enableInMemoryText.setWrapStyleWord(true);
+ enableInMemoryText.setEditable(false);
+ enableInMemoryText.setFocusable(false);
+ enableInMemoryText.setOpaque(false);
+ enableInMemoryText.setFont(plain);
+
+ enableInMemoryTextDisabled = new DialogTextArea(
+ "If you enable in-memory storage of data when provenance collection is turned on then provenance will not be available after you shutdown Taverna as the in-memory data will be lost.");
+ enableInMemoryTextDisabled.setLineWrap(true);
+ enableInMemoryTextDisabled.setWrapStyleWord(true);
+ enableInMemoryTextDisabled.setEditable(false);
+ enableInMemoryTextDisabled.setFocusable(false);
+ enableInMemoryTextDisabled.setOpaque(false);
+ enableInMemoryTextDisabled.setFont(plain);
+ enableInMemoryTextDisabled.setForeground(RED);
+ enableInMemoryTextDisabled.setVisible(false);
+
+ // Disable warning as inMemory is default
+ // To re-enable - also see resetFields()
+
+ if (ADD_WARNING_LISTENERS) {
+ enableInMemory.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ enableInMemoryTextDisabled.setVisible(enableProvenance
+ .isSelected() && enableInMemory.isSelected());
+ }
+ });
+ enableProvenance.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ enableInMemoryTextDisabled.setVisible(enableProvenance
+ .isSelected() && enableInMemory.isSelected());
+ }
+ });
+ }
+
+ storageText = new JTextArea(
+ "Select how Taverna stores the data and provenance produced when a workflow is run. This includes workflow results and intermediate results.");
+ storageText.setLineWrap(true);
+ storageText.setWrapStyleWord(true);
+ storageText.setEditable(false);
+ storageText.setFocusable(false);
+ storageText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ storageText.setFont(plain);
+
+ JComponent portPanel = createDerbyServerStatusComponent();
+
+ c.anchor = WEST;
+ c.insets = new Insets(0, 0, 10, 0);
+ c.gridx = 0;
+ c.gridy = RELATIVE;
+ c.weightx = 0.0;
+ c.weighty = 0.0;
+ c.fill = HORIZONTAL;
+ gridbag.setConstraints(storageText, c);
+ add(storageText);
+
+ c.ipady = 0;
+ c.insets = new Insets(0, 0, 5, 0);
+ c.fill = NONE;
+ gridbag.setConstraints(enableProvenance, c);
+ add(enableProvenance);
+
+ c.insets = new Insets(0, 20, 15, 20);
+ c.fill = HORIZONTAL;
+ gridbag.setConstraints(enableProvenanceText, c);
+ add(enableProvenanceText);
+
+ c.insets = new Insets(0, 0, 5, 0);
+ c.fill = GridBagConstraints.NONE;
+ gridbag.setConstraints(enableInMemory, c);
+ add(enableInMemory);
+
+ c.insets = new Insets(0, 20, 15, 20);
+ c.fill = HORIZONTAL;
+ gridbag.setConstraints(enableInMemoryText, c);
+ add(enableInMemoryText);
+
+ c.insets = new Insets(0, 20, 15, 20);
+ c.fill = HORIZONTAL;
+ gridbag.setConstraints(enableInMemoryTextDisabled, c);
+ add(enableInMemoryTextDisabled);
+
+ c.insets = new Insets(0, 20, 15, 20);
+ gridbag.setConstraints(portPanel, c);
+ add(portPanel);
+
+ c.insets = new Insets(0, 0, 5, 0);
+ c.fill = NONE;
+ exposeDatanatureBox = new JCheckBox(
+ "Allow setting of input data encoding");
+ gridbag.setConstraints(exposeDatanatureBox, c);
+ add(exposeDatanatureBox);
+
+ exposeDatanatureText = new JTextArea(
+ "Select if you want to control how Taverna handles files read as input data");
+ exposeDatanatureText.setLineWrap(true);
+ exposeDatanatureText.setWrapStyleWord(true);
+ exposeDatanatureText.setEditable(false);
+ exposeDatanatureText.setFocusable(false);
+ exposeDatanatureText.setOpaque(false);
+ exposeDatanatureText.setFont(plain);
+
+ c.insets = new Insets(0, 20, 15, 20);
+ c.fill = HORIZONTAL;
+ gridbag.setConstraints(exposeDatanatureText, c);
+ add(exposeDatanatureText);
+
+ JPanel buttonPanel = createButtonPanel();
+ c.weightx = 1.0;
+ c.weighty = 1.0;
+ c.fill = BOTH;
+ c.insets = new Insets(0, 0, 5, 0);
+ gridbag.setConstraints(buttonPanel, c);
+ add(buttonPanel);
+ return gridbag;
+ }
+
+ private JComponent createDerbyServerStatusComponent() {
+ DialogTextArea textArea = new DialogTextArea();
+ boolean running;
+
+ try (Connection connection = databaseManager.getConnection()) {
+ running = databaseManager.isRunning();
+ } catch (Exception e) {
+ running = false;
+ }
+
+ if (running)
+ textArea.setText("The database is currently running on port: "
+ + configuration.getCurrentPort() + ".");
+ else
+ textArea.setText("Unable to retrieve a database connection - "
+ + "the database is not available.");
+
+ textArea.setLineWrap(true);
+ textArea.setWrapStyleWord(true);
+ textArea.setEditable(false);
+ textArea.setFocusable(false);
+ textArea.setOpaque(false);
+ textArea.setAlignmentX(CENTER_ALIGNMENT);
+ textArea.setFont(textArea.getFont().deriveFont(PLAIN, 11));
+ textArea.setVisible(configuration.getStartInternalDerbyServer());
+ return textArea;
+ }
+
+ // for testing only
+// public static void main(String[] args) {
+// JDialog dialog = new JDialog();
+// dialog.add(new DataManagementConfigurationPanel());
+// dialog.setModal(true);
+// dialog.setSize(500, 300);
+// dialog.setVisible(true);
+// System.exit(0);
+// }
+
+ public void resetFields() {
+ enableInMemory.setSelected(configuration.isInMemory());
+ enableProvenance.setSelected(configuration.isProvenanceEnabled());
+ exposeDatanatureBox.setSelected(configuration.isExposeDatanature());
+
+ if (ADD_WARNING_LISTENERS) {
+ enableInMemoryTextDisabled.setVisible(enableProvenance.isSelected()
+ && enableInMemory.isSelected());
+ }
+ }
+
+ /*private boolean workflowInstances() {
+ return DataflowRunsComponent.getInstance().getRunListCount()>0;
+ }*/
+
+ private void applySettings() {
+ configuration.setProvenanceEnabled(enableProvenance.isSelected());
+ configuration.setInMemory(enableInMemory.isSelected());
+ configuration.setExposeDatanature(exposeDatanatureBox.isSelected());
+ }
+
+ private JPanel createButtonPanel() {
+ final JPanel panel = new JPanel();
+
+ helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ resetFields();
+ }
+ });
+ panel.add(resetButton);
+
+ applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ applySettings();
+ resetFields();
+ }
+ });
+ panel.add(applyButton);
+
+ return panel;
+ }
+}
diff --git a/taverna-workbench-data-management-config-ui/src/main/java/net/sf/taverna/t2/workbench/reference/config/DataManagementConfigurationUIFactory.java b/taverna-workbench-data-management-config-ui/src/main/java/net/sf/taverna/t2/workbench/reference/config/DataManagementConfigurationUIFactory.java
new file mode 100644
index 0000000..2799c7e
--- /dev/null
+++ b/taverna-workbench-data-management-config-ui/src/main/java/net/sf/taverna/t2/workbench/reference/config/DataManagementConfigurationUIFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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.reference.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+import uk.org.taverna.configuration.database.DatabaseConfiguration;
+import uk.org.taverna.configuration.database.DatabaseManager;
+
+public class DataManagementConfigurationUIFactory implements
+ ConfigurationUIFactory {
+ private DatabaseConfiguration databaseConfiguration;
+ private DatabaseManager databaseManager;
+
+ private DataManagementConfigurationPanel configPanel;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ if (configPanel == null)
+ configPanel = new DataManagementConfigurationPanel(
+ databaseConfiguration, databaseManager);
+ configPanel.resetFields();
+ return configPanel;
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return databaseConfiguration;
+ }
+
+ public void setDatabaseConfiguration(
+ DatabaseConfiguration databaseConfiguration) {
+ this.databaseConfiguration = databaseConfiguration;
+ }
+
+ public void setDatabaseManager(DatabaseManager databaseManager) {
+ this.databaseManager = databaseManager;
+ }
+}
diff --git a/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..8afa6ca
--- /dev/null
+++ b/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.reference.config.DataManagementConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/spring/data-management-config-ui-context-osgi.xml b/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/spring/data-management-config-ui-context-osgi.xml
new file mode 100644
index 0000000..89f84a7
--- /dev/null
+++ b/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/spring/data-management-config-ui-context-osgi.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="DataManagementConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <reference id="databaseConfiguration" interface="uk.org.taverna.configuration.database.DatabaseConfiguration" />
+ <reference id="databaseManager" interface="uk.org.taverna.configuration.database.DatabaseManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/spring/data-management-config-ui-context.xml b/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/spring/data-management-config-ui-context.xml
new file mode 100644
index 0000000..f9f40ed
--- /dev/null
+++ b/taverna-workbench-data-management-config-ui/src/main/resources/META-INF/spring/data-management-config-ui-context.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="DataManagementConfigurationUIFactory" class="net.sf.taverna.t2.workbench.reference.config.DataManagementConfigurationUIFactory">
+ <property name="databaseConfiguration" ref="databaseConfiguration"/>
+ <property name="databaseManager" ref="databaseManager"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-design-ui/pom.xml b/taverna-workbench-design-ui/pom.xml
new file mode 100644
index 0000000..f88f676
--- /dev/null
+++ b/taverna-workbench-design-ui/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>design-ui</artifactId>
+ <name>Design UI</name>
+ <packaging>bundle</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddConditionAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddConditionAction.java
new file mode 100644
index 0000000..510775f
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddConditionAction.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * Action for adding a condition to the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AddConditionAction extends DataflowEditAction {
+ private static final Logger logger = Logger.getLogger(AddConditionAction.class);
+ private static final Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ private Processor control;
+ private Processor target;
+
+ public AddConditionAction(Workflow dataflow, Processor control,
+ Processor target, Component component, EditManager editManager,
+ SelectionManager selectionManager,
+ ActivityIconManager activityIconManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.control = control;
+ this.target = target;
+ ProcessorBinding processorBinding = scufl2Tools
+ .processorBindingForProcessor(control, dataflow.getParent()
+ .getMainProfile());
+ putValue(SMALL_ICON,
+ activityIconManager.iconForActivity(processorBinding
+ .getBoundActivity().getType()));
+ putValue(NAME, control.getName());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ try {
+ BlockingControlLink controlLink = new BlockingControlLink();
+ controlLink.setUntilFinished(control);
+ controlLink.setBlock(target);
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new AddChildEdit<>(dataflow, controlLink));
+ } catch (EditException e) {
+ logger.debug("Create control link between '" + control.getName()
+ + "' and '" + target.getName() + "' failed");
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddDataflowInputAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddDataflowInputAction.java
new file mode 100644
index 0000000..ff56997
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddDataflowInputAction.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.workbench.design.ui.DataflowInputPortPanel;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddWorkflowInputPortEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+/**
+ * Action for adding an input port to the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AddDataflowInputAction extends DataflowEditAction {
+ private static final Logger logger = Logger
+ .getLogger(AddDataflowInputAction.class);
+
+ public AddDataflowInputAction(Workflow dataflow, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ putValue(SMALL_ICON, WorkbenchIcons.inputIcon);
+ putValue(NAME, "Workflow input port");
+ putValue(SHORT_DESCRIPTION, "Add workflow input port");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ try {
+ Set<String> usedInputPorts = new HashSet<>();
+ for (InputWorkflowPort inputPort : dataflow.getInputPorts())
+ usedInputPorts.add(inputPort.getName());
+
+ DataflowInputPortPanel inputPanel = new DataflowInputPortPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ "Add Workflow Input Port", inputPanel);
+ vuid.addTextComponentValidation(inputPanel.getPortNameField(),
+ "Set the workflow input port name.", usedInputPorts,
+ "Duplicate workflow input port name.",
+ "[\\p{L}\\p{Digit}_.]+",
+ "Invalid workflow input port name.");
+ vuid.addMessageComponent(inputPanel.getSingleValueButton(),
+ "Set the input port type.");
+ vuid.addMessageComponent(inputPanel.getListValueButton(),
+ "Set the input port list depth.");
+ vuid.setSize(new Dimension(400, 250));
+
+ inputPanel.setPortDepth(0);
+
+ if (vuid.show(component)) {
+ InputWorkflowPort dataflowInputPort = new InputWorkflowPort();
+ dataflowInputPort.setName(inputPanel.getPortName());
+ dataflowInputPort.setDepth(inputPanel.getPortDepth());
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new AddWorkflowInputPortEdit(dataflow,
+ dataflowInputPort));
+ }
+ } catch (EditException e) {
+ logger.warn("Adding a new workflow input port failed");
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddDataflowOutputAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddDataflowOutputAction.java
new file mode 100644
index 0000000..98bf8be
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/AddDataflowOutputAction.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.workbench.design.ui.DataflowOutputPortPanel;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddWorkflowOutputPortEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * Action for adding an output port to the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AddDataflowOutputAction extends DataflowEditAction {
+ private static final Logger logger = Logger
+ .getLogger(AddDataflowOutputAction.class);
+
+ public AddDataflowOutputAction(Workflow dataflow, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ putValue(SMALL_ICON, WorkbenchIcons.outputIcon);
+ putValue(NAME, "Workflow output port");
+ putValue(SHORT_DESCRIPTION, "Add workflow output port");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ try {
+ Set<String> usedOutputPorts = new HashSet<>();
+ for (OutputWorkflowPort outputPort : dataflow.getOutputPorts())
+ usedOutputPorts.add(outputPort.getName());
+
+ DataflowOutputPortPanel inputPanel = new DataflowOutputPortPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ "Add Workflow Output Port", inputPanel);
+ vuid.addTextComponentValidation(inputPanel.getPortNameField(),
+ "Set the workflow output port name.", usedOutputPorts,
+ "Duplicate workflow output port name.",
+ "[\\p{L}\\p{Digit}_.]+",
+ "Invalid workflow output port name.");
+ vuid.setSize(new Dimension(400, 200));
+
+ if (vuid.show(component)) {
+ String portName = inputPanel.getPortName();
+ OutputWorkflowPort dataflowOutputPort = new OutputWorkflowPort();
+ dataflowOutputPort.setName(portName);
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new AddWorkflowOutputPortEdit(dataflow,
+ dataflowOutputPort));
+ }
+ } catch (EditException e) {
+ logger.debug("Create workflow output port failed", e);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/DataflowEditAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/DataflowEditAction.java
new file mode 100644
index 0000000..a2ca5ea
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/DataflowEditAction.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Abstract superclass of dataflow edit actions.
+ *
+ * @author David Withers
+ */
+public abstract class DataflowEditAction extends AbstractAction {
+ private static final long serialVersionUID = -1155192575675025091L;
+
+ protected final SelectionManager selectionManager;
+ protected EditManager editManager;
+ protected DataflowSelectionModel dataflowSelectionModel;
+ protected Workflow dataflow;
+ protected Component component;
+ protected Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public DataflowEditAction(Workflow dataflow, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ this.dataflow = dataflow;
+ this.component = component;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ dataflowSelectionModel = selectionManager
+ .getDataflowSelectionModel(dataflow.getParent());
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/EditDataflowInputPortAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/EditDataflowInputPortAction.java
new file mode 100644
index 0000000..e4513d2
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/EditDataflowInputPortAction.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.workbench.design.ui.DataflowInputPortPanel;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.ChangeDepthEdit;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+/**
+ * Action for editing a dataflow input port.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class EditDataflowInputPortAction extends DataflowEditAction {
+ private static Logger logger = Logger
+ .getLogger(EditDataflowInputPortAction.class);
+
+ private InputWorkflowPort port;
+
+ public EditDataflowInputPortAction(Workflow dataflow,
+ InputWorkflowPort port, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.port = port;
+ putValue(SMALL_ICON, WorkbenchIcons.renameIcon);
+ putValue(NAME, "Edit workflow input port...");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Set<String> usedInputPorts = new HashSet<>();
+ for (InputWorkflowPort usedInputPort : dataflow.getInputPorts())
+ if (!usedInputPort.getName().equals(port.getName()))
+ usedInputPorts.add(usedInputPort.getName());
+
+ DataflowInputPortPanel inputPanel = new DataflowInputPortPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ "Edit Workflow Input Port", inputPanel);
+ vuid.addTextComponentValidation(inputPanel.getPortNameField(),
+ "Set the workflow input port name.", usedInputPorts,
+ "Duplicate workflow input port name.", "[\\p{L}\\p{Digit}_.]+",
+ "Invalid workflow input port name.");
+ vuid.addMessageComponent(inputPanel.getSingleValueButton(),
+ "Set the input port type.");
+ vuid.addMessageComponent(inputPanel.getListValueButton(),
+ "Set the input port list depth.");
+ vuid.setSize(new Dimension(400, 250));
+
+ inputPanel.setPortName(port.getName());
+ inputPanel.setPortDepth(port.getDepth());
+
+ try {
+ if (vuid.show(component))
+ changeInputPort(inputPanel);
+ } catch (EditException e1) {
+ logger.warn("Rename workflow input port failed", e1);
+ }
+ }
+
+ private void changeInputPort(DataflowInputPortPanel inputPanel)
+ throws EditException {
+ List<Edit<?>> editList = new ArrayList<>();
+ String portName = inputPanel.getPortName();
+ if (!portName.equals(port.getName()))
+ editList.add(new RenameEdit<>(port, portName));
+ int portDepth = inputPanel.getPortDepth();
+ if (portDepth != port.getDepth())
+ editList.add(new ChangeDepthEdit<>(port, portDepth));
+ if (editList.size() == 1)
+ editManager.doDataflowEdit(dataflow.getParent(), editList.get(0));
+ else if (editList.size() > 1)
+ editManager.doDataflowEdit(dataflow.getParent(), new CompoundEdit(
+ editList));
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/EditDataflowOutputPortAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/EditDataflowOutputPortAction.java
new file mode 100644
index 0000000..da7c0e2
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/EditDataflowOutputPortAction.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.workbench.design.ui.DataflowOutputPortPanel;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * Action for editing a dataflow output port.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class EditDataflowOutputPortAction extends DataflowEditAction {
+ private static final Logger logger = Logger
+ .getLogger(EditDataflowOutputPortAction.class);
+
+ private OutputWorkflowPort port;
+
+ public EditDataflowOutputPortAction(Workflow dataflow,
+ OutputWorkflowPort port, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.port = port;
+ putValue(SMALL_ICON, WorkbenchIcons.renameIcon);
+ putValue(NAME, "Edit workflow output port...");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Set<String> usedOutputPorts = new HashSet<>();
+ for (OutputWorkflowPort usedOutputPort : dataflow.getOutputPorts())
+ if (!usedOutputPort.getName().equals(port.getName()))
+ usedOutputPorts.add(usedOutputPort.getName());
+
+ DataflowOutputPortPanel inputPanel = new DataflowOutputPortPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ "Edit Workflow Output Port", inputPanel);
+ vuid.addTextComponentValidation(inputPanel.getPortNameField(),
+ "Set the workflow output port name.", usedOutputPorts,
+ "Duplicate workflow output port name.",
+ "[\\p{L}\\p{Digit}_.]+", "Invalid workflow output port name.");
+ vuid.setSize(new Dimension(400, 200));
+
+ inputPanel.setPortName(port.getName());
+
+ try {
+ if (vuid.show(component))
+ changeOutputPort(inputPanel);
+ } catch (EditException ex) {
+ logger.debug("Rename workflow output port failed", ex);
+ }
+ }
+
+ private void changeOutputPort(DataflowOutputPortPanel inputPanel)
+ throws EditException {
+ editManager.doDataflowEdit(dataflow.getParent(), new RenameEdit<>(port,
+ inputPanel.getPortName()));
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveConditionAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveConditionAction.java
new file mode 100644
index 0000000..89036f0
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveConditionAction.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.deleteIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RemoveChildEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Action for removing a condition from the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RemoveConditionAction extends DataflowEditAction {
+ private static final Logger logger = Logger
+ .getLogger(RemoveConditionAction.class);
+
+ private ControlLink controlLink;
+
+ public RemoveConditionAction(Workflow dataflow, ControlLink controlLink,
+ Component component, EditManager editManager,
+ SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.controlLink = controlLink;
+ putValue(SMALL_ICON, deleteIcon);
+ putValue(NAME, "Delete control link");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ dataflowSelectionModel.removeSelection(controlLink);
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new RemoveChildEdit<>(dataflow, controlLink));
+ } catch (EditException e1) {
+ logger.debug("Delete control link failed", e1);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDataflowInputPortAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDataflowInputPortAction.java
new file mode 100644
index 0000000..5483ea5
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDataflowInputPortAction.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.deleteIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RemoveDataLinkEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveWorkflowInputPortEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+/**
+ * Action for removing an input port from the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RemoveDataflowInputPortAction extends DataflowEditAction {
+ private static Logger logger = Logger
+ .getLogger(RemoveDataflowInputPortAction.class);
+
+ private InputWorkflowPort port;
+
+ public RemoveDataflowInputPortAction(Workflow dataflow,
+ InputWorkflowPort port, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.port = port;
+ putValue(SMALL_ICON, deleteIcon);
+ putValue(NAME, "Delete workflow input port");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ dataflowSelectionModel.removeSelection(port);
+ List<DataLink> datalinks = scufl2Tools.datalinksFrom(port);
+ if (datalinks.isEmpty())
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new RemoveWorkflowInputPortEdit(dataflow, port));
+ else {
+ List<Edit<?>> editList = new ArrayList<>();
+ for (DataLink datalink : datalinks)
+ editList.add(new RemoveDataLinkEdit(dataflow, datalink));
+ editList.add(new RemoveWorkflowInputPortEdit(dataflow, port));
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new CompoundEdit(editList));
+ }
+ } catch (EditException e1) {
+ logger.debug("Delete workflow input port failed", e1);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDataflowOutputPortAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDataflowOutputPortAction.java
new file mode 100644
index 0000000..ed91d41
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDataflowOutputPortAction.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.deleteIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RemoveDataLinkEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveWorkflowOutputPortEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * Action for removing an output port from the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RemoveDataflowOutputPortAction extends DataflowEditAction {
+ private static final Logger logger = Logger
+ .getLogger(RemoveDataflowOutputPortAction.class);
+
+ private OutputWorkflowPort port;
+
+ public RemoveDataflowOutputPortAction(Workflow dataflow,
+ OutputWorkflowPort port, Component component,
+ EditManager editManager, SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.port = port;
+ putValue(SMALL_ICON, deleteIcon);
+ putValue(NAME, "Delete workflow output port");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ dataflowSelectionModel.removeSelection(port);
+ List<DataLink> datalinks = scufl2Tools.datalinksTo(port);
+ if (datalinks.isEmpty())
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new RemoveWorkflowOutputPortEdit(dataflow, port));
+ else {
+ List<Edit<?>> editList = new ArrayList<>();
+ for (DataLink datalink : datalinks)
+ editList.add(new RemoveDataLinkEdit(dataflow, datalink));
+ editList.add(new RemoveWorkflowOutputPortEdit(dataflow, port));
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new CompoundEdit(editList));
+ }
+ } catch (EditException ex) {
+ logger.debug("Delete workflow output port failed", ex);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDatalinkAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDatalinkAction.java
new file mode 100644
index 0000000..e4df75d
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveDatalinkAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.deleteIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RemoveDataLinkEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Action for removing a datalink from the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RemoveDatalinkAction extends DataflowEditAction {
+ private static final Logger logger = Logger.getLogger(RemoveDatalinkAction.class);
+
+ private DataLink datalink;
+
+ public RemoveDatalinkAction(Workflow dataflow, DataLink datalink,
+ Component component, EditManager editManager,
+ SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.datalink = datalink;
+ putValue(SMALL_ICON, deleteIcon);
+ putValue(NAME, "Delete data link");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ try {
+ dataflowSelectionModel.removeSelection(datalink);
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new RemoveDataLinkEdit(dataflow, datalink));
+ } catch (EditException ex) {
+ logger.debug("Delete data link failed", ex);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveProcessorAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveProcessorAction.java
new file mode 100644
index 0000000..063a346
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RemoveProcessorAction.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.deleteIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RemoveChildEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveDataLinkEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Action for removing a processor from the dataflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RemoveProcessorAction extends DataflowEditAction {
+ private static final Logger logger = Logger
+ .getLogger(RemoveProcessorAction.class);
+
+ private Processor processor;
+
+ public RemoveProcessorAction(Workflow dataflow, Processor processor,
+ Component component, EditManager editManager,
+ SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.processor = processor;
+ putValue(SMALL_ICON, deleteIcon);
+ putValue(NAME, "Delete service");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ dataflowSelectionModel.removeSelection(processor);
+
+ NamedSet<InputProcessorPort> inputPorts = processor.getInputPorts();
+ NamedSet<OutputProcessorPort> outputPorts = processor
+ .getOutputPorts();
+ List<BlockingControlLink> controlLinksBlocking = scufl2Tools
+ .controlLinksBlocking(processor);
+ List<BlockingControlLink> controlLinksWaitingFor = scufl2Tools
+ .controlLinksWaitingFor(processor);
+ List<Edit<?>> editList = new ArrayList<>();
+ for (InputProcessorPort inputPort : inputPorts)
+ for (DataLink datalink : scufl2Tools.datalinksTo(inputPort))
+ editList.add(new RemoveDataLinkEdit(dataflow, datalink));
+ for (OutputProcessorPort outputPort : outputPorts)
+ for (DataLink datalink : scufl2Tools.datalinksFrom(outputPort))
+ editList.add(new RemoveDataLinkEdit(dataflow, datalink));
+ for (ControlLink controlLink : controlLinksBlocking)
+ editList.add(new RemoveChildEdit<>(dataflow, controlLink));
+ for (ControlLink controlLink : controlLinksWaitingFor)
+ editList.add(new RemoveChildEdit<>(dataflow, controlLink));
+
+ for (Profile profile : dataflow.getParent().getProfiles()) {
+ List<ProcessorBinding> processorBindings = scufl2Tools
+ .processorBindingsForProcessor(processor, profile);
+ for (ProcessorBinding processorBinding : processorBindings) {
+ Activity boundActivity = processorBinding
+ .getBoundActivity();
+ List<ProcessorBinding> processorBindingsToActivity = scufl2Tools
+ .processorBindingsToActivity(boundActivity);
+ if (processorBindingsToActivity.size() == 1) {
+ editList.add(new RemoveChildEdit<>(profile,
+ boundActivity));
+ for (Configuration configuration : scufl2Tools
+ .configurationsFor(boundActivity, profile))
+ editList.add(new RemoveChildEdit<Profile>(profile,
+ configuration));
+ }
+ editList.add(new RemoveChildEdit<Profile>(profile,
+ processorBinding));
+ }
+ }
+ for (Profile profile : dataflow.getParent().getProfiles()) {
+ List<Configuration> configurations = scufl2Tools
+ .configurationsFor(processor, profile);
+ for (Configuration configuration : configurations)
+ editList.add(new RemoveChildEdit<>(profile, configuration));
+ }
+ if (editList.isEmpty())
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new RemoveChildEdit<>(dataflow, processor));
+ else {
+ editList.add(new RemoveChildEdit<>(dataflow, processor));
+ editManager.doDataflowEdit(dataflow.getParent(),
+ new CompoundEdit(editList));
+ }
+ } catch (EditException e1) {
+ logger.error("Delete processor failed", e1);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RenameProcessorAction.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RenameProcessorAction.java
new file mode 100644
index 0000000..5b5b733
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/actions/RenameProcessorAction.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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.design.actions;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.workbench.design.ui.ProcessorPanel;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Action for renaming a processor.
+ *
+ * @author David Withers
+ */
+public class RenameProcessorAction extends DataflowEditAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private static Logger logger = Logger
+ .getLogger(RenameProcessorAction.class);
+
+ private Processor processor;
+
+ public RenameProcessorAction(Workflow dataflow, Processor processor,
+ Component component, EditManager editManager,
+ SelectionManager selectionManager) {
+ super(dataflow, component, editManager, selectionManager);
+ this.processor = processor;
+ putValue(SMALL_ICON, WorkbenchIcons.renameIcon);
+ putValue(NAME, "Rename service...");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Set<String> usedProcessors = new HashSet<>();
+ for (Processor usedProcessor : dataflow.getProcessors())
+ if (!usedProcessor.getName().equals(processor.getName()))
+ usedProcessors.add(usedProcessor.getName());
+
+ ProcessorPanel inputPanel = new ProcessorPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ "Rename service", inputPanel);
+ vuid.addTextComponentValidation(inputPanel.getProcessorNameField(),
+ "Set the service name.", usedProcessors, "Duplicate service.",
+ "[\\p{L}\\p{Digit}_.]+", "Invalid service name.");
+ vuid.setSize(new Dimension(400, 200));
+
+ inputPanel.setProcessorName(processor.getName());
+
+ try {
+ if (vuid.show(component))
+ changeProcessorName(inputPanel);
+ } catch (EditException e1) {
+ logger.debug("Rename service (processor) failed", e1);
+ }
+ }
+
+ private void changeProcessorName(ProcessorPanel inputPanel)
+ throws EditException {
+ String processorName = inputPanel.getProcessorName();
+ editManager.doDataflowEdit(dataflow.getParent(), new RenameEdit<>(
+ processor, processorName));
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/DataflowInputPortPanel.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/DataflowInputPortPanel.java
new file mode 100644
index 0000000..e578ef2
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/DataflowInputPortPanel.java
@@ -0,0 +1,203 @@
+/*******************************************************************************
+ * 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.design.ui;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.NORTHWEST;
+import static java.awt.GridBagConstraints.WEST;
+import static java.awt.event.ItemEvent.SELECTED;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * UI for creating/editing dataflow input ports.
+ *
+ * @author David Withers
+ */
+public class DataflowInputPortPanel extends JPanel {
+ private static final long serialVersionUID = 2650486705615513458L;
+
+ private JTextField portNameField;
+ private JRadioButton singleValueButton;
+ private JRadioButton listValueButton;
+ private JSpinner listDepthSpinner;
+
+ public DataflowInputPortPanel() {
+ super(new GridBagLayout());
+
+ portNameField = new JTextField();
+ singleValueButton = new JRadioButton("Single value");
+ listValueButton = new JRadioButton("List of depth ");
+ listDepthSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1));
+
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ GridBagConstraints constraints = new GridBagConstraints();
+
+ constraints.anchor = WEST;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.ipadx = 10;
+ add(new JLabel("Name:"), constraints);
+
+ constraints.gridx = 1;
+ constraints.gridwidth = 2;
+ constraints.ipadx = 0;
+ constraints.weightx = 1d;
+ constraints.fill = HORIZONTAL;
+ add(portNameField, constraints);
+
+ constraints.gridx = 0;
+ constraints.gridy = 1;
+ constraints.gridwidth = 1;
+ constraints.weightx = 0d;
+ constraints.fill = NONE;
+ constraints.ipadx = 10;
+ constraints.insets = new Insets(10, 0, 0, 0);
+ add(new JLabel("Type:"), constraints);
+
+ ButtonGroup buttonGroup = new ButtonGroup();
+ buttonGroup.add(singleValueButton);
+ buttonGroup.add(listValueButton);
+
+ final JLabel helpLabel = new JLabel(
+ "Depth 1 is a list, 2 is a list of lists, etc.");
+ helpLabel.setFont(helpLabel.getFont().deriveFont(11f));
+
+ singleValueButton.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ boolean selected = (e.getStateChange() == SELECTED);
+ listDepthSpinner.setEnabled(!selected);
+ helpLabel.setEnabled(!selected);
+ }
+ });
+
+ constraints.gridx = 1;
+ constraints.gridwidth = 2;
+ constraints.ipadx = 0;
+ add(singleValueButton, constraints);
+ constraints.gridy = 2;
+ constraints.gridwidth = 1;
+ constraints.insets = new Insets(0, 0, 0, 0);
+ add(listValueButton, constraints);
+ constraints.gridx = 2;
+ add(listDepthSpinner, constraints);
+ constraints.gridx = 1;
+ constraints.gridy = 3;
+ constraints.gridwidth = 2;
+ constraints.weighty = 1d;
+ constraints.anchor = NORTHWEST;
+ constraints.insets = new Insets(0, 20, 0, 0);
+ add(helpLabel, constraints);
+ }
+
+ /**
+ * Returns the portNameField.
+ *
+ * @return the portNameField
+ */
+ public JTextField getPortNameField() {
+ return portNameField;
+ }
+
+ /**
+ * Returns the singleValueButton.
+ *
+ * @return the singleValueButton
+ */
+ public JRadioButton getSingleValueButton() {
+ return singleValueButton;
+ }
+
+ /**
+ * Returns the listValueButton.
+ *
+ * @return the listValueButton
+ */
+ public JRadioButton getListValueButton() {
+ return listValueButton;
+ }
+
+ /**
+ * Returns the port name.
+ *
+ * @return the port name
+ */
+ public String getPortName() {
+ return portNameField.getText();
+ }
+
+ /**
+ * Sets the port name.
+ *
+ * @param name
+ * the name of the port
+ */
+ public void setPortName(String name) {
+ portNameField.setText(name);
+ // Select the text
+ if (!name.isEmpty()) {
+ portNameField.setSelectionStart(0);
+ portNameField.setSelectionEnd(name.length());
+ }
+ }
+
+ /**
+ * Returns the port depth.
+ *
+ * @return the port depth
+ */
+ public int getPortDepth() {
+ if (singleValueButton.isSelected())
+ return 0;
+ return (Integer) listDepthSpinner.getValue();
+ }
+
+ /**
+ * Sets the port depth.
+ *
+ * @param depth
+ * the depth of the port
+ */
+ public void setPortDepth(int depth) {
+ if (depth == 0) {
+ singleValueButton.setSelected(true);
+ } else {
+ listValueButton.setSelected(true);
+ listDepthSpinner.setValue(depth);
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/DataflowOutputPortPanel.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/DataflowOutputPortPanel.java
new file mode 100644
index 0000000..59ac0f7
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/DataflowOutputPortPanel.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * 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.design.ui;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.VERTICAL;
+import static java.awt.GridBagConstraints.WEST;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * UI for creating/editing dataflow output ports.
+ *
+ * @author David Withers
+ */
+public class DataflowOutputPortPanel extends JPanel {
+ private static final long serialVersionUID = -2542858679939965553L;
+
+ private JTextField portNameField;
+
+ public DataflowOutputPortPanel() {
+ super(new GridBagLayout());
+
+ portNameField = new JTextField();
+
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ GridBagConstraints constraints = new GridBagConstraints();
+
+ constraints.anchor = WEST;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.ipadx = 10;
+ add(new JLabel("Name:"), constraints);
+
+ constraints.gridx = 1;
+ constraints.gridwidth = 2;
+ constraints.ipadx = 0;
+ constraints.weightx = 1d;
+ constraints.fill = HORIZONTAL;
+ add(portNameField, constraints);
+
+ constraints.gridx = 0;
+ constraints.gridy = 1;
+ constraints.fill = VERTICAL;
+ constraints.weighty = 1d;
+ add(new JPanel(), constraints);
+ }
+
+ /**
+ * Returns the portNameField.
+ *
+ * @return the portNameField
+ */
+ public JTextField getPortNameField() {
+ return portNameField;
+ }
+
+ /**
+ * Returns the port name.
+ *
+ * @return the port name
+ */
+ public String getPortName() {
+ return portNameField.getText();
+ }
+
+ /**
+ * Sets the port name.
+ *
+ * @param name the name of the port
+ */
+ public void setPortName(String name) {
+ portNameField.setText(name);
+ // Select the text
+ if (!name.isEmpty()) {
+ portNameField.setSelectionStart(0);
+ portNameField.setSelectionEnd(name.length());
+ }
+ }
+}
diff --git a/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/ProcessorPanel.java b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/ProcessorPanel.java
new file mode 100644
index 0000000..4528727
--- /dev/null
+++ b/taverna-workbench-design-ui/src/main/java/net/sf/taverna/t2/workbench/design/ui/ProcessorPanel.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * 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.design.ui;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.VERTICAL;
+import static java.awt.GridBagConstraints.WEST;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * UI for editing processors.
+ *
+ * @author David Withers
+ */
+public class ProcessorPanel extends JPanel {
+ private static final long serialVersionUID = 260705376633425003L;
+
+ private JTextField processorNameField;
+
+ public ProcessorPanel() {
+ super(new GridBagLayout());
+
+ processorNameField = new JTextField();
+
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ GridBagConstraints constraints = new GridBagConstraints();
+
+ constraints.anchor = WEST;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.ipadx = 10;
+ add(new JLabel("Name:"), constraints);
+
+ constraints.gridx = 1;
+ constraints.gridwidth = 2;
+ constraints.ipadx = 0;
+ constraints.weightx = 1d;
+ constraints.fill = HORIZONTAL;
+ add(processorNameField, constraints);
+
+ constraints.gridx = 0;
+ constraints.gridy = 1;
+ constraints.fill = VERTICAL;
+ constraints.weighty = 1d;
+ add(new JPanel(), constraints);
+ }
+
+ /**
+ * Returns the processorNameField.
+ *
+ * @return the processorNameField
+ */
+ public JTextField getProcessorNameField() {
+ return processorNameField;
+ }
+
+ /**
+ * Returns the processor name.
+ *
+ * @return the processor name
+ */
+ public String getProcessorName() {
+ return processorNameField.getText();
+ }
+
+ /**
+ * Sets the processor name.
+ *
+ * @param name
+ * the name of the processor
+ */
+ public void setProcessorName(String name) {
+ processorNameField.setText(name);
+ }
+}
diff --git a/taverna-workbench-edits-api/pom.xml b/taverna-workbench-edits-api/pom.xml
new file mode 100644
index 0000000..1b52630
--- /dev/null
+++ b/taverna-workbench-edits-api/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Edits API</name>
+ <description>API for doing workflow edits and undo.</description>
+ <dependencies>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/CompoundEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/CompoundEdit.java
new file mode 100644
index 0000000..58c7add
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/CompoundEdit.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * 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.edits;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * Implementation of Edit which contains an ordered list of child edits. Child
+ * edits are applied collectively and in order, any failure in any child edit
+ * causes an undo of previously applied children and a propogation of the edit
+ * exception.
+ *
+ * @author Tom Oinn
+ */
+public class CompoundEdit implements Edit<WorkflowBean> {
+ private final transient List<Edit<?>> childEdits;
+ private transient boolean applied = false;
+
+ /**
+ * Create a new compound edit with no existing Edit objects.
+ *
+ */
+ public CompoundEdit() {
+ this.childEdits = new ArrayList<>();
+ }
+
+ /**
+ * Create a new compound edit with the specified edits as children.
+ */
+ public CompoundEdit(List<Edit<?>> edits) {
+ this.childEdits = edits;
+ }
+
+ /**
+ * Get the list of edits.
+ * @return a live-editable list.
+ */
+ public List<Edit<?>> getChildEdits() {
+ return childEdits;
+ }
+
+ /**
+ * Attempts to call the doEdit method of all child edits. If any of those
+ * children throws an EditException any successful edits are rolled back and
+ * the exception is rethrown as the cause of a new EditException from the
+ * CompoundEdit
+ */
+ @Override
+ public synchronized WorkflowBean doEdit() throws EditException {
+ if (isApplied())
+ throw new EditException("Cannot apply an edit more than once!");
+ List<Edit<?>> doneEdits = new ArrayList<>();
+ try {
+ for (Edit<?> edit : childEdits) {
+ edit.doEdit();
+ /*
+ * Insert the done edit at position 0 in the list so we can
+ * iterate over the list in the normal order if we need to
+ * rollback, this ensures that the most recent edit is first.
+ */
+ doneEdits.add(0, edit);
+ }
+ applied = true;
+ } catch (EditException ee) {
+ for (Edit<?> undoMe : doneEdits)
+ undoMe.undo();
+ applied = false;
+ throw new EditException("Failed child of compound edit", ee);
+ }
+ return null;
+ }
+
+ /**
+ * There is no explicit subject for a compound edit, so this method always
+ * returns null.
+ */
+ @Override
+ public Object getSubject() {
+ return null;
+ }
+
+ /**
+ * Rolls back all child edits in reverse order
+ */
+ @Override
+ public synchronized void undo() {
+ for (int i = childEdits.size() - 1; i >= 0; i--)
+ // Undo child edits in reverse order
+ childEdits.get(i).undo();
+ applied = false;
+ }
+
+ @Override
+ public boolean isApplied() {
+ return applied;
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/Edit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/Edit.java
new file mode 100644
index 0000000..fad211e
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/Edit.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * 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.edits;
+
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * The workflow object model exposed by this API is read only. Properties of the
+ * model can only be changed through implementations of this interface, this
+ * ensures a consistant approach to grouped edits (transactions) and undo / redo
+ * support within the UI. It also potentially allows for capture of editing
+ * provenance where a workflow is repurposed or created from an aggregate of
+ * several others.
+ *
+ * @author Tom Oinn
+ */
+public interface Edit<TargetType extends WorkflowBean> {
+ /**
+ * Perform the edit
+ *
+ * @throws EditException
+ * if the edit fails. If an edit throws EditException it should
+ * try to ensure the subject is unaltered. Where this is
+ * impossible consider breaking edits down into a compound edit.
+ */
+ TargetType doEdit() throws EditException;
+
+ /**
+ * Undo the edit, reverting the subject to the state it was in prior to the
+ * edit
+ */
+ void undo();
+
+ /**
+ * Return the object to which this edit applies
+ *
+ * @return
+ */
+ Object getSubject();
+
+ /**
+ * Has the edit been applied yet?
+ *
+ * @return true if and only if the edit has been successfully applied to the
+ * subject
+ */
+ boolean isApplied();
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/EditException.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/EditException.java
new file mode 100644
index 0000000..3c61d84
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/EditException.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.edits;
+
+/**
+ * Superclass of all exceptions thrown when altering the workflow model through
+ * the edit manager.
+ *
+ * @author Tom Oinn
+ */
+@SuppressWarnings("serial")
+public class EditException extends Exception {
+ public EditException(String string) {
+ super(string);
+ }
+
+ public EditException(String string, Throwable cause) {
+ super(string, cause);
+ }
+
+ public EditException(Throwable t) {
+ super(t);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/EditManager.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/EditManager.java
new file mode 100644
index 0000000..ce8b917
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/EditManager.java
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * 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.edits;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+
+/**
+ * Manager that can handle {@link Edit edits} for a {@link WorkflowBundle}.
+ * <p>
+ * Edits to a workflow that are to be undoable or redoable should be created by
+ * using {@link EditManager#getEdits()} to get an {@link Edits} object. Using
+ * this to create {@link Edit}s, instead of calling {@link Edit#doEdit()}, use
+ * {@link EditManager#doDataflowEdit(WorkflowBundle, Edit)} to associate the
+ * edit with the specified Dataflow.
+ * <p>
+ * It is possible to undo a series of edits done on a particular dataflow in
+ * this way by using {@link #undoDataflowEdit(WorkflowBundle)}. If one or more
+ * undoes have been performed, they can be redone step by step using
+ * {@link #redoDataflowEdit(WorkflowBundle)}. Note that it is no longer possible
+ * to call {@link #redoDataflowEdit(WorkflowBundle)} after a
+ * {@link #doDataflowEdit(WorkflowBundle, Edit)}.
+ * <p>
+ * The EditManager is {@link Observable}. If you
+ * {@linkplain Observable#addObserver(net.sf.taverna.t2.lang.observer.Observer)
+ * add an observer} you can be notified on {@linkplain DataflowEditEvent edits},
+ * {@linkplain DataFlowUndoEvent undoes} and {@linkplain DataFlowRedoEvent
+ * redoes}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public interface EditManager extends Observable<EditManagerEvent> {
+ /**
+ * <code>true</code> if {@link #redoDataflowEdit(WorkflowBundle)} on the
+ * given dataflow would redo the last undone edit. If there are no previous
+ * edits, return <code>false</code>.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} which last affecting edit is to be
+ * undone
+ * @return <code>true</code if and only if
+ * {@link #redoDataflowEdit(WorkflowBundle)} would undo
+ */
+ boolean canRedoDataflowEdit(WorkflowBundle dataflow);
+
+ /**
+ * <code>true</code> if {@link #undoDataflowEdit(WorkflowBundle)} on the
+ * given dataflow would undo the last edit. If there are no previous edits,
+ * return <code>false</code>.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} which last affecting edit is to be
+ * undone
+ * @return <code>true</code if {@link #undoDataflowEdit(WorkflowBundle)}
+ * would undo
+ */
+ boolean canUndoDataflowEdit(WorkflowBundle dataflow);
+
+ /**
+ * Do an {@link Edit} affecting the given {@link WorkflowBundle}.
+ * <p>
+ * The edit is {@link Edit#doEdit() performed} and the edit can later be
+ * undone using {@link EditManager#undoDataflowEdit(WorkflowBundle)}.
+ * <p>
+ * Note that any events previously undone with
+ * {@link EditManager#undoDataflowEdit(WorkflowBundle)} for the given
+ * dataflow can no longer be
+ * {@link EditManager#redoDataflowEdit(WorkflowBundle) redone} after calling
+ * this method.
+ *
+ * @see EditManager#undoDataflowEdit(WorkflowBundle)
+ * @param dataflow
+ * {@link WorkflowBundle} this edit is affecting
+ * @param edit
+ * {@link Edit} that should be done using {@link Edit#doEdit()}.
+ * @throws EditException
+ * If {@link Edit#doEdit()} fails
+ */
+ void doDataflowEdit(WorkflowBundle dataflow, Edit<?> edit)
+ throws EditException;
+
+ /**
+ * Redo the last {@link Edit} that was undone using
+ * {@link #undoDataflowEdit(WorkflowBundle)}.
+ * <p>
+ * Note that the {@link EditManager} might only be able to redo a reasonable
+ * number of steps.
+ * <p>
+ * It is not possible to use {@link #redoDataflowEdit(WorkflowBundle)} after
+ * a {@link #doDataflowEdit(WorkflowBundle, Edit)} affecting the same
+ * {@link WorkflowBundle}, or if no edits have been undone yet. No action
+ * would be taken in these cases.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} which last affecting edit is to be
+ * redone
+ * @throws EditException
+ * If {@link Edit#doEdit()} fails
+ */
+ void redoDataflowEdit(WorkflowBundle dataflow) throws EditException;
+
+ /**
+ * Undo the last {@link Edit} affecting the given {@link WorkflowBundle}.
+ * <p>
+ * This can be called in succession until there are no more known undoes.
+ * Note that the {@link EditManager} might only be able to undo a reasonable
+ * number of steps.
+ * <p>
+ * The last edit must have been performed using
+ * {@link EditManager#doDataflowEdit(WorkflowBundle, Edit)} or
+ * {@link EditManager#redoDataflowEdit(WorkflowBundle)}. The undo is done
+ * using {@link Edit#undo()}. If no edits have been performed for the
+ * dataflow yet, no action is taken.
+ * <p>
+ * Undoes can be redone using {@link #redoDataflowEdit(WorkflowBundle)}.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} which last affecting edit is to be
+ * undone
+ */
+ void undoDataflowEdit(WorkflowBundle dataflow);
+
+ /**
+ * An event about an {@link Edit} on a {@link WorkflowBundle}, accessible
+ * through {@link AbstractDataflowEditEvent#getEdit()} and
+ * {@link AbstractDataflowEditEvent#getDataFlow()}.
+ */
+ public static abstract class AbstractDataflowEditEvent implements
+ EditManagerEvent {
+ private final WorkflowBundle dataFlow;
+ private final Edit<?> edit;
+
+ public AbstractDataflowEditEvent(WorkflowBundle dataFlow, Edit<?> edit) {
+ if (dataFlow == null || edit == null)
+ throw new NullPointerException(
+ "Dataflow and/or Edit can't be null");
+ this.dataFlow = dataFlow;
+ this.edit = edit;
+ }
+
+ /**
+ * The {@link WorkflowBundle} this event affected.
+ *
+ * @return A {@link WorkflowBundle}
+ */
+ public WorkflowBundle getDataFlow() {
+ return dataFlow;
+ }
+
+ /**
+ * The {@link Edit} that was performed, undoed or redone on the
+ * {@link #getDataFlow() dataflow}.
+ *
+ * @return An {@link Edit}
+ */
+ @Override
+ public Edit<?> getEdit() {
+ return edit;
+ }
+ }
+
+ /**
+ * An event sent when an {@link Edit} has been performed on a
+ * {@link WorkflowBundle}.
+ *
+ */
+ public static class DataflowEditEvent extends AbstractDataflowEditEvent {
+ public DataflowEditEvent(WorkflowBundle dataFlow, Edit<?> edit) {
+ super(dataFlow, edit);
+ }
+ }
+
+ /**
+ * An event sent when a previously undone {@link Edit} has been redone on a
+ * {@link WorkflowBundle}.
+ *
+ */
+ public static class DataFlowRedoEvent extends AbstractDataflowEditEvent {
+ public DataFlowRedoEvent(WorkflowBundle dataFlow, Edit<?> edit) {
+ super(dataFlow, edit);
+ }
+ }
+
+ /**
+ * An event sent when an {@link Edit} has been undone on a
+ * {@link WorkflowBundle}.
+ *
+ */
+ public static class DataFlowUndoEvent extends AbstractDataflowEditEvent {
+ public DataFlowUndoEvent(WorkflowBundle dataFlow, Edit<?> edit) {
+ super(dataFlow, edit);
+ }
+ }
+
+ /**
+ * An event given to {@link Observer}s registered with
+ * {@link Observable#addObserver(Observer)}.
+ */
+ public interface EditManagerEvent {
+ public Edit<?> getEdit();
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/package-info.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/package-info.java
new file mode 100644
index 0000000..c1df7c9
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workbench/edits/package-info.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/**
+ * A {@link net.sf.taverna.t2.workbench.edits.EditManager} that can manage
+ * {@link net.sf.taverna.t2.workflowmodel.Edit}s performed from the UI.
+ * <p>
+ * To perform an edit that is to be undoable, use
+ * {@link EditManager#doDataflowEdit(net.sf.taverna.t2.workflowmodel.Dataflow, net.sf.taverna.t2.workflowmodel.Edit)}
+ * instead of {@link net.sf.taverna.t2.workflowmodel.Edit#doEdit()}. Such edits
+ * can be
+ * {@link EditManager#undoDataflowEdit(net.sf.taverna.t2.workflowmodel.Dataflow) undone}
+ * and
+ * {@link EditManager#redoDataflowEdit(net.sf.taverna.t2.workflowmodel.Dataflow) redone}.
+ * </p>
+ * <p>
+ * Edits are organised by {@link net.sf.taverna.t2.workflowmodel.Dataflow} so
+ * that if a user changes the active workflow in the Workbench and does "Undo" -
+ * that would undo the last undo done related to that workflow.
+ * </p>
+ * <p>
+ * The {@link net.sf.taverna.t2.workbench.edits.impl} implementation of the
+ * EditManager is discovered by {@link net.sf.taverna.t2.workbench.edits.EditManager#getInstance()}. The
+ * implementation also includes {@link net.sf.taverna.t2.ui.menu.MenuComponent}s
+ * for Undo and Redo.
+ * </p>
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+package net.sf.taverna.t2.workbench.edits;
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AbstractEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AbstractEdit.java
new file mode 100644
index 0000000..e6c8e68
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AbstractEdit.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (C) 2007-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.workflow.edits;
+
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * An abstract {@link Edit} implementation that checks if an edit has been
+ * applied or not.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ * @param <Subject>
+ * Subject of this edit
+ */
+public abstract class AbstractEdit<Subject extends WorkflowBean> implements
+ Edit<Subject> {
+ private boolean applied = false;
+ private final Subject subject;
+ protected final Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ /**
+ * Construct an AbstractEdit.
+ *
+ * @param subjectType
+ * The expected implementation type of the subject. The edit will
+ * not go through unless the subject is an instance of this type.
+ * If the edit don't care about the implementation type, provide
+ * the official SubjectInterface instead.
+ * @param subject
+ * The subject of this edit
+ */
+ public AbstractEdit(Subject subject) {
+ if (subject == null && !isNullSubjectAllowed())
+ throw new IllegalArgumentException(
+ "Cannot construct an edit with null subject");
+ this.subject = subject;
+ }
+
+ protected boolean isNullSubjectAllowed() {
+ return false;
+ }
+
+ @Override
+ public final Subject doEdit() throws EditException {
+ if (applied)
+ throw new EditException("Edit has already been applied!");
+ try {
+ synchronized (subject) {
+ doEditAction(subject);
+ applied = true;
+ return this.subject;
+ }
+ } catch (EditException ee) {
+ applied = false;
+ throw ee;
+ }
+ }
+
+ /**
+ * Do the actual edit here
+ *
+ * @param subject
+ * The instance to which the edit applies
+ * @throws EditException
+ */
+ protected abstract void doEditAction(Subject subject)
+ throws EditException;
+
+ /**
+ * Undo any edit effects here
+ *
+ * @param subject
+ * The instance to which the edit applies
+ */
+ protected abstract void undoEditAction(Subject subject);
+
+ @Override
+ public final Subject getSubject() {
+ return subject;
+ }
+
+ @Override
+ public final boolean isApplied() {
+ return applied;
+ }
+
+ @Override
+ public final void undo() {
+ if (!applied)
+ throw new RuntimeException(
+ "Attempt to undo edit that was never applied");
+ synchronized (subject) {
+ undoEditAction(subject);
+ applied = false;
+ }
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityEdit.java
new file mode 100644
index 0000000..c6ffa7c
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityEdit.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * Creates a ProcessorBinding binding for the Activity and Processor and adds the binding to the
+ * Profile containing the Activity.
+ *
+ * @author David Withers
+ */
+public class AddActivityEdit extends AbstractEdit<Processor> {
+ private Activity activity;
+ private ProcessorBinding addedProcessorBinding;
+
+ public AddActivityEdit(Processor processor, Activity activity) {
+ super(processor);
+ this.activity = activity;
+ }
+
+ @Override
+ protected void doEditAction(Processor processor) {
+ ProcessorBinding binding = new ProcessorBinding();
+ binding.setBoundProcessor(processor);
+ binding.setBoundActivity(activity);
+ binding.setParent(activity.getParent());
+ addedProcessorBinding = binding;
+ }
+
+ @Override
+ protected void undoEditAction(Processor processor) {
+ addedProcessorBinding.setParent(null);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityInputPortMappingEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityInputPortMappingEdit.java
new file mode 100644
index 0000000..9a7e8b7
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityInputPortMappingEdit.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+
+public class AddActivityInputPortMappingEdit extends AbstractEdit<Activity> {
+ private final InputProcessorPort inputProcessorPort;
+ private final InputActivityPort inputActivityPort;
+ private List<ProcessorInputPortBinding> portBindings;
+
+ public AddActivityInputPortMappingEdit(Activity activity,
+ InputProcessorPort inputProcessorPort,
+ InputActivityPort inputActivityPort) {
+ super(activity);
+ this.inputProcessorPort = inputProcessorPort;
+ this.inputActivityPort = inputActivityPort;
+ }
+
+ @Override
+ protected void doEditAction(Activity activity) {
+ portBindings = new ArrayList<>();
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ portBindings.add(new ProcessorInputPortBinding(binding,
+ inputProcessorPort, inputActivityPort));
+ }
+
+ @Override
+ protected void undoEditAction(Activity activity) {
+ for (ProcessorInputPortBinding binding : portBindings)
+ binding.setParent(null);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityOutputPortMappingEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityOutputPortMappingEdit.java
new file mode 100644
index 0000000..edafe8e
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddActivityOutputPortMappingEdit.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+
+public class AddActivityOutputPortMappingEdit extends AbstractEdit<Activity> {
+ private final OutputProcessorPort outputProcessorPort;
+ private final OutputActivityPort outputActivityPort;
+ private List<ProcessorOutputPortBinding> portBindings;
+
+ public AddActivityOutputPortMappingEdit(Activity activity,
+ OutputProcessorPort outputProcessorPort,
+ OutputActivityPort outputActivityPort) {
+ super(activity);
+ this.outputProcessorPort = outputProcessorPort;
+ this.outputActivityPort = outputActivityPort;
+ }
+
+ @Override
+ protected void doEditAction(Activity activity) {
+ portBindings = new ArrayList<>();
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ portBindings.add(new ProcessorOutputPortBinding(binding,
+ outputActivityPort, outputProcessorPort));
+ }
+
+ @Override
+ protected void undoEditAction(Activity activity) {
+ for (ProcessorOutputPortBinding binding : portBindings)
+ binding.setParent(null);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddChildEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddChildEdit.java
new file mode 100644
index 0000000..abd8d9f
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddChildEdit.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.common.Child;
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * Adds a child to a parent.
+ *
+ * @author David Withers
+ */
+public class AddChildEdit<T extends WorkflowBean> extends AbstractEdit<T> {
+ private Child<T> child;
+
+ public AddChildEdit(T parent, Child<T> child) {
+ super(parent);
+ this.child = child;
+ }
+
+ @Override
+ protected void doEditAction(T parent) {
+ child.setParent(parent);
+ }
+
+ @Override
+ protected void undoEditAction(T parent) {
+ child.setParent(null);
+ }
+
+ public Child<T> getChild() {
+ return child;
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddDataLinkEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddDataLinkEdit.java
new file mode 100644
index 0000000..10ff290
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddDataLinkEdit.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.iterationstrategy.CrossProduct;
+import uk.org.taverna.scufl2.api.iterationstrategy.DotProduct;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyParent;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyTopNode;
+import uk.org.taverna.scufl2.api.iterationstrategy.PortNode;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+
+/**
+ * Adds a DataLink to a Workflow.
+ * <p>
+ * Handles setting the merge position of all dataLinks with the same receiver port.
+ * <p>
+ * Modifies the processor's iteration strategy or when the first DataLink is added.
+ *
+ * @author David Withers
+ */
+public class AddDataLinkEdit extends AbstractEdit<Workflow> {
+ private DataLink dataLink;
+ private PortNode portNode;
+
+ public AddDataLinkEdit(Workflow workflow, DataLink dataLink) {
+ super(workflow);
+ this.dataLink = dataLink;
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) {
+ ReceiverPort sink = dataLink.getSendsTo();
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(sink);
+ if (datalinksTo.size() > 0) {
+ if (datalinksTo.size() == 1)
+ datalinksTo.get(0).setMergePosition(0);
+ dataLink.setMergePosition(datalinksTo.size());
+ } else {
+ dataLink.setMergePosition(null);
+ if (sink instanceof InputProcessorPort) {
+ InputProcessorPort inputProcessorPort = (InputProcessorPort) sink;
+ for (IterationStrategyTopNode node : inputProcessorPort.getParent().getIterationStrategyStack()) {
+ portNode = new PortNode(node, inputProcessorPort);
+ portNode.setDesiredDepth(inputProcessorPort.getDepth());
+ break;
+ }
+ }
+ }
+ dataLink.setParent(workflow);
+ }
+
+ @Override
+ protected void undoEditAction(Workflow workflow) {
+ dataLink.setParent(null);
+ ReceiverPort sink = dataLink.getSendsTo();
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(sink);
+ if (datalinksTo.size() == 1)
+ datalinksTo.get(0).setMergePosition(null);
+ else if (datalinksTo.isEmpty()&&portNode != null) {
+ IterationStrategyParent parent = portNode.getParent();
+ if (parent instanceof DotProduct)
+ ((DotProduct) parent).remove(portNode);
+ else if (parent instanceof CrossProduct)
+ ((CrossProduct) parent).remove(portNode);
+ }
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddIterationStrategyEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddIterationStrategyEdit.java
new file mode 100644
index 0000000..8677352
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddIterationStrategyEdit.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyTopNode;
+
+/**
+ * Adds an IterationStrategyTopNode to an IterationStrategyStack.
+ *
+ * @author David Withers
+ */
+public class AddIterationStrategyEdit extends AbstractEdit<IterationStrategyStack> {
+ private final IterationStrategyTopNode iterationStrategyTopNode;
+
+ public AddIterationStrategyEdit(IterationStrategyStack iterationStrategyStack,
+ IterationStrategyTopNode iterationStrategyTopNode) {
+ super(iterationStrategyStack);
+ this.iterationStrategyTopNode = iterationStrategyTopNode;
+ }
+
+ @Override
+ public void doEditAction(IterationStrategyStack iterationStrategyStack) {
+ iterationStrategyStack.add(iterationStrategyTopNode);
+ }
+
+ @Override
+ public void undoEditAction(IterationStrategyStack iterationStrategyStack) {
+ iterationStrategyStack.remove(iterationStrategyTopNode);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddIterationStrategyInputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddIterationStrategyInputPortEdit.java
new file mode 100644
index 0000000..bb51fc6
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddIterationStrategyInputPortEdit.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
+import uk.org.taverna.scufl2.api.iterationstrategy.PortNode;
+
+/**
+ * Adds an iteration strategy input port node to an iteration strategy.
+ *
+ * @author David Withers
+ */
+public class AddIterationStrategyInputPortEdit extends
+ AbstractEdit<IterationStrategyStack> {
+ private final PortNode portNode;
+
+ public AddIterationStrategyInputPortEdit(
+ IterationStrategyStack iterationStrategy, PortNode portNode) {
+ super(iterationStrategy);
+ this.portNode = portNode;
+ }
+
+ @Override
+ public void doEditAction(IterationStrategyStack iterationStrategy) {
+ portNode.setParent(iterationStrategy.get(0));
+ }
+
+ @Override
+ public void undoEditAction(IterationStrategyStack iterationStrategy) {
+ portNode.setParent(null);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorEdit.java
new file mode 100644
index 0000000..04cf73b
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorEdit.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Adds a Processor to a Workflow.
+ *
+ * @author Stuart Owen
+ * @author David Withers
+ */
+public class AddProcessorEdit extends AddChildEdit<Workflow> {
+ private Processor processor;
+
+ public AddProcessorEdit(Workflow workflow, Processor processor) {
+ super(workflow, processor);
+ this.processor = processor;
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) {
+ getSubject().getProcessors().addWithUniqueName(processor);
+ super.doEditAction(workflow);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorInputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorInputPortEdit.java
new file mode 100644
index 0000000..f722ddc
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorInputPortEdit.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+
+/**
+ * Adds an input port to a processor.
+ *
+ * @author Tom Oinn
+ * @author David Withers
+ */
+public class AddProcessorInputPortEdit extends AddChildEdit<Processor> {
+ private final InputProcessorPort port;
+
+ public AddProcessorInputPortEdit(Processor processor, InputProcessorPort port) {
+ super(processor, port);
+ this.port = port;
+ }
+
+ @Override
+ protected void doEditAction(Processor processor) {
+ processor.getInputPorts().addWithUniqueName(port);
+ super.doEditAction(processor);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorOutputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorOutputPortEdit.java
new file mode 100644
index 0000000..eec51b1
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddProcessorOutputPortEdit.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+
+/**
+ * Adds an output port to a processor.
+ *
+ * @author Tom Oinn
+ * @author David Withers
+ */
+public class AddProcessorOutputPortEdit extends AddChildEdit<Processor> {
+ private final OutputProcessorPort port;
+
+ public AddProcessorOutputPortEdit(Processor processor,
+ OutputProcessorPort port) {
+ super(processor, port);
+ this.port = port;
+ }
+
+ @Override
+ protected void doEditAction(Processor processor) {
+ processor.getOutputPorts().addWithUniqueName(port);
+ super.doEditAction(processor);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddWorkflowInputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddWorkflowInputPortEdit.java
new file mode 100644
index 0000000..91a9153
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddWorkflowInputPortEdit.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.workflow.edits;
+
+import static uk.org.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Adds an input port to a workflow.
+ *
+ * @author David Withers
+ */
+public class AddWorkflowInputPortEdit extends AbstractEdit<Workflow> {
+ private final InputWorkflowPort port;
+ private final CompoundEdit nestedPortEdit = new CompoundEdit();
+
+ public AddWorkflowInputPortEdit(Workflow workflow, InputWorkflowPort port) {
+ super(workflow);
+ this.port = port;
+ WorkflowBundle workflowBundle = workflow.getParent();
+ if (workflowBundle != null)
+ for (Profile profile : workflowBundle.getProfiles())
+ for (Activity activity : profile.getActivities())
+ if (activity.getType().equals(NESTED_WORKFLOW))
+ for (Configuration c : scufl2Tools.configurationsFor(
+ activity, profile))
+ defineEditsForOneConfiguration(workflow, port,
+ workflowBundle, activity, c);
+ }
+
+ private void defineEditsForOneConfiguration(Workflow workflow,
+ InputWorkflowPort port, WorkflowBundle workflowBundle,
+ Activity activity, Configuration c) {
+ List<Edit<?>> edits = nestedPortEdit.getChildEdits();
+ JsonNode nested = c.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = workflowBundle.getWorkflows().getByName(
+ nested.asText());
+
+ if (nestedWorkflow == workflow) {
+ InputActivityPort activityPort = new InputActivityPort();
+ activityPort.setName(port.getName());
+ activityPort.setDepth(port.getDepth());
+ edits.add(new AddChildEdit<>(activity, activityPort));
+
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity)) {
+ Processor processor = binding.getBoundProcessor();
+ InputProcessorPort processorPort = new InputProcessorPort();
+ processorPort.setName(port.getName());
+ processorPort.setDepth(port.getDepth());
+ edits.add(new AddProcessorInputPortEdit(processor,
+ processorPort));
+
+ ProcessorInputPortBinding portBinding = new ProcessorInputPortBinding();
+ portBinding.setBoundProcessorPort(processorPort);
+ portBinding.setBoundActivityPort(activityPort);
+ edits.add(new AddChildEdit<>(binding, portBinding));
+ }
+ }
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) throws EditException {
+ workflow.getInputPorts().addWithUniqueName(port);
+ port.setParent(workflow);
+ nestedPortEdit.doEdit();
+ }
+
+ @Override
+ protected void undoEditAction(Workflow workflow) {
+ port.setParent(null);
+ nestedPortEdit.undo();
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddWorkflowOutputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddWorkflowOutputPortEdit.java
new file mode 100644
index 0000000..7e505f1
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/AddWorkflowOutputPortEdit.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.workflow.edits;
+
+import static uk.org.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Adds an output port to a workflow.
+ *
+ * @author David Withers
+ */
+public class AddWorkflowOutputPortEdit extends AbstractEdit<Workflow> {
+ private final OutputWorkflowPort port;
+ private final CompoundEdit nestedPortEdit = new CompoundEdit();
+
+ public AddWorkflowOutputPortEdit(Workflow workflow, OutputWorkflowPort port) {
+ super(workflow);
+ this.port = port;
+ WorkflowBundle workflowBundle = workflow.getParent();
+ if (workflowBundle != null)
+ for (Profile profile : workflowBundle.getProfiles())
+ for (Activity activity : profile.getActivities())
+ if (activity.getType().equals(NESTED_WORKFLOW))
+ for (Configuration c : scufl2Tools.configurationsFor(
+ activity, profile))
+ defineEditsForOneConfiguration(workflow, port,
+ workflowBundle, activity, c);
+ }
+
+ private void defineEditsForOneConfiguration(Workflow workflow,
+ OutputWorkflowPort port, WorkflowBundle workflowBundle,
+ Activity activity, Configuration c) {
+ List<Edit<?>> edits = nestedPortEdit.getChildEdits();
+ JsonNode nested = c.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = workflowBundle.getWorkflows().getByName(
+ nested.asText());
+ if (nestedWorkflow == workflow) {
+ OutputActivityPort activityPort = new OutputActivityPort();
+ activityPort.setName(port.getName());
+ activityPort.setDepth(0);
+ activityPort.setGranularDepth(0);
+ edits.add(new AddChildEdit<>(activity, activityPort));
+
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity)) {
+ Processor processor = binding.getBoundProcessor();
+ OutputProcessorPort processorPort = new OutputProcessorPort();
+ processorPort.setName(port.getName());
+ processorPort.setDepth(0);
+ processorPort.setGranularDepth(0);
+ edits.add(new AddProcessorOutputPortEdit(processor,
+ processorPort));
+
+ ProcessorOutputPortBinding portBinding = new ProcessorOutputPortBinding();
+ portBinding.setBoundProcessorPort(processorPort);
+ portBinding.setBoundActivityPort(activityPort);
+ edits.add(new AddChildEdit<>(binding, portBinding));
+ }
+ }
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) throws EditException {
+ workflow.getOutputPorts().addWithUniqueName(port);
+ port.setParent(workflow);
+ nestedPortEdit.doEdit();
+ }
+
+ @Override
+ protected void undoEditAction(Workflow workflow) {
+ port.setParent(null);
+ nestedPortEdit.undo();
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeDepthEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeDepthEdit.java
new file mode 100644
index 0000000..071b653
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeDepthEdit.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import static uk.org.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.ActivityPort;
+import uk.org.taverna.scufl2.api.port.DepthPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Changes the depth of a port.
+ *
+ * @author David Withers
+ */
+public class ChangeDepthEdit<T extends DepthPort> extends AbstractEdit<T> {
+ private Integer newDepth, oldDepth;
+
+ public ChangeDepthEdit(T depthPort, Integer newDepth) {
+ super(depthPort);
+ this.newDepth = newDepth;
+ oldDepth = depthPort.getDepth();
+ }
+
+ @Override
+ protected void doEditAction(T depthPort) {
+ depthPort.setDepth(newDepth);
+ if (depthPort instanceof InputWorkflowPort)
+ checkNestedPortDepths((InputWorkflowPort) depthPort, newDepth);
+ }
+
+ @Override
+ protected void undoEditAction(T depthPort) {
+ depthPort.setDepth(oldDepth);
+ if (depthPort instanceof InputWorkflowPort)
+ checkNestedPortDepths((InputWorkflowPort) depthPort, oldDepth);
+ }
+
+ private void checkNestedPortDepths(InputWorkflowPort workflowPort,
+ Integer depth) {
+ Workflow workflow = workflowPort.getParent();
+ if (workflow != null) {
+ WorkflowBundle workflowBundle = workflow.getParent();
+ if (workflowBundle != null)
+ for (Profile profile : workflowBundle.getProfiles())
+ for (Activity activity : profile.getActivities())
+ if (activity.getType().equals(NESTED_WORKFLOW))
+ for (Configuration c : scufl2Tools
+ .configurationsFor(activity, profile))
+ checkOneConfiguration(workflowPort, depth,
+ workflow, workflowBundle, activity, c);
+ }
+ }
+
+ private void checkOneConfiguration(InputWorkflowPort workflowPort,
+ Integer depth, Workflow workflow, WorkflowBundle workflowBundle,
+ Activity activity, Configuration c) {
+ JsonNode nested = c.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = workflowBundle.getWorkflows().getByName(
+ nested.asText());
+ if (nestedWorkflow != workflow)
+ return;
+
+ ActivityPort activityPort = activity.getInputPorts().getByName(
+ workflowPort.getName());
+ activityPort.setDepth(depth);
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ for (ProcessorInputPortBinding portBinding : binding
+ .getInputPortBindings())
+ if (portBinding.getBoundActivityPort() == activityPort) {
+ InputProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ processorPort.setDepth(depth);
+ }
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeGranularDepthEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeGranularDepthEdit.java
new file mode 100644
index 0000000..e9f1b54
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeGranularDepthEdit.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.port.GranularDepthPort;
+
+/**
+ * Changes the granular depth of a port.
+ *
+ * @author David Withers
+ */
+public class ChangeGranularDepthEdit<T extends GranularDepthPort> extends
+ AbstractEdit<T> {
+ private Integer newGranularDepth, oldGranularDepth;
+
+ public ChangeGranularDepthEdit(T granularDepthPort, Integer newGranularDepth) {
+ super(granularDepthPort);
+ this.newGranularDepth = newGranularDepth;
+ oldGranularDepth = granularDepthPort.getGranularDepth();
+ }
+
+ @Override
+ protected void doEditAction(T granularDepthPort) {
+ granularDepthPort.setGranularDepth(newGranularDepth);
+ }
+
+ @Override
+ protected void undoEditAction(T granularDepthPort) {
+ granularDepthPort.setGranularDepth(oldGranularDepth);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeJsonEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeJsonEdit.java
new file mode 100644
index 0000000..2c79ff3
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ChangeJsonEdit.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Changes the JSON of a configuration.
+ *
+ * @author David Withers
+ */
+public class ChangeJsonEdit extends AbstractEdit<Configuration> {
+ private JsonNode oldJson, newJson;
+
+ public ChangeJsonEdit(Configuration configuration, JsonNode newJson) {
+ super(configuration);
+ this.newJson = newJson;
+ newJson = configuration.getJson();
+ }
+
+ @Override
+ protected void doEditAction(Configuration configuration) {
+ configuration.setJson(newJson);
+ }
+
+ @Override
+ protected void undoEditAction(Configuration configuration) {
+ configuration.setJson(oldJson);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ClearIterationStrategyStackEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ClearIterationStrategyStackEdit.java
new file mode 100644
index 0000000..956857c
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ClearIterationStrategyStackEdit.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
+
+/**
+ * Removes all the iteration strategies from an iteration strategy stack.
+ *
+ * @author David Withers
+ */
+public class ClearIterationStrategyStackEdit extends
+ AbstractEdit<IterationStrategyStack> {
+ private IterationStrategyStack oldIterationStrategyStack;
+
+ public ClearIterationStrategyStackEdit(
+ IterationStrategyStack iterationStrategyStack) {
+ super(iterationStrategyStack);
+ }
+
+ @Override
+ protected void doEditAction(IterationStrategyStack iterationStrategyStack) {
+ oldIterationStrategyStack = new IterationStrategyStack();
+ oldIterationStrategyStack.addAll(iterationStrategyStack);
+ iterationStrategyStack.clear();
+ }
+
+ @Override
+ public void undoEditAction(IterationStrategyStack iterationStrategyStack) {
+ iterationStrategyStack.addAll(oldIterationStrategyStack);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ConfigureEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ConfigureEdit.java
new file mode 100644
index 0000000..f4274b0
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ConfigureEdit.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.common.Configurable;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+/**
+ * An Edit that configures a {@link Configurable} with a given
+ * {@link Configuration}.
+ *
+ * @author David Withers
+ */
+public class ConfigureEdit<ConfigurableType extends Configurable> extends
+ AbstractEdit<ConfigurableType> {
+ private final Configuration oldConfiguration;
+ private final Configuration newConfiguration;
+
+ public ConfigureEdit(ConfigurableType configurable,
+ Configuration oldConfiguration, Configuration newConfiguration) {
+ super(configurable);
+ this.oldConfiguration = oldConfiguration;
+ this.newConfiguration = newConfiguration;
+ }
+
+ @Override
+ protected void doEditAction(ConfigurableType configurable) {
+ oldConfiguration.setConfigures(null);
+ newConfiguration.setConfigures(configurable);
+ }
+
+ @Override
+ protected void undoEditAction(ConfigurableType configurable) {
+ oldConfiguration.setConfigures(configurable);
+ newConfiguration.setConfigures(null);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityEdit.java
new file mode 100644
index 0000000..1350d88
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityEdit.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * Remove an Activity from a Processor.
+ *
+ * @author alanrw
+ */
+public class RemoveActivityEdit extends AbstractEdit<Processor> {
+ private Activity activityToRemove;
+ private ProcessorBinding removedProcessorBinding;
+
+ public RemoveActivityEdit(Processor processor, Activity activity) {
+ super(processor);
+ this.activityToRemove = activity;
+ }
+
+ @Override
+ protected void doEditAction(Processor processor) {
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activityToRemove))
+ if (binding.getBoundProcessor().equals(processor)) {
+ removedProcessorBinding = binding;
+ removedProcessorBinding.setParent(null);
+ }
+ }
+
+ @Override
+ protected void undoEditAction(Processor processor) {
+ removedProcessorBinding.setParent(activityToRemove.getParent());
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityInputPortMappingEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityInputPortMappingEdit.java
new file mode 100644
index 0000000..ce058bf
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityInputPortMappingEdit.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+
+public class RemoveActivityInputPortMappingEdit extends AbstractEdit<Activity> {
+ private final InputActivityPort inputActivityPort;
+ private ProcessorInputPortBinding removedPortBinding;
+ private ProcessorBinding processorBinding;
+
+ public RemoveActivityInputPortMappingEdit(Activity activity,
+ InputActivityPort inputActivityPort) {
+ super(activity);
+ this.inputActivityPort = inputActivityPort;
+ }
+
+ @Override
+ protected void doEditAction(Activity activity) {
+ removedPortBinding = scufl2Tools.processorPortBindingForPort(
+ inputActivityPort, activity.getParent());
+ processorBinding = removedPortBinding.getParent();
+ removedPortBinding.setParent(null);
+ }
+
+ @Override
+ protected void undoEditAction(Activity activity) {
+ removedPortBinding.setParent(processorBinding);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityOutputPortMappingEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityOutputPortMappingEdit.java
new file mode 100644
index 0000000..8639dbd
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveActivityOutputPortMappingEdit.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+
+public class RemoveActivityOutputPortMappingEdit extends AbstractEdit<Activity> {
+ private final OutputActivityPort outputActivityPort;
+ private ProcessorOutputPortBinding removedPortBinding;
+ private ProcessorBinding processorBinding;
+
+ public RemoveActivityOutputPortMappingEdit(Activity activity,
+ OutputActivityPort outputActivityPort) {
+ super(activity);
+ this.outputActivityPort = outputActivityPort;
+ }
+
+ @Override
+ protected void doEditAction(Activity activity) {
+ removedPortBinding = scufl2Tools.processorPortBindingForPort(
+ outputActivityPort, activity.getParent());
+ processorBinding = removedPortBinding.getParent();
+ removedPortBinding.setParent(null);
+ }
+
+ @Override
+ protected void undoEditAction(Activity activity) {
+ removedPortBinding.setParent(processorBinding);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveChildEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveChildEdit.java
new file mode 100644
index 0000000..1b9d5d2
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveChildEdit.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.common.Child;
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * Removes a child from a parent.
+ *
+ * @author David Withers
+ */
+public class RemoveChildEdit<T extends WorkflowBean> extends AbstractEdit<T> {
+ private Child<T> child;
+
+ public RemoveChildEdit(T parent, Child<T> child) {
+ super(parent);
+ this.child = child;
+ }
+
+ @Override
+ protected void doEditAction(T parent) {
+ child.setParent(null);
+ }
+
+ @Override
+ protected void undoEditAction(T parent) {
+ child.setParent(parent);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveDataLinkEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveDataLinkEdit.java
new file mode 100644
index 0000000..9f8d243
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveDataLinkEdit.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyNode;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyParent;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyTopNode;
+import uk.org.taverna.scufl2.api.iterationstrategy.PortNode;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+
+/**
+ * Remove a DataLink from a Workflow.
+ * <p>
+ * Handles setting the merge position of all dataLinks with the same receiver port.
+ *
+ * @author David Withers
+ */
+public class RemoveDataLinkEdit extends AbstractEdit<Workflow> {
+ private final DataLink dataLink;
+ private PortNode portNode;
+ private int portPosition;
+ private IterationStrategyTopNode parent;
+
+ public RemoveDataLinkEdit(Workflow workflow, DataLink dataLink) {
+ super(workflow);
+ this.dataLink = dataLink;
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) {
+ dataLink.setParent(null);
+ ReceiverPort sink = dataLink.getSendsTo();
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(sink);
+ if (datalinksTo.isEmpty()) {
+ if (sink instanceof InputProcessorPort) {
+ InputProcessorPort port = (InputProcessorPort) sink;
+ for (IterationStrategyTopNode topNode : port.getParent().getIterationStrategyStack()) {
+ portNode = findPortNode(topNode, port);
+ if (portNode != null) {
+ IterationStrategyParent parentNode = portNode.getParent();
+ if (parentNode instanceof IterationStrategyTopNode) {
+ parent = (IterationStrategyTopNode) parentNode;
+ portPosition = parent.indexOf(portNode);
+ parent.remove(portNode);
+ }
+ break;
+ }
+ }
+ }
+ } else if (datalinksTo.size() == 1) {
+ datalinksTo.get(0).setMergePosition(null);
+ } else {
+ for (int i = 0; i < datalinksTo.size(); i++)
+ datalinksTo.get(i).setMergePosition(i);
+ }
+ }
+
+ @Override
+ protected void undoEditAction(Workflow workflow) {
+ ReceiverPort sink = dataLink.getSendsTo();
+ List<DataLink> datalinksTo = scufl2Tools.datalinksTo(sink);
+ if (dataLink.getMergePosition() != null)
+ for (int i = dataLink.getMergePosition(); i < datalinksTo.size(); i++)
+ datalinksTo.get(i).setMergePosition(i + 1);
+ if (portNode != null) {
+ parent.add(portPosition, portNode);
+ portNode.setParent(parent);
+ }
+ dataLink.setParent(workflow);
+ }
+
+ private PortNode findPortNode(IterationStrategyTopNode topNode,
+ InputProcessorPort port) {
+ for (IterationStrategyNode node : topNode) {
+ if (node instanceof PortNode) {
+ PortNode portNode = (PortNode) node;
+ if (port.equals(portNode.getInputProcessorPort()))
+ return portNode;
+ } else if (node instanceof IterationStrategyTopNode) {
+ IterationStrategyTopNode iterationStrategyTopNode = (IterationStrategyTopNode) node;
+ PortNode result = findPortNode(iterationStrategyTopNode, port);
+ if (result != null)
+ return result;
+ }
+ }
+ return null;
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveProcessorInputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveProcessorInputPortEdit.java
new file mode 100644
index 0000000..497f700
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveProcessorInputPortEdit.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+
+public class RemoveProcessorInputPortEdit extends RemoveChildEdit<Processor> {
+ public RemoveProcessorInputPortEdit(Processor processor,
+ InputProcessorPort port) {
+ super(processor, port);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveProcessorOutputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveProcessorOutputPortEdit.java
new file mode 100644
index 0000000..72a1238
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveProcessorOutputPortEdit.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+
+public class RemoveProcessorOutputPortEdit extends RemoveChildEdit<Processor> {
+ public RemoveProcessorOutputPortEdit(Processor processor,
+ OutputProcessorPort port) {
+ super(processor, port);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveWorkflowInputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveWorkflowInputPortEdit.java
new file mode 100644
index 0000000..a6ac8e4
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveWorkflowInputPortEdit.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.workflow.edits;
+
+import static uk.org.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Removes an input port from a workflow.
+ *
+ * @author David Withers
+ */
+public class RemoveWorkflowInputPortEdit extends AbstractEdit<Workflow> {
+ private final InputWorkflowPort port;
+ private final CompoundEdit nestedPortEdit = new CompoundEdit();
+
+ public RemoveWorkflowInputPortEdit(Workflow workflow, InputWorkflowPort port) {
+ super(workflow);
+ this.port = port;
+ WorkflowBundle workflowBundle = workflow.getParent();
+ if (workflowBundle != null)
+ for (Profile profile : workflowBundle.getProfiles())
+ for (Activity activity : profile.getActivities())
+ if (activity.getType().equals(NESTED_WORKFLOW))
+ for (Configuration c : scufl2Tools.configurationsFor(
+ activity, profile))
+ defineEditsForConfiguration(workflow, port,
+ workflowBundle, activity, c);
+ }
+
+ private void defineEditsForConfiguration(Workflow workflow,
+ InputWorkflowPort port, WorkflowBundle workflowBundle,
+ Activity activity, Configuration c) {
+ List<Edit<?>> edits = nestedPortEdit.getChildEdits();
+ JsonNode nested = c.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = workflowBundle.getWorkflows().getByName(
+ nested.asText());
+ if (nestedWorkflow != workflow)
+ return;
+
+ InputActivityPort activityPort = activity.getInputPorts().getByName(
+ port.getName());
+ edits.add(new RemoveChildEdit<>(activity, activityPort));
+
+ for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity)) {
+ Processor processor = binding.getBoundProcessor();
+ for (ProcessorInputPortBinding portBinding : binding
+ .getInputPortBindings())
+ if (portBinding.getBoundActivityPort() == activityPort) {
+ InputProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ edits.add(new RemoveProcessorInputPortEdit(processor,
+ processorPort));
+ edits.add(new RemoveChildEdit<>(binding, portBinding));
+ }
+ }
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) throws EditException {
+ port.setParent(null);
+ nestedPortEdit.doEdit();
+ }
+
+ @Override
+ protected void undoEditAction(Workflow workflow) {
+ port.setParent(workflow);
+ nestedPortEdit.undo();
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveWorkflowOutputPortEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveWorkflowOutputPortEdit.java
new file mode 100644
index 0000000..2c71da6
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RemoveWorkflowOutputPortEdit.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.workflow.edits;
+
+import static uk.org.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Removes an output port from a workflow.
+ *
+ * @author David Withers
+ */
+public class RemoveWorkflowOutputPortEdit extends AbstractEdit<Workflow> {
+ private final OutputWorkflowPort port;
+ private final CompoundEdit nestedPortEdit = new CompoundEdit();
+
+ public RemoveWorkflowOutputPortEdit(Workflow workflow,
+ OutputWorkflowPort port) {
+ super(workflow);
+ this.port = port;
+ WorkflowBundle workflowBundle = workflow.getParent();
+ if (workflowBundle != null)
+ for (Profile profile : workflowBundle.getProfiles())
+ for (Activity activity : profile.getActivities())
+ if (activity.getType().equals(NESTED_WORKFLOW))
+ for (Configuration c : scufl2Tools.configurationsFor(
+ activity, profile))
+ defineEditsForConfiguration(workflow, port,
+ workflowBundle, activity, c);
+ }
+
+ private void defineEditsForConfiguration(Workflow workflow,
+ OutputWorkflowPort port, WorkflowBundle workflowBundle,
+ Activity activity, Configuration c) {
+ List<Edit<?>> edits = nestedPortEdit.getChildEdits();
+ JsonNode nested = c.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = workflowBundle.getWorkflows().getByName(
+ nested.asText());
+ if (nestedWorkflow != workflow)
+ return;
+
+ OutputActivityPort activityPort = activity.getOutputPorts().getByName(
+ port.getName());
+ edits.add(new RemoveChildEdit<>(activity, activityPort));
+ for (ProcessorBinding processorBinding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ for (ProcessorOutputPortBinding portBinding : processorBinding
+ .getOutputPortBindings())
+ if (portBinding.getBoundActivityPort() == activityPort) {
+ OutputProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ edits.add(new RemoveProcessorOutputPortEdit(
+ processorBinding.getBoundProcessor(), processorPort));
+ edits.add(new RemoveChildEdit<>(processorBinding,
+ portBinding));
+ }
+ }
+
+ @Override
+ protected void doEditAction(Workflow workflow) throws EditException {
+ port.setParent(null);
+ nestedPortEdit.doEdit();
+ }
+
+ @Override
+ protected void undoEditAction(Workflow workflow) {
+ port.setParent(workflow);
+ nestedPortEdit.undo();
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RenameEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RenameEdit.java
new file mode 100644
index 0000000..beb4913
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/RenameEdit.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import static uk.org.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Named;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.ActivityPort;
+import uk.org.taverna.scufl2.api.port.InputPort;
+import uk.org.taverna.scufl2.api.port.ProcessorPort;
+import uk.org.taverna.scufl2.api.port.WorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Renames a Named WorkflowBean.
+ *
+ * @author David Withers
+ */
+public class RenameEdit<T extends Named> extends AbstractEdit<T> {
+ private String oldName, newName;
+
+ public RenameEdit(T named, String newName) {
+ super(named);
+ this.newName = newName;
+ oldName = named.getName();
+ }
+
+ @Override
+ protected void doEditAction(T named) {
+ named.setName(newName);
+ if (named instanceof WorkflowPort)
+ checkNestedPortNames((WorkflowPort) named, oldName, newName);
+ }
+
+ @Override
+ protected void undoEditAction(T named) {
+ named.setName(oldName);
+ if (named instanceof WorkflowPort)
+ checkNestedPortNames((WorkflowPort) named, newName, oldName);
+ }
+
+ private void checkNestedPortNames(WorkflowPort workflowPort, String oldName, String newName) {
+ Workflow workflow = workflowPort.getParent();
+ if (workflow == null)
+ return;
+ WorkflowBundle workflowBundle = workflow.getParent();
+ if (workflowBundle == null)
+ return;
+ for (Profile profile : workflowBundle.getProfiles())
+ for (Activity activity : profile.getActivities())
+ if (activity.getType().equals(NESTED_WORKFLOW))
+ for (Configuration c : scufl2Tools.configurationsFor(activity, profile))
+ changeActivityPortName(workflowPort, oldName,
+ newName, workflow, workflowBundle, activity, c);
+ }
+
+ private void changeActivityPortName(WorkflowPort workflowPort,
+ String oldName, String newName, Workflow workflow,
+ WorkflowBundle workflowBundle, Activity activity, Configuration c) {
+ JsonNode nested = c.getJson().get("nestedWorkflow");
+ Workflow nestedWorkflow = workflowBundle.getWorkflows().getByName(
+ nested.asText());
+ if (nestedWorkflow != workflow)
+ return;
+
+ ActivityPort activityPort;
+ if (workflowPort instanceof InputPort) {
+ activityPort = activity.getInputPorts().getByName(oldName);
+ changeProcessorInputPortName(oldName, newName, activity,
+ activityPort);
+ } else {
+ activityPort = activity.getOutputPorts().getByName(oldName);
+ changeProcessorOutputPortName(oldName, newName, activity,
+ activityPort);
+ }
+ activityPort.setName(newName);
+ }
+
+ private void changeProcessorInputPortName(String oldName, String newName,
+ Activity activity, ActivityPort activityPort) {
+ bindings: for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ for (ProcessorInputPortBinding portBinding : binding
+ .getInputPortBindings())
+ if (portBinding.getBoundActivityPort() == activityPort) {
+ ProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ if (processorPort.getName().equals(oldName)) {
+ processorPort.setName(newName);
+ continue bindings;
+ }
+ }
+ }
+
+ private void changeProcessorOutputPortName(String oldName, String newName,
+ Activity activity, ActivityPort activityPort) {
+ bindings: for (ProcessorBinding binding : scufl2Tools
+ .processorBindingsToActivity(activity))
+ for (ProcessorOutputPortBinding portBinding : binding
+ .getOutputPortBindings())
+ if (portBinding.getBoundActivityPort() == activityPort) {
+ ProcessorPort processorPort = portBinding
+ .getBoundProcessorPort();
+ if (processorPort.getName().equals(oldName)) {
+ processorPort.setName(newName);
+ continue bindings;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ReorderMergePositionsEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ReorderMergePositionsEdit.java
new file mode 100644
index 0000000..49caa32
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/ReorderMergePositionsEdit.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workflow.edits;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.EditException;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+
+/**
+ * Change datalink merge positions based on ordered list of data links.
+ *
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public class ReorderMergePositionsEdit extends AbstractEdit<ReceiverPort> {
+ private List<DataLink> newMergePositions;
+ private final List<DataLink> oldMergePositions;
+
+ public ReorderMergePositionsEdit(List<DataLink> dataLinks,
+ List<DataLink> newMergePositions) {
+ super(dataLinks.get(0).getSendsTo());
+ this.oldMergePositions = dataLinks;
+ this.newMergePositions = newMergePositions;
+ }
+
+ @Override
+ protected void doEditAction(ReceiverPort subject) throws EditException {
+ for (int i = 0; i < newMergePositions.size(); i++)
+ newMergePositions.get(i).setMergePosition(i);
+ }
+
+ @Override
+ protected void undoEditAction(ReceiverPort subject) {
+ for (int i = 0; i < oldMergePositions.size(); i++)
+ oldMergePositions.get(i).setMergePosition(i);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/SetIterationStrategyStackEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/SetIterationStrategyStackEdit.java
new file mode 100644
index 0000000..e16b95f
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/SetIterationStrategyStackEdit.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
+
+/**
+ * Set the iteration strategy
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class SetIterationStrategyStackEdit extends AbstractEdit<Processor> {
+ private final IterationStrategyStack iterationStrategyStack;
+ private IterationStrategyStack oldStrategyStack;
+
+ public SetIterationStrategyStackEdit(Processor processor,
+ IterationStrategyStack iterationStrategyStack) {
+ super(processor);
+ this.iterationStrategyStack = iterationStrategyStack;
+ }
+
+ @Override
+ protected void doEditAction(Processor processor) {
+ oldStrategyStack = processor.getIterationStrategyStack();
+ processor.setIterationStrategyStack(iterationStrategyStack);
+ }
+
+ @Override
+ protected void undoEditAction(Processor processor) {
+ processor.setIterationStrategyStack(oldStrategyStack);
+ }
+}
diff --git a/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/UpdateDataflowInternalIdentifierEdit.java b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/UpdateDataflowInternalIdentifierEdit.java
new file mode 100644
index 0000000..f246d60
--- /dev/null
+++ b/taverna-workbench-edits-api/src/main/java/net/sf/taverna/t2/workflow/edits/UpdateDataflowInternalIdentifierEdit.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.workflow.edits;
+
+import java.net.URI;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class UpdateDataflowInternalIdentifierEdit extends
+ AbstractEdit<WorkflowBundle> {
+ private URI newId;
+ private URI oldId;
+
+ public UpdateDataflowInternalIdentifierEdit(WorkflowBundle dataflow,
+ URI newId) {
+ super(dataflow);
+ this.newId = newId;
+ this.oldId = dataflow.getGlobalBaseURI();
+ }
+
+ @Override
+ protected void doEditAction(WorkflowBundle dataflow) {
+ dataflow.setGlobalBaseURI(newId);
+ }
+
+ @Override
+ protected void undoEditAction(WorkflowBundle dataflow) {
+ dataflow.setGlobalBaseURI(oldId);
+ }
+}
diff --git a/taverna-workbench-edits-impl/pom.xml b/taverna-workbench-edits-impl/pom.xml
new file mode 100644
index 0000000..b5b725a
--- /dev/null
+++ b/taverna-workbench-edits-impl/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>edits-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Edits implementation</name>
+ <description>
+ Implementation for doing workflow edits and undo.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
new file mode 100644
index 0000000..f97d36c
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * 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.edits.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Implementation of {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class EditManagerImpl implements EditManager {
+ private static Logger logger = Logger.getLogger(EditManagerImpl.class);
+
+ private MultiCaster<EditManagerEvent> multiCaster = new MultiCaster<>(this);
+ private Map<WorkflowBundle, DataflowEdits> editsForDataflow = new HashMap<>();
+
+ @Override
+ public void addObserver(Observer<EditManagerEvent> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ @Override
+ public boolean canRedoDataflowEdit(WorkflowBundle dataflow) {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ return edits.canRedo();
+ }
+
+ @Override
+ public boolean canUndoDataflowEdit(WorkflowBundle dataflow) {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ return edits.canUndo();
+ }
+
+ @Override
+ public void doDataflowEdit(WorkflowBundle dataflow, Edit<?> edit)
+ throws EditException {
+ // We do the edit before we notify the observers
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ synchronized (edits) {
+ // Make sure the edits are in the order they were performed
+ edit.doEdit();
+ edits.addEdit(edit);
+ }
+ multiCaster.notify(new DataflowEditEvent(dataflow, edit));
+ }
+
+ @Override
+ public List<Observer<EditManagerEvent>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ @Override
+ public void redoDataflowEdit(WorkflowBundle dataflow) throws EditException {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ Edit<?> edit;
+ synchronized (edits) {
+ if (!edits.canRedo())
+ return;
+ edit = edits.getLastUndo();
+ edit.doEdit();
+ edits.addRedo(edit);
+ }
+ multiCaster.notify(new DataFlowRedoEvent(dataflow, edit));
+ }
+
+ @Override
+ public void removeObserver(Observer<EditManagerEvent> observer) {
+ multiCaster.removeObserver(observer);
+ }
+
+ @Override
+ public void undoDataflowEdit(WorkflowBundle dataflow) {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ Edit<?> edit;
+ synchronized (edits) {
+ if (!edits.canUndo())
+ return;
+ edit = edits.getLastEdit();
+ edit.undo();
+ edits.addUndo(edit);
+ }
+ logger.info("Undoing an edit");
+ multiCaster.notify(new DataFlowUndoEvent(dataflow, edit));
+ }
+
+ /**
+ * Get the set of edits for a given dataflow, creating if neccessary.
+ *
+ * @param dataflow
+ * Dataflow the edits relate to
+ * @return A {@link DataflowEdits} instance to keep edits for the given
+ * dataflow
+ */
+ protected synchronized DataflowEdits getEditsForDataflow(WorkflowBundle dataflow) {
+ DataflowEdits edits = editsForDataflow.get(dataflow);
+ if (edits == null) {
+ edits = new DataflowEdits();
+ editsForDataflow.put(dataflow, edits);
+ }
+ return edits;
+ }
+
+ /**
+ * A set of edits and undoes for a {@link Dataflow}
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ public class DataflowEdits {
+ /**
+ * List of edits that have been performed and can be undone.
+ */
+ private List<Edit<?>> edits = new ArrayList<>();
+ /**
+ * List of edits that have been undone and can be redone
+ */
+ private List<Edit<?>> undoes = new ArrayList<>();
+
+ /**
+ * Add an {@link Edit} that has been done by the EditManager.
+ * <p>
+ * This can later be retrieved using {@link #getLastEdit()}. After
+ * calling this {@link #canRedo()} will be false.
+ *
+ * @param edit
+ * {@link Edit} that has been undone
+ */
+ public synchronized void addEdit(Edit<?> edit) {
+ addEditOrRedo(edit, false);
+ }
+
+ /**
+ * Add an {@link Edit} that has been redone by the EditManager.
+ * <p>
+ * The {@link Edit} must be the same as the last undo returned through
+ * {@link #getLastUndo()}.
+ * <p>
+ * This method works like {@link #addEdit(Edit)} except that instead of
+ * removing all possible redoes, only the given {@link Edit} is removed.
+ *
+ * @param edit
+ * {@link Edit} that has been redone
+ */
+ public synchronized void addRedo(Edit<?> edit) {
+ addEditOrRedo(edit, true);
+ }
+
+ /**
+ * Add an {@link Edit} that has been undone by the EditManager.
+ * <p>
+ * After calling this method {@link #canRedo()} will be true, and the
+ * edit can be retrieved using {@link #getLastUndo()}.
+ * </p>
+ * <p>
+ * The {@link Edit} must be the last edit returned from
+ * {@link #getLastEdit()}, after calling this method
+ * {@link #getLastEdit()} will return the previous edit or
+ * {@link #canUndo()} will be false if there are no more edits.
+ *
+ * @param edit
+ * {@link Edit} that has been undone
+ */
+ public synchronized void addUndo(Edit<?> edit) {
+ int lastIndex = edits.size() - 1;
+ if (lastIndex < 0 || !edits.get(lastIndex).equals(edit))
+ throw new IllegalArgumentException("Can't undo unknown edit "
+ + edit);
+ undoes.add(edit);
+ edits.remove(lastIndex);
+ }
+
+ /**
+ * True if there are undone events that can be redone.
+ *
+ * @return <code>true</code> if there are undone events
+ */
+ public boolean canRedo() {
+ return !undoes.isEmpty();
+ }
+
+ /**
+ * True if there are edits that can be undone and later added with
+ * {@link #addUndo(Edit)}.
+ *
+ * @return <code>true</code> if there are edits that can be undone
+ */
+ public boolean canUndo() {
+ return !edits.isEmpty();
+ }
+
+ /**
+ * Get the last edit that can be undone. This edit was the last one to
+ * be added with {@link #addEdit(Edit)} or {@link #addRedo(Edit)}.
+ *
+ * @return The last added {@link Edit}
+ * @throws IllegalStateException
+ * If there are no more edits (Check with {@link #canUndo()}
+ * first)
+ *
+ */
+ public synchronized Edit<?> getLastEdit() throws IllegalStateException {
+ if (edits.isEmpty())
+ throw new IllegalStateException("No more edits");
+ int lastEdit = edits.size() - 1;
+ return edits.get(lastEdit);
+ }
+
+ /**
+ * Get the last edit that can be redone. This edit was the last one to
+ * be added with {@link #addUndo(Edit)}.
+ *
+ * @return The last undone {@link Edit}
+ * @throws IllegalStateException
+ * If there are no more edits (Check with {@link #canRedo()}
+ * first)
+ *
+ */
+ public synchronized Edit<?> getLastUndo() throws IllegalStateException {
+ if (undoes.isEmpty())
+ throw new IllegalStateException("No more undoes");
+ int lastUndo = undoes.size() - 1;
+ return undoes.get(lastUndo);
+ }
+
+ /**
+ * Add an edit or redo. Common functionallity called by
+ * {@link #addEdit(Edit)} and {@link #addRedo(Edit)}.
+ *
+ * @see #addEdit(Edit)
+ * @see #addRedo(Edit)
+ * @param edit
+ * The {@link Edit} to add
+ * @param isRedo
+ * True if this is a redo
+ */
+ protected void addEditOrRedo(Edit<?> edit, boolean isRedo) {
+ edits.add(edit);
+ if (undoes.isEmpty())
+ return;
+ if (isRedo) {
+ // It's a redo, remove only the last one
+ int lastUndoIndex = undoes.size() - 1;
+ Edit<?> lastUndo = undoes.get(lastUndoIndex);
+ if (!edit.equals(lastUndo))
+ throw new IllegalArgumentException(
+ "Can only redo last undo");
+ undoes.remove(lastUndoIndex);
+ } else
+ // It's a new edit, remove all redos
+ undoes.clear();
+ }
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
new file mode 100644
index 0000000..97d14a6
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.edits.impl.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_Y;
+import static java.awt.event.KeyEvent.VK_Z;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.redoIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.undoIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractUndoAction extends AbstractAction {
+ protected EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public AbstractUndoAction(String label, EditManager editManager) {
+ super(label);
+ this.editManager = editManager;
+ if (label.equals("Undo")) {
+ this.putValue(SMALL_ICON, undoIcon);
+ this.putValue(SHORT_DESCRIPTION, "Undo an action");
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_Z, getDefaultToolkit()
+ .getMenuShortcutKeyMask()));
+ } else if (label.equals("Redo")) {
+ this.putValue(SMALL_ICON, redoIcon);
+ this.putValue(SHORT_DESCRIPTION, "Redo an action");
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_Y, getDefaultToolkit()
+ .getMenuShortcutKeyMask()));
+ }
+ editManager.addObserver(new EditManagerObserver());
+ updateStatus();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ WorkflowBundle workflowBundle = getCurrentDataflow();
+ if (workflowBundle != null)
+ performUndoOrRedo(workflowBundle);
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus() {
+ WorkflowBundle workflowBundle = getCurrentDataflow();
+ if (workflowBundle == null)
+ setEnabled(false);
+ setEnabled(isActive(workflowBundle));
+ }
+
+ /**
+ * Retrieve the current dataflow from the {@link ModelMap}, or
+ * <code>null</code> if no workflow is active.
+ *
+ * @return The current {@link Dataflow}
+ */
+ protected WorkflowBundle getCurrentDataflow() {
+ if (selectionManager == null)
+ return null;
+ return selectionManager.getSelectedWorkflowBundle();
+ }
+
+ /**
+ * Return <code>true</code> if the action should be enabled when the given
+ * {@link Dataflow} is the current, ie. if it's undoable or redoable.
+ *
+ * @param dataflow
+ * Current {@link Dataflow}
+ * @return <code>true</code> if the action should be enabled.
+ */
+ protected abstract boolean isActive(WorkflowBundle workflowBundle);
+
+ /**
+ * Called by {@link #actionPerformed(ActionEvent)} when the current dataflow
+ * is not <code>null</code>.
+ *
+ * @param dataflow
+ * {@link Dataflow} on which to undo or redo
+ */
+ protected abstract void performUndoOrRedo(WorkflowBundle workflowBundle);
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ if (selectionManager != null)
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ /**
+ * Update the status if there's been an edit done on the current workflow.
+ *
+ */
+ protected class EditManagerObserver implements Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (!(message instanceof AbstractDataflowEditEvent))
+ return;
+ AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+ if (dataflowEdit.getDataFlow().equals(dataflowEdit.getDataFlow()))
+ // It's an edit that could effect our undoability
+ updateStatus();
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent)
+ updateStatus();
+ else if (message instanceof PerspectiveSelectionEvent) {
+ PerspectiveSelectionEvent perspectiveSelectionEvent = (PerspectiveSelectionEvent) message;
+ if (DESIGN_PERSPECTIVE_ID.equals(perspectiveSelectionEvent
+ .getSelectedPerspective().getID()))
+ updateStatus();
+ else
+ setEnabled(false);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
new file mode 100644
index 0000000..2abc139
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * 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.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Redo the previous {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class RedoMenuAction extends AbstractMenuAction {
+ private static Logger logger = Logger.getLogger(RedoMenuAction.class);
+ private final EditManager editManager;
+ private SelectionManager selectionManager;
+ private AbstractUndoAction undoAction;
+
+ public RedoMenuAction(EditManager editManager) {
+ super(UNDO_SECTION_URI, 20);
+ this.editManager = editManager;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ undoAction = new AbstractUndoAction("Redo", editManager) {
+ @Override
+ protected boolean isActive(WorkflowBundle workflowBundle) {
+ return editManager.canRedoDataflowEdit(workflowBundle);
+ }
+
+ @Override
+ protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+ try {
+ editManager.redoDataflowEdit(workflowBundle);
+ } catch (EditException | RuntimeException e) {
+ logger.warn("Could not redo for " + workflowBundle, e);
+ showMessageDialog(null, "Could not redo for workflow "
+ + workflowBundle + ":\n" + e, "Could not redo",
+ ERROR_MESSAGE);
+ }
+ }
+ };
+ undoAction.setSelectionManager(selectionManager);
+ return undoAction;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ if (undoAction != null)
+ undoAction.setSelectionManager(selectionManager);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
new file mode 100644
index 0000000..e1242b3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * 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.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Undo the last {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class UndoMenuAction extends AbstractMenuAction {
+ private static Logger logger = Logger.getLogger(UndoMenuAction.class);
+ private final EditManager editManager;
+ private SelectionManager selectionManager;
+ private AbstractUndoAction undoAction;
+
+ public UndoMenuAction(EditManager editManager) {
+ super(UNDO_SECTION_URI, 10);
+ this.editManager = editManager;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ undoAction = new AbstractUndoAction("Undo", editManager) {
+ @Override
+ protected boolean isActive(WorkflowBundle workflowBundle) {
+ return editManager.canUndoDataflowEdit(workflowBundle);
+ }
+
+ @Override
+ protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+ try {
+ editManager.undoDataflowEdit(workflowBundle);
+ } catch (RuntimeException e) {
+ logger.warn("Could not undo for " + workflowBundle, e);
+ showMessageDialog(null, "Could not undo for workflow "
+ + workflowBundle + ":\n" + e, "Could not undo",
+ ERROR_MESSAGE);
+ }
+ }
+ };
+ undoAction.setSelectionManager(selectionManager);
+ return undoAction;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ if (undoAction != null)
+ undoAction.setSelectionManager(selectionManager);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
new file mode 100644
index 0000000..b83a650
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.edits.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * A section of the Edit menu that contains {@link UndoMenuSection undo} and
+ * {@link RedoMenuAction redo}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class UndoMenuSection extends AbstractMenuSection {
+ public static final URI UNDO_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/edits#undoSection");
+ public static final URI EDIT_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#edit");
+
+ public UndoMenuSection() {
+ super(EDIT_MENU_URI, 10, UNDO_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
new file mode 100644
index 0000000..9eea85a
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class EditToolbarSection extends AbstractMenuSection {
+ public static final URI EDIT_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+
+ public EditToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 60, EDIT_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
new file mode 100644
index 0000000..09c0058
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction;
+
+public class RedoToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_REDO_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarRedo");
+ private final RedoMenuAction redoMenuAction;
+
+ public RedoToolbarAction(RedoMenuAction redoMenuAction) {
+ super(EDIT_TOOLBAR_SECTION, 20, EDIT_TOOLBAR_REDO_URI);
+ this.redoMenuAction = redoMenuAction;
+ }
+
+ @Override
+ protected Action createAction() {
+ return redoMenuAction.getAction();
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
new file mode 100644
index 0000000..8e31ed3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction;
+
+public class UndoToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_UNDO_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarUndo");
+ private final UndoMenuAction undoMenuAction;
+
+ public UndoToolbarAction(UndoMenuAction undoMenuAction) {
+ super(EDIT_TOOLBAR_SECTION, 10, EDIT_TOOLBAR_UNDO_URI);
+ this.undoMenuAction = undoMenuAction;
+ }
+
+ @Override
+ protected Action createAction() {
+ return undoMenuAction.getAction();
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6938308
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,6 @@
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection
+net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
new file mode 100644
index 0000000..92ee088
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
new file mode 100644
index 0000000..8eb7041
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UndoMenuSection" auto-export="interfaces" />
+ <service ref="UndoMenuAction" auto-export="interfaces" />
+ <service ref="RedoMenuAction" auto-export="interfaces" />
+ <service ref="EditToolbarSection" auto-export="interfaces" />
+ <service ref="UndoToolbarAction" auto-export="interfaces" />
+ <service ref="RedoToolbarAction" auto-export="interfaces" />
+
+ <service ref="EditManagerImpl" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" cardinality="0..1" />
+
+</beans:beans>
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
new file mode 100644
index 0000000..33f0b7b
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UndoMenuSection" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection" />
+ <bean id="UndoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction">
+ <constructor-arg name="editManager">
+ <ref local="EditManagerImpl" />
+ </constructor-arg>
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RedoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction">
+ <constructor-arg name="editManager">
+ <ref local="EditManagerImpl" />
+ </constructor-arg>
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="EditToolbarSection" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection" />
+ <bean id="UndoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction">
+ <constructor-arg>
+ <ref local="UndoMenuAction" />
+ </constructor-arg>
+ </bean>
+ <bean id="RedoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction">
+ <constructor-arg>
+ <ref local="RedoMenuAction" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="EditManagerImpl" class="net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl" />
+
+</beans>
diff --git a/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
new file mode 100644
index 0000000..9123671
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * 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.edits.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class TestEditManagerImpl {
+
+ private Workflow dataflow;
+
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+
+ private Processor processor;
+
+ @Test
+ public void addProcessor() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+ assertFalse("Edit was already applied", edit.isApplied());
+ assertTrue("Did already add processor", dataflow.getProcessors()
+ .isEmpty());
+
+ editManager.doDataflowEdit(dataflow.getParent(), edit);
+ assertTrue("Edit was not applied", edit.isApplied());
+ assertEquals("Did not add processor", processor, dataflow.getProcessors().first());
+
+ // Should have received the edit event
+ assertEquals("Incorrect number of events", 1,
+ editManagerObserver.events.size());
+ EditManagerEvent event = editManagerObserver.events.get(0);
+ assertTrue("Event was not a DataflowEditEvent",
+ event instanceof DataflowEditEvent);
+ DataflowEditEvent dataEditEvent = (DataflowEditEvent) event;
+ assertEquals("Event did not have correct workflow", dataflow,
+ dataEditEvent.getDataFlow().getWorkflows().first());
+ assertEquals("Event did not have correct edit", edit, dataEditEvent
+ .getEdit());
+
+ }
+
+ @Test
+ public void undoAddProcessor() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+ editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+ assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+ editManager.undoDataflowEdit(dataflow.getParent());
+ assertTrue("Did not undo add processor", dataflow.getProcessors()
+ .isEmpty());
+
+ // Should have received the undo event
+ assertEquals("Incorrect number of events", 2,
+ editManagerObserver.events.size());
+ EditManagerEvent event = editManagerObserver.events.get(1);
+ assertTrue("Event was not a DataflowEditEvent",
+ event instanceof DataFlowUndoEvent);
+ DataFlowUndoEvent dataEditEvent = (DataFlowUndoEvent) event;
+ assertEquals("Event did not have correct workflow", dataflow,
+ dataEditEvent.getDataFlow().getWorkflows().first());
+ assertEquals("Event did not have correct edit", edit, dataEditEvent
+ .getEdit());
+ assertFalse("Edit was still applied", edit.isApplied());
+ }
+
+ @Test
+ public void multipleUndoesRedoes() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Workflow dataflowA = createDataflow();
+ Workflow dataflowB = createDataflow();
+ Workflow dataflowC = createDataflow();
+
+ Processor processorA1 = createProcessor();
+ Processor processorA2 = createProcessor();
+ Processor processorA3 = createProcessor();
+ Processor processorB1 = createProcessor();
+ Processor processorC1 = createProcessor();
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflowA, processorA1);
+ editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowB, processorB1);
+ editManager.doDataflowEdit(dataflowB.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowA, processorA2);
+ editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowC, processorC1);
+ editManager.doDataflowEdit(dataflowC.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowA, processorA3);
+ editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+
+
+ assertFalse("Did not add processors", dataflowA.getProcessors().isEmpty());
+ assertEquals(3, dataflowA.getProcessors().size());
+ editManager.undoDataflowEdit(dataflowA.getParent());
+ assertEquals(2, dataflowA.getProcessors().size());
+ editManager.undoDataflowEdit(dataflowA.getParent());
+ assertEquals(1, dataflowA.getProcessors().size());
+ editManager.undoDataflowEdit(dataflowA.getParent());
+ assertEquals(0, dataflowA.getProcessors().size());
+
+ assertEquals(1, dataflowB.getProcessors().size());
+ assertEquals(1, dataflowC.getProcessors().size());
+
+ assertTrue(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+ editManager.undoDataflowEdit(dataflowC.getParent());
+ assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+ editManager.undoDataflowEdit(dataflowC.getParent()); // extra one
+ assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+
+
+ assertEquals(1, dataflowB.getProcessors().size());
+ assertEquals(0, dataflowC.getProcessors().size());
+
+ editManager.undoDataflowEdit(dataflowB.getParent());
+ assertEquals(0, dataflowA.getProcessors().size());
+ assertEquals(0, dataflowB.getProcessors().size());
+ assertEquals(0, dataflowC.getProcessors().size());
+
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(1, dataflowA.getProcessors().size());
+
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(2, dataflowA.getProcessors().size());
+
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(3, dataflowA.getProcessors().size());
+
+ // does not affect it
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(3, dataflowA.getProcessors().size());
+ assertEquals(0, dataflowB.getProcessors().size());
+ assertEquals(0, dataflowC.getProcessors().size());
+ }
+
+ @Test
+ public void emptyUndoDoesNotFail() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+ editManager.undoDataflowEdit(dataflow.getParent());
+ }
+
+ @Test
+ public void extraUndoesDoesNotFail() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+ editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+ assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+ editManager.undoDataflowEdit(dataflow.getParent());
+ assertTrue("Did not undo add processor", dataflow.getProcessors()
+ .isEmpty());
+ editManager.undoDataflowEdit(dataflow.getParent());
+ }
+
+ @Before
+ public void makeDataflow() {
+ dataflow = createDataflow();
+ }
+
+ protected Workflow createDataflow() {
+ WorkflowBundle workflowBundle = new WorkflowBundle();
+ Workflow workflow = new Workflow();
+ workflow.setParent(workflowBundle);
+ return workflow;
+ }
+
+ protected Processor createProcessor() {
+ Processor processor = new Processor();
+ processor.setName("proc-" + UUID.randomUUID());
+ return processor;
+ }
+
+ @Before
+ public void makeProcessor() {
+ processor = createProcessor();
+ }
+
+ private class EditManagerObserver implements Observer<EditManagerEvent> {
+
+ public List<EditManagerEvent> events = new ArrayList<>();
+
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ events.add(message);
+ if (message instanceof DataflowEditEvent) {
+ DataflowEditEvent dataflowEdit = (DataflowEditEvent) message;
+ assertTrue("Edit was not applied on edit event", dataflowEdit
+ .getEdit().isApplied());
+ } else if (message instanceof DataFlowUndoEvent) {
+ DataFlowUndoEvent dataflowUndo = (DataFlowUndoEvent) message;
+ assertFalse("Edit was applied on undo event", dataflowUndo
+ .getEdit().isApplied());
+ } else if (message instanceof DataFlowRedoEvent) {
+ DataFlowRedoEvent dataflowEdit = (DataFlowRedoEvent) message;
+ assertTrue("Edit was not applied on edit event", dataflowEdit
+ .getEdit().isApplied());
+ } else {
+ fail("Unknown event: " + message);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-file-api/pom.xml b/taverna-workbench-file-api/pom.xml
new file mode 100644
index 0000000..2b6205d
--- /dev/null
+++ b/taverna-workbench-file-api/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>File opening API</name>
+ <description>
+ API for doing file (ie. workflow) open/save in the workbench.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/AbstractDataflowPersistenceHandler.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/AbstractDataflowPersistenceHandler.java
new file mode 100644
index 0000000..8dc34e8
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/AbstractDataflowPersistenceHandler.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.file;
+
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public abstract class AbstractDataflowPersistenceHandler implements
+ DataflowPersistenceHandler {
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<FileType> getSaveFileTypes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<Class<?>> getSaveDestinationTypes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DataflowInfo saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination) throws SaveException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean wouldOverwriteDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination, DataflowInfo lastDataflowInfo) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/DataflowInfo.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/DataflowInfo.java
new file mode 100644
index 0000000..c5eaa26
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/DataflowInfo.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * 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.file;
+
+import java.util.Date;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Information about a WorkflowBundle that has been opened by the
+ * {@link FileManager}.
+ * <p>
+ * This class, or a subclass of it, is used by
+ * {@link DataflowPersistenceHandler}s to keep information about where a
+ * {@link WorkflowBundle} came from or where it was saved to.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class DataflowInfo {
+ private final FileType fileType;
+ private final WorkflowBundle worflowBundle;
+ private final Date lastModified;
+ private final Object canonicalSource;
+
+ public DataflowInfo(FileType fileType, Object canonicalSource,
+ WorkflowBundle worflowBundle, Date lastModified) {
+ this.fileType = fileType;
+ this.canonicalSource = canonicalSource;
+ this.worflowBundle = worflowBundle;
+ this.lastModified = lastModified;
+ }
+
+ public DataflowInfo(FileType fileType, Object canonicalSource,
+ WorkflowBundle worflowBundle) {
+ this(fileType, canonicalSource, worflowBundle, null);
+ }
+
+ /**
+ * Return the canonical source of where the WorkflowBundle was opened from
+ * or saved to.
+ * <p>
+ * This is not necessarily the source provided to
+ * {@link FileManager#openDataflow(FileType, Object)} or
+ * {@link FileManager#saveDataflow(WorkflowBundle, FileType, Object, boolean)}
+ * , but it's canonical version.
+ * <p>
+ * For instance, if a WorkflowBundle was opened from a
+ * File("relative/something.wfbundle) this canonical source would resolve
+ * the relative path.
+ *
+ * @return
+ */
+ public Object getCanonicalSource() {
+ return canonicalSource;
+ }
+
+ /**
+ * Return the WorkflowBundle that is open.
+ *
+ * @return The open WorkflowBundle
+ */
+ public WorkflowBundle getDataflow() {
+ return worflowBundle;
+ }
+
+ /**
+ * Get the last modified {@link Date} of the source at the time when it was
+ * opened/saved.
+ * <p>
+ * It is important that this value is checked on creation time, and not on
+ * demand.
+ *
+ * @return The {@link Date} of the source/destination's last modified
+ * timestamp, or <code>null</code> if unknown.
+ */
+ public Date getLastModified() {
+ return lastModified;
+ }
+
+ /**
+ * The {@link FileType} of this {@link WorkflowBundle} serialisation used
+ * for opening/saving.
+ *
+ * @return The {@link FileType}, for instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.WorkflowBundleFileType}
+ */
+ public FileType getFileType() {
+ return fileType;
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/DataflowPersistenceHandler.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/DataflowPersistenceHandler.java
new file mode 100644
index 0000000..f8d2ddb
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/DataflowPersistenceHandler.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * 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.file;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Collection;
+
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * A handler for opening or saving {@link WorkflowBundle} from the
+ * {@link FileManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public interface DataflowPersistenceHandler {
+ /**
+ * A collection of supported file types for
+ * {@link #openDataflow(FileType, Object)}, or an empty collection if
+ * opening is not supported by this handler.
+ *
+ * @return A collection of supported {@link FileType}s for opening.
+ */
+ Collection<FileType> getOpenFileTypes();
+
+ /**
+ * A collection of supported source classes for
+ * {@link #openDataflow(FileType, Object)}, or an empty collection if
+ * opening is not supported by this handler.
+ * <p>
+ * For example, a handler that supports sources opened from a {@link File}
+ * and {@link URL} could return
+ * <code>Arrays.asList(File.class, URL.class)</code>
+ *
+ * @return A collection of supported {@link Class}es of the open source
+ * types.
+ */
+ Collection<Class<?>> getOpenSourceTypes();
+
+ /**
+ * A collection of supported destination classes for
+ * {@link #saveDataflow(Dataflow, FileType, Object)}, or an empty collection
+ * if saving is not supported by this handler.
+ * <p>
+ * For example, a handler that supports saving to destinations that are
+ * instances of a {@link File} could return
+ * <code>Arrays.asList(File.class)</code>
+ *
+ * @return A collection of supported {{@link Class}es of the save
+ * destination types.
+ */
+ Collection<Class<?>> getSaveDestinationTypes();
+
+ /**
+ * A collection of supported file types for
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object)}, or an empty
+ * collection if saving is not supported by this handler.
+ *
+ * @return A collection of supported {@link FileType}s for saving.
+ */
+ Collection<FileType> getSaveFileTypes();
+
+ /**
+ * Open a dataflow from a source containing a dataflow of the given
+ * {@link FileType}.
+ * <p>
+ * The {@link FileType} will be equal to one of the types from
+ * {@link #getOpenFileTypes()}, and the source class will be one that is
+ * assignable to one of the classes from {@link #getOpenSourceTypes()}.
+ *
+ * @param fileType
+ * {@link FileType} determining which serialisation method has
+ * been used
+ * @param source
+ * Source for reading the WorkflowBundle
+ * @return {@link DataflowInfo} describing the opened WorkflowBundle,
+ * including the WorkflowBundle itself
+ * @throws OpenException
+ * If the WorkflowBundle could not be read, parsed or opened for
+ * any reason.
+ */
+ DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException;
+
+ /**
+ * Save a WorkflowBundle to a destination of the given {@link FileType}.
+ * <p>
+ * The {@link FileType} will be equal to one of the types from
+ * {@link #getSaveFileTypes()}, and the destination class will be one that
+ * is assignable to one of the classes from
+ * {@link #getSaveDestinationTypes()}.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} to be saved
+ * @param fileType
+ * {@link FileType} determining which serialisation method to use
+ * @param destination
+ * Destination for writing the WorkflowBundle
+ * @return {@link DataflowInfo} describing the saved WorkflowBundle,
+ * including the WorkflowBundle itself
+ * @throws OpenException
+ * If the WorkflowBundle could not be read, parsed or opened for
+ * any reason.
+ */
+ DataflowInfo saveDataflow(WorkflowBundle dataflow, FileType fileType,
+ Object destination) throws SaveException;
+
+ /**
+ * Return <code>true</code> if a call to
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object)} would overwrite
+ * the destination, and the destination is different from last
+ * {@link #openDataflow(FileType, Object)} or
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object)} of the given
+ * dataflow.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} that is to be saved
+ * @param fileType
+ * {@link FileType} for saving WorkflowBundle
+ * @param destination
+ * destination for writing WorkflowBundle
+ * @param lastDataflowInfo
+ * last provided {@link DataflowInfo} returned by
+ * {@link #openDataflow(FileType, Object)} or
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object)}. (but
+ * not necessarily from this handler)
+ * @return <code>true</code> if the save would overwrite
+ */
+ boolean wouldOverwriteDataflow(WorkflowBundle dataflow, FileType fileType,
+ Object destination, DataflowInfo lastDataflowInfo);
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/FileManager.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/FileManager.java
new file mode 100644
index 0000000..f449bb5
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/FileManager.java
@@ -0,0 +1,573 @@
+/*******************************************************************************
+ * 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.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Manager of open files (WorkflowBundleBundles) in the workbench.
+ * <p>
+ * A {@link WorkflowBundle} can be opened for the workbench using
+ * {@link #openDataflow(FileType, Object)} or {@link #openDataflow(WorkflowBundle)}.
+ * {@link Observer}s of the FileManager gets notified with an
+ * {@link OpenedDataflowEvent}. The opened workflow is also
+ * {@link #setCurrentDataflow(WorkflowBundle) made the current dataflow}, available
+ * through {@link #getCurrentDataflow()} or by observing the
+ * {@link net.sf.taverna.t2.lang.ui.ModelMap} for the model name
+ * {@link net.sf.taverna.t2.workbench.ModelMapConstants#CURRENT_DATAFLOW}.
+ * <p>
+ * A dataflow can be saved using
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)}. Observers will be
+ * presented a {@link SavedDataflowEvent}.
+ * <p>
+ * If a dataflow was previously opened from a saveable destination or previously
+ * saved using {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)},
+ * {@link #saveDataflow(WorkflowBundle, boolean)} can be used to resave to that
+ * destination.
+ * <p>
+ * You can get the last opened/saved source and type using
+ * {@link #getDataflowSource(WorkflowBundle)} and {@link #getDataflowType(WorkflowBundle)}.
+ * <p>
+ * If the save methods are used with failOnOverwrite=true, an
+ * {@link OverwriteException} will be thrown if the destination file already
+ * exists and was not last written by a previous save on that dataflow. (This is
+ * typically checked using timestamps on the file).
+ * <p>
+ * A dataflow can be closed using {@link #closeDataflow(WorkflowBundle, boolean)}. A
+ * closed dataflow is no longer monitored for changes and can no longer be used
+ * with the other operations, except {@link #openDataflow(WorkflowBundle)}.
+ * <p>
+ * If a dataflow has been changed using the {@link EditManager},
+ * {@link #isDataflowChanged(WorkflowBundle)} will return true until the next save. If
+ * the close methods are used with failOnUnsaved=true, an
+ * {@link UnsavedException} will be thrown if the dataflow has been changed.
+ * <p>
+ * The implementation of this interface is an OSGi Service.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public interface FileManager extends Observable<FileManagerEvent> {
+ /**
+ * True if {@link #saveDataflow(WorkflowBundle, boolean)} can save the
+ * workflow, i.e., that there exists an SPI implementation of
+ * {@link DataflowPersistenceHandler} that can save to
+ * {@link #getDataflowSource(WorkflowBundle)} using
+ * {@link #getDataflowType(WorkflowBundle)}.
+ *
+ * @see #saveDataflow(WorkflowBundle, boolean)
+ * @param dataflow
+ * The dataflow to check
+ * @return <code>true</code> if the given dataflow can be saved without
+ * providing a destination and filetype
+ */
+ boolean canSaveWithoutDestination(WorkflowBundle dataflow);
+
+ /**
+ * Close the specified dataflow.
+ * <p>
+ * A closed dataflow can no longer be used with the save methods, and will
+ * disappear from the UI's list of open dataflows.
+ * <p>
+ * If no more dataflows would be open after the close, a new empty dataflow
+ * is opened as through {@link #newDataflow()}.
+ * <p>
+ * If the failOnUnsaved parameters is <code>true</code>, and
+ * {@link #isDataflowChanged(WorkflowBundle)} is <code>true</code>, an
+ * {@link UnsavedException} will be thrown, typically because the workflow
+ * has been changed using the {@link EditManager} since the last change.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link ClosedDataflowEvent}.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} to close
+ * @param failOnUnsaved
+ * If <code>true</code>, fail on unsaved changes
+ * @throws UnsavedException
+ * If failOnUnsaved was <code>true</code> and there has been
+ * changes to the dataflow since the last save
+ */
+ boolean closeDataflow(WorkflowBundle dataflow, boolean failOnUnsaved)
+ throws UnsavedException;
+
+ /**
+ * Get the current dataflow.
+ * <p>
+ * The current workflow is typically the one currently showed on the screen,
+ * and is also in {@link #getOpenDataflows()}.
+ * <p>
+ * The current dataflow is set through {@link #setCurrentDataflow(WorkflowBundle)}
+ * or the {@link net.sf.taverna.t2.lang.ui.ModelMap} using the key
+ * {@link net.sf.taverna.t2.workbench.ModelMapConstants#CURRENT_DATAFLOW}.
+ *
+ * @return The current dataflow, or <code>null</code> if no dataflow is
+ * current
+ */
+ WorkflowBundle getCurrentDataflow();
+
+ /**
+ * Get the dataflow that was opened from or last saved to the given source.
+ *
+ * @param source
+ * The source as opened with or saved to
+ * {@link #openDataflow(FileType, Object)}
+ * @return The opened {@link WorkflowBundle} or <code>null</code> if no matching
+ * dataflow found.
+ */
+ WorkflowBundle getDataflowBySource(Object source);
+
+ /**
+ * Get a name to represent this dataflow.
+ * <p>
+ * The name will primarily be deduced from the source of where the workflow
+ * is opened from, unless {@link Object#toString()} is not overridden (for
+ * instance opened from an InputStream) or if the source is unknown, in
+ * which case the dataflow's internal name {@link WorkflowBundle#getName()}
+ * is returned.
+ * <p>
+ * The returned name can be used in listings like the WorkflowBundles menu, but is
+ * not guaranteed to be unique. (For instance a workflow could be opened
+ * twice from the same source).
+ *
+ * @param dataflow
+ * WorkflowBundle to get the name for
+ * @return The deduced workflow name
+ */
+ String getDataflowName(WorkflowBundle dataflow);
+
+ /**
+ * Returns the default name to use when creating new workflows.
+ *
+ * @return the default name to use when creating new workflows
+ */
+ String getDefaultWorkflowName();
+
+ /**
+ * Get the last opened/saved source/destination for the given dataflow.
+ * <p>
+ * The source is the last source used with
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)} for the given
+ * dataflow, or {@link #openDataflow(FileType, Object)} if it has not yet
+ * been saved.
+ * <p>
+ * If the given dataflow's last opened/saved location was unknown (opened
+ * with {@link #newDataflow()} or {@link #openDataflow(WorkflowBundle)}), return
+ * <code>null</code>.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} which file is to be returned
+ * @return The last opened/saved source for the given dataflow, or
+ * <code>null</code> if unknown.
+ */
+ Object getDataflowSource(WorkflowBundle dataflow);
+
+ /**
+ * Get the last opened/saved source/destination FileType for the given
+ * dataflow.
+ * <p>
+ * The type is the last {@link FileType} used with
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)} for the given
+ * dataflow, or {@link #openDataflow(FileType, Object)} if it has not yet
+ * been saved.
+ * <p>
+ * If the given dataflow's last opened/saved file type was unknown (opened
+ * with {@link #newDataflow()} or {@link #openDataflow(WorkflowBundle)}), return
+ * <code>null</code>.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} which file is to be returned
+ * @return The last opened/saved {@link FileType} for the given dataflow, or
+ * <code>null</code> if unknown.
+ */
+ FileType getDataflowType(WorkflowBundle dataflow);
+
+ /**
+ * Get the list of currently open dataflows. This list of dataflows are
+ * typically displayed in the UI in the "WorkflowBundles" menu to allow switching
+ * the {@link #getCurrentDataflow() current dataflow}.
+ *
+ * @return A copy of the {@link List} of open {@link WorkflowBundle}s
+ */
+ List<WorkflowBundle> getOpenDataflows();
+
+ /**
+ * Get a list of {@link FileFilter}s for supported {@link FileType}s that
+ * can be opened with any source class.
+ *
+ * @return A {@link List} of {@link FileFilter}s supported by
+ * {@link #openDataflow(FileType, Object)}
+ */
+ List<FileFilter> getOpenFileFilters();
+
+ /**
+ * Get a list of {@link FileFilter}s for supported {@link FileType}s that
+ * can be opened with given source class.
+ *
+ * @param sourceClass
+ * Source class that can be opened from
+ * @return A {@link List} of {@link FileFilter}s supported by
+ * {@link #openDataflow(FileType, Object)}
+ */
+ List<FileFilter> getOpenFileFilters(Class<?> sourceClass);
+
+ /**
+ * Get a list of {@link FileFilter}s for supported {@link FileType}s that
+ * can be saved to any destination class.
+ *
+ * @return A {@link List} of {@link FileFilter}s supported by
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)}
+ */
+ List<FileFilter> getSaveFileFilters();
+
+ /**
+ * Get a list of {@link FileFilter}s for supported {@link FileType}s that
+ * can be saved to the given destination class.
+ *
+ * @param destinationClass
+ * Destination class that can be saved to
+ * @return A {@link List} of {@link FileFilter}s supported by
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)}
+ */
+ List<FileFilter> getSaveFileFilters(Class<?> destinationClass);
+
+ /**
+ * Return <code>true</code> if the dataflow has been changed (through the
+ * {@link EditManager} or {@link #setDataflowChanged(WorkflowBundle, boolean)})
+ * since last save.
+ *
+ * @param dataflow
+ * WorkflowBundle which changed status is to be checked
+ * @return <code>true</code> if the dataflow has been changed since last
+ * save.
+ */
+ boolean isDataflowChanged(WorkflowBundle dataflow);
+
+ /**
+ * True if the given dataflow has been opened and is in
+ * {@link #getOpenDataflows()}.
+ *
+ * @param dataflow
+ * Dataflow to check
+ * @return <code>true</code> if dataflow is open
+ */
+ boolean isDataflowOpen(WorkflowBundle dataflow);
+
+ /**
+ * Create and open a new, blank dataflow. The dataflow will not initially be
+ * marked as changed.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link OpenedDataflowEvent}.
+ * <p>
+ * Note, if the dataflow is later changed, it will not be possible to save
+ * it to any original location using
+ * {@link #saveDataflow(WorkflowBundle, boolean)}, only
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)}.
+ *
+ * @return The newly opened blank {@link WorkflowBundle}
+ */
+ WorkflowBundle newDataflow();
+
+ /**
+ * Open a {@link WorkflowBundle} instance that has been created outside the
+ * {@link FileManager}. The dataflow will not initially be marked as
+ * changed.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link OpenedDataflowEvent}.
+ * <p>
+ * Note, if the dataflow is later changed, it will not be possible to save
+ * it to its original location using
+ * {@link #saveDataflow(WorkflowBundle, boolean)}, only
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)}.
+ * <p>
+ * Instead of using this option it is recommended to create your own
+ * {@link FileType} and/or source type and a
+ * {@link DataflowPersistenceHandler} to implement save and/or reopen
+ * (revert).
+ * <p>
+ * If there is only one workflow open before opening this workflow, and it
+ * is an unchanged blank workflow, the blank workflow will be closed.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} instance that is to be added as an open
+ * dataflow
+ */
+ void openDataflow(WorkflowBundle dataflow);
+
+ /**
+ * Open a dataflow from a source. The dataflow will not initially be marked
+ * as changed, and will be set as the new current workflow.
+ * <p>
+ * The file manager will find implementations of the SPI
+ * {@link DataflowPersistenceHandler} to perform the opening for the given file
+ * type and destination class.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link OpenedDataflowEvent}.
+ * <p>
+ * If there is only one workflow open before opening this workflow, and it
+ * is an unchanged blank workflow, the blank workflow will be closed.
+ *
+ * @param fileType
+ * The filetype, for instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType}.
+ * The file type must be supported by an implementation of the
+ * SPI DataflowPersistenceHandler.
+ * @param source
+ * The source, for instance a {@link File} or {@link URL}. The
+ * source type must be supported by an implementation of
+ * DataflowPersistenceHandler.
+ * @return The opened {@link WorkflowBundle}.
+ * @throws OpenException
+ * If there was no matching DataflowPersistenceHandler found or
+ * the source could not be opened for any other reason, such as
+ * IO errors or syntax errors.
+ */
+ WorkflowBundle openDataflow(FileType fileType, Object source)
+ throws OpenException;
+
+ /**
+ * Open a dataflow from a source silently. The dataflow will not be listed
+ * as open, and will not be made the current workflow.
+ * <p>
+ * The file manager will find implementations of the SPI
+ * {@link DataflowPersistenceHandler} to perform the opening for the given file
+ * type and destination class.
+ * <p>
+ * Listeners will <strong>not</strong> be notified.
+ *
+ * @param fileType
+ * The filetype, for instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType}.
+ * The file type must be supported by an implementation of the
+ * SPI DataflowPersistenceHandler.
+ * @param source
+ * The source, for instance a {@link File} or {@link URL}. The
+ * source type must be supported by an implementation of
+ * DataflowPersistenceHandler.
+ * @return The {@link DataflowInfo} describing the opened dataflow.
+ * @throws OpenException
+ * If there was no matching DataflowPersistenceHandler found or
+ * the source could not be opened for any other reason, such as
+ * IO errors or syntax errors.
+ */
+ DataflowInfo openDataflowSilently(FileType fileType, Object source)
+ throws OpenException;
+
+ /**
+ * Save the dataflow to the last saved destination and FileType from
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)} or the last
+ * opened source and FileType from {@link #openDataflow(FileType, Object)}.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link SavedDataflowEvent}.
+ *
+ * @param dataflow
+ * Dataflow to save. Dataflow must have been opened with
+ * {@link #openDataflow(FileType, Object)} or saved using
+ * {@link #saveDataflow(WorkflowBundle, FileType, Object, boolean)}.
+ * @param failOnOverwrite
+ * If <code>true</code>, an {@link OverwriteException} is thrown
+ * if a save would overwrite the destination because it has been
+ * changed since last open/save.
+ * @throws OverwriteException
+ * if failOnOverwrite was true, and a save would overwrite the
+ * destination because it has been changed since last open/save.
+ * The save was not performed.
+ * @throws SaveException
+ * If any other error occurs during saving, including the case
+ * that a dataflow is not connected to a source or destination,
+ * that there are no handlers (some source types can't be saved
+ * to, such as HTTP URLs), or any other IO error occurring while
+ * saving.
+ */
+ void saveDataflow(WorkflowBundle dataflow, boolean failOnOverwrite)
+ throws SaveException, OverwriteException;
+
+ /**
+ * Save the dataflow to the given destination using the given filetype.
+ * <p>
+ * The file manager will find implementations of the SPI
+ * {@link DataflowPersistenceHandler} to perform the save for the given file
+ * type and destination class.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link SavedDataflowEvent}.
+ *
+ * @param dataflow
+ * {@link Dataflow} to be saved
+ * @param fileType
+ * {@link FileType} to save dataflow as, for instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType}.
+ * The file type must be supported by an SPI implementation of
+ * {@link DataflowPersistenceHandler}.
+ * @param destination
+ * Destination to save dataflow to, for instance a {@link File}
+ * @param failOnOverwrite
+ * If <code>true</code>, an {@link OverwriteException} is thrown
+ * if a save would overwrite the destination because it already
+ * exists, but was not opened or save to using the file manager
+ * for the given dataflow. (ie. a repeated call to this function
+ * should not throw an OverwriteException unless someone outside
+ * has modified the file)
+ * @throws OverwriteException
+ * if failOnOverwrite was true, and a save would overwrite the
+ * destination because it already existed, and was not last
+ * written to by a previous save. The save was not performed.
+ * @throws SaveException
+ * If any other error occurs during saving, including the case
+ * that a dataflow is not connected to a source or destination,
+ * that there are no handlers (some source types can't be saved
+ * to, such as HTTP URLs), or any other IO error occurring while
+ * saving.
+ */
+ void saveDataflow(WorkflowBundle dataflow, FileType fileType,
+ Object destination, boolean failOnOverwrite) throws SaveException,
+ OverwriteException;
+
+ /**
+ * Silently save the dataflow to the given destination using the given
+ * filetype.
+ * <p>
+ * The file manager will find implementations of the SPI
+ * {@link DataflowPersistenceHandler} to perform the save for the given file
+ * type and destination class.
+ * <p>
+ * Listeners will <strong>not</strong> be notified, and the dataflow does
+ * not previously have to be opened. getDataflowSource(),
+ * isDataflowChanged() etc will not be affected - as if the silent save
+ * never happened.
+ *
+ * @param dataflow
+ * {@link WorkflowBundle} to be saved
+ * @param fileType
+ * {@link FileType} to save dataflow as, for instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType}.
+ * The file type must be supported by an SPI implementation of
+ * {@link DataflowPersistenceHandler}.
+ * @param destination
+ * Destination to save dataflow to, for instance a {@link File}
+ * @param failOnOverwrite
+ * If <code>true</code>, an {@link OverwriteException} is thrown
+ * if a save would overwrite the destination because it already
+ * exists, but was not opened or save to using the file manager
+ * for the given dataflow. (ie. a repeated call to this function
+ * should not throw an OverwriteException unless someone outside
+ * has modified the file)
+ * @return The {@link DataflowInfo} describing where the workflow was saved
+ * @throws OverwriteException
+ * if failOnOverwrite was true, and a save would overwrite the
+ * destination because it already existed, and was not last
+ * written to by a previous save. The save was not performed.
+ * @throws SaveException
+ * If any other error occurs during saving, including the case
+ * that a dataflow is not connected to a source or destination,
+ * that there are no handlers (some source types can't be saved
+ * to, such as HTTP URLs), or any other IO error occurring while
+ * saving.
+ */
+ DataflowInfo saveDataflowSilently(WorkflowBundle dataflow, FileType fileType,
+ Object destination, boolean failOnOverwrite) throws SaveException,
+ OverwriteException;
+
+ /**
+ * Set the current dataflow to the one provided.
+ * <p>
+ * The current dataflow can be retrieved using {@link #getCurrentDataflow()}
+ * . Note that opening a dataflow will normally also set it as the current
+ * dataflow.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link SetCurrentDataflowEvent}.
+ * <p>
+ * Note, the dataflow must already be open. If this is not the case, use one
+ * of the openDataflow() methods or
+ * {@link #setCurrentDataflow(WorkflowBundle, boolean)}.
+ *
+ * @see #setCurrentDataflow(WorkflowBundle, boolean)
+ * @param dataflow
+ * {@link WorkflowBundle} to be made current
+ */
+ void setCurrentDataflow(WorkflowBundle dataflow);
+
+ /**
+ * Set the current dataflow to the one provided.
+ * <p>
+ * The current dataflow can be retrieved using {@link #getCurrentDataflow()}
+ * . Note that opening a dataflow will normally also set it as the current
+ * dataflow.
+ * <p>
+ * Listeners registered using {@link Observable#addObserver(Observer)} will
+ * be notified with an {@link SetCurrentDataflowEvent}.
+ * <p>
+ * Unless <code>openIfNeeded</code> is <code>true</code>, the dataflow must
+ * already be open.
+ *
+ * @see #setCurrentDataflow(WorkflowBundle, boolean)
+ * @param dataflow
+ * {@link WorkflowBundle} to be made current
+ * @param openIfNeeded
+ * If <code>true</code>, open the dataflow if needed
+ */
+ void setCurrentDataflow(WorkflowBundle dataflow, boolean openIfNeeded);
+
+ /**
+ * Set a dataflow as changed or not. This changes the value returned by
+ * {@link #isDataflowChanged(WorkflowBundle)}.
+ * <p>
+ * This method can be used if the dataflow has been changed outside the
+ * {@link EditManager}.
+ *
+ * @param dataflow
+ * Dataflow which is to be marked
+ * @param isChanged
+ * <code>true</code> if the dataflow is to be marked as changed,
+ * <code>false</code> if it is to be marked as not changed.
+ */
+ void setDataflowChanged(WorkflowBundle dataflow, boolean isChanged);
+
+ /**
+ * Returns the canonical form of the source where the dataflow was opened
+ * from or saved to. The code for this method was devised based on
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener#openDataflow(FileType fileType, Object source)}.
+ */
+ Object getCanonical(Object source) throws IllegalArgumentException,
+ URISyntaxException, IOException;
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/FileType.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/FileType.java
new file mode 100644
index 0000000..001d82c
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/FileType.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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.file;
+
+/**
+ * A filetype to identify a way to (de)serialise a {@link WorkflowBundle} with
+ * the {@link FileManager}.
+ * <p>
+ * Two filetypes are considered equal if they share an extension or mime type or
+ * are the same instance.
+ *
+ * @see net.sf.taverna.t2.workbench.file.impl.WorkflowBundleFileType
+ * @author Stian Soiland-Reyes
+ */
+public abstract class FileType {
+ public abstract String getExtension();
+
+ public abstract String getMimeType();
+
+ public abstract String getDescription();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof FileType))
+ return false;
+ FileType other = (FileType) obj;
+ if (getMimeType() != null && other.getMimeType() != null)
+ return getMimeType().equalsIgnoreCase(other.getMimeType());
+ if (getExtension() != null && other.getExtension() != null)
+ return getExtension().equalsIgnoreCase(other.getExtension());
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 31 * hash + getExtension().hashCode();
+ hash = 31 * hash + getMimeType().hashCode();
+ return hash;
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/AbstractDataflowEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/AbstractDataflowEvent.java
new file mode 100644
index 0000000..942e097
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/AbstractDataflowEvent.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Abstract FileManagerEvent that relates to a {@link WorkflowBundle}
+ *
+ * @see AbstractDataflowEvent
+ * @see ClosedDataflowEvent
+ * @see OpenedDataflowEvent
+ * @see SavedDataflowEvent
+ * @see SetCurrentDataflowEvent
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractDataflowEvent extends FileManagerEvent {
+ private final WorkflowBundle workflowBundle;
+
+ public AbstractDataflowEvent(WorkflowBundle workflowBundle) {
+ this.workflowBundle = workflowBundle;
+ }
+
+ public WorkflowBundle getDataflow() {
+ return workflowBundle;
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/ClosedDataflowEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/ClosedDataflowEvent.java
new file mode 100644
index 0000000..5e7e884
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/ClosedDataflowEvent.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * {@link FileManagerEvent} that means a {@link WorkflowBundle} has been closed.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class ClosedDataflowEvent extends AbstractDataflowEvent {
+ public ClosedDataflowEvent(WorkflowBundle workflowBundle) {
+ super(workflowBundle);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/ClosingDataflowEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/ClosingDataflowEvent.java
new file mode 100644
index 0000000..094ea10
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/ClosingDataflowEvent.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * {@link FileManagerEvent} that means a {@link WorkflowBundle} is being closed.
+ * <i>This event is abortable;</i> if aborted, the close will not occur.
+ *
+ * @author Alan R Williams
+ */
+public class ClosingDataflowEvent extends AbstractDataflowEvent {
+ private boolean abortClose = false;
+
+ public boolean isAbortClose() {
+ return abortClose;
+ }
+
+ public void setAbortClose(boolean abortClose) {
+ this.abortClose = abortClose;
+ }
+
+ public ClosingDataflowEvent(WorkflowBundle workflowBundle) {
+ super(workflowBundle);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/FileManagerEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/FileManagerEvent.java
new file mode 100644
index 0000000..84c3886
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/FileManagerEvent.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+/**
+ * An event given to {@link FileManager} observers registered using
+ * {@link Observable#addObserver(net.sf.taverna.t2.lang.observer.Observer)}.
+ *
+ * @see AbstractDataflowEvent
+ * @see ClosedDataflowEvent
+ * @see OpenedDataflowEvent
+ * @see SavedDataflowEvent
+ * @see SetCurrentDataflowEvent
+ * @author Stian Soiland-Reyes
+ */
+public class FileManagerEvent {
+
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/OpenedDataflowEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/OpenedDataflowEvent.java
new file mode 100644
index 0000000..b479a83
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/OpenedDataflowEvent.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * {@link FileManagerEvent} that means a dataflow has been opened
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class OpenedDataflowEvent extends AbstractDataflowEvent {
+ public OpenedDataflowEvent(WorkflowBundle workflowBundle) {
+ super(workflowBundle);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/SavedDataflowEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/SavedDataflowEvent.java
new file mode 100644
index 0000000..f0f22ae
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/SavedDataflowEvent.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * {@link FileManagerEvent} that means a {@link WorkflowBundle} has been saved.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class SavedDataflowEvent extends AbstractDataflowEvent {
+ public SavedDataflowEvent(WorkflowBundle workflowBundle) {
+ super(workflowBundle);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/SetCurrentDataflowEvent.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/SetCurrentDataflowEvent.java
new file mode 100644
index 0000000..ff44cf2
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/events/SetCurrentDataflowEvent.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * 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.file.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * {@link FileManagerEvent} that means a {@link WorkflowBundle} has been made
+ * current.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class SetCurrentDataflowEvent extends AbstractDataflowEvent {
+ public SetCurrentDataflowEvent(WorkflowBundle workflowBundle) {
+ super(workflowBundle);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/FileException.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/FileException.java
new file mode 100644
index 0000000..a2cb9ce
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/FileException.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.file.exceptions;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+/**
+ * Superclass of exceptions thrown by the {@link FileManager}.
+ */
+@SuppressWarnings("serial")
+public class FileException extends Exception {
+ public FileException() {
+ }
+
+ public FileException(String message) {
+ super(message);
+ }
+
+ public FileException(Throwable cause) {
+ super(cause);
+ }
+
+ public FileException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/OpenException.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/OpenException.java
new file mode 100644
index 0000000..057679b
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/OpenException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.file.exceptions;
+
+/** Indicate that something went wrong during opening a file */
+@SuppressWarnings("serial")
+public class OpenException extends FileException {
+ public OpenException() {
+ }
+
+ public OpenException(String message) {
+ super(message);
+ }
+
+ public OpenException(Throwable cause) {
+ super(cause);
+ }
+
+ public OpenException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/OverwriteException.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/OverwriteException.java
new file mode 100644
index 0000000..6d410a3
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/OverwriteException.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.file.exceptions;
+
+/** Indicate that something could not be overwritten. */
+@SuppressWarnings("serial")
+public class OverwriteException extends SaveException {
+ private final Object destination;
+
+ public OverwriteException(Object destination) {
+ super("Save would overwrite existing destination " + destination);
+ this.destination = destination;
+ }
+
+ public Object getDestination() {
+ return destination;
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/SaveException.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/SaveException.java
new file mode 100644
index 0000000..8c4266f
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/SaveException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.file.exceptions;
+
+/** Indicate that a workflow could not be saved. */
+@SuppressWarnings("serial")
+public class SaveException extends FileException {
+ public SaveException() {
+ }
+
+ public SaveException(String message) {
+ super(message);
+ }
+
+ public SaveException(Throwable cause) {
+ super(cause);
+ }
+
+ public SaveException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/UnsavedException.java b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/UnsavedException.java
new file mode 100644
index 0000000..41c01f8
--- /dev/null
+++ b/taverna-workbench-file-api/src/main/java/net/sf/taverna/t2/workbench/file/exceptions/UnsavedException.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * 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.file.exceptions;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/** Indicate that a workflow bundle is not saved. */
+@SuppressWarnings("serial")
+public class UnsavedException extends FileException {
+ private final WorkflowBundle workflowBundle;
+
+ public UnsavedException(WorkflowBundle workflowBundle) {
+ super("WorkflowBundle was not saved: " + workflowBundle);
+ this.workflowBundle = workflowBundle;
+ }
+
+ public WorkflowBundle getDataflow() {
+ return workflowBundle;
+ }
+}
diff --git a/taverna-workbench-file-impl/pom.xml b/taverna-workbench-file-impl/pom.xml
new file mode 100644
index 0000000..98dcce7
--- /dev/null
+++ b/taverna-workbench-file-impl/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>file-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>File opening implementation</name>
+ <description>
+ Implementation for doing file (i.e. workflow) open/save in the
+ workbench.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>com.springsource.org.apache.commons.lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>edits-impl</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-t2flow</artifactId>
+ <version>${scufl2.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-rdfxml</artifactId>
+ <version>${scufl2.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
new file mode 100644
index 0000000..86bc091
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowFromDataflowPersistenceHandler extends
+ AbstractDataflowPersistenceHandler implements
+ DataflowPersistenceHandler {
+ private static final WorkflowBundleFileType WORKFLOW_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (!getOpenFileTypes().contains(fileType))
+ throw new IllegalArgumentException("Unsupported file type "
+ + fileType);
+
+ WorkflowBundle workflowBundle = (WorkflowBundle) source;
+ Date lastModified = null;
+ Object canonicalSource = null;
+ return new DataflowInfo(WORKFLOW_BUNDLE_FILE_TYPE, canonicalSource,
+ workflowBundle, lastModified);
+ }
+
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Arrays.<FileType> asList(WORKFLOW_BUNDLE_FILE_TYPE);
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Arrays.<Class<?>> asList(Workflow.class);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
new file mode 100644
index 0000000..39117e9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import static org.apache.commons.collections.map.LazyMap.decorate;
+import static org.apache.commons.lang.ClassUtils.getAllInterfaces;
+import static org.apache.commons.lang.ClassUtils.getAllSuperclasses;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.commons.collections.Factory;
+
+// TODO: Cache lookups / build one massive structure
+public class DataflowPersistenceHandlerRegistry {
+ private static final MapFactory MAP_FACTORY = new MapFactory();
+ private static final SetFactory SET_FACTORY = new SetFactory();
+
+ @SuppressWarnings("unchecked")
+ protected static List<Class<?>> findAllParentClasses(
+ final Class<?> sourceClass) {
+ List<Class<?>> superClasses = new ArrayList<>();
+ superClasses.add(sourceClass);
+ superClasses.addAll(getAllSuperclasses(sourceClass));
+ superClasses.addAll(getAllInterfaces(sourceClass));
+ return superClasses;
+ }
+
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> openClassToHandlers;
+ private Map<Class<?>, Set<FileType>> openClassToTypes;
+ private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> openFileClassToHandler;
+ private Map<FileType, Set<DataflowPersistenceHandler>> openFileToHandler;
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> saveClassToHandlers;
+ private Map<Class<?>, Set<FileType>> saveClassToTypes;
+ private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> saveFileClassToHandler;
+ private Map<FileType, Set<DataflowPersistenceHandler>> saveFileToHandler;
+
+ private List<DataflowPersistenceHandler> dataflowPersistenceHandlers;
+
+ public DataflowPersistenceHandlerRegistry() {
+ }
+
+ public Set<FileType> getOpenFileTypes() {
+ return getOpenFileClassToHandler().keySet();
+ }
+
+ public Set<FileType> getOpenFileTypesFor(Class<?> sourceClass) {
+ Set<FileType> fileTypes = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ fileTypes.addAll(getOpenClassToTypes().get(candidateClass));
+ return fileTypes;
+ }
+
+ public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+ Class<? extends Object> sourceClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ handlers.addAll(getOpenClassToHandlers().get(candidateClass));
+ return handlers;
+ }
+
+ public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+ FileType fileType, Class<? extends Object> sourceClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+ candidateClass));
+ return handlers;
+ }
+
+ public Set<DataflowPersistenceHandler> getOpenHandlersForType(
+ FileType fileType) {
+ return getOpenFileToHandler().get(fileType);
+ }
+
+ public synchronized Set<DataflowPersistenceHandler> getOpenHandlersForType(
+ FileType fileType, Class<?> sourceClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+ candidateClass));
+ return handlers;
+ }
+
+ public Set<FileType> getSaveFileTypes() {
+ return getSaveFileClassToHandler().keySet();
+ }
+
+ public Set<FileType> getSaveFileTypesFor(Class<?> destinationClass) {
+ Set<FileType> fileTypes = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+ fileTypes.addAll(getSaveClassToTypes().get(candidateClass));
+ return fileTypes;
+ }
+
+ public Set<DataflowPersistenceHandler> getSaveHandlersFor(
+ Class<? extends Object> destinationClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+ handlers.addAll(getSaveClassToHandlers().get(candidateClass));
+ return handlers;
+ }
+
+ public Set<DataflowPersistenceHandler> getSaveHandlersForType(
+ FileType fileType, Class<?> destinationClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+ handlers.addAll(getSaveFileClassToHandler().get(fileType).get(
+ candidateClass));
+ return handlers;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private synchronized void createCollections() {
+ openFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+ openFileToHandler = decorate(new HashMap(), SET_FACTORY);
+ openClassToTypes = decorate(new HashMap(), SET_FACTORY);
+ openClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+
+ saveFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+ saveFileToHandler = decorate(new HashMap(), SET_FACTORY);
+ saveClassToTypes = decorate(new HashMap(), SET_FACTORY);
+ saveClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+ }
+
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> getOpenClassToHandlers() {
+ return openClassToHandlers;
+ }
+
+ private synchronized Map<Class<?>, Set<FileType>> getOpenClassToTypes() {
+ return openClassToTypes;
+ }
+
+ private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getOpenFileClassToHandler() {
+ return openFileClassToHandler;
+ }
+
+ private Map<FileType, Set<DataflowPersistenceHandler>> getOpenFileToHandler() {
+ return openFileToHandler;
+ }
+
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> getSaveClassToHandlers() {
+ return saveClassToHandlers;
+ }
+
+ private synchronized Map<Class<?>, Set<FileType>> getSaveClassToTypes() {
+ return saveClassToTypes;
+ }
+
+ private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getSaveFileClassToHandler() {
+ return saveFileClassToHandler;
+ }
+
+ /**
+ * Bind method for SpringDM.
+ *
+ * @param service
+ * @param properties
+ */
+ public void update(Object service, Map<?, ?> properties) {
+ if (dataflowPersistenceHandlers != null)
+ updateColletions();
+ }
+
+ public synchronized void updateColletions() {
+ createCollections();
+ for (DataflowPersistenceHandler handler : dataflowPersistenceHandlers) {
+ for (FileType openFileType : handler.getOpenFileTypes()) {
+ Set<DataflowPersistenceHandler> set = openFileToHandler
+ .get(openFileType);
+ set.add(handler);
+ for (Class<?> openClass : handler.getOpenSourceTypes()) {
+ openFileClassToHandler.get(openFileType).get(openClass)
+ .add(handler);
+ openClassToTypes.get(openClass).add(openFileType);
+ }
+ }
+ for (Class<?> openClass : handler.getOpenSourceTypes())
+ openClassToHandlers.get(openClass).add(handler);
+
+ for (FileType saveFileType : handler.getSaveFileTypes()) {
+ saveFileToHandler.get(saveFileType).add(handler);
+ for (Class<?> saveClass : handler.getSaveDestinationTypes()) {
+ saveFileClassToHandler.get(saveFileType).get(saveClass)
+ .add(handler);
+ saveClassToTypes.get(saveClass).add(saveFileType);
+ }
+ }
+ for (Class<?> openClass : handler.getSaveDestinationTypes())
+ saveClassToHandlers.get(openClass).add(handler);
+ }
+ }
+
+ public void setDataflowPersistenceHandlers(
+ List<DataflowPersistenceHandler> dataflowPersistenceHandlers) {
+ this.dataflowPersistenceHandlers = dataflowPersistenceHandlers;
+ }
+
+ private static class MapFactory implements Factory {
+ @Override
+ @SuppressWarnings("rawtypes")
+ public Object create() {
+ return decorate(new HashMap(), SET_FACTORY);
+ }
+ }
+
+ private static class SetFactory implements Factory {
+ @Override
+ public Object create() {
+ return new LinkedHashSet<Object>();
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
new file mode 100644
index 0000000..89ae39c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Information about an open dataflow that was opened from or saved to a
+ * {@link File}.
+ *
+ * @see DataflowInfo
+ * @see FileManager
+ * @author Stian Soiland-Reyes
+ */
+public class FileDataflowInfo extends DataflowInfo {
+ private static Logger logger = Logger.getLogger(FileDataflowInfo.class);
+
+ public FileDataflowInfo(FileType fileType, File source,
+ WorkflowBundle workflowBundle) {
+ super(fileType, canonicalFile(source), workflowBundle,
+ lastModifiedFile(source));
+ }
+
+ protected static Date lastModifiedFile(File file) {
+ long lastModifiedLong = file.lastModified();
+ if (lastModifiedLong == 0)
+ return null;
+ return new Date(lastModifiedLong);
+ }
+
+ public static File canonicalFile(File file) {
+ try {
+ return file.getCanonicalFile();
+ } catch (IOException e) {
+ logger.warn("Could not find canonical file for " + file);
+ return file;
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
new file mode 100644
index 0000000..aadb3f1
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
@@ -0,0 +1,601 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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.file.impl;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+import static java.util.Collections.singleton;
+import static javax.swing.SwingUtilities.invokeAndWait;
+import static javax.swing.SwingUtilities.isEventDispatchThread;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.ClosingDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Implementation of {@link FileManager}
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class FileManagerImpl implements FileManager {
+ private static Logger logger = Logger.getLogger(FileManagerImpl.class);
+ private static int nameIndex = 1;
+
+ /**
+ * The last blank workflowBundle created using #newDataflow() until it has
+ * been changed - when this variable will be set to null again. Used to
+ * automatically close unmodified blank workflowBundles on open.
+ */
+ private WorkflowBundle blankWorkflowBundle = null;
+ @SuppressWarnings("unused")
+ private EditManager editManager;
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ protected MultiCaster<FileManagerEvent> observers = new MultiCaster<>(this);
+ /**
+ * Ordered list of open WorkflowBundle
+ */
+ private LinkedHashMap<WorkflowBundle, OpenDataflowInfo> openDataflowInfos = new LinkedHashMap<>();
+ private DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry;
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+ private WorkflowBundle currentWorkflowBundle;
+
+ public DataflowPersistenceHandlerRegistry getPersistanceHandlerRegistry() {
+ return dataflowPersistenceHandlerRegistry;
+ }
+
+ public FileManagerImpl(EditManager editManager) {
+ this.editManager = editManager;
+ editManager.addObserver(editManagerObserver);
+ }
+
+ /**
+ * Add an observer to be notified of {@link FileManagerEvent}s, such as
+ * {@link OpenedDataflowEvent} and {@link SavedDataflowEvent}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void addObserver(Observer<FileManagerEvent> observer) {
+ observers.addObserver(observer);
+ }
+
+ @Override
+ public boolean canSaveWithoutDestination(WorkflowBundle workflowBundle) {
+ OpenDataflowInfo dataflowInfo = getOpenDataflowInfo(workflowBundle);
+ if (dataflowInfo.getSource() == null)
+ return false;
+ Set<?> handlers = getPersistanceHandlerRegistry()
+ .getSaveHandlersForType(
+ dataflowInfo.getFileType(),
+ dataflowInfo.getDataflowInfo().getCanonicalSource()
+ .getClass());
+ return !handlers.isEmpty();
+ }
+
+ @Override
+ public boolean closeDataflow(WorkflowBundle workflowBundle,
+ boolean failOnUnsaved) throws UnsavedException {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ ClosingDataflowEvent message = new ClosingDataflowEvent(workflowBundle);
+ observers.notify(message);
+ if (message.isAbortClose())
+ return false;
+ if ((failOnUnsaved && getOpenDataflowInfo(workflowBundle).isChanged()))
+ throw new UnsavedException(workflowBundle);
+ if (workflowBundle.equals(getCurrentDataflow())) {
+ // We'll need to change current workflowBundle
+ // Find best candidate to the left or right
+ List<WorkflowBundle> workflowBundles = getOpenDataflows();
+ int openIndex = workflowBundles.indexOf(workflowBundle);
+ if (openIndex == -1)
+ throw new IllegalArgumentException("Workflow was not opened "
+ + workflowBundle);
+
+ if (openIndex > 0)
+ setCurrentDataflow(workflowBundles.get(openIndex - 1));
+ else if (openIndex == 0 && workflowBundles.size() > 1)
+ setCurrentDataflow(workflowBundles.get(1));
+ else
+ // If it was the last one, start a new, empty workflowBundle
+ newDataflow();
+ }
+ if (workflowBundle == blankWorkflowBundle)
+ blankWorkflowBundle = null;
+ openDataflowInfos.remove(workflowBundle);
+ observers.notify(new ClosedDataflowEvent(workflowBundle));
+ return true;
+ }
+
+ @Override
+ public WorkflowBundle getCurrentDataflow() {
+ return currentWorkflowBundle;
+ }
+
+ @Override
+ public WorkflowBundle getDataflowBySource(Object source) {
+ for (Entry<WorkflowBundle, OpenDataflowInfo> infoEntry : openDataflowInfos
+ .entrySet()) {
+ OpenDataflowInfo info = infoEntry.getValue();
+ if (source.equals(info.getSource()))
+ return infoEntry.getKey();
+ }
+ // Not found
+ return null;
+ }
+
+ @Override
+ public String getDataflowName(WorkflowBundle workflowBundle) {
+ Object source = null;
+ if (isDataflowOpen(workflowBundle))
+ source = getDataflowSource(workflowBundle);
+ // Fallback
+ String name;
+ Workflow workflow = workflowBundle.getMainWorkflow();
+ if (workflow != null)
+ name = workflow.getName();
+ else
+ name = workflowBundle.getName();
+ if (source == null)
+ return name;
+ if (source instanceof File)
+ return ((File) source).getAbsolutePath();
+ else if (source instanceof URL)
+ return source.toString();
+
+ // Check if it has implemented a toString() method
+ Method toStringMethod = null;
+ Method toStringMethodFromObject = null;
+ try {
+ toStringMethod = source.getClass().getMethod("toString");
+ toStringMethodFromObject = Object.class.getMethod("toString");
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Source did not implement Object.toString() " + source);
+ }
+ if (!toStringMethod.equals(toStringMethodFromObject))
+ return source.toString();
+ return name;
+ }
+
+ @Override
+ public String getDefaultWorkflowName() {
+ return "Workflow" + (nameIndex++);
+ }
+
+ @Override
+ public Object getDataflowSource(WorkflowBundle workflowBundle) {
+ return getOpenDataflowInfo(workflowBundle).getSource();
+ }
+
+ @Override
+ public FileType getDataflowType(WorkflowBundle workflowBundle) {
+ return getOpenDataflowInfo(workflowBundle).getFileType();
+ }
+
+ @Override
+ public List<Observer<FileManagerEvent>> getObservers() {
+ return observers.getObservers();
+ }
+
+ /**
+ * Get the {@link OpenDataflowInfo} for the given WorkflowBundle
+ *
+ * @throws NullPointerException
+ * if the WorkflowBundle was <code>null</code>
+ * @throws IllegalArgumentException
+ * if the WorkflowBundle was not open.
+ * @param workflowBundle
+ * WorkflowBundle which information is to be found
+ * @return The {@link OpenDataflowInfo} describing the WorkflowBundle
+ */
+ protected synchronized OpenDataflowInfo getOpenDataflowInfo(
+ WorkflowBundle workflowBundle) {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ OpenDataflowInfo info = openDataflowInfos.get(workflowBundle);
+ if (info == null)
+ throw new IllegalArgumentException("Workflow was not opened "
+ + workflowBundle);
+ return info;
+ }
+
+ @Override
+ public List<WorkflowBundle> getOpenDataflows() {
+ return new ArrayList<>(openDataflowInfos.keySet());
+ }
+
+ @Override
+ public List<FileFilter> getOpenFileFilters() {
+ List<FileFilter> fileFilters = new ArrayList<>();
+
+ Set<FileType> fileTypes = getPersistanceHandlerRegistry()
+ .getOpenFileTypes();
+ if (!fileTypes.isEmpty())
+ fileFilters.add(new MultipleFileTypes(fileTypes,
+ "All supported workflows"));
+ for (FileType fileType : fileTypes)
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public List<FileFilter> getOpenFileFilters(Class<?> sourceClass) {
+ List<FileFilter> fileFilters = new ArrayList<>();
+ for (FileType fileType : getPersistanceHandlerRegistry()
+ .getOpenFileTypesFor(sourceClass))
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public List<FileFilter> getSaveFileFilters() {
+ List<FileFilter> fileFilters = new ArrayList<>();
+ for (FileType fileType : getPersistanceHandlerRegistry()
+ .getSaveFileTypes())
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public List<FileFilter> getSaveFileFilters(Class<?> destinationClass) {
+ List<FileFilter> fileFilters = new ArrayList<>();
+ for (FileType fileType : getPersistanceHandlerRegistry()
+ .getSaveFileTypesFor(destinationClass))
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public boolean isDataflowChanged(WorkflowBundle workflowBundle) {
+ return getOpenDataflowInfo(workflowBundle).isChanged();
+ }
+
+ @Override
+ public boolean isDataflowOpen(WorkflowBundle workflowBundle) {
+ return openDataflowInfos.containsKey(workflowBundle);
+ }
+
+ @Override
+ public WorkflowBundle newDataflow() {
+ WorkflowBundle workflowBundle = new WorkflowBundle();
+ workflowBundle.setMainWorkflow(new Workflow());
+ workflowBundle.getMainWorkflow().setName(getDefaultWorkflowName());
+ workflowBundle.setMainProfile(new Profile());
+ scufl2Tools.setParents(workflowBundle);
+ blankWorkflowBundle = null;
+ openDataflowInternal(workflowBundle);
+ blankWorkflowBundle = workflowBundle;
+ observers.notify(new OpenedDataflowEvent(workflowBundle));
+ return workflowBundle;
+ }
+
+ @Override
+ public void openDataflow(WorkflowBundle workflowBundle) {
+ openDataflowInternal(workflowBundle);
+ observers.notify(new OpenedDataflowEvent(workflowBundle));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WorkflowBundle openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (isHeadless())
+ return performOpenDataflow(fileType, source);
+
+ OpenDataflowRunnable r = new OpenDataflowRunnable(this, fileType,
+ source);
+ if (isEventDispatchThread()) {
+ r.run();
+ } else
+ try {
+ invokeAndWait(r);
+ } catch (InterruptedException | InvocationTargetException e) {
+ throw new OpenException("Opening was interrupted", e);
+ }
+ OpenException thrownException = r.getException();
+ if (thrownException != null)
+ throw thrownException;
+ return r.getDataflow();
+ }
+
+ public WorkflowBundle performOpenDataflow(FileType fileType, Object source)
+ throws OpenException {
+ DataflowInfo dataflowInfo;
+ WorkflowBundle workflowBundle;
+ dataflowInfo = openDataflowSilently(fileType, source);
+ workflowBundle = dataflowInfo.getDataflow();
+ openDataflowInternal(workflowBundle);
+ getOpenDataflowInfo(workflowBundle).setOpenedFrom(dataflowInfo);
+ observers.notify(new OpenedDataflowEvent(workflowBundle));
+ return workflowBundle;
+ }
+
+ @Override
+ public DataflowInfo openDataflowSilently(FileType fileType, Object source)
+ throws OpenException {
+ Set<DataflowPersistenceHandler> handlers;
+ Class<? extends Object> sourceClass = source.getClass();
+
+ boolean unknownFileType = (fileType == null);
+ if (unknownFileType)
+ handlers = getPersistanceHandlerRegistry().getOpenHandlersFor(
+ sourceClass);
+ else
+ handlers = getPersistanceHandlerRegistry().getOpenHandlersFor(
+ fileType, sourceClass);
+ if (handlers.isEmpty())
+ throw new OpenException("Unsupported file type or class "
+ + fileType + " " + sourceClass);
+
+ Throwable lastException = null;
+ for (DataflowPersistenceHandler handler : handlers) {
+ Collection<FileType> fileTypes;
+ if (unknownFileType)
+ fileTypes = handler.getOpenFileTypes();
+ else
+ fileTypes = singleton(fileType);
+ for (FileType candidateFileType : fileTypes) {
+ if (unknownFileType && (source instanceof File))
+ /*
+ * If source is file but fileType was not explicitly set
+ * from the open workflow dialog - check the file extension
+ * and decide which handler to use based on that (so that we
+ * do not loop though all handlers)
+ */
+ if (!((File) source).getPath().endsWith(
+ candidateFileType.getExtension()))
+ continue;
+
+ try {
+ DataflowInfo openDataflow = handler.openDataflow(
+ candidateFileType, source);
+ WorkflowBundle workflowBundle = openDataflow.getDataflow();
+ logger.info("Loaded workflow: " + workflowBundle.getName()
+ + " " + workflowBundle.getGlobalBaseURI()
+ + " from " + source + " using " + handler);
+ return openDataflow;
+ } catch (OpenException ex) {
+ logger.warn("Could not open workflow " + source + " using "
+ + handler + " of type " + candidateFileType);
+ lastException = ex;
+ }
+ }
+ }
+ throw new OpenException("Could not open workflow " + source + "\n",
+ lastException);
+ }
+
+ /**
+ * Mark the WorkflowBundle as opened, and close the blank WorkflowBundle if
+ * needed.
+ *
+ * @param workflowBundle
+ * WorkflowBundle that has been opened
+ */
+ protected void openDataflowInternal(WorkflowBundle workflowBundle) {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ if (isDataflowOpen(workflowBundle))
+ throw new IllegalArgumentException("Workflow is already open: "
+ + workflowBundle);
+
+ openDataflowInfos.put(workflowBundle, new OpenDataflowInfo());
+ setCurrentDataflow(workflowBundle);
+ if (openDataflowInfos.size() == 2 && blankWorkflowBundle != null)
+ /*
+ * Behave like a word processor and close the blank WorkflowBundle
+ * when another workflow has been opened
+ */
+ try {
+ closeDataflow(blankWorkflowBundle, true);
+ } catch (UnsavedException e) {
+ logger.error("Blank workflow was modified "
+ + "and could not be closed");
+ }
+ }
+
+ @Override
+ public void removeObserver(Observer<FileManagerEvent> observer) {
+ observers.removeObserver(observer);
+ }
+
+ @Override
+ public void saveDataflow(WorkflowBundle workflowBundle,
+ boolean failOnOverwrite) throws SaveException {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ OpenDataflowInfo lastSave = getOpenDataflowInfo(workflowBundle);
+ if (lastSave.getSource() == null)
+ throw new SaveException("Can't save without source "
+ + workflowBundle);
+ saveDataflow(workflowBundle, lastSave.getFileType(),
+ lastSave.getSource(), failOnOverwrite);
+ }
+
+ @Override
+ public void saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination, boolean failOnOverwrite) throws SaveException {
+ DataflowInfo savedDataflow = saveDataflowSilently(workflowBundle,
+ fileType, destination, failOnOverwrite);
+ getOpenDataflowInfo(workflowBundle).setSavedTo(savedDataflow);
+ observers.notify(new SavedDataflowEvent(workflowBundle));
+ }
+
+ @Override
+ public DataflowInfo saveDataflowSilently(WorkflowBundle workflowBundle,
+ FileType fileType, Object destination, boolean failOnOverwrite)
+ throws SaveException, OverwriteException {
+ Set<DataflowPersistenceHandler> handlers;
+
+ Class<? extends Object> destinationClass = destination.getClass();
+ if (fileType != null)
+ handlers = getPersistanceHandlerRegistry().getSaveHandlersForType(
+ fileType, destinationClass);
+ else
+ handlers = getPersistanceHandlerRegistry().getSaveHandlersFor(
+ destinationClass);
+
+ SaveException lastException = null;
+ for (DataflowPersistenceHandler handler : handlers) {
+ if (failOnOverwrite) {
+ OpenDataflowInfo openDataflowInfo = getOpenDataflowInfo(workflowBundle);
+ if (handler.wouldOverwriteDataflow(workflowBundle, fileType,
+ destination, openDataflowInfo.getDataflowInfo()))
+ throw new OverwriteException(destination);
+ }
+ try {
+ DataflowInfo savedDataflow = handler.saveDataflow(
+ workflowBundle, fileType, destination);
+ savedDataflow.getDataflow();
+ logger.info("Saved workflow: " + workflowBundle.getName() + " "
+ + workflowBundle.getGlobalBaseURI() + " to "
+ + savedDataflow.getCanonicalSource() + " using "
+ + handler);
+ return savedDataflow;
+ } catch (SaveException ex) {
+ logger.warn("Could not save to " + destination + " using "
+ + handler);
+ lastException = ex;
+ }
+ }
+
+ if (lastException == null)
+ throw new SaveException("Unsupported file type or class "
+ + fileType + " " + destinationClass);
+ throw new SaveException("Could not save to " + destination + ":\n"
+ + lastException.getLocalizedMessage(), lastException);
+ }
+
+ @Override
+ public void setCurrentDataflow(WorkflowBundle workflowBundle) {
+ setCurrentDataflow(workflowBundle, false);
+ }
+
+ @Override
+ public void setCurrentDataflow(WorkflowBundle workflowBundle,
+ boolean openIfNeeded) {
+ currentWorkflowBundle = workflowBundle;
+ if (!isDataflowOpen(workflowBundle)) {
+ if (!openIfNeeded)
+ throw new IllegalArgumentException("Workflow is not open: "
+ + workflowBundle);
+ openDataflow(workflowBundle);
+ return;
+ }
+ observers.notify(new SetCurrentDataflowEvent(workflowBundle));
+ }
+
+ @Override
+ public void setDataflowChanged(WorkflowBundle workflowBundle,
+ boolean isChanged) {
+ getOpenDataflowInfo(workflowBundle).setIsChanged(isChanged);
+ if (blankWorkflowBundle == workflowBundle)
+ blankWorkflowBundle = null;
+ }
+
+ @Override
+ public Object getCanonical(Object source) throws IllegalArgumentException,
+ URISyntaxException, IOException {
+ Object canonicalSource = source;
+
+ if (source instanceof URL) {
+ URL url = ((URL) source);
+ if (url.getProtocol().equalsIgnoreCase("file"))
+ canonicalSource = new File(url.toURI());
+ }
+
+ if (canonicalSource instanceof File)
+ canonicalSource = ((File) canonicalSource).getCanonicalFile();
+ return canonicalSource;
+ }
+
+ public void setDataflowPersistenceHandlerRegistry(
+ DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry) {
+ this.dataflowPersistenceHandlerRegistry = dataflowPersistenceHandlerRegistry;
+ }
+
+ /**
+ * Observe the {@link EditManager} for changes to open workflowBundles. A
+ * change of an open workflow would set it as changed using
+ * {@link FileManagerImpl#setDataflowChanged(Dataflow, boolean)}.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ private final class EditManagerObserver implements
+ Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent) {
+ AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+ WorkflowBundle workflowBundle = dataflowEdit.getDataFlow();
+ /**
+ * TODO: on undo/redo - keep last event or similar to determine
+ * if workflow was saved before. See
+ * FileManagerTest#isChangedWithUndo().
+ */
+ setDataflowChanged(workflowBundle, true);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
new file mode 100644
index 0000000..6416163
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class FileTypeFileFilter extends FileFilter {
+ private final FileType fileType;
+
+ public FileTypeFileFilter(FileType fileType) {
+ this.fileType = fileType;
+ }
+
+ @Override
+ public String getDescription() {
+ return fileType.getDescription();
+ }
+
+ @Override
+ public boolean accept(File file) {
+ if (file.isDirectory())
+ // Don't grey out directories
+ return true;
+ if (fileType.getExtension() == null)
+ return false;
+ return file.getName().toLowerCase()
+ .endsWith("." + fileType.getExtension());
+ }
+
+ public FileType getFileType() {
+ return fileType;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
new file mode 100644
index 0000000..c398805
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+import java.util.Set;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class MultipleFileTypes extends FileFilter {
+ private String description;
+ private final Set<FileType> fileTypes;
+
+ public MultipleFileTypes(Set<FileType> fileTypes, String description) {
+ this.fileTypes = fileTypes;
+ this.description = description;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public boolean accept(File file) {
+ if (file.isDirectory())
+ return true;
+
+ String lowerFileName = file.getName().toLowerCase();
+ for (FileType fileType : fileTypes) {
+ if (fileType.getExtension() == null)
+ continue;
+ if (lowerFileName.endsWith(fileType.getExtension()))
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
new file mode 100644
index 0000000..dc08cff
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
@@ -0,0 +1,88 @@
+
+/*******************************************************************************
+ * Copyright (C) 2009 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.file.impl;
+
+import static java.awt.BorderLayout.CENTER;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * Dialog that is popped up while we are opening a workflow.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class OpenDataflowInProgressDialog extends HelpEnabledDialog {
+ private boolean userCancelled = false;
+
+ public OpenDataflowInProgressDialog() {
+ super(getMainWindow(), "Opening workflow", true);
+ setResizable(false);
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(new EmptyBorder(10,10,10,10));
+
+ JPanel textPanel = new JPanel();
+ JLabel text = new JLabel(workingIcon);
+ text.setText("Opening workflow...");
+ text.setBorder(new EmptyBorder(10,0,10,0));
+ textPanel.add(text);
+ panel.add(textPanel, CENTER);
+
+/*
+ * Cancellation does not work when opening
+
+ // Cancel button
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ userCancelled = true;
+ setVisible(false);
+ dispose();
+ }
+ });
+ JPanel cancelButtonPanel = new JPanel();
+ cancelButtonPanel.add(cancelButton);
+ panel.add(cancelButtonPanel, BorderLayout.SOUTH);
+*/
+ setContentPane(panel);
+ setPreferredSize(new Dimension(300, 100));
+
+ pack();
+ }
+
+ public boolean hasUserCancelled() {
+ return userCancelled;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
new file mode 100644
index 0000000..4a4a1e3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+/**
+ * Information about an open dataflow.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class OpenDataflowInfo {
+ private DataflowInfo dataflowInfo;
+ private boolean isChanged;
+ private Date openedAt;
+
+ public OpenDataflowInfo() {
+ }
+
+ public FileType getFileType() {
+ if (dataflowInfo == null)
+ return null;
+ return dataflowInfo.getFileType();
+ }
+
+ public Date getLastModified() {
+ if (dataflowInfo == null)
+ return null;
+ return dataflowInfo.getLastModified();
+ }
+
+ public Date getOpenedAtDate() {
+ return openedAt;
+ }
+
+ public Object getSource() {
+ if (dataflowInfo == null)
+ return null;
+ return dataflowInfo.getCanonicalSource();
+ }
+
+ public boolean isChanged() {
+ return isChanged;
+ }
+
+ public void setIsChanged(boolean isChanged) {
+ this.isChanged = isChanged;
+ }
+
+ public synchronized void setOpenedFrom(DataflowInfo dataflowInfo) {
+ setDataflowInfo(dataflowInfo);
+ setOpenedAt(new Date());
+ setIsChanged(false);
+ }
+
+ public synchronized void setSavedTo(DataflowInfo dataflowInfo) {
+ setDataflowInfo(dataflowInfo);
+ setIsChanged(false);
+ }
+
+ private void setDataflowInfo(DataflowInfo dataflowInfo) {
+ this.dataflowInfo = dataflowInfo;
+ }
+
+ private void setOpenedAt(Date openedAt) {
+ this.openedAt = openedAt;
+ }
+
+ public DataflowInfo getDataflowInfo() {
+ return dataflowInfo;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
new file mode 100644
index 0000000..9d687b8
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static java.lang.Thread.sleep;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ */
+public class OpenDataflowRunnable implements Runnable {
+ private final FileManagerImpl fileManager;
+ private final FileType fileType;
+ private final Object source;
+ private WorkflowBundle dataflow;
+ private OpenException e;
+
+ public OpenDataflowRunnable(FileManagerImpl fileManager, FileType fileType,
+ Object source) {
+ this.fileManager = fileManager;
+ this.fileType = fileType;
+ this.source = source;
+ }
+
+ @Override
+ public void run() {
+ OpenDataflowSwingWorker openDataflowSwingWorker = new OpenDataflowSwingWorker(
+ fileType, source, fileManager);
+ OpenDataflowInProgressDialog dialog = new OpenDataflowInProgressDialog();
+ openDataflowSwingWorker
+ .addPropertyChangeListener(new SwingWorkerCompletionWaiter(
+ dialog));
+ openDataflowSwingWorker.execute();
+
+ /*
+ * Give a chance to the SwingWorker to finish so we do not have to
+ * display the dialog
+ */
+ try {
+ sleep(500);
+ } catch (InterruptedException e) {
+ this.e = new OpenException("Opening was interrupted");
+ }
+ if (!openDataflowSwingWorker.isDone())
+ dialog.setVisible(true); // this will block the GUI
+ boolean userCancelled = dialog.hasUserCancelled(); // see if user cancelled the dialog
+
+ if (userCancelled) {
+ // Stop the OpenDataflowSwingWorker if it is still working
+ openDataflowSwingWorker.cancel(true);
+ dataflow = null;
+ this.e = new OpenException("Opening was cancelled");
+ // exit
+ return;
+ }
+ dataflow = openDataflowSwingWorker.getDataflow();
+ this.e = openDataflowSwingWorker.getException();
+ }
+
+ public WorkflowBundle getDataflow() {
+ return dataflow;
+ }
+
+ public OpenException getException() {
+ return this.e;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
new file mode 100644
index 0000000..4cbd2f8
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.file.impl;
+
+import javax.swing.SwingWorker;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class OpenDataflowSwingWorker extends
+ SwingWorker<WorkflowBundle, Object> {
+ @SuppressWarnings("unused")
+ private Logger logger = Logger.getLogger(OpenDataflowSwingWorker.class);
+ private FileType fileType;
+ private Object source;
+ private FileManagerImpl fileManagerImpl;
+ private WorkflowBundle workflowBundle;
+ private OpenException e = null;
+
+ public OpenDataflowSwingWorker(FileType fileType, Object source,
+ FileManagerImpl fileManagerImpl) {
+ this.fileType = fileType;
+ this.source = source;
+ this.fileManagerImpl = fileManagerImpl;
+ }
+
+ @Override
+ protected WorkflowBundle doInBackground() throws Exception {
+ try {
+ workflowBundle = fileManagerImpl.performOpenDataflow(fileType,
+ source);
+ } catch (OpenException e) {
+ this.e = e;
+ }
+ return workflowBundle;
+ }
+
+ public WorkflowBundle getDataflow() {
+ return workflowBundle;
+ }
+
+ public OpenException getException() {
+ return e;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
new file mode 100644
index 0000000..bf37faf
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class T2DataflowOpener extends AbstractDataflowPersistenceHandler
+ implements DataflowPersistenceHandler {
+ private static final T2FlowFileType T2_FLOW_FILE_TYPE = new T2FlowFileType();
+ private static Logger logger = Logger.getLogger(T2DataflowOpener.class);
+
+ private WorkflowBundleIO workflowBundleIO;
+
+ @SuppressWarnings("resource")
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (!getOpenFileTypes().contains(fileType))
+ throw new OpenException("Unsupported file type "
+ + fileType);
+ InputStream inputStream;
+ Date lastModified = null;
+ Object canonicalSource = source;
+ if (source instanceof InputStream)
+ inputStream = (InputStream) source;
+ else if (source instanceof File)
+ try {
+ inputStream = new FileInputStream((File) source);
+ } catch (FileNotFoundException e) {
+ throw new OpenException("Could not open file " + source + ":\n" + e.getLocalizedMessage(), e);
+ }
+ else if (source instanceof URL) {
+ URL url = ((URL) source);
+ try {
+ URLConnection connection = url.openConnection();
+ connection.setRequestProperty("Accept", "text/xml");
+ inputStream = connection.getInputStream();
+ if (connection.getLastModified() != 0)
+ lastModified = new Date(connection.getLastModified());
+ } catch (IOException e) {
+ throw new OpenException("Could not open connection to URL "
+ + source+ ":\n" + e.getLocalizedMessage(), e);
+ }
+ try {
+ if (url.getProtocol().equalsIgnoreCase("file"))
+ canonicalSource = new File(url.toURI());
+ } catch (URISyntaxException e) {
+ logger.warn("Invalid file URI created from " + url);
+ }
+ } else {
+ throw new OpenException("Unsupported source type "
+ + source.getClass());
+ }
+
+ final WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = openDataflowStream(inputStream);
+ } finally {
+ try {
+ if (!(source instanceof InputStream))
+ // We created the stream, we'll close it
+ inputStream.close();
+ } catch (IOException ex) {
+ logger.warn("Could not close inputstream " + inputStream, ex);
+ }
+ }
+ if (canonicalSource instanceof File)
+ return new FileDataflowInfo(T2_FLOW_FILE_TYPE,
+ (File) canonicalSource, workflowBundle);
+ return new DataflowInfo(T2_FLOW_FILE_TYPE, canonicalSource,
+ workflowBundle, lastModified);
+ }
+
+ protected WorkflowBundle openDataflowStream(InputStream workflowXMLstream)
+ throws OpenException {
+ WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = workflowBundleIO.readBundle(workflowXMLstream, null);
+ } catch (ReaderException e) {
+ throw new OpenException("Could not read the workflow", e);
+ } catch (IOException e) {
+ throw new OpenException("Could not open the workflow file for parsing", e);
+ } catch (Exception e) {
+ throw new OpenException("Error while opening workflow", e);
+ }
+
+ return workflowBundle;
+ }
+
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Arrays.<FileType> asList(new T2FlowFileType());
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Arrays.<Class<?>> asList(InputStream.class, URL.class,
+ File.class);
+ }
+
+ public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+ this.workflowBundleIO = workflowBundleIO;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
new file mode 100644
index 0000000..62b5892
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public class T2FileFilter extends FileFilter {
+ @Override
+ public boolean accept(final File file) {
+ return file.getName().toLowerCase().endsWith(".t2flow");
+ }
+
+ @Override
+ public String getDescription() {
+ return "Taverna 2 workflows";
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
new file mode 100644
index 0000000..a2774e4
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class T2FlowFileType extends FileType {
+ public static final String APPLICATION_VND_TAVERNA_T2FLOW_XML = "application/vnd.taverna.t2flow+xml";
+
+ @Override
+ public String getDescription() {
+ return "Taverna 2 workflow";
+ }
+
+ @Override
+ public String getExtension() {
+ return "t2flow";
+ }
+
+ @Override
+ public String getMimeType() {
+ return APPLICATION_VND_TAVERNA_T2FLOW_XML;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
new file mode 100644
index 0000000..d272b41
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public class WorkflowBundleFileFilter extends FileFilter {
+ @Override
+ public boolean accept(final File file) {
+ return file.getName().toLowerCase().endsWith(".wfbundle");
+ }
+
+ @Override
+ public String getDescription() {
+ return "Taverna 3 workflows";
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
new file mode 100644
index 0000000..09b30b0
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class WorkflowBundleFileType extends FileType {
+ public static final String APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE = "application/vnd.taverna.scufl2.workflow-bundle";
+
+ @Override
+ public String getDescription() {
+ return "Taverna 3 workflow";
+ }
+
+ @Override
+ public String getExtension() {
+ return "wfbundle";
+ }
+
+ @Override
+ public String getMimeType() {
+ return APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
new file mode 100644
index 0000000..7c54f7e
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class WorkflowBundleOpener extends AbstractDataflowPersistenceHandler
+ implements DataflowPersistenceHandler {
+ private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+ private static Logger logger = Logger.getLogger(WorkflowBundleOpener.class);
+ private WorkflowBundleIO workflowBundleIO;
+
+ @SuppressWarnings("resource")
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (!getOpenFileTypes().contains(fileType))
+ throw new OpenException("Unsupported file type " + fileType);
+ InputStream inputStream;
+ Date lastModified = null;
+ Object canonicalSource = source;
+ if (source instanceof InputStream) {
+ inputStream = (InputStream) source;
+ } else if (source instanceof File) {
+ try {
+ inputStream = new FileInputStream((File) source);
+ } catch (FileNotFoundException e) {
+ throw new OpenException("Could not open file " + source + ":\n"
+ + e.getLocalizedMessage(), e);
+ }
+ } else if (source instanceof URL) {
+ URL url = ((URL) source);
+ try {
+ URLConnection connection = url.openConnection();
+ connection.setRequestProperty("Accept", "application/zip");
+ inputStream = connection.getInputStream();
+ if (connection.getLastModified() != 0)
+ lastModified = new Date(connection.getLastModified());
+ } catch (IOException e) {
+ throw new OpenException("Could not open connection to URL "
+ + source + ":\n" + e.getLocalizedMessage(), e);
+ }
+ try {
+ if (url.getProtocol().equalsIgnoreCase("file"))
+ canonicalSource = new File(url.toURI());
+ } catch (URISyntaxException e) {
+ logger.warn("Invalid file URI created from " + url);
+ }
+ } else
+ throw new OpenException("Unsupported source type "
+ + source.getClass());
+
+ final WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = openDataflowStream(inputStream);
+ } finally {
+ // We created the stream, we'll close it
+ try {
+ if (!(source instanceof InputStream))
+ inputStream.close();
+ } catch (IOException ex) {
+ logger.warn("Could not close inputstream " + inputStream, ex);
+ }
+ }
+ if (canonicalSource instanceof File)
+ return new FileDataflowInfo(WF_BUNDLE_FILE_TYPE,
+ (File) canonicalSource, workflowBundle);
+ return new DataflowInfo(WF_BUNDLE_FILE_TYPE, canonicalSource,
+ workflowBundle, lastModified);
+ }
+
+ protected WorkflowBundle openDataflowStream(InputStream inputStream)
+ throws OpenException {
+ WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = workflowBundleIO.readBundle(inputStream, null);
+ } catch (ReaderException e) {
+ throw new OpenException("Could not read the workflow", e);
+ } catch (IOException e) {
+ throw new OpenException("Could not open the workflow for parsing",
+ e);
+ } catch (Exception e) {
+ throw new OpenException("Error while opening workflow", e);
+ }
+
+ return workflowBundle;
+ }
+
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Arrays.<FileType> asList(WF_BUNDLE_FILE_TYPE);
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Arrays.<Class<?>> asList(InputStream.class, URL.class,
+ File.class);
+ }
+
+ public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+ this.workflowBundleIO = workflowBundleIO;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
new file mode 100644
index 0000000..f6b7108
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import static net.sf.taverna.t2.workbench.file.impl.WorkflowBundleFileType.APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class WorkflowBundleSaver extends AbstractDataflowPersistenceHandler
+ implements DataflowPersistenceHandler {
+ private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+ private static Logger logger = Logger.getLogger(WorkflowBundleSaver.class);
+ private WorkflowBundleIO workflowBundleIO;
+
+ @Override
+ public DataflowInfo saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination) throws SaveException {
+ if (!getSaveFileTypes().contains(fileType))
+ throw new IllegalArgumentException("Unsupported file type "
+ + fileType);
+ OutputStream outStream;
+ if (destination instanceof File)
+ try {
+ outStream = new FileOutputStream((File) destination);
+ } catch (FileNotFoundException e) {
+ throw new SaveException("Can't create workflow file "
+ + destination + ":\n" + e.getLocalizedMessage(), e);
+ }
+ else if (destination instanceof OutputStream)
+ outStream = (OutputStream) destination;
+ else
+ throw new SaveException("Unsupported destination type "
+ + destination.getClass());
+
+ try {
+ saveDataflowToStream(workflowBundle, outStream);
+ } finally {
+ try {
+ // Only close if we opened the stream
+ if (!(destination instanceof OutputStream))
+ outStream.close();
+ } catch (IOException e) {
+ logger.warn("Could not close stream", e);
+ }
+ }
+
+ if (destination instanceof File)
+ return new FileDataflowInfo(WF_BUNDLE_FILE_TYPE, (File) destination,
+ workflowBundle);
+ return new DataflowInfo(WF_BUNDLE_FILE_TYPE, destination, workflowBundle);
+ }
+
+ protected void saveDataflowToStream(WorkflowBundle workflowBundle,
+ OutputStream fileOutStream) throws SaveException {
+ try {
+ workflowBundleIO.writeBundle(workflowBundle, fileOutStream,
+ APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE);
+ } catch (Exception e) {
+ throw new SaveException("Can't write workflow:\n"
+ + e.getLocalizedMessage(), e);
+ }
+ }
+
+ @Override
+ public List<FileType> getSaveFileTypes() {
+ return Arrays.<FileType> asList(WF_BUNDLE_FILE_TYPE);
+ }
+
+ @Override
+ public List<Class<?>> getSaveDestinationTypes() {
+ return Arrays.<Class<?>> asList(File.class, OutputStream.class);
+ }
+
+ @Override
+ public boolean wouldOverwriteDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination, DataflowInfo lastDataflowInfo) {
+ if (!getSaveFileTypes().contains(fileType))
+ throw new IllegalArgumentException("Unsupported file type "
+ + fileType);
+ if (!(destination instanceof File))
+ return false;
+
+ File file;
+ try {
+ file = ((File) destination).getCanonicalFile();
+ } catch (IOException e) {
+ return false;
+ }
+ if (!file.exists())
+ return false;
+ if (lastDataflowInfo == null)
+ return true;
+ Object lastDestination = lastDataflowInfo.getCanonicalSource();
+ if (!(lastDestination instanceof File))
+ return true;
+ File lastFile = (File) lastDestination;
+ if (!lastFile.getAbsoluteFile().equals(file))
+ return true;
+
+ Date lastModified = new Date(file.lastModified());
+ if (lastModified.equals(lastDataflowInfo.getLastModified()))
+ return false;
+ return true;
+ }
+
+ public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+ this.workflowBundleIO = workflowBundleIO;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java
new file mode 100644
index 0000000..2309b9c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_L;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeAllIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class CloseAllWorkflowsAction extends AbstractAction {
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(CloseWorkflowAction.class);
+ private static final String CLOSE_ALL_WORKFLOWS = "Close all workflows";
+ private FileManager fileManager;
+ private CloseWorkflowAction closeWorkflowAction;
+
+ public CloseAllWorkflowsAction(EditManager editManager, FileManager fileManager) {
+ super(CLOSE_ALL_WORKFLOWS, closeAllIcon);
+ this.fileManager = fileManager;
+ closeWorkflowAction = new CloseWorkflowAction(editManager, fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_W, getDefaultToolkit().getMenuShortcutKeyMask()
+ | SHIFT_DOWN_MASK));
+ putValue(MNEMONIC_KEY, VK_L);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ Component parentComponent = null;
+ if (event.getSource() instanceof Component)
+ parentComponent = (Component) event.getSource();
+ closeAllWorkflows(parentComponent);
+ }
+
+ public boolean closeAllWorkflows(Component parentComponent) {
+ // Close in reverse so we can save nested workflows first
+ List<WorkflowBundle> workflowBundles = fileManager.getOpenDataflows();
+
+ Collections.reverse(workflowBundles);
+
+ for (WorkflowBundle workflowBundle : workflowBundles) {
+ boolean success = closeWorkflowAction.closeWorkflow(
+ parentComponent, workflowBundle);
+ if (!success)
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java
new file mode 100644
index 0000000..03c90a6
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_C;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class CloseWorkflowAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(CloseWorkflowAction.class);
+ private static final String CLOSE_WORKFLOW = "Close workflow";
+ private final SaveWorkflowAction saveWorkflowAction;
+ private FileManager fileManager;
+
+ public CloseWorkflowAction(EditManager editManager, FileManager fileManager) {
+ super(CLOSE_WORKFLOW, closeIcon);
+ this.fileManager = fileManager;
+ saveWorkflowAction = new SaveWorkflowAction(editManager, fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_W, getDefaultToolkit().getMenuShortcutKeyMask()));
+ putValue(MNEMONIC_KEY, VK_C);
+
+ }
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Component parentComponent = null;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ closeWorkflow(parentComponent, fileManager.getCurrentDataflow());
+ }
+
+ public boolean closeWorkflow(Component parentComponent, WorkflowBundle workflowBundle) {
+ if (workflowBundle == null) {
+ logger.warn("Attempted to close a null workflow");
+ return false;
+ }
+
+ try {
+ return fileManager.closeDataflow(workflowBundle, true);
+ } catch (UnsavedException e1) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ String msg = "Do you want to save changes before closing the workflow "
+ + fileManager.getDataflowName(workflowBundle) + "?";
+ switch (showConfirmDialog(parentComponent, msg, "Save workflow?",
+ YES_NO_CANCEL_OPTION)) {
+ case NO_OPTION:
+ try {
+ fileManager.closeDataflow(workflowBundle, false);
+ return true;
+ } catch (UnsavedException e2) {
+ logger.error("Unexpected UnsavedException while "
+ + "closing workflow", e2);
+ return false;
+ }
+ case YES_OPTION:
+ boolean saved = saveWorkflowAction.saveDataflow(
+ parentComponent, workflowBundle);
+ if (!saved)
+ return false;
+ return closeWorkflow(parentComponent, workflowBundle);
+ case CANCEL_OPTION:
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java
new file mode 100644
index 0000000..3b9256a
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_N;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.newIcon;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class NewWorkflowAction extends AbstractAction {
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(NewWorkflowAction.class);
+ private static final String NEW_WORKFLOW = "New workflow";
+ private FileManager fileManager;
+
+ public NewWorkflowAction(FileManager fileManager) {
+ super(NEW_WORKFLOW, newIcon);
+ this.fileManager = fileManager;
+ putValue(SHORT_DESCRIPTION, NEW_WORKFLOW);
+ putValue(MNEMONIC_KEY, KeyEvent.VK_N);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_N, getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fileManager.newDataflow();
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java
new file mode 100644
index 0000000..08030c7
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import java.awt.Component;
+import java.io.File;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a nested workflow from a file.
+ *
+ * @author Alex Nenadic
+ */
+public class OpenNestedWorkflowAction extends OpenWorkflowAction {
+ private static final long serialVersionUID = -5398423684000142379L;
+ private static Logger logger = Logger
+ .getLogger(OpenNestedWorkflowAction.class);
+
+ public OpenNestedWorkflowAction(FileManager fileManager) {
+ super(fileManager);
+ }
+
+ /**
+ * Opens a nested workflow from a file (should be one file even though the
+ * method takes a list of files - this is because it overrides the
+ * {@link OpenWorkflowAction#openWorkflows(Component, File[], FileType, OpenCallback)
+ * openWorkflows(...)} method).
+ */
+ @Override
+ public void openWorkflows(final Component parentComponent, File[] files,
+ FileType fileType, OpenCallback openCallback) {
+ ErrorLoggingOpenCallbackWrapper callback = new ErrorLoggingOpenCallbackWrapper(
+ openCallback);
+ for (File file : files)
+ try {
+ callback.aboutToOpenDataflow(file);
+ WorkflowBundle workflowBundle = fileManager.openDataflow(
+ fileType, file);
+ callback.openedDataflow(file, workflowBundle);
+ } catch (final RuntimeException ex) {
+ logger.warn("Could not open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ } catch (final OpenException ex) {
+ logger.warn("Could not open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ return;
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java
new file mode 100644
index 0000000..e2ecbd7
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java
@@ -0,0 +1,395 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_O;
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a workflow from a file. All file types exposed by the
+ * {@link FileManager} as compatible with the {@link File} type are supported.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class OpenWorkflowAction extends AbstractAction {
+ private static final long serialVersionUID = 103237694130052153L;
+ private static Logger logger = Logger.getLogger(OpenWorkflowAction.class);
+ private static final String OPEN_WORKFLOW = "Open workflow...";
+
+ public final OpenCallback DUMMY_OPEN_CALLBACK = new OpenCallbackAdapter();
+ protected FileManager fileManager;
+
+ public OpenWorkflowAction(FileManager fileManager) {
+ super(OPEN_WORKFLOW, openIcon);
+ this.fileManager = fileManager;
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_O, getDefaultToolkit().getMenuShortcutKeyMask()));
+ putValue(MNEMONIC_KEY, VK_O);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ else
+ parentComponent = null;
+ openWorkflows(parentComponent);
+ }
+
+ /**
+ * Pop up an Open-dialogue to select one or more workflow files to open.
+ * <p>
+ * Note that the file opening occurs in a separate thread. If you want to
+ * check if the file was opened or not, which workflow was opened, etc, use
+ * {@link #openWorkflows(Component, OpenCallback)} instead.
+ *
+ * @see #openWorkflows(Component, OpenCallback)
+ * @param parentComponent
+ * The UI parent component to use for pop up dialogues
+ *
+ * @return <code>false</code> if no files were selected or the dialogue was
+ * cancelled, or <code>true</code> if the process of opening one or
+ * more files has been started.
+ */
+ public void openWorkflows(Component parentComponent) {
+ openWorkflows(parentComponent, DUMMY_OPEN_CALLBACK);
+ }
+
+ /**
+ * Open an array of workflow files.
+ *
+ * @param parentComponent
+ * Parent component for UI dialogues
+ * @param files
+ * Array of files to be opened
+ * @param fileType
+ * {@link FileType} of the files that are to be opened, for
+ * instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType},
+ * or <code>null</code> to guess.
+ * @param openCallback
+ * An {@link OpenCallback} to be invoked during and after opening
+ * the file. Use {@link OpenWorkflowAction#DUMMY_OPEN_CALLBACK}
+ * if no callback is needed.
+ */
+ public void openWorkflows(final Component parentComponent, File[] files,
+ FileType fileType, OpenCallback openCallback) {
+ ErrorLoggingOpenCallbackWrapper callback = new ErrorLoggingOpenCallbackWrapper(
+ openCallback);
+ for (File file : files)
+ try {
+ Object canonicalSource = fileManager.getCanonical(file);
+ WorkflowBundle alreadyOpen = fileManager.getDataflowBySource(canonicalSource);
+ if (alreadyOpen != null) {
+ /*
+ * The workflow from the same source is already opened - ask
+ * the user if they want to switch to it or open another
+ * copy...
+ */
+
+ Object[] options = { "Switch to opened", "Open new copy",
+ "Cancel" };
+ switch (showOptionDialog(
+ null,
+ "The workflow from the same location is already opened.\n"
+ + "Do you want to switch to it or open a new copy?",
+ "File Manager Alert", YES_NO_CANCEL_OPTION,
+ QUESTION_MESSAGE, null, options, // the titles of buttons
+ options[0])) { // default button title
+ case YES_OPTION:
+ fileManager.setCurrentDataflow(alreadyOpen);
+ return;
+ case CANCEL_OPTION:
+ // do nothing
+ return;
+ }
+ // else open the workflow as usual
+ }
+
+ callback.aboutToOpenDataflow(file);
+ WorkflowBundle workflowBundle = fileManager.openDataflow(fileType, file);
+ callback.openedDataflow(file, workflowBundle);
+ } catch (RuntimeException ex) {
+ logger.warn("Failed to open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ } catch (Exception ex) {
+ logger.warn("Failed to open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ return;
+ }
+ }
+
+ /**
+ * Pop up an Open-dialogue to select one or more workflow files to open.
+ *
+ * @param parentComponent
+ * The UI parent component to use for pop up dialogues
+ * @param openCallback
+ * An {@link OpenCallback} to be called during the file opening.
+ * The callback will be invoked for each file that has been
+ * opened, as file opening happens in a separate thread that
+ * might execute after the return of this method.
+ * @return <code>false</code> if no files were selected or the dialogue was
+ * cancelled, or <code>true</code> if the process of opening one or
+ * more files has been started.
+ */
+ public boolean openWorkflows(final Component parentComponent,
+ OpenCallback openCallback) {
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = userNodeForPackage(getClass());
+ String curDir = prefs
+ .get("currentDir", System.getProperty("user.home"));
+ fileChooser.setDialogTitle(OPEN_WORKFLOW);
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ List<FileFilter> fileFilters = fileManager.getOpenFileFilters();
+ if (fileFilters.isEmpty()) {
+ logger.warn("No file types found for opening workflow");
+ showMessageDialog(parentComponent,
+ "No file types found for opening workflow.", "Error",
+ ERROR_MESSAGE);
+ return false;
+ }
+ for (FileFilter fileFilter : fileFilters)
+ fileChooser.addChoosableFileFilter(fileFilter);
+ fileChooser.setFileFilter(fileFilters.get(0));
+ fileChooser.setCurrentDirectory(new File(curDir));
+ fileChooser.setMultiSelectionEnabled(true);
+
+ int returnVal = fileChooser.showOpenDialog(parentComponent);
+ if (returnVal == APPROVE_OPTION) {
+ prefs.put("currentDir", fileChooser.getCurrentDirectory()
+ .toString());
+ final File[] selectedFiles = fileChooser.getSelectedFiles();
+ if (selectedFiles.length == 0) {
+ logger.warn("No files selected");
+ return false;
+ }
+ FileFilter fileFilter = fileChooser.getFileFilter();
+ FileType fileType;
+ if (fileFilter instanceof FileTypeFileFilter)
+ fileType = ((FileTypeFileFilter) fileChooser.getFileFilter())
+ .getFileType();
+ else
+ // Unknown filetype, try all of them
+ fileType = null;
+ new FileOpenerThread(parentComponent, selectedFiles, fileType,
+ openCallback).start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Show an error message if a file could not be opened
+ *
+ * @param parentComponent
+ * @param file
+ * @param throwable
+ */
+ protected void showErrorMessage(final Component parentComponent,
+ final File file, final Throwable throwable) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ Throwable cause = throwable;
+ while (cause.getCause() != null)
+ cause = cause.getCause();
+ showMessageDialog(
+ parentComponent,
+ "Failed to open workflow from " + file + ": \n"
+ + cause.getMessage(), "Warning",
+ WARNING_MESSAGE);
+ }
+ });
+
+ }
+
+ /**
+ * Callback interface for openWorkflows().
+ * <p>
+ * The callback will be invoked during the invocation of
+ * {@link OpenWorkflowAction#openWorkflows(Component, OpenCallback)} and
+ * {@link OpenWorkflowAction#openWorkflows(Component, File[], FileType, OpenCallback)}
+ * as file opening happens in a separate thread.
+ *
+ * @author Stian Soiland-Reyes
+ */
+ public interface OpenCallback {
+ /**
+ * Called before a workflowBundle is to be opened from the given file
+ *
+ * @param file
+ * File which workflowBundle is to be opened
+ */
+ void aboutToOpenDataflow(File file);
+
+ /**
+ * Called if an exception happened while attempting to open the
+ * workflowBundle.
+ *
+ * @param file
+ * File which was attempted to be opened
+ * @param ex
+ * An {@link OpenException} or a {@link RuntimeException}.
+ * @return <code>true</code> if the error has been handled, or
+ * <code>false</code>3 if a UI warning dialogue is to be opened.
+ */
+ boolean couldNotOpenDataflow(File file, Exception ex);
+
+ /**
+ * Called when a workflowBundle has been successfully opened. The workflowBundle
+ * will be registered in {@link FileManager#getOpenDataflows()}.
+ *
+ * @param file
+ * File from which workflowBundle was opened
+ * @param workflowBundle
+ * WorkflowBundle that was opened
+ */
+ void openedDataflow(File file, WorkflowBundle workflowBundle);
+ }
+
+ /**
+ * Adapter for {@link OpenCallback}
+ *
+ * @author Stian Soiland-Reyes
+ */
+ public static class OpenCallbackAdapter implements OpenCallback {
+ @Override
+ public void aboutToOpenDataflow(File file) {
+ }
+
+ @Override
+ public boolean couldNotOpenDataflow(File file, Exception ex) {
+ return false;
+ }
+
+ @Override
+ public void openedDataflow(File file, WorkflowBundle workflowBundle) {
+ }
+ }
+
+ private final class FileOpenerThread extends Thread {
+ private final File[] files;
+ private final FileType fileType;
+ private final OpenCallback openCallback;
+ private final Component parentComponent;
+
+ private FileOpenerThread(Component parentComponent,
+ File[] selectedFiles, FileType fileType,
+ OpenCallback openCallback) {
+ super("Opening workflows(s) " + Arrays.asList(selectedFiles));
+ this.parentComponent = parentComponent;
+ this.files = selectedFiles;
+ this.fileType = fileType;
+ this.openCallback = openCallback;
+ }
+
+ @Override
+ public void run() {
+ openWorkflows(parentComponent, files, fileType, openCallback);
+ }
+ }
+
+ /**
+ * A wrapper for {@link OpenCallback} implementations that logs exceptions
+ * thrown without disrupting the caller of the callback.
+ *
+ * @author Stian Soiland-Reyes
+ */
+ protected class ErrorLoggingOpenCallbackWrapper implements OpenCallback {
+ private final OpenCallback wrapped;
+
+ public ErrorLoggingOpenCallbackWrapper(OpenCallback wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public void aboutToOpenDataflow(File file) {
+ try {
+ wrapped.aboutToOpenDataflow(file);
+ } catch (RuntimeException wrapperEx) {
+ logger.warn("Failed OpenCallback " + wrapped
+ + ".aboutToOpenDataflow(File)", wrapperEx);
+ }
+ }
+
+ @Override
+ public boolean couldNotOpenDataflow(File file, Exception ex) {
+ try {
+ return wrapped.couldNotOpenDataflow(file, ex);
+ } catch (RuntimeException wrapperEx) {
+ logger.warn("Failed OpenCallback " + wrapped
+ + ".couldNotOpenDataflow(File, Exception)", wrapperEx);
+ return false;
+ }
+ }
+
+ @Override
+ public void openedDataflow(File file, WorkflowBundle workflowBundle) {
+ try {
+ wrapped.openedDataflow(file, workflowBundle);
+ } catch (RuntimeException wrapperEx) {
+ logger.warn("Failed OpenCallback " + wrapped
+ + ".openedDataflow(File, Dataflow)", wrapperEx);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
new file mode 100644
index 0000000..e98a8f2
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_L;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openurlIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URL;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a workflow from a url.
+ *
+ * @author David Withers
+ */
+public class OpenWorkflowFromURLAction extends AbstractAction {
+ private static final long serialVersionUID = 1474356457949961974L;
+ private static Logger logger = Logger
+ .getLogger(OpenWorkflowFromURLAction.class);
+ private static Preferences prefs = Preferences
+ .userNodeForPackage(OpenWorkflowFromURLAction.class);
+ private static final String PREF_CURRENT_URL = "currentUrl";
+ private static final String ACTION_NAME = "Open workflow location...";
+ private static final String ACTION_DESCRIPTION = "Open a workflow from the web into a new workflow";
+
+ private Component component;
+ private FileManager fileManager;
+
+ public OpenWorkflowFromURLAction(final Component component,
+ FileManager fileManager) {
+ this.component = component;
+ this.fileManager = fileManager;
+ putValue(SMALL_ICON, openurlIcon);
+ putValue(NAME, ACTION_NAME);
+ putValue(SHORT_DESCRIPTION, ACTION_DESCRIPTION);
+ putValue(MNEMONIC_KEY, VK_L);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_L, getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String currentUrl = prefs.get(PREF_CURRENT_URL, "http://");
+
+ final String url = (String) showInputDialog(component,
+ "Enter the URL of a workflow definition to load",
+ "Workflow URL", QUESTION_MESSAGE, null, null, currentUrl);
+ if (url != null)
+ new Thread("OpenWorkflowFromURLAction") {
+ @Override
+ public void run() {
+ openFromURL(url);
+ }
+ }.start();
+ }
+
+ private void openFromURL(String urlString) {
+ try {
+ URL url = new URL(urlString);
+
+ Object canonicalSource = fileManager.getCanonical(url);
+ WorkflowBundle alreadyOpen = fileManager
+ .getDataflowBySource(canonicalSource);
+ if (alreadyOpen != null) {
+ /*
+ * The workflow from the same source is already opened - ask the
+ * user if they want to switch to it or open another copy.
+ */
+
+ Object[] options = { "Switch to opened", "Open new copy",
+ "Cancel" };
+ int iSelected = showOptionDialog(
+ null,
+ "The workflow from the same location is already opened.\n"
+ + "Do you want to switch to it or open a new copy?",
+ "File Manager Alert", YES_NO_CANCEL_OPTION,
+ QUESTION_MESSAGE, null, options, // the titles of buttons
+ options[0]); // default button title
+
+ if (iSelected == YES_OPTION) {
+ fileManager.setCurrentDataflow(alreadyOpen);
+ return;
+ } else if (iSelected == CANCEL_OPTION) {
+ // do nothing
+ return;
+ }
+ // else open the workflow as usual
+ }
+
+ fileManager.openDataflow(null, url);
+ prefs.put(PREF_CURRENT_URL, urlString);
+ } catch (Exception ex) {
+ logger.warn("Failed to open the workflow from url " + urlString
+ + " \n", ex);
+ showMessageDialog(component,
+ "Failed to open the workflow from url " + urlString + " \n"
+ + ex.getMessage(), "Error!", ERROR_MESSAGE);
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java
new file mode 100644
index 0000000..401a232
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.EventQueue.invokeLater;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+
+/**
+ * Simple dialogue to handle username/password input for workflow URL requiring
+ * http authentication.
+ *
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class PasswordInput extends HelpEnabledDialog {
+ private static Logger logger = Logger.getLogger(PasswordInput.class);
+
+ private String password = null;
+ private String username = null;
+ private URL url = null;
+ private int tryCount = 0;
+ private final static int MAX_TRIES = 3;
+
+ private JButton cancelButton;
+ private JLabel jLabel1;
+ private JLabel jLabel2;
+ private JLabel messageLabel;
+ private JButton okButton;
+ private JPasswordField passwordTextField;
+ private JLabel urlLabel;
+ private JTextField usernameTextField;
+
+ public void setUrl(URL url) {
+ this.url = url;
+ urlLabel.setText(url.toExternalForm());
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public PasswordInput(JFrame parent) {
+ super(parent, "Authorization", true, null);
+ initComponents();
+ }
+
+ /** Creates new form PasswordInput */
+ public PasswordInput() {
+ super((JFrame) null, "Authorization", true, null);
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ private void initComponents() {
+ usernameTextField = new javax.swing.JTextField();
+ cancelButton = new javax.swing.JButton();
+ okButton = new javax.swing.JButton();
+ passwordTextField = new javax.swing.JPasswordField();
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ messageLabel = new javax.swing.JLabel();
+ urlLabel = new javax.swing.JLabel();
+
+ getContentPane().setLayout(null);
+
+ setModal(true);
+ // setResizable(false);
+ getContentPane().add(usernameTextField);
+ usernameTextField.setBounds(20, 80, 280, 22);
+
+ cancelButton.setText("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelButtonActionPerformed(evt);
+ }
+ });
+
+ getContentPane().add(cancelButton);
+ cancelButton.setBounds(230, 160, 75, 29);
+
+ okButton.setText("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+
+ getContentPane().add(okButton);
+ okButton.setBounds(150, 160, 75, 29);
+
+ getContentPane().add(passwordTextField);
+ passwordTextField.setBounds(20, 130, 280, 22);
+
+ jLabel1.setText("Username");
+ getContentPane().add(jLabel1);
+ jLabel1.setBounds(20, 60, 70, 16);
+
+ jLabel2.setText("Password");
+ getContentPane().add(jLabel2);
+ jLabel2.setBounds(20, 110, 70, 16);
+
+ messageLabel.setText("A username and password is required for:");
+ getContentPane().add(messageLabel);
+ messageLabel.setBounds(20, 10, 270, 20);
+
+ urlLabel.setText("service");
+ getContentPane().add(urlLabel);
+ urlLabel.setBounds(20, 30, 270, 16);
+
+ pack();
+ }
+
+ private void okButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ String password = String.valueOf(passwordTextField.getPassword());
+ String username = usernameTextField.getText();
+ HttpURLConnection connection;
+ try {
+ connection = (HttpURLConnection) url.openConnection();
+ String userPassword = username + ":" + password;
+ /*
+ * Note: non-latin1 support for basic auth is fragile/unsupported
+ * and must be MIME-encoded (RFC2047) according to
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=41489
+ */
+ byte[] encoded = Base64.encodeBase64(userPassword
+ .getBytes("latin1"));
+ connection.setRequestProperty("Authorization", "Basic "
+ + new String(encoded, "ascii"));
+ connection.setRequestProperty("Accept", "text/xml");
+ int code = connection.getResponseCode();
+
+ /*
+ * NB: myExperiment gives a 500 response for an invalid
+ * username/password
+ */
+ if (code == 401 || code == 500) {
+ tryCount++;
+ showMessageDialog(this, "The username and password failed",
+ "Invalid username or password", ERROR_MESSAGE);
+ if (tryCount >= MAX_TRIES) { // close after 3 attempts.
+ this.password = null;
+ this.username = null;
+ this.setVisible(false);
+ }
+ } else {
+ this.username = username;
+ this.password = password;
+ this.setVisible(false);
+ }
+ } catch (IOException ex) {
+ logger.error("Could not get password", ex);
+ }
+ }
+
+ private void cancelButtonActionPerformed(ActionEvent evt) {
+ this.password = null;
+ this.username = null;
+ this.setVisible(false);
+ }
+
+ /**
+ * @param args
+ * the command line arguments
+ */
+ public static void main(String args[]) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ new PasswordInput().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java
new file mode 100644
index 0000000..6b011d3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_A;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAllIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class SaveAllWorkflowsAction extends AbstractAction {
+ private final class FileManagerObserver implements
+ Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ updateEnabled();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger
+ .getLogger(SaveAllWorkflowsAction.class);
+ private static final String SAVE_ALL_WORKFLOWS = "Save all workflows";
+
+ private final SaveWorkflowAction saveWorkflowAction;
+ private FileManager fileManager;
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+ public SaveAllWorkflowsAction(EditManager editManager,
+ FileManager fileManager) {
+ super(SAVE_ALL_WORKFLOWS, saveAllIcon);
+ this.fileManager = fileManager;
+ saveWorkflowAction = new SaveWorkflowAction(editManager, fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_S, getDefaultToolkit().getMenuShortcutKeyMask()
+ | SHIFT_DOWN_MASK));
+ putValue(MNEMONIC_KEY, VK_A);
+
+ fileManager.addObserver(fileManagerObserver);
+ updateEnabled();
+ }
+
+ public void updateEnabled() {
+ setEnabled(!(fileManager.getOpenDataflows().isEmpty()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ Component parentComponent = null;
+ if (ev.getSource() instanceof Component)
+ parentComponent = (Component) ev.getSource();
+ saveAllDataflows(parentComponent);
+ }
+
+ public void saveAllDataflows(Component parentComponent) {
+ // Save in reverse so we save nested workflows first
+ List<WorkflowBundle> workflowBundles = fileManager.getOpenDataflows();
+ Collections.reverse(workflowBundles);
+
+ for (WorkflowBundle workflowBundle : workflowBundles)
+ if (!saveWorkflowAction.saveDataflow(parentComponent,
+ workflowBundle))
+ break;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java
new file mode 100644
index 0000000..9776550
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class SaveWorkflowAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(SaveWorkflowAction.class);
+ private static final String SAVE_WORKFLOW = "Save workflow";
+
+ private final SaveWorkflowAsAction saveWorkflowAsAction;
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ private FileManager fileManager;
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+ public SaveWorkflowAction(EditManager editManager, FileManager fileManager) {
+ super(SAVE_WORKFLOW, saveIcon);
+ this.fileManager = fileManager;
+ saveWorkflowAsAction = new SaveWorkflowAsAction(fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_S, getDefaultToolkit().getMenuShortcutKeyMask()));
+ putValue(MNEMONIC_KEY, VK_S);
+ editManager.addObserver(editManagerObserver);
+ fileManager.addObserver(fileManagerObserver);
+ updateEnabledStatus(fileManager.getCurrentDataflow());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ Component parentComponent = null;
+ if (ev.getSource() instanceof Component)
+ parentComponent = (Component) ev.getSource();
+ saveCurrentDataflow(parentComponent);
+ }
+
+ public boolean saveCurrentDataflow(Component parentComponent) {
+ WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+ return saveDataflow(parentComponent, workflowBundle);
+ }
+
+ public boolean saveDataflow(Component parentComponent,
+ WorkflowBundle workflowBundle) {
+ if (!fileManager.canSaveWithoutDestination(workflowBundle))
+ return saveWorkflowAsAction.saveDataflow(parentComponent,
+ workflowBundle);
+
+ try {
+ try {
+ fileManager.saveDataflow(workflowBundle, true);
+ Object workflowBundleSource = fileManager
+ .getDataflowSource(workflowBundle);
+ logger.info("Saved workflow " + workflowBundle + " to "
+ + workflowBundleSource);
+ return true;
+ } catch (OverwriteException ex) {
+ Object workflowBundleSource = fileManager
+ .getDataflowSource(workflowBundle);
+ logger.info("Workflow was changed on source: "
+ + workflowBundleSource);
+ fileManager.setCurrentDataflow(workflowBundle);
+ String msg = "Workflow destination " + workflowBundleSource
+ + " has been changed from elsewhere, "
+ + "are you sure you want to overwrite?";
+ int ret = showConfirmDialog(parentComponent, msg,
+ "Workflow changed", YES_NO_CANCEL_OPTION);
+ if (ret == YES_OPTION) {
+ fileManager.saveDataflow(workflowBundle, false);
+ logger.info("Saved workflow " + workflowBundle
+ + " by overwriting " + workflowBundleSource);
+ return true;
+ } else if (ret == NO_OPTION) {
+ // Pop up Save As instead to choose another name
+ return saveWorkflowAsAction.saveDataflow(parentComponent,
+ workflowBundle);
+ } else {
+ logger.info("Aborted overwrite of " + workflowBundleSource);
+ return false;
+ }
+ }
+ } catch (SaveException ex) {
+ logger.warn("Could not save workflow " + workflowBundle, ex);
+ showMessageDialog(parentComponent, "Could not save workflow: \n\n"
+ + ex.getMessage(), "Warning", WARNING_MESSAGE);
+ return false;
+ } catch (RuntimeException ex) {
+ logger.warn("Could not save workflow " + workflowBundle, ex);
+ showMessageDialog(parentComponent, "Could not save workflow: \n\n"
+ + ex.getMessage(), "Warning", WARNING_MESSAGE);
+ return false;
+ }
+ }
+
+ protected void updateEnabledStatus(WorkflowBundle workflowBundle) {
+ setEnabled(workflowBundle != null
+ && fileManager.isDataflowChanged(workflowBundle));
+ }
+
+ private final class EditManagerObserver implements
+ Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent) {
+ WorkflowBundle workflowBundle = ((AbstractDataflowEditEvent) message)
+ .getDataFlow();
+ if (workflowBundle == fileManager.getCurrentDataflow())
+ updateEnabledStatus(workflowBundle);
+ }
+ }
+ }
+
+ private final class FileManagerObserver implements
+ Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof SavedDataflowEvent)
+ updateEnabledStatus(((SavedDataflowEvent) message)
+ .getDataflow());
+ else if (message instanceof SetCurrentDataflowEvent)
+ updateEnabledStatus(((SetCurrentDataflowEvent) message)
+ .getDataflow());
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java
new file mode 100644
index 0000000..1872d5d
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * 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.file.impl.actions;
+
+import static java.awt.event.KeyEvent.VK_F6;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAsIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+@SuppressWarnings("serial")
+public class SaveWorkflowAsAction extends AbstractAction {
+ private static final String SAVE_WORKFLOW_AS = "Save workflow as...";
+ private static final String PREF_CURRENT_DIR = "currentDir";
+ private static Logger logger = Logger.getLogger(SaveWorkflowAsAction.class);
+ private FileManager fileManager;
+
+ public SaveWorkflowAsAction(FileManager fileManager) {
+ super(SAVE_WORKFLOW_AS, saveAsIcon);
+ this.fileManager = fileManager;
+ fileManager.addObserver(new FileManagerObserver());
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F6, 0));
+ putValue(MNEMONIC_KEY, VK_S);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Component parentComponent = null;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+ if (workflowBundle == null) {
+ showMessageDialog(parentComponent, "No workflow open yet",
+ "No workflow to save", ERROR_MESSAGE);
+ return;
+ }
+ saveCurrentDataflow(parentComponent);
+ }
+
+ public boolean saveCurrentDataflow(Component parentComponent) {
+ WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+ return saveDataflow(parentComponent, workflowBundle);
+ }
+
+ private String determineFileName(final WorkflowBundle workflowBundle) {
+ String result;
+ Object source = fileManager.getDataflowSource(workflowBundle);
+ String fileName = null;
+ if (source instanceof File)
+ fileName = ((File) source).getName();
+ else if (source instanceof URL)
+ fileName = ((URL) source).getPath();
+
+ if (fileName != null) {
+ int lastIndex = fileName.lastIndexOf(".");
+ if (lastIndex > 0)
+ fileName = fileName.substring(0, fileName.lastIndexOf("."));
+ result = fileName;
+ } else {
+ Workflow mainWorkflow = workflowBundle.getMainWorkflow();
+ if (mainWorkflow != null)
+ result = mainWorkflow.getName();
+ else
+ result = workflowBundle.getName();
+ }
+ return result;
+ }
+
+ public boolean saveDataflow(Component parentComponent, WorkflowBundle workflowBundle) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs
+ .get(PREF_CURRENT_DIR, System.getProperty("user.home"));
+ fileChooser.setDialogTitle(SAVE_WORKFLOW_AS);
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+
+ List<FileFilter> fileFilters = fileManager
+ .getSaveFileFilters(File.class);
+ if (fileFilters.isEmpty()) {
+ logger.warn("No file types found for saving workflow "
+ + workflowBundle);
+ showMessageDialog(parentComponent,
+ "No file types found for saving workflow.", "Error",
+ ERROR_MESSAGE);
+ return false;
+ }
+ for (FileFilter fileFilter : fileFilters)
+ fileChooser.addChoosableFileFilter(fileFilter);
+ fileChooser.setFileFilter(fileFilters.get(0));
+ fileChooser.setCurrentDirectory(new File(curDir));
+
+ File possibleName = new File(determineFileName(workflowBundle));
+ boolean tryAgain = true;
+ while (tryAgain) {
+ tryAgain = false;
+ fileChooser.setSelectedFile(possibleName);
+ int returnVal = fileChooser.showSaveDialog(parentComponent);
+ if (returnVal == APPROVE_OPTION) {
+ prefs.put(PREF_CURRENT_DIR, fileChooser.getCurrentDirectory()
+ .toString());
+ File file = fileChooser.getSelectedFile();
+ FileTypeFileFilter fileFilter = (FileTypeFileFilter) fileChooser
+ .getFileFilter();
+ FileType fileType = fileFilter.getFileType();
+ String extension = "." + fileType.getExtension();
+ if (!file.getName().toLowerCase().endsWith(extension)) {
+ String newName = file.getName() + extension;
+ file = new File(file.getParentFile(), newName);
+ }
+
+ // TODO: Open in separate thread to avoid hanging UI
+ try {
+ try {
+ fileManager.saveDataflow(workflowBundle, fileType,
+ file, true);
+ logger.info("Saved workflow " + workflowBundle + " to "
+ + file);
+ return true;
+ } catch (OverwriteException ex) {
+ logger.info("File already exists: " + file);
+ String msg = "Are you sure you want to overwrite existing file "
+ + file + "?";
+ int ret = showConfirmDialog(parentComponent, msg,
+ "File already exists", YES_NO_CANCEL_OPTION);
+ if (ret == YES_OPTION) {
+ fileManager.saveDataflow(workflowBundle, fileType,
+ file, false);
+ logger.info("Saved workflow " + workflowBundle
+ + " by overwriting " + file);
+ return true;
+ } else if (ret == NO_OPTION) {
+ tryAgain = true;
+ continue;
+ } else {
+ logger.info("Aborted overwrite of " + file);
+ return false;
+ }
+ }
+ } catch (SaveException ex) {
+ logger.warn("Could not save workflow to " + file, ex);
+ showMessageDialog(parentComponent,
+ "Could not save workflow to " + file + ": \n\n"
+ + ex.getMessage(), "Warning",
+ WARNING_MESSAGE);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected void updateEnabledStatus(WorkflowBundle workflowBundle) {
+ setEnabled(workflowBundle != null);
+ }
+
+ private final class FileManagerObserver implements Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof SetCurrentDataflowEvent)
+ updateEnabledStatus(((SetCurrentDataflowEvent) message)
+ .getDataflow());
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
new file mode 100644
index 0000000..6c0be19
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2010 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.file.impl.hooks;
+
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseAllWorkflowsAction;
+
+/**
+ * Close open workflows (and ask the user if she wants to save changes) on
+ * shutdown.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class CloseWorkflowsOnShutdown implements ShutdownSPI {
+ private CloseAllWorkflowsAction closeAllWorkflowsAction;
+
+ public CloseWorkflowsOnShutdown(EditManager editManager,
+ FileManager fileManager) {
+ closeAllWorkflowsAction = new CloseAllWorkflowsAction(editManager,
+ fileManager);
+ }
+
+ @Override
+ public int positionHint() {
+ /*
+ * Quite early, we don't want to do various clean-up in case the user
+ * clicks Cancel
+ */
+ return 50;
+ }
+
+ @Override
+ public boolean shutdown() {
+ return closeAllWorkflowsAction.closeAllWorkflows(null);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java
new file mode 100644
index 0000000..e8e5252
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseAllWorkflowsAction;
+
+public class FileCloseAllMenuAction extends AbstractMenuAction {
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileCloseAll");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileCloseAllMenuAction(EditManager editManager,
+ FileManager fileManager) {
+ super(FILE_URI, 39, FILE_CLOSE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CloseAllWorkflowsAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java
new file mode 100644
index 0000000..a97219f
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseWorkflowAction;
+
+public class FileCloseMenuAction extends AbstractMenuAction {
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileClose");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileCloseMenuAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_URI, 30, FILE_CLOSE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CloseWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java
new file mode 100644
index 0000000..3a48e0d
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.NewWorkflowAction;
+
+public class FileNewMenuAction extends AbstractMenuAction {
+ private static final URI FILE_NEW_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileNew");
+ private final FileManager fileManager;
+
+ public FileNewMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 10, FILE_NEW_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new NewWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java
new file mode 100644
index 0000000..9af1d6b
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowFromURLAction;
+
+public class FileOpenFromURLMenuAction extends AbstractMenuAction {
+
+ private static final URI FILE_OPEN_FROM_URL_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenURL");
+ private final FileManager fileManager;
+
+ public FileOpenFromURLMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 30, FILE_OPEN_FROM_URL_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowFromURLAction(null, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java
new file mode 100644
index 0000000..4ee4e39
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowAction;
+
+public class FileOpenMenuAction extends AbstractMenuAction {
+ private static final URI FILE_OPEN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpen");
+ private final FileManager fileManager;
+
+ public FileOpenMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 20, FILE_OPEN_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java
new file mode 100644
index 0000000..46ef476
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileOpenMenuSection extends AbstractMenuSection {
+ public static final URI FILE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ public static final URI FILE_OPEN_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenSection");
+
+ public FileOpenMenuSection() {
+ super(FILE_URI, 20, FILE_OPEN_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java
new file mode 100644
index 0000000..76ef759
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java
@@ -0,0 +1,418 @@
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_0;
+import static java.awt.event.KeyEvent.VK_R;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static javax.swing.Action.NAME;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.AbstractDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class FileOpenRecentMenuAction extends AbstractMenuCustom implements
+ Observer<FileManagerEvent> {
+ public static final URI RECENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenRecent");
+ private static final String CONF = "conf";
+ private static Logger logger = Logger
+ .getLogger(FileOpenRecentMenuAction.class);
+ private static final String RECENT_WORKFLOWS_XML = "recentWorkflows.xml";
+ private static final int MAX_ITEMS = 10;
+
+ private FileManager fileManager;
+ private ApplicationConfiguration applicationConfiguration;
+ private JMenu menu;
+ private List<Recent> recents = new ArrayList<>();
+ private Thread loadRecentThread;
+
+ public FileOpenRecentMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 30, RECENT_URI);
+ this.fileManager = fileManager;
+ fileManager.addObserver(this);
+ }
+
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ FileManager fileManager = (FileManager) sender;
+ if (message instanceof OpenedDataflowEvent
+ || message instanceof SavedDataflowEvent) {
+ AbstractDataflowEvent dataflowEvent = (AbstractDataflowEvent) message;
+ WorkflowBundle dataflow = dataflowEvent.getDataflow();
+ Object dataflowSource = fileManager.getDataflowSource(dataflow);
+ FileType dataflowType = fileManager.getDataflowType(dataflow);
+ addRecent(dataflowSource, dataflowType);
+ }
+ if (message instanceof ClosedDataflowEvent)
+ // Make sure enabled/disabled status is correct
+ updateRecentMenu();
+ }
+
+ public void updateRecentMenu() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ updateRecentMenuGUI();
+ }
+ });
+ saveRecent();
+ }
+
+ protected void addRecent(Object dataflowSource, FileType dataflowType) {
+ if (dataflowSource == null)
+ return;
+ if (!(dataflowSource instanceof Serializable)) {
+ logger.warn("Can't serialize workflow source for 'Recent workflows': "
+ + dataflowSource);
+ return;
+ }
+ synchronized (recents) {
+ Recent recent = new Recent((Serializable) dataflowSource, dataflowType);
+ if (recents.contains(recent))
+ recents.remove(recent);
+ recents.add(0, recent); // Add to front
+ }
+ updateRecentMenu();
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ action = new DummyAction("Recent workflows");
+ action.putValue(MNEMONIC_KEY, VK_R);
+ menu = new JMenu(action);
+ // Disabled until we have loaded the recent workflows
+ menu.setEnabled(false);
+ loadRecentThread = new Thread("Loading recent workflow menu") {
+ // Avoid hanging GUI initialization while deserialising
+ @Override
+ public void run() {
+ loadRecent();
+ updateRecentMenu();
+ }
+ };
+ loadRecentThread.start();
+ return menu;
+ }
+
+ protected synchronized void loadRecent() {
+ File confDir = new File(applicationConfiguration.getApplicationHomeDir(), CONF);
+ confDir.mkdir();
+ File recentFile = new File(confDir, RECENT_WORKFLOWS_XML);
+ if (!recentFile.isFile())
+ return;
+ try {
+ loadRecent(recentFile);
+ } catch (JDOMException|IOException e) {
+ logger.warn("Could not read recent workflows from file "
+ + recentFile, e);
+ }
+ }
+
+ private void loadRecent(File recentFile) throws FileNotFoundException,
+ IOException, JDOMException {
+ SAXBuilder builder = new SAXBuilder();
+ @SuppressWarnings("unused")
+ Document document;
+ try (InputStream fileInputStream = new BufferedInputStream(
+ new FileInputStream(recentFile))) {
+ document = builder.build(fileInputStream);
+ }
+ synchronized (recents) {
+ recents.clear();
+ //RecentDeserializer deserialiser = new RecentDeserializer();
+ try {
+ // recents.addAll(deserialiser.deserializeRecent(document
+ // .getRootElement()));
+ } catch (Exception e) {
+ logger.warn("Could not read recent workflows from file "
+ + recentFile, e);
+ }
+ }
+ }
+
+ protected synchronized void saveRecent() {
+ File confDir = new File(applicationConfiguration.getApplicationHomeDir(), CONF);
+ confDir.mkdir();
+ File recentFile = new File(confDir, RECENT_WORKFLOWS_XML);
+
+ try {
+ saveRecent(recentFile);
+// } catch (JDOMException e) {
+// logger.warn("Could not generate XML for recent workflows to file "
+// + recentFile, e);
+ } catch (IOException e) {
+ logger.warn("Could not write recent workflows to file "
+ + recentFile, e);
+ }
+ }
+
+ private void saveRecent(File recentFile) throws FileNotFoundException,
+ IOException {
+ // RecentSerializer serializer = new RecentSerializer();
+ // XMLOutputter outputter = new XMLOutputter();
+
+ // Element serializedRecent;
+ synchronized (recents) {
+ if (recents.size() > MAX_ITEMS)
+ // Remove excess entries
+ recents.subList(MAX_ITEMS, recents.size()).clear();
+ // serializedRecent = serializer.serializeRecent(recents);
+ }
+ try (OutputStream outputStream = new BufferedOutputStream(
+ new FileOutputStream(recentFile))) {
+ // outputter.output(serializedRecent, outputStream);
+ }
+ }
+
+ protected void updateRecentMenuGUI() {
+ int items = 0;
+ menu.removeAll();
+ synchronized (recents) {
+ for (Recent recent : recents) {
+ if (++items >= MAX_ITEMS)
+ break;
+ OpenRecentAction openRecentAction = new OpenRecentAction(
+ recent, fileManager);
+ if (fileManager.getDataflowBySource(recent.getDataflowSource()) != null)
+ openRecentAction.setEnabled(false);
+ // else setEnabled(true)
+ JMenuItem menuItem = new JMenuItem(openRecentAction);
+ if (items < 10) {
+ openRecentAction.putValue(NAME, items + " "
+ + openRecentAction.getValue(NAME));
+ menuItem.setMnemonic(VK_0 + items);
+ }
+ menu.add(menuItem);
+ }
+ }
+ menu.setEnabled(items > 0);
+ menu.revalidate();
+ }
+
+ @SuppressWarnings("serial")
+ public static class OpenRecentAction extends AbstractAction implements
+ Runnable {
+ private final Recent recent;
+ private Component component = null;
+ private final FileManager fileManager;
+
+ public OpenRecentAction(Recent recent, FileManager fileManager) {
+ this.recent = recent;
+ this.fileManager = fileManager;
+ Serializable source = recent.getDataflowSource();
+ String name;
+ if (source instanceof File)
+ name = ((File) source).getAbsolutePath();
+ else
+ name = source.toString();
+ this.putValue(NAME, name);
+ this.putValue(SHORT_DESCRIPTION, "Open the workflow " + name);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ component = null;
+ if (e.getSource() instanceof Component)
+ component = (Component) e.getSource();
+ setEnabled(false);
+ new Thread(this, "Opening workflow from "
+ + recent.getDataflowSource()).start();
+ }
+
+ /**
+ * Opening workflow in separate thread
+ */
+ @Override
+ public void run() {
+ final Serializable source = recent.getDataflowSource();
+ try {
+ fileManager.openDataflow(recent.makefileType(), source);
+ } catch (OpenException ex) {
+ logger.warn("Failed to open the workflow from " + source
+ + " \n", ex);
+ showMessageDialog(component,
+ "Failed to open the workflow from url " + source
+ + " \n" + ex.getMessage(), "Error!",
+ ERROR_MESSAGE);
+ } finally {
+ setEnabled(true);
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class Recent implements Serializable {
+ private final class RecentFileType extends FileType {
+ @Override
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ @Override
+ public String getExtension() {
+ return extension;
+ }
+
+ @Override
+ public String getDescription() {
+ return "File type " + extension + " " + mimeType;
+ }
+ }
+
+ private Serializable dataflowSource;
+ private String mimeType;
+ private String extension;
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public void setExtension(String extension) {
+ this.extension = extension;
+ }
+
+ public Recent() {
+ }
+
+ public FileType makefileType() {
+ if (mimeType == null && extension == null)
+ return null;
+ return new RecentFileType();
+ }
+
+ public Recent(Serializable dataflowSource, FileType dataflowType) {
+ setDataflowSource(dataflowSource);
+ if (dataflowType != null) {
+ setMimeType(dataflowType.getMimeType());
+ setExtension(dataflowType.getExtension());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime
+ * result
+ + ((dataflowSource == null) ? 0 : dataflowSource.hashCode());
+ result = prime * result
+ + ((extension == null) ? 0 : extension.hashCode());
+ result = prime * result
+ + ((mimeType == null) ? 0 : mimeType.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof Recent))
+ return false;
+ Recent other = (Recent) obj;
+
+ if (dataflowSource == null) {
+ if (other.dataflowSource != null)
+ return false;
+ } else if (!dataflowSource.equals(other.dataflowSource))
+ return false;
+
+ if (extension == null) {
+ if (other.extension != null)
+ return false;
+ } else if (!extension.equals(other.extension))
+ return false;
+
+ if (mimeType == null) {
+ if (other.mimeType != null)
+ return false;
+ } else if (!mimeType.equals(other.mimeType))
+ return false;
+
+ return true;
+ }
+
+ public Serializable getDataflowSource() {
+ return dataflowSource;
+ }
+
+ public void setDataflowSource(Serializable dataflowSource) {
+ this.dataflowSource = dataflowSource;
+ }
+
+ @Override
+ public String toString() {
+ return getDataflowSource() + "";
+ }
+ }
+
+ // TODO find new serialization
+// protected static class RecentDeserializer extends AbstractXMLDeserializer {
+// public Collection<Recent> deserializeRecent(Element el) {
+// return (Collection<Recent>) super.createBean(el, getClass()
+// .getClassLoader());
+// }
+// }
+//
+// protected static class RecentSerializer extends AbstractXMLSerializer {
+// public Element serializeRecent(List<Recent> x) throws JDOMException,
+// IOException {
+// Element beanAsElement = super.beanAsElement(x);
+// return beanAsElement;
+// }
+// }
+
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java
new file mode 100644
index 0000000..86edacb
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveAllWorkflowsAction;
+
+public class FileSaveAllMenuAction extends AbstractMenuAction {
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileSaveAllMenuAction(EditManager editManager,
+ FileManager fileManager) {
+ super(FILE_SAVE_SECTION_URI, 30);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveAllWorkflowsAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java
new file mode 100644
index 0000000..77917c9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAsAction;
+
+public class FileSaveAsMenuAction extends AbstractMenuAction {
+ private final FileManager fileManager;
+
+ public FileSaveAsMenuAction(FileManager fileManager) {
+ super(FILE_SAVE_SECTION_URI, 20);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveWorkflowAsAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java
new file mode 100644
index 0000000..eeaecb3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAction;
+
+public class FileSaveMenuAction extends AbstractMenuAction {
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileSaveMenuAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_SAVE_SECTION_URI, 10);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java
new file mode 100644
index 0000000..a75a855
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileSaveMenuSection extends AbstractMenuSection {
+ public static final URI FILE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ public static final URI FILE_SAVE_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileSaveSection");
+
+ public FileSaveMenuSection() {
+ super(FILE_URI, 40, FILE_SAVE_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java
new file mode 100644
index 0000000..e056572
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * 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.file.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_0;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.AbstractDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class WorkflowsMenu extends AbstractMenuCustom {
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ private FileManager fileManager;
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+ private JMenu workflowsMenu;
+
+ public WorkflowsMenu(EditManager editManager, FileManager fileManager) {
+ super(DEFAULT_MENU_BAR, 900);
+ this.fileManager = fileManager;
+ fileManager.addObserver(fileManagerObserver);
+ editManager.addObserver(editManagerObserver);
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ DummyAction action = new DummyAction("Workflows");
+ action.putValue(MNEMONIC_KEY, VK_W);
+
+ workflowsMenu = new JMenu(action);
+
+ updateWorkflowsMenu();
+ return workflowsMenu;
+ }
+
+ public void updateWorkflowsMenu() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ updateWorkflowsMenuUI();
+ }
+ });
+ }
+
+ protected void updateWorkflowsMenuUI() {
+ workflowsMenu.setEnabled(false);
+ workflowsMenu.removeAll();
+ ButtonGroup workflowsGroup = new ButtonGroup();
+
+ int i = 0;
+ WorkflowBundle currentDataflow = fileManager.getCurrentDataflow();
+ for (WorkflowBundle workflowBundle : fileManager.getOpenDataflows()) {
+ String name = fileManager.getDataflowName(workflowBundle);
+ if (fileManager.isDataflowChanged(workflowBundle))
+ name = "*" + name;
+ // A counter
+ name = ++i + " " + name;
+
+ SwitchWorkflowAction switchWorkflowAction = new SwitchWorkflowAction(
+ name, workflowBundle);
+ if (i < 10)
+ switchWorkflowAction.putValue(MNEMONIC_KEY, new Integer(VK_0
+ + i));
+
+ JRadioButtonMenuItem switchWorkflowMenuItem = new JRadioButtonMenuItem(
+ switchWorkflowAction);
+ workflowsGroup.add(switchWorkflowMenuItem);
+ if (workflowBundle.equals(currentDataflow))
+ switchWorkflowMenuItem.setSelected(true);
+ workflowsMenu.add(switchWorkflowMenuItem);
+ }
+ if (i == 0)
+ workflowsMenu.add(new NoWorkflowsOpen());
+ workflowsMenu.setEnabled(true);
+ workflowsMenu.revalidate();
+ }
+
+ private final class EditManagerObserver implements
+ Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent)
+ updateWorkflowsMenu();
+ }
+ }
+
+ private final class FileManagerObserver implements
+ Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEvent)
+ updateWorkflowsMenu();
+ // TODO: Don't rebuild whole menu
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private final class NoWorkflowsOpen extends AbstractAction {
+ private NoWorkflowsOpen() {
+ super("No workflows open");
+ setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // No-op
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private final class SwitchWorkflowAction extends AbstractAction {
+ private final WorkflowBundle workflowBundle;
+
+ private SwitchWorkflowAction(String name, WorkflowBundle workflowBundle) {
+ super(name);
+ this.workflowBundle = workflowBundle;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java
new file mode 100644
index 0000000..68ef3f9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseWorkflowAction;
+
+/**
+ * Action to close the current workflow.
+ *
+ * @author Alex Nenadic
+ */
+public class CloseToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarClose");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public CloseToolbarAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 30, FILE_CLOSE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CloseWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java
new file mode 100644
index 0000000..257d590
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.file.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileToolbarMenuSection extends AbstractMenuSection {
+ public static final URI FILE_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarSection");
+
+ public FileToolbarMenuSection() {
+ super(DEFAULT_TOOL_BAR, 10, FILE_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java
new file mode 100644
index 0000000..2c8e922
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.NewWorkflowAction;
+
+public class NewToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_NEW_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarNew");
+ private final FileManager fileManager;
+
+ public NewToolbarAction(FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 10, FILE_NEW_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new NewWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java
new file mode 100644
index 0000000..ae99509
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowAction;
+
+public class OpenToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_OPEN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarOpen");
+ private final FileManager fileManager;
+
+ public OpenToolbarAction(FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 20, FILE_OPEN_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java
new file mode 100644
index 0000000..2554063
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowFromURLAction;
+
+public class OpenWorkflowFromURLToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_OPEN_FROM_URL_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarOpenFromURL");
+ private final FileManager fileManager;
+
+ public OpenWorkflowFromURLToolbarAction(FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 25, FILE_OPEN_FROM_URL_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowFromURLAction(null, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java
new file mode 100644
index 0000000..53ba720
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAction;
+
+public class SaveToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_SAVE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarSave");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public SaveToolbarAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 40, FILE_SAVE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..100915c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,20 @@
+net.sf.taverna.t2.workbench.file.impl.menu.FileCloseMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileNewMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenFromURLMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenRecentMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAllMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAsMenuAction
+
+net.sf.taverna.t2.workbench.file.impl.menu.WorkflowsMenu
+net.sf.taverna.t2.workbench.file.impl.menu.FileCloseAllMenuAction
+
+net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection
+net.sf.taverna.t2.workbench.file.impl.toolbar.NewToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.OpenToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.OpenWorkflowFromURLToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.SaveToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.CloseToolbarAction
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..cc53d36
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.file.impl.hooks.CloseWorkflowsOnShutdown
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
new file mode 100644
index 0000000..cfd1c7a
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener
+net.sf.taverna.t2.workbench.file.impl.DataflowFromDataflowPersistenceHandler
\ No newline at end of file
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager
new file mode 100644
index 0000000..656feeb
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.file.impl.FileManagerImpl
\ No newline at end of file
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml
new file mode 100644
index 0000000..7c6e290
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="FileCloseMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.close" />
+ </service-properties>
+ </service>
+ <service ref="FileNewMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.new" />
+ </service-properties>
+ </service>
+ <service ref="FileOpenMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.open" />
+ </service-properties>
+ </service>
+ <service ref="FileOpenFromURLMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.open.url" />
+ </service-properties>
+ </service>
+ <service ref="FileOpenMenuSection" auto-export="interfaces" />
+ <service ref="FileOpenRecentMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.open.recent" />
+ </service-properties>
+ </service>
+ <service ref="FileSaveMenuSection" auto-export="interfaces" />
+ <service ref="FileSaveMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.save" />
+ </service-properties>
+ </service>
+ <service ref="FileSaveAllMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.save.all" />
+ </service-properties>
+ </service>
+ <service ref="FileSaveAsMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.save.as" />
+ </service-properties>
+ </service>
+ <service ref="WorkflowsMenu" auto-export="interfaces" />
+ <service ref="FileCloseAllMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.close.all" />
+ </service-properties>
+ </service>
+ <service ref="FileToolbarMenuSection" auto-export="interfaces" />
+ <service ref="NewToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.new" />
+ </service-properties>
+ </service>
+ <service ref="OpenToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.open" />
+ </service-properties>
+ </service>
+ <service ref="OpenWorkflowFromURLToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.open.url" />
+ </service-properties>
+ </service>
+ <service ref="SaveToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.save" />
+ </service-properties>
+ </service>
+ <service ref="CloseToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.close" />
+ </service-properties>
+ </service>
+
+ <service ref="T2DataflowOpener" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+
+ <service ref="WorkflowBundleOpener" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+ <service ref="WorkflowBundleSaver" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+
+ <service ref="CloseWorkflowsOnShutdown" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+ <service ref="FileManagerImpl" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+ <reference id="workflowBundleIO" interface="uk.org.taverna.scufl2.api.io.WorkflowBundleIO" />
+
+ <list id="dataflowPersistenceHandlers" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" cardinality="0..N">
+ <listener ref="DataflowPersistenceHandlerRegistry" bind-method="update" unbind-method="update" />
+ </list>
+</beans:beans>
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml
new file mode 100644
index 0000000..493df5f
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="FileCloseMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileCloseMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileNewMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileNewMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileOpenMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileOpenFromURLMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenFromURLMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileOpenMenuSection" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection" />
+ <bean id="FileOpenRecentMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenRecentMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ </bean>
+ <bean id="FileSaveMenuSection" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection" />
+ <bean id="FileSaveMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileSaveAllMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAllMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileSaveAsMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAsMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="WorkflowsMenu" class="net.sf.taverna.t2.workbench.file.impl.menu.WorkflowsMenu">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileCloseAllMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileCloseAllMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileToolbarMenuSection" class="net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection" />
+ <bean id="NewToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.NewToolbarAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="OpenToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.OpenToolbarAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="OpenWorkflowFromURLToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.OpenWorkflowFromURLToolbarAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="SaveToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.SaveToolbarAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="CloseToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.CloseToolbarAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="T2DataflowOpener" class="net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener">
+ <property name="workflowBundleIO" ref="workflowBundleIO"/>
+ </bean>
+
+ <bean id="WorkflowBundleOpener" class="net.sf.taverna.t2.workbench.file.impl.WorkflowBundleOpener">
+ <property name="workflowBundleIO" ref="workflowBundleIO"/>
+ </bean>
+ <bean id="WorkflowBundleSaver" class="net.sf.taverna.t2.workbench.file.impl.WorkflowBundleSaver">
+ <property name="workflowBundleIO" ref="workflowBundleIO"/>
+ </bean>
+
+ <bean id="CloseWorkflowsOnShutdown" class="net.sf.taverna.t2.workbench.file.impl.hooks.CloseWorkflowsOnShutdown">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="FileManagerImpl" class="net.sf.taverna.t2.workbench.file.impl.FileManagerImpl">
+ <constructor-arg name="editManager" ref="editManager" />
+ <property name="dataflowPersistenceHandlerRegistry">
+ <ref local="DataflowPersistenceHandlerRegistry"/>
+ </property>
+ </bean>
+
+ <bean id="DataflowPersistenceHandlerRegistry" class="net.sf.taverna.t2.workbench.file.impl.DataflowPersistenceHandlerRegistry">
+ <property name="dataflowPersistenceHandlers" ref="dataflowPersistenceHandlers" />
+ </bean>
+
+
+</beans>
diff --git a/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java b/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java
new file mode 100644
index 0000000..691b278
--- /dev/null
+++ b/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * 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.file.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleReader;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleWriter;
+import uk.org.taverna.scufl2.rdfxml.RDFXMLReader;
+import uk.org.taverna.scufl2.rdfxml.RDFXMLWriter;
+import uk.org.taverna.scufl2.translator.t2flow.T2FlowReader;
+
+public class FileManagerTest {
+
+ private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+ private static final T2FlowFileType T2_FLOW_FILE_TYPE = new T2FlowFileType();
+
+ private static final String DUMMY_WORKFLOW_T2FLOW = "dummy-workflow.t2flow";
+
+ private FileManagerImpl fileManager;
+ private EditManager editManager;
+
+ private FileManagerObserver fileManagerObserver= new FileManagerObserver();;
+
+ @Test
+ public void close() throws Exception {
+ assertTrue("Non-empty set of open dataflows", fileManager
+ .getOpenDataflows().isEmpty());
+ WorkflowBundle dataflow = openDataflow();
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(dataflow), fileManager.getOpenDataflows());
+ fileManager.closeDataflow(dataflow, true);
+ assertNotSame(dataflow, fileManager.getOpenDataflows().get(0));
+ assertTrue("Did not insert empty dataflow after close", fileManager
+ .getOpenDataflows().get(0).getMainWorkflow().getProcessors().isEmpty());
+ }
+
+ @Test
+ public void openRemovesEmptyDataflow() throws Exception {
+ WorkflowBundle newDataflow = fileManager.newDataflow();
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(newDataflow), fileManager.getOpenDataflows());
+ WorkflowBundle dataflow = openDataflow();
+ // Should have removed newDataflow
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(dataflow), fileManager.getOpenDataflows());
+ }
+
+ @Test
+ public void isChanged() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ assertFalse("Dataflow should not have changed", fileManager
+ .isDataflowChanged(dataflow));
+
+ // Do a change
+ Processor emptyProcessor = new Processor();
+ Edit<Workflow> addProcessorEdit = new AddProcessorEdit(dataflow.getMainWorkflow(),
+ emptyProcessor);
+ editManager.doDataflowEdit(dataflow, addProcessorEdit);
+ assertTrue("Dataflow should have changed", fileManager
+ .isDataflowChanged(dataflow));
+
+ // Save it with the change
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertFalse("Dataflow should no longer be marked as changed",
+ fileManager.isDataflowChanged(dataflow));
+ }
+
+ @Ignore("Undo support for ischanged not yet implemented")
+ @Test
+ public void isChangedWithUndo() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ // Do a change
+ Processor emptyProcessor = new Processor();
+ Edit<Workflow> addProcessorEdit = new AddProcessorEdit(dataflow.getMainWorkflow(),
+ emptyProcessor);
+ editManager.doDataflowEdit(dataflow, addProcessorEdit);
+ assertTrue("Dataflow should have changed", fileManager
+ .isDataflowChanged(dataflow));
+ editManager.undoDataflowEdit(dataflow);
+ assertFalse(
+ "Dataflow should no longer be marked as changed after undo",
+ fileManager.isDataflowChanged(dataflow));
+ editManager.redoDataflowEdit(dataflow);
+ assertTrue("Dataflow should have changed after redo before save",
+ fileManager.isDataflowChanged(dataflow));
+
+ // Save it with the change
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertFalse("Dataflow should no longer be marked as changed",
+ fileManager.isDataflowChanged(dataflow));
+
+ editManager.undoDataflowEdit(dataflow);
+ assertTrue("Dataflow should have changed after undo", fileManager
+ .isDataflowChanged(dataflow));
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ editManager.redoDataflowEdit(dataflow);
+ assertTrue("Dataflow should have changed after redo after save",
+ fileManager.isDataflowChanged(dataflow));
+ }
+
+ @Test
+ public void isListed() throws Exception {
+ assertTrue("Non-empty set of open data flows", fileManager
+ .getOpenDataflows().isEmpty());
+ WorkflowBundle dataflow = openDataflow();
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(dataflow), fileManager.getOpenDataflows());
+ }
+
+ /**
+ * Always uses a <strong>new</strong> file manager instead of the instance
+ * one from {@link FileManager#getInstance()}.
+ *
+ * @see #getFileManagerInstance()
+ *
+ */
+ @Before
+ public void makeFileManager() {
+ System.setProperty("java.awt.headless", "true");
+ editManager = new EditManagerImpl();
+ fileManager = new FileManagerImpl(editManager);
+ fileManagerObserver = new FileManagerObserver();
+ fileManager.addObserver(fileManagerObserver);
+ WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
+ workflowBundleIO.setReaders(Arrays.<WorkflowBundleReader>asList(new RDFXMLReader(), new T2FlowReader()));
+ workflowBundleIO.setWriters(Arrays.<WorkflowBundleWriter>asList(new RDFXMLWriter()));
+ T2DataflowOpener t2DataflowOpener = new T2DataflowOpener();
+ t2DataflowOpener.setWorkflowBundleIO(workflowBundleIO);
+ WorkflowBundleOpener workflowBundleOpener = new WorkflowBundleOpener();
+ workflowBundleOpener.setWorkflowBundleIO(workflowBundleIO);
+ WorkflowBundleSaver workflowBundleSaver = new WorkflowBundleSaver();
+ workflowBundleSaver.setWorkflowBundleIO(workflowBundleIO);
+ DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry = new DataflowPersistenceHandlerRegistry();
+ dataflowPersistenceHandlerRegistry.setDataflowPersistenceHandlers(Arrays.asList(
+ new DataflowPersistenceHandler[] {t2DataflowOpener, workflowBundleOpener, workflowBundleSaver}));
+ dataflowPersistenceHandlerRegistry.updateColletions();
+ fileManager.setDataflowPersistenceHandlerRegistry(dataflowPersistenceHandlerRegistry);
+ }
+
+ @Test
+ public void open() throws Exception {
+ assertTrue("ModelMapObserver already contained messages",
+ fileManagerObserver.messages.isEmpty());
+ WorkflowBundle dataflow = openDataflow();
+ assertNotNull("Dataflow was not loaded", dataflow);
+ assertEquals("Loaded dataflow was not set as current dataflow",
+ dataflow, fileManager.getCurrentDataflow());
+ assertFalse("ModelMapObserver did not contain message",
+ fileManagerObserver.messages.isEmpty());
+ assertEquals("ModelMapObserver contained unexpected messages", 2,
+ fileManagerObserver.messages.size());
+ FileManagerEvent event = fileManagerObserver.messages.get(0);
+ assertTrue(event instanceof SetCurrentDataflowEvent);
+ assertEquals(dataflow, ((SetCurrentDataflowEvent) event).getDataflow());
+ }
+
+ @Test
+ public void openSilently() throws Exception {
+ assertTrue("ModelMapObserver already contained messages",
+ fileManagerObserver.messages.isEmpty());
+ URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+ DataflowInfo info = fileManager.openDataflowSilently(T2_FLOW_FILE_TYPE, url);
+
+ WorkflowBundle dataflow = info.getDataflow();
+ assertNotNull("Dataflow was not loaded", dataflow);
+
+ assertNotSame("Loaded dataflow was set as current dataflow",
+ dataflow, fileManager.getCurrentDataflow());
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+ }
+
+ @Test
+ public void canSaveDataflow() throws Exception {
+ WorkflowBundle savedDataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ fileManager.saveDataflow(savedDataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ assertTrue(fileManager.canSaveWithoutDestination(savedDataflow));
+ fileManager.saveDataflow(savedDataflow, true);
+ fileManager.closeDataflow(savedDataflow, true);
+
+ WorkflowBundle otherFlow = fileManager.openDataflow(WF_BUNDLE_FILE_TYPE, dataflowFile.toURI()
+ .toURL());
+ assertTrue(fileManager.canSaveWithoutDestination(otherFlow));
+ }
+
+ @Test
+ public void save() throws Exception {
+ WorkflowBundle savedDataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ assertFalse("File should not exist", dataflowFile.isFile());
+ fileManager.saveDataflow(savedDataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertTrue("File should exist", dataflowFile.isFile());
+ WorkflowBundle loadedDataflow = fileManager.openDataflow(WF_BUNDLE_FILE_TYPE, dataflowFile.toURI()
+ .toURL());
+ assertNotSame("Dataflow was not reopened", savedDataflow,
+ loadedDataflow);
+ assertEquals("Unexpected number of processors in saved dataflow", 1,
+ savedDataflow.getMainWorkflow().getProcessors().size());
+ assertEquals("Unexpected number of processors in loaded dataflow", 1,
+ loadedDataflow.getMainWorkflow().getProcessors().size());
+
+ Processor savedProcessor = savedDataflow.getMainWorkflow().getProcessors().first();
+ Processor loadedProcessor = loadedDataflow.getMainWorkflow().getProcessors().first();
+ assertEquals("Loaded processor had wrong name", savedProcessor
+ .getName(), loadedProcessor.getName());
+
+ // TODO convert to scufl2
+// BeanshellActivity savedActivity = (BeanshellActivity) savedProcessor
+// .getActivityList().get(0);
+// BeanshellActivity loadedActivity = (BeanshellActivity) loadedProcessor
+// .getActivityList().get(0);
+// String savedScript = savedActivity.getConfiguration().getScript();
+// String loadedScript = loadedActivity.getConfiguration().getScript();
+// assertEquals("Unexpected saved script",
+// "String output = input + \"XXX\";", savedScript);
+// assertEquals("Loaded script did not matched saved script", savedScript,
+// loadedScript);
+ }
+
+ @Test
+ public void saveSilent() throws Exception {
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+
+ URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+ DataflowInfo info = fileManager.openDataflowSilently(T2_FLOW_FILE_TYPE, url);
+ WorkflowBundle dataflow = info.getDataflow();
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ assertFalse("File should not exist", dataflowFile.isFile());
+
+ fileManager.saveDataflowSilently(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertTrue("File should exist", dataflowFile.isFile());
+
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+
+ }
+
+ @Test
+ public void saveOverwriteAgain() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.delete();
+ dataflowFile.deleteOnExit();
+ // File did NOT exist, should not fail
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+
+ Processor processor = dataflow.getMainWorkflow().getProcessors().first();
+ Edit<Processor> renameEdit = new RenameEdit<Processor>(processor,
+ processor.getName() + "-changed");
+ editManager.doDataflowEdit(dataflow, renameEdit);
+
+ // Last save was OURs, so should *not* fail - even if we now use
+ // the specific saveDataflow() method
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+
+ //Thread.sleep(1500);
+ WorkflowBundle otherFlow = openDataflow();
+ // Saving another flow to same file should still fail
+ try {
+ fileManager.saveDataflow(otherFlow,WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ fail("Should have thrown OverwriteException");
+ } catch (OverwriteException ex) {
+ // Expected
+ }
+ }
+
+ @Test(expected = OverwriteException.class)
+ public void saveOverwriteWarningFails() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ // Should fail as file already exists
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ }
+
+ @Test
+ public void saveOverwriteWarningWorks() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.delete();
+ dataflowFile.deleteOnExit();
+ // File did NOT exist, should not fail
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ }
+
+ @After
+ public void stopListeningToModelMap() {
+ fileManager.removeObserver(fileManagerObserver);
+ }
+
+ protected WorkflowBundle openDataflow() throws OpenException {
+ URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+ assertNotNull(url);
+ WorkflowBundle dataflow = fileManager.openDataflow(T2_FLOW_FILE_TYPE, url);
+ assertNotNull(dataflow);
+ return dataflow;
+ }
+
+ private final class FileManagerObserver implements Observer<FileManagerEvent> {
+ protected List<FileManagerEvent> messages = new ArrayList<FileManagerEvent>();
+
+ @Override
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message) throws Exception {
+ messages.add(message);
+ if (message instanceof SetCurrentDataflowEvent) {
+ assertTrue("Dataflow was not listed as open when set current",
+ fileManager.getOpenDataflows().contains(
+ ((SetCurrentDataflowEvent) message).getDataflow()));
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow b/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow
new file mode 100644
index 0000000..b9a1075
--- /dev/null
+++ b/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow
@@ -0,0 +1,157 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow" version="1" producedBy="test">
+ <dataflow id="ec0991ba-275c-49ed-b1d6-38534180fb7c" role="top">
+ <name>simple_workflow_with_input</name>
+ <inputPorts>
+ <port>
+ <name>input</name>
+ <depth>0</depth>
+ <granularDepth>0</granularDepth>
+ </port>
+ </inputPorts>
+ <outputPorts>
+ <port>
+ <name>output</name>
+ </port>
+ </outputPorts>
+ <processors>
+ <processor>
+ <name>Concat_XXX</name>
+ <inputPorts>
+ <port>
+ <name>input</name>
+ <depth>0</depth>
+ </port>
+ </inputPorts>
+ <outputPorts>
+ <port>
+ <name>output</name>
+ <depth>0</depth>
+ <granularDepth>0</granularDepth>
+ </port>
+ </outputPorts>
+ <annotations />
+ <activities>
+ <activity>
+ <class>
+ net.sf.taverna.t2.activities.beanshell.BeanshellActivity
+ </class>
+ <inputMap>
+ <map from="input" to="input" />
+ </inputMap>
+ <outputMap>
+ <map from="output" to="output" />
+ </outputMap>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean
+ xmlns="">
+ <script>String output = input + "XXX";</script>
+ <dependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>input</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>0</granularDepth>
+ <name>output</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+ </net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean>
+ </configBean>
+ </activity>
+ </activities>
+ <dispatchStack>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize
+ </class>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig
+ xmlns="">
+ <maxJobs>1</maxJobs>
+ </net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig>
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry
+ </class>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig
+ xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+ </net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig>
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ </dispatchStack>
+ <iterationStrategyStack>
+ <iteration>
+ <strategy>
+ <port name="input" depth="0" />
+ </strategy>
+ </iteration>
+ </iterationStrategyStack>
+ </processor>
+ </processors>
+ <conditions />
+ <datalinks>
+ <datalink>
+ <sink type="processor">
+ <processor>Concat_XXX</processor>
+ <port>input</port>
+ </sink>
+ <source type="dataflow">
+ <port>input</port>
+ </source>
+ </datalink>
+ <datalink>
+ <sink type="dataflow">
+ <port>output</port>
+ </sink>
+ <source type="processor">
+ <processor>Concat_XXX</processor>
+ <port>output</port>
+ </source>
+ </datalink>
+ </datalinks>
+ </dataflow>
+</workflow>
diff --git a/taverna-workbench-graph-model/pom.xml b/taverna-workbench-graph-model/pom.xml
new file mode 100644
index 0000000..8e255ba
--- /dev/null
+++ b/taverna-workbench-graph-model/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-model</artifactId>
+ <packaging>bundle</packaging>
+ <name>Graph Model</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>2.6</version>
+ <executions>
+ <execution>
+ <id>javacc</id>
+ <goals>
+ <goal>jjtree-javacc</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>
+ org.codehaus.mojo
+ </groupId>
+ <artifactId>
+ javacc-maven-plugin
+ </artifactId>
+ <versionRange>
+ [2.6,)
+ </versionRange>
+ <goals>
+ <goal>jjtree-javacc</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <execute>
+ <runOnIncremental>false</runOnIncremental>
+ </execute>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>configuration-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>io</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.batik</groupId>
+ <artifactId>batik-osgi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>${commons.beanutils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/DefaultGraphEventManager.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/DefaultGraphEventManager.java
new file mode 100644
index 0000000..d434e48
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/DefaultGraphEventManager.java
@@ -0,0 +1,271 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static javax.swing.SwingUtilities.convertPointFromScreen;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.models.graph.GraphController.PortStyle.ALL;
+import static net.sf.taverna.t2.workbench.models.graph.GraphController.PortStyle.NONE;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * Manager for handling UI events on GraphElements.
+ *
+ * @author David Withers
+ */
+public class DefaultGraphEventManager implements GraphEventManager {
+ private static final URI NESTED_WORKFLOW_URI = URI
+ .create("http://ns.taverna.org.uk/2010/activity/nested-workflow");
+
+ private GraphController graphController;
+ private Component component;
+ private JPopupMenu menu;
+ private MenuManager menuManager;
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ /**
+ * Constructs a new instance of GraphEventManager.
+ *
+ * @param graphController
+ * @param component
+ * component to use when displaying popup menus
+ */
+ public DefaultGraphEventManager(GraphController graphController, Component component,
+ MenuManager menuManager) {
+ this.graphController = graphController;
+ this.component = component;
+ this.menuManager = menuManager;
+ }
+
+ @Override
+ public void mouseClicked(final GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, final int x, final int y, int screenX, int screenY) {
+ Object dataflowObject = graphElement.getWorkflowBean();
+
+ // For both left and right click - add to selection model
+ if (graphController.getDataflowSelectionModel() != null)
+ graphController.getDataflowSelectionModel().addSelection(dataflowObject);
+
+ if ((button != 2) && !ctrlKey)return;
+
+ // If this was a right click - show a pop-up as well
+ if (dataflowObject == null)
+ menu = menuManager.createContextMenu(graphController.getWorkflow(),
+ graphController.getWorkflow(), component);
+ else {
+ menu = menuManager.createContextMenu(graphController.getWorkflow(),
+ dataflowObject, component);
+ if (dataflowObject instanceof Processor) {
+ final Processor processor = (Processor) dataflowObject;
+ ProcessorBinding processorBinding = scufl2Tools
+ .processorBindingForProcessor(processor,
+ graphController.getProfile());
+ final Activity activity = processorBinding.getBoundActivity();
+ if (menu == null)
+ menu = new JPopupMenu();
+ if (graphElement instanceof GraphNode) {
+ defineMenuForGraphElement(graphElement, x, y, processor,
+ activity);
+ } else if (graphElement instanceof Graph) {
+ defineMenuForGraphBackground(activity);
+ }
+ }
+ }
+
+ if (menu != null) {
+ final Point p = new Point(screenX, screenY);
+ convertPointFromScreen(p, component);
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ menu.show(component, p.x, p.y);
+ }
+ });
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private void defineMenuForGraphBackground(final Activity activity) {
+ if (activity.getType().equals(NESTED_WORKFLOW_URI)) {
+ menu.addSeparator();
+ menu.add(new JMenuItem(new AbstractAction("Hide nested workflow") {
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ graphController.setExpandNestedDataflow(activity, false);
+ graphController.redraw();
+ }
+ }));
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private void defineMenuForGraphElement(final GraphElement graphElement,
+ final int x, final int y, final Processor processor,
+ final Activity activity) {
+ if (graphController.getPortStyle(processor).equals(NONE)) {
+ menu.addSeparator();
+ menu.add(new JMenuItem(new AbstractAction("Show ports") {
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ graphController.setPortStyle(processor, ALL);
+ graphController.redraw();
+ }
+ }));
+ } else if (graphController.getPortStyle(processor).equals(ALL)) {
+ menu.addSeparator();
+ menu.add(new JMenuItem(new AbstractAction("Hide ports") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setPortStyle(processor, NONE);
+ graphController.redraw();
+ }
+ }));
+ }
+
+ if (activity.getType().equals(NESTED_WORKFLOW_URI)) {
+ menu.addSeparator();
+ menu.add(new JMenuItem(new AbstractAction("Show nested workflow") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setExpandNestedDataflow(activity, true);
+ graphController.redraw();
+ }
+ }));
+ }
+
+ menu.addSeparator();
+
+ GraphNode graphNode = (GraphNode) graphElement;
+
+ List<GraphNode> sourceNodes = graphNode.getSourceNodes();
+ if (sourceNodes.size() == 1) {
+ final GraphNode sourceNode = sourceNodes.get(0);
+ if (sourceNode.getLabel() != null) {
+ menu.add(new JMenuItem(new AbstractAction("Link from output '"
+ + sourceNode.getLabel() + "'") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.startEdgeCreation(sourceNode,
+ new Point(x, y));
+ }
+ }));
+ }
+ } else if (sourceNodes.size() > 0) {
+ JMenu linkMenu = new JMenu("Link from output...");
+ menu.add(linkMenu);
+ for (final GraphNode sourceNode : sourceNodes) {
+ linkMenu.add(new JMenuItem(new AbstractAction(sourceNode
+ .getLabel()) {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.startEdgeCreation(sourceNode,
+ new Point(x, y));
+ }
+ }));
+ }
+ }
+
+ List<GraphNode> sinkNodes = graphNode.getSinkNodes();
+ if (sinkNodes.size() == 1) {
+ final GraphNode sinkNode = sinkNodes.get(0);
+ if (sinkNode.getLabel() != null) {
+ menu.add(new JMenuItem(new AbstractAction("Link to input '"
+ + sinkNode.getLabel() + "'") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.startEdgeCreation(sinkNode, new Point(
+ x, y));
+ }
+ }));
+ }
+ } else if (sinkNodes.size() > 0) {
+ JMenu linkMenu = new JMenu("Link to input...");
+ menu.add(linkMenu);
+ for (final GraphNode sinkNode : sinkNodes) {
+ linkMenu.add(new JMenuItem(new AbstractAction(sinkNode
+ .getLabel()) {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.startEdgeCreation(sinkNode, new Point(
+ x, y));
+ }
+ }));
+ }
+ }
+ }
+
+ @Override
+ public void mouseDown(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ if (button == 0)
+ graphController.startEdgeCreation(graphElement, new Point(x, y));
+ }
+
+ @Override
+ public void mouseUp(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, final int x,
+ final int y, int screenX, int screenY) {
+ if (button == 0)
+ graphController.stopEdgeCreation(graphElement, new Point(screenX,
+ screenY));
+ }
+
+ @Override
+ public void mouseMoved(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ graphController.moveEdgeCreationTarget(graphElement, new Point(x, y));
+ }
+
+ @Override
+ public void mouseOver(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ if (graphElement.getWorkflowBean() != null)
+ graphElement.setActive(true);
+ }
+
+ @Override
+ public void mouseOut(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ if (graphElement.getWorkflowBean() != null)
+ graphElement.setActive(false);
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/DotWriter.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/DotWriter.java
new file mode 100644
index 0000000..07cdbad
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/DotWriter.java
@@ -0,0 +1,253 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static java.lang.String.format;
+import static net.sf.taverna.t2.workbench.models.graph.Graph.Alignment.HORIZONTAL;
+import static net.sf.taverna.t2.workbench.models.graph.Graph.Alignment.VERTICAL;
+import static net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle.NONE;
+import static net.sf.taverna.t2.workbench.models.graph.GraphShapeElement.Shape.RECORD;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+
+/**
+ * Writer for creating a graphical representation of a Graph in the DOT language.
+ *
+ * @author David Withers
+ */
+public class DotWriter {
+ private static final String EOL = System.getProperty("line.separator");
+
+ private Writer writer;
+
+ /**
+ * Constructs a new instance of DotWriter.
+ *
+ * @param writer
+ */
+ public DotWriter(Writer writer) {
+ this.writer = writer;
+ }
+
+ /**
+ * Writes a graphical representation of a Graph in the DOT language to a Writer.
+ *
+ * @param graph
+ * @throws IOException
+ */
+ public void writeGraph(Graph graph) throws IOException {
+ writeLine("digraph \"" + graph.getId() + "\" {");
+
+ // Overall graph style
+ writeLine(" graph [");
+ writeLine(" bgcolor=\"" + getHexValue(graph.getFillColor()) + "\"");
+ writeLine(" color=\"black\"");
+ writeLine(" fontsize=\"10\"");
+ writeLine(" labeljust=\"left\"");
+ writeLine(" clusterrank=\"local\"");
+ writeLine(" ranksep=\"0.22\"");
+ writeLine(" nodesep=\"0.05\"");
+ // Set left to right view if alignment is horizontal
+ if (graph.getAlignment().equals(HORIZONTAL))
+ writeLine(" rankdir=\"LR\"");
+ writeLine(" ]");
+
+ // Overall node style
+ writeLine(" node [");
+ writeLine(" fontname=\"Helvetica\"");
+ writeLine(" fontsize=\"10\"");
+ writeLine(" fontcolor=\"black\"");
+ writeLine(" shape=\"record\"");
+ writeLine(" height=\"0\"");
+ writeLine(" width=\"0\"");
+ writeLine(" color=\"black\"");
+ writeLine(" fillcolor=\"lightgoldenrodyellow\"");
+ writeLine(" style=\"filled\"");
+ writeLine(" ];");
+
+ // Overall edge style
+ writeLine(" edge [");
+ writeLine(" fontname=\"Helvetica\"");
+ writeLine(" fontsize=\"8\"");
+ writeLine(" fontcolor=\"black\"");
+ writeLine(" color=\"black\"");
+ writeLine(" ];");
+
+ for (GraphNode node : graph.getNodes()) {
+ if (node.isExpanded())
+ writeSubGraph(node.getGraph(), " ");
+ else
+ writeNode(node, graph.getAlignment(), " ");
+ }
+
+ for (Graph subGraph : graph.getSubgraphs())
+ writeSubGraph(subGraph, " ");
+
+ for (GraphEdge edge : graph.getEdges())
+ writeEdges(edge, graph.getAlignment(), " ");
+
+ writeLine("}");
+ }
+
+ private void writeSubGraph(Graph graph, String indent) throws IOException {
+ writeLine(format("%ssubgraph \"cluster_%s\" {", indent, graph.getId()));
+ writeLine(format("%s rank=\"same\"", indent));
+
+ StringBuilder style = new StringBuilder();
+ if (graph.getFillColor() != null) {
+ writeLine(format("%s fillcolor=\"%s\"", indent,
+ getHexValue(graph.getFillColor())));
+ style.append("filled");
+ }
+ if (graph.getLineStyle() != null) {
+ style.append(style.length() == 0 ? "" : ",");
+ if (graph.getLineStyle().equals(NONE))
+ style.append("invis");
+ else
+ style.append(graph.getLineStyle().toString().toLowerCase());
+ }
+ writeLine(format("%s style=\"%s\"", indent, style));
+
+ if (graph.getLabel() != null)
+ writeLine(format("%s label=\"%s\"", indent, graph.getLabel()));
+
+ for(GraphNode node : graph.getNodes()) {
+ if (node.isExpanded())
+ writeSubGraph(node.getGraph(), indent + " ");
+ else
+ writeNode(node, graph.getAlignment(), indent + " ");
+ }
+
+ for (Graph subGraph : graph.getSubgraphs())
+ writeSubGraph(subGraph, indent + " ");
+
+ for (GraphEdge edge : graph.getEdges())
+ writeEdges(edge, graph.getAlignment(), indent + " ");
+
+ writeLine(indent + "}");
+ }
+
+ private void writeEdges(GraphEdge edge, Alignment alignment, String indent) throws IOException {
+ GraphNode source = edge.getSource();
+ GraphNode sink = edge.getSink();
+ String sourceId = "\"" + source.getId() + "\"";
+ String sinkId = "\"" + sink.getId() + "\"";
+
+ if (source.getParent() instanceof GraphNode) {
+ GraphNode parent = (GraphNode) source.getParent();
+ sourceId = "\"" + parent.getId() + "\":" + sourceId;
+ }
+ if (sink.getParent() instanceof GraphNode) {
+ GraphNode parent = (GraphNode) sink.getParent();
+ sinkId = "\"" + parent.getId() + "\":" + sinkId;
+ }
+ /*
+ * the compass point is required with newer versions of dot (e.g.
+ * 2.26.3) but is not compatible with older versions (e.g. 1.3)
+ */
+ if (alignment.equals(HORIZONTAL)) {
+ sourceId = sourceId + ":e";
+ sinkId = sinkId + ":w";
+ } else {
+ sourceId = sourceId + ":s";
+ sinkId = sinkId + ":n";
+ }
+ writeLine(format("%s%s -> %s [", indent, sourceId, sinkId));
+ writeLine(format("%s arrowhead=\"%s\"", indent, edge
+ .getArrowHeadStyle().toString().toLowerCase()));
+ writeLine(format("%s, arrowtail=\"%s\"", indent, edge
+ .getArrowTailStyle().toString().toLowerCase()));
+ if (edge.getColor() != null)
+ writeLine(format("%s color=\"%s\"", indent,
+ getHexValue(edge.getColor())));
+ writeLine(format("%s]", indent));
+ }
+
+ private void writeNode(GraphNode node, Alignment alignment, String indent) throws IOException {
+ writeLine(format("%s\"%s\" [", indent, node.getId()));
+
+ StringBuilder style = new StringBuilder();
+ if (node.getFillColor() != null) {
+ writeLine(format("%s fillcolor=\"%s\"", indent,
+ getHexValue(node.getFillColor())));
+ style.append("filled");
+ }
+ if (node.getLineStyle() != null) {
+ style.append(style.length() == 0 ? "" : ",");
+ style.append(node.getLineStyle().toString().toLowerCase());
+ }
+ writeLine(format("%s style=\"%s\"", indent, style));
+
+ writeLine(format("%s shape=\"%s\"", indent, node.getShape().toString().toLowerCase()));
+ writeLine(format("%s width=\"%s\"", indent, node.getWidth() / 72f));
+ writeLine(format("%s height=\"%s\"", indent, node.getHeight() / 72f));
+
+ if (node.getShape().equals(RECORD)) {
+ StringBuilder labelString = new StringBuilder();
+ if (alignment.equals(VERTICAL)) {
+ labelString.append("{{");
+ addNodeLabels(node.getSinkNodes(), labelString);
+ labelString.append("}|").append(node.getLabel()).append("|{");
+ addNodeLabels(node.getSourceNodes(), labelString);
+ labelString.append("}}");
+ } else {
+ labelString.append(node.getLabel()).append("|{{");
+ addNodeLabels(node.getSinkNodes(), labelString);
+ labelString.append("}|{");
+ addNodeLabels(node.getSourceNodes(), labelString);
+ labelString.append("}}");
+ }
+ writeLine(format("%s label=\"%s\"", indent, labelString));
+ } else {
+ writeLine(format("%s label=\"%s\"", indent, node.getLabel()));
+ }
+
+ writeLine(format("%s];", indent));
+ }
+
+ private void addNodeLabels(List<GraphNode> nodes, StringBuilder labelString) {
+ String sep = "";
+ for (GraphNode node : nodes)
+ if (node.getLabel() != null) {
+ labelString.append(sep);
+ labelString.append("<");
+ labelString.append(node.getId());
+ labelString.append(">");
+ labelString.append(node.getLabel());
+ sep = "|";
+ }
+ }
+
+ private String getHexValue(Color color) {
+ return format("#%02x%02x%02x", color.getRed(), color.getGreen(),
+ color.getBlue());
+ }
+
+ private void writeLine(String line) throws IOException {
+ writer.write(line);
+ writer.write(EOL);
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/Graph.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/Graph.java
new file mode 100644
index 0000000..0ff3852
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/Graph.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A graph model of a dataflow.
+ *
+ * @author David Withers
+ */
+public class Graph extends GraphShapeElement {
+ public enum Alignment {
+ HORIZONTAL, VERTICAL
+ }
+
+ private List<GraphNode> nodes = new ArrayList<>();
+ private Set<GraphEdge> edges = new HashSet<>();
+ private Set<Graph> subgraphs = new HashSet<>();
+ private Alignment alignment = Alignment.VERTICAL;
+
+ /**
+ * Constructs a Graph that uses the specified GraphEventManager to handle
+ * any user generated events on GraphElements.
+ *
+ * @param eventManager
+ */
+ public Graph(GraphController graphController) {
+ super(graphController);
+ }
+
+ /**
+ * Adds an edge to the Graph and sets its parent to be this Graph.
+ *
+ * @param edge
+ * the edge to add
+ */
+ public void addEdge(GraphEdge edge) {
+ edge.setParent(this);
+ edges.add(edge);
+ }
+
+ /**
+ * Adds a node to the Graph and sets its parent to be this Graph.
+ *
+ * @param node
+ * the node to add
+ */
+ public void addNode(GraphNode node) {
+ node.setParent(this);
+ nodes.add(node);
+ }
+
+ /**
+ * Adds a subgraph to the Graph and sets its parent to be this Graph.
+ *
+ * @param subgraph
+ * the subgraph to add
+ */
+ public void addSubgraph(Graph subgraph) {
+ subgraph.setParent(this);
+ subgraphs.add(subgraph);
+ }
+
+ /**
+ * Returns the alignment of the Graph.
+ *
+ * @return the alignment of the Graph
+ */
+ public Alignment getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Returns the edges contained in the Graph.
+ *
+ * @return the edges contained in the Graph
+ */
+ public Set<GraphEdge> getEdges() {
+ return Collections.unmodifiableSet(edges);
+ }
+
+ /**
+ * Returns the nodes contained in the Graph.
+ *
+ * @return the nodes contained in the Graph
+ */
+ public List<GraphNode> getNodes() {
+ return Collections.unmodifiableList(nodes);
+ }
+
+ /**
+ * Returns the subgraphs contained in the Graph.
+ *
+ * @return the subgraphs contained in the Graph
+ */
+ public Set<Graph> getSubgraphs() {
+ return Collections.unmodifiableSet(subgraphs);
+ }
+
+ /**
+ * Removes an edge from the Graph.
+ *
+ * @param edge
+ * the edge to remove
+ * @return true if the edge is removed from the Graph
+ */
+ public boolean removeEdge(GraphEdge edge) {
+ return edges.remove(edge);
+ }
+
+ /**
+ * Removes a node from the Graph.
+ *
+ * @param node
+ * the node to remove
+ * @return true if the node is removed from the Graph
+ */
+ public boolean removeNode(GraphNode node) {
+ return nodes.remove(node);
+ }
+
+ /**
+ * Removes a subgraph from the Graph.
+ *
+ * @param subgraph
+ * the subgraph to remove
+ * @return true if the subgraph is removed from the Graph
+ */
+ public boolean removeSubgraph(Graph subgraph) {
+ return subgraphs.remove(subgraph);
+ }
+
+ /**
+ * Sets the alignment of the Graph.
+ *
+ * @param alignment
+ * the new alignment
+ */
+ public void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphColorManager.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphColorManager.java
new file mode 100644
index 0000000..1f44076
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphColorManager.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import java.awt.Color;
+import java.lang.reflect.InvocationTargetException;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+/**
+ * Manages the colour of elements in a graph.
+ *
+ * @author David Withers
+ * @author Start Owen
+ */
+public class GraphColorManager {
+ private static final String BEANSHELL = "http://ns.taverna.org.uk/2010/activity/beanshell";
+ private static final String LOCALWORKER = "http://ns.taverna.org.uk/2010/activity/localworker";
+
+ private static Color[] subGraphFillColors = new Color[] {
+ Color.decode("#ffffff"), Color.decode("#f0f8ff"),
+ Color.decode("#faebd7"), Color.decode("#f5f5dc") };
+
+ /**
+ * Returns the colour associated with the Activity.
+ *
+ * For unknown activities Color.WHITE is returned.
+ *
+ * For {@link LocalworkerActivity} which have been user configured use the
+ * BeanshellActivity colour
+ *
+ * @return the colour associated with the Activity
+ */
+ public static Color getFillColor(Activity activity, ColourManager colourManager) {
+ try {
+ if (activity.getType().equals(LOCALWORKER)) {
+ // To avoid compile time dependency - read isAltered property as bean
+ if (Boolean.TRUE.equals(PropertyUtils.getProperty(activity, "altered"))) {
+ Color colour = colourManager.getPreferredColour(BEANSHELL);
+ return colour;
+ }
+ }
+ } catch (IllegalAccessException | InvocationTargetException
+ | NoSuchMethodException e) {
+ }
+ Color colour = colourManager.getPreferredColour(activity.getType().toASCIIString());
+ return colour;
+ }
+
+ public static Color getSubGraphFillColor(int depth) {
+ return subGraphFillColors[depth % subGraphFillColors.length];
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphController.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphController.java
new file mode 100644
index 0000000..0fb87a4
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphController.java
@@ -0,0 +1,1276 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static javax.swing.JOptionPane.PLAIN_MESSAGE;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.net.URI;
+import java.util.ArrayList;
+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 net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge.ArrowStyle;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle;
+import net.sf.taverna.t2.workbench.models.graph.GraphShapeElement.Shape;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workflow.edits.AddDataLinkEdit;
+import net.sf.taverna.t2.workflow.edits.RemoveDataLinkEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.Port;
+import uk.org.taverna.scufl2.api.port.ProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+import uk.org.taverna.scufl2.api.port.WorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * @author David Withers
+ */
+public abstract class GraphController implements
+ Observer<DataflowSelectionMessage> {
+ public enum PortStyle {
+ ALL {
+ @Override
+ Shape inputShape() {
+ return Shape.INVHOUSE;
+ }
+
+ @Override
+ Shape outputShape() {
+ return Shape.HOUSE;
+ }
+
+ @Override
+ Shape processorShape() {
+ return Shape.RECORD;
+ }
+ },
+ BOUND {
+ @Override
+ Shape inputShape() {
+ return Shape.INVHOUSE;
+ }
+
+ @Override
+ Shape outputShape() {
+ return Shape.HOUSE;
+ }
+
+ @Override
+ Shape processorShape() {
+ return Shape.RECORD;
+ }
+ },
+ NONE {
+ @Override
+ Shape inputShape() {
+ return Shape.BOX;
+ }
+
+ @Override
+ Shape outputShape() {
+ return Shape.BOX;
+ }
+
+ @Override
+ Shape processorShape() {
+ return Shape.BOX;
+ }
+ },
+ BLOB {
+ @Override
+ Shape inputShape() {
+ return Shape.CIRCLE;
+ }
+
+ @Override
+ Shape outputShape() {
+ return Shape.CIRCLE;
+ }
+
+ @Override
+ Shape processorShape() {
+ return Shape.CIRCLE;
+ }
+ };
+
+ abstract Shape inputShape();
+
+ abstract Shape outputShape();
+
+ abstract Shape processorShape();
+
+ Shape mergeShape() {
+ return Shape.CIRCLE;
+ }
+ }
+
+ private static Logger logger = Logger.getLogger(GraphController.class);
+
+ private Map<String, GraphElement> idToElement = new HashMap<>();
+ private Map<WorkflowBean, GraphElement> workflowToGraph = new HashMap<>();
+ private Map<Port, GraphNode> ports = new HashMap<>();
+ private Map<Graph, GraphNode> inputControls = new HashMap<>();
+ private Map<Graph, GraphNode> outputControls = new HashMap<>();
+ private Map<Port, Port> nestedWorkflowPorts = new HashMap<>();
+ private Map<WorkflowPort, ProcessorPort> workflowPortToProcessorPort = new HashMap<>();
+ private Map<Port, Processor> portToProcessor = new HashMap<>();
+
+ private EditManager editManager;
+ private final Workflow workflow;
+ private final Profile profile;
+ private DataflowSelectionModel dataflowSelectionModel;
+ private GraphEventManager graphEventManager;
+ private Component componentForPopups;
+
+ // graph settings
+ private PortStyle portStyle = PortStyle.NONE;
+ private Map<Processor, PortStyle> processorPortStyle = new HashMap<>();
+ private Alignment alignment = Alignment.VERTICAL;
+ private boolean expandNestedDataflows = true;
+ private Map<Activity, Boolean> dataflowExpansion = new HashMap<>();
+ protected Map<String, GraphElement> graphElementMap = new HashMap<>();
+ protected GraphElement edgeCreationSource, edgeCreationSink;
+ protected GraphEdge edgeMoveElement;
+ protected boolean edgeCreationFromSource = false;
+ protected boolean edgeCreationFromSink = false;
+ private Graph graph;
+ private boolean interactive;
+ private final ColourManager colourManager;
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public GraphController(Workflow workflow, Profile profile,
+ boolean interactive, Component componentForPopups,
+ EditManager editManager, MenuManager menuManager,
+ ColourManager colourManager) {
+ this(workflow, profile, interactive, componentForPopups,
+ Alignment.VERTICAL, PortStyle.NONE, editManager, menuManager,
+ colourManager);
+ }
+
+ public GraphController(Workflow workflow, Profile profile,
+ boolean interactive, Component componentForPopups,
+ Alignment alignment, PortStyle portStyle, EditManager editManager,
+ MenuManager menuManager, ColourManager colourManager) {
+ this.workflow = workflow;
+ this.profile = profile;
+ this.interactive = interactive;
+ this.componentForPopups = componentForPopups;
+ this.alignment = alignment;
+ this.portStyle = portStyle;
+ this.editManager = editManager;
+ this.colourManager = colourManager;
+ this.graphEventManager = new DefaultGraphEventManager(this,
+ componentForPopups, menuManager);
+ graph = generateGraph();
+ }
+
+ public abstract Graph createGraph();
+
+ public abstract GraphNode createGraphNode();
+
+ public abstract GraphEdge createGraphEdge();
+
+ public void mapElement(String id, GraphElement element) {
+ idToElement.put(id, element);
+ }
+
+ public GraphElement getElement(String id) {
+ return idToElement.get(id);
+ }
+
+ public Graph getGraph() {
+ return graph;
+ }
+
+ public abstract void redraw();
+
+ /**
+ * Generates a graph model of a dataflow.
+ *
+ * @return
+ */
+ public Graph generateGraph() {
+ workflowToGraph.clear();
+ ports.clear();
+ inputControls.clear();
+ outputControls.clear();
+ nestedWorkflowPorts.clear();
+ workflowPortToProcessorPort.clear();
+ graphElementMap.clear();
+ portToProcessor.clear();
+ return generateGraph(workflow, "", workflow.getName(), 0);
+ }
+
+ private Graph generateGraph(Workflow dataflow, String prefix, String name,
+ int depth) {
+ Graph graph = createGraph();
+ graph.setId(prefix + name);
+ graph.setAlignment(getAlignment());
+ if (getPortStyle().equals(PortStyle.BLOB) || depth == 0)
+ graph.setLabel("");
+ else
+ graph.setLabel(name);
+ graph.setFillColor(GraphColorManager.getSubGraphFillColor(depth));
+ if (depth == 0)
+ graph.setLineStyle(LineStyle.NONE);
+ else
+ graph.setLineStyle(LineStyle.SOLID);
+ graph.setColor(Color.BLACK);
+ graph.setShape(Shape.BOX);
+
+ if (depth == 0)
+ graph.setWorkflowBean(dataflow);
+ if (interactive)
+ graph.setWorkflowBean(dataflow);
+
+ // processors
+ for (Processor processor : dataflow.getProcessors())
+ graph.addNode(generateProcessorNode(processor, graph.getId(), depth));
+
+ // dataflow outputs
+ NamedSet<OutputWorkflowPort> outputPorts = dataflow.getOutputPorts();
+ if (outputPorts.size() > 0 || depth > 0)
+ graph.addSubgraph(generateOutputsGraph(outputPorts, graph.getId(),
+ graph, depth));
+
+ // dataflow inputs
+ NamedSet<InputWorkflowPort> inputPorts = dataflow.getInputPorts();
+ if (inputPorts.size() > 0 || depth > 0)
+ graph.addSubgraph(generateInputsGraph(inputPorts, graph.getId(),
+ graph, depth));
+
+ // datalinks
+ for (DataLink datalink : dataflow.getDataLinks()) {
+ GraphEdge edge = generateDataLinkEdge(datalink, depth);
+ if (edge != null)
+ graph.addEdge(edge);
+ }
+
+ // controlLinks
+ for (ControlLink controlLink : dataflow.getControlLinks())
+ if (controlLink instanceof BlockingControlLink) {
+ GraphEdge edge = generateControlLinkEdge(
+ (BlockingControlLink) controlLink, depth);
+ if (edge != null)
+ graph.addEdge(edge);
+ }
+
+ graphElementMap.put(graph.getId(), graph);
+ return graph;
+ }
+
+ public void transformGraph(Graph oldGraph, Graph newGraph) {
+ oldGraph.setAlignment(newGraph.getAlignment());
+ transformGraphElement(oldGraph, newGraph);
+ List<GraphEdge> oldEdges = new ArrayList<>(oldGraph.getEdges());
+ List<GraphEdge> newEdges = new ArrayList<>(newGraph.getEdges());
+ for (GraphEdge oldEdge : oldEdges) {
+ int index = newEdges.indexOf(oldEdge);
+ if (index >= 0) {
+ GraphEdge newEdge = newEdges.remove(index);
+ oldEdge.setPath(newEdge.getPath());
+ workflowToGraph.put(oldEdge.getWorkflowBean(), oldEdge);
+ } else
+ oldGraph.removeEdge(oldEdge);
+ }
+ List<GraphNode> newNodes = new ArrayList<>(newGraph.getNodes());
+ List<GraphNode> oldNodes = new ArrayList<>(oldGraph.getNodes());
+ for (GraphNode oldNode : oldNodes) {
+ int index = newNodes.indexOf(oldNode);
+ if (index >= 0) {
+ GraphNode newNode = newNodes.remove(index);
+ oldNode.setExpanded(newNode.isExpanded());
+ List<GraphNode> newSourceNodes = new ArrayList<>(
+ newNode.getSourceNodes());
+ List<GraphNode> oldSourceNodes = new ArrayList<>(
+ oldNode.getSourceNodes());
+ for (GraphNode oldSourceNode : oldSourceNodes) {
+ int sourceNodeIndex = newSourceNodes.indexOf(oldSourceNode);
+ if (sourceNodeIndex >= 0) {
+ GraphNode newSourceNode = newSourceNodes
+ .remove(sourceNodeIndex);
+ transformGraphElement(oldSourceNode, newSourceNode);
+ } else
+ oldNode.removeSourceNode(oldSourceNode);
+ }
+ for (GraphNode sourceNode : newSourceNodes)
+ oldNode.addSourceNode(sourceNode);
+ List<GraphNode> newSinkNodes = new ArrayList<>(
+ newNode.getSinkNodes());
+ List<GraphNode> oldSinkNodes = new ArrayList<>(
+ oldNode.getSinkNodes());
+ for (GraphNode oldSinkNode : oldSinkNodes) {
+ int sinkNodeIndex = newSinkNodes.indexOf(oldSinkNode);
+ if (sinkNodeIndex >= 0) {
+ GraphNode newSinkNode = newSinkNodes
+ .remove(sinkNodeIndex);
+ transformGraphElement(oldSinkNode, newSinkNode);
+ } else
+ oldNode.removeSinkNode(oldSinkNode);
+ }
+ for (GraphNode sinkNode : newSinkNodes)
+ oldNode.addSinkNode(sinkNode);
+ Graph oldSubGraph = oldNode.getGraph();
+ Graph newSubGraph = newNode.getGraph();
+ if (oldSubGraph != null && newSubGraph != null)
+ transformGraph(oldSubGraph, newSubGraph);
+ transformGraphElement(oldNode, newNode);
+ } else
+ oldGraph.removeNode(oldNode);
+ }
+ List<Graph> newSubGraphs = new ArrayList<>(newGraph.getSubgraphs());
+ List<Graph> oldSubGraphs = new ArrayList<>(oldGraph.getSubgraphs());
+ for (Graph oldSubGraph : oldSubGraphs) {
+ int index = newSubGraphs.indexOf(oldSubGraph);
+ if (index >= 0) {
+ Graph newSubGraph = newSubGraphs.remove(index);
+ transformGraph(oldSubGraph, newSubGraph);
+ } else
+ oldGraph.removeSubgraph(oldSubGraph);
+ }
+ for (GraphNode node : newNodes)
+ oldGraph.addNode(node);
+ for (Graph graph : newSubGraphs)
+ oldGraph.addSubgraph(graph);
+ for (GraphEdge newEdge : newEdges)
+ oldGraph.addEdge(newEdge);
+ }
+
+ public void transformGraphElement(GraphShapeElement oldGraphElement,
+ GraphShapeElement newGraphElement) {
+ oldGraphElement.setWorkflowBean(newGraphElement.getWorkflowBean());
+ oldGraphElement.setShape(newGraphElement.getShape());
+ oldGraphElement.setSize(newGraphElement.getSize());
+ oldGraphElement.setPosition(newGraphElement.getPosition());
+ oldGraphElement.setLabel(newGraphElement.getLabel());
+ oldGraphElement.setLabelPosition(newGraphElement.getLabelPosition());
+ oldGraphElement.setLineStyle(newGraphElement.getLineStyle());
+ oldGraphElement.setOpacity(newGraphElement.getOpacity());
+ oldGraphElement.setVisible(newGraphElement.isVisible());
+ oldGraphElement.setColor(newGraphElement.getColor());
+ oldGraphElement.setFillColor(newGraphElement.getFillColor());
+ workflowToGraph.put(oldGraphElement.getWorkflowBean(), oldGraphElement);
+ }
+
+ public void filterGraph(Set<?> dataflowEntities) {
+ Set<GraphElement> graphElements = new HashSet<>();
+ for (Entry<WorkflowBean, GraphElement> entry : workflowToGraph
+ .entrySet())
+ if (!dataflowEntities.contains(entry.getKey()))
+ graphElements.add(entry.getValue());
+ filterGraph(getGraph(), graphElements);
+ }
+
+ private void filterGraph(Graph graph, Set<GraphElement> graphElements) {
+ for (GraphNode node : graph.getNodes()) {
+ node.setFiltered(graphElements.contains(node));
+ Graph subgraph = node.getGraph();
+ if (subgraph != null)
+ if (graphElements.contains(subgraph)) {
+ removeFilter(subgraph);
+ subgraph.setFiltered(true);
+ } else {
+ subgraph.setFiltered(false);
+ filterGraph(subgraph, graphElements);
+ }
+ }
+ for (GraphEdge edge : graph.getEdges())
+ edge.setFiltered(graphElements.contains(edge));
+ for (Graph subgraph : graph.getSubgraphs())
+ if (graphElements.contains(subgraph)) {
+ removeFilter(subgraph);
+ subgraph.setFiltered(true);
+ } else {
+ subgraph.setFiltered(false);
+ filterGraph(subgraph, graphElements);
+ }
+ }
+
+ public void removeFilter() {
+ for (Entry<WorkflowBean, GraphElement> entry : workflowToGraph
+ .entrySet())
+ entry.getValue().setFiltered(false);
+ }
+
+ private void removeFilter(Graph graph) {
+ for (GraphNode node : graph.getNodes()) {
+ node.setOpacity(1f);
+ Graph subgraph = node.getGraph();
+ if (subgraph != null) {
+ subgraph.setFiltered(false);
+ removeFilter(subgraph);
+ }
+ }
+ for (GraphEdge edge : graph.getEdges())
+ edge.setFiltered(false);
+ for (Graph subgraph : graph.getSubgraphs()) {
+ subgraph.setFiltered(false);
+ removeFilter(subgraph);
+ }
+ }
+
+ private GraphEdge generateControlLinkEdge(BlockingControlLink condition,
+ int depth) {
+ GraphEdge edge = null;
+ GraphElement source = workflowToGraph.get(condition.getUntilFinished());
+ GraphElement sink = workflowToGraph.get(condition.getBlock());
+ if (source != null && sink != null) {
+ edge = createGraphEdge();
+ if (source instanceof Graph)
+ edge.setSource(outputControls.get(source));
+ else if (source instanceof GraphNode)
+ edge.setSource((GraphNode) source);
+ if (sink instanceof Graph)
+ edge.setSink(inputControls.get(sink));
+ else if (sink instanceof GraphNode)
+ edge.setSink((GraphNode) sink);
+ String sourceId = edge.getSource().getId();
+ String sinkId = edge.getSink().getId();
+ edge.setId(sourceId + "->" + sinkId);
+ edge.setLineStyle(LineStyle.SOLID);
+ edge.setColor(Color.decode("#505050"));
+ edge.setFillColor(null);
+ edge.setArrowHeadStyle(ArrowStyle.DOT);
+ if (depth == 0)
+ edge.setWorkflowBean(condition);
+ if (interactive)
+ edge.setWorkflowBean(condition);
+ workflowToGraph.put(condition, edge);
+ graphElementMap.put(edge.getId(), edge);
+ }
+ return edge;
+ }
+
+ private GraphEdge generateDataLinkEdge(DataLink datalink, int depth) {
+ GraphEdge edge = null;
+ Port sourcePort = datalink.getReceivesFrom();
+ Port sinkPort = datalink.getSendsTo();
+ if (nestedWorkflowPorts.containsKey(sourcePort))
+ sourcePort = nestedWorkflowPorts.get(sourcePort);
+ if (nestedWorkflowPorts.containsKey(sinkPort))
+ sinkPort = nestedWorkflowPorts.get(sinkPort);
+ GraphNode sourceNode = ports.get(sourcePort);
+ GraphNode sinkNode = ports.get(sinkPort);
+ if (sourceNode != null && sinkNode != null) {
+ edge = createGraphEdge();
+ edge.setSource(sourceNode);
+ edge.setSink(sinkNode);
+
+ StringBuilder id = new StringBuilder();
+ if (sourceNode.getParent() instanceof GraphNode) {
+ id.append(sourceNode.getParent().getId());
+ id.append(":");
+ id.append(sourceNode.getId());
+ } else
+ id.append(sourceNode.getId());
+ id.append("->");
+ if (sinkNode.getParent() instanceof GraphNode) {
+ id.append(sinkNode.getParent().getId());
+ id.append(":");
+ id.append(sinkNode.getId());
+ } else
+ id.append(sinkNode.getId());
+ edge.setId(id.toString());
+ edge.setLineStyle(LineStyle.SOLID);
+ edge.setColor(Color.BLACK);
+ edge.setFillColor(Color.BLACK);
+ if (depth == 0)
+ edge.setWorkflowBean(datalink);
+ if (interactive)
+ edge.setWorkflowBean(datalink);
+ workflowToGraph.put(datalink, edge);
+ graphElementMap.put(edge.getId(), edge);
+ }
+ return edge;
+ }
+
+ private Graph generateInputsGraph(NamedSet<InputWorkflowPort> inputPorts,
+ String prefix, Graph graph, int depth) {
+ Graph inputs = createGraph();
+ inputs.setId(prefix + "sources");
+ inputs.setColor(Color.BLACK);
+ inputs.setFillColor(null);
+ inputs.setShape(Shape.BOX);
+ inputs.setLineStyle(LineStyle.DOTTED);
+ if (getPortStyle().equals(PortStyle.BLOB))
+ inputs.setLabel("");
+ else
+ inputs.setLabel("Workflow input ports");
+
+ GraphNode triangle = createGraphNode();
+ triangle.setId(prefix + "WORKFLOWINTERNALSOURCECONTROL");
+ triangle.setLabel("");
+ triangle.setShape(Shape.TRIANGLE);
+ triangle.setSize(new Dimension((int) (0.2f * 72), (int) ((Math.sin(Math
+ .toRadians(60)) * 0.2) * 72)));
+ triangle.setFillColor(Color.decode("#ff4040"));
+ triangle.setColor(Color.BLACK);
+ triangle.setLineStyle(LineStyle.SOLID);
+ inputs.addNode(triangle);
+ inputControls.put(graph, triangle);
+
+ for (InputWorkflowPort inputWorkflowPort : inputPorts) {
+ GraphNode inputNode = createGraphNode();
+ inputNode.setId(prefix + "WORKFLOWINTERNALSOURCE_"
+ + inputWorkflowPort.getName());
+ if (getPortStyle().equals(PortStyle.BLOB)) {
+ inputNode.setLabel("");
+ inputNode.setSize(new Dimension((int) (0.3f * 72),
+ (int) (0.3f * 72)));
+ } else
+ inputNode.setLabel(inputWorkflowPort.getName());
+ inputNode.setShape(getPortStyle().inputShape());
+ inputNode.setColor(Color.BLACK);
+ inputNode.setLineStyle(LineStyle.SOLID);
+ inputNode.setFillColor(Color.decode("#8ed6f0"));
+ if (depth == 0)
+ inputNode.setInteractive(true);
+ if (interactive)
+ inputNode.setInteractive(true);
+ if (depth < 2) {
+ inputNode.setWorkflowBean(inputWorkflowPort);
+ if (workflowPortToProcessorPort.containsKey(inputWorkflowPort)) {
+ ProcessorPort port = workflowPortToProcessorPort
+ .get(inputWorkflowPort);
+ inputNode.setWorkflowBean(port);
+ workflowToGraph.put(port, inputNode);
+ } else {
+ inputNode.setWorkflowBean(inputWorkflowPort);
+ workflowToGraph.put(inputWorkflowPort, inputNode);
+ }
+ }
+ ports.put(inputWorkflowPort, inputNode);
+ inputs.addNode(inputNode);
+ graphElementMap.put(inputNode.getId(), inputNode);
+ }
+ return inputs;
+ }
+
+ private Graph generateOutputsGraph(
+ NamedSet<OutputWorkflowPort> outputPorts, String prefix,
+ Graph graph, int depth) {
+ Graph outputs = createGraph();
+ outputs.setId(prefix + "sinks");
+ outputs.setColor(Color.BLACK);
+ outputs.setFillColor(null);
+ outputs.setShape(Shape.BOX);
+ outputs.setLineStyle(LineStyle.DOTTED);
+ if (getPortStyle().equals(PortStyle.BLOB))
+ outputs.setLabel("");
+ else
+ outputs.setLabel("Workflow output ports");
+
+ GraphNode triangle = createGraphNode();
+ triangle.setId(prefix + "WORKFLOWINTERNALSINKCONTROL");
+ triangle.setLabel("");
+ triangle.setShape(Shape.INVTRIANGLE);
+ triangle.setSize(new Dimension((int) (0.2f * 72), (int) ((Math.sin(Math
+ .toRadians(60)) * 0.2) * 72)));
+ triangle.setFillColor(Color.decode("#66cd00"));
+ triangle.setColor(Color.BLACK);
+ triangle.setLineStyle(LineStyle.SOLID);
+ outputs.addNode(triangle);
+ outputControls.put(graph, triangle);
+
+ for (OutputWorkflowPort outputWorkflowPort : outputPorts) {
+ GraphNode outputNode = createGraphNode();
+ outputNode.setId(prefix + "WORKFLOWINTERNALSINK_"
+ + outputWorkflowPort.getName());
+ if (getPortStyle().equals(PortStyle.BLOB)) {
+ outputNode.setLabel("");
+ outputNode.setSize(new Dimension((int) (0.3f * 72),
+ (int) (0.3f * 72)));
+ } else
+ outputNode.setLabel(outputWorkflowPort.getName());
+ outputNode.setShape(getPortStyle().outputShape());
+ outputNode.setColor(Color.BLACK);
+ outputNode.setLineStyle(LineStyle.SOLID);
+ outputNode.setFillColor(Color.decode("#8ed6f0"));
+ if (depth == 0)
+ outputNode.setInteractive(true);
+ if (interactive)
+ outputNode.setInteractive(true);
+ if (depth < 2) {
+ if (workflowPortToProcessorPort.containsKey(outputWorkflowPort)) {
+ ProcessorPort port = workflowPortToProcessorPort
+ .get(outputWorkflowPort);
+ outputNode.setWorkflowBean(port);
+ workflowToGraph.put(port, outputNode);
+ } else {
+ outputNode.setWorkflowBean(outputWorkflowPort);
+ workflowToGraph.put(outputWorkflowPort, outputNode);
+ }
+ }
+ ports.put(outputWorkflowPort, outputNode);
+ outputs.addNode(outputNode);
+ graphElementMap.put(outputNode.getId(), outputNode);
+ }
+ return outputs;
+ }
+
+ private GraphNode generateProcessorNode(Processor processor, String prefix,
+ int depth) {
+ // Blatantly ignoring any other activities for now
+ ProcessorBinding processorBinding = scufl2Tools
+ .processorBindingForProcessor(processor, profile);
+ Activity activity = processorBinding.getBoundActivity();
+ @SuppressWarnings("unused")
+ URI activityType = activity.getType();
+
+ GraphNode node = createGraphNode();
+ node.setId(prefix + processor.getName());
+ if (getPortStyle().equals(PortStyle.BLOB)) {
+ node.setLabel("");
+ node.setSize(new Dimension((int) (0.3f * 72), (int) (0.3f * 72)));
+ } else
+ node.setLabel(processor.getName());
+ node.setShape(getPortStyle(processor).processorShape());
+ node.setColor(Color.BLACK);
+ node.setLineStyle(LineStyle.SOLID);
+ // if (activityType.equals(URI.create(NonExecutableActivity.URI))) {
+ // if (activityType.equals(URI.create(DisabledActivity.URI))) {
+ // node.setFillColor(GraphColorManager
+ // .getFillColor(((DisabledActivity) activity)
+ // .getActivity(), colourManager));
+ // } else {
+ // node.setFillColor(GraphColorManager
+ // .getFillColor(activityType, colourManager));
+ // }
+ // node.setOpacity(0.3f);
+ // } else
+ node.setFillColor(GraphColorManager.getFillColor(activity,
+ colourManager));
+
+ // check whether the nested workflow processors should be clickable or
+ // not, if top level workflow then should be clickable regardless
+ if (depth == 0) {
+ node.setInteractive(true);
+ node.setWorkflowBean(processor);
+ }
+ if (interactive) {
+ node.setInteractive(true);
+ node.setWorkflowBean(processor);
+ }
+
+ if (scufl2Tools.containsNestedWorkflow(processor, profile)
+ && expandNestedDataflow(activity)) {
+ Workflow subDataflow = scufl2Tools.nestedWorkflowForProcessor(
+ processor, profile);
+
+ NamedSet<InputWorkflowPort> inputWorkflowPorts = subDataflow
+ .getInputPorts();
+ for (InputActivityPort inputActivityPort : activity.getInputPorts()) {
+ InputWorkflowPort inputWorkflowPort = inputWorkflowPorts
+ .getByName(inputActivityPort.getName());
+ InputProcessorPort inputProcessorPort = scufl2Tools
+ .processorPortBindingForPort(inputActivityPort, profile)
+ .getBoundProcessorPort();
+ nestedWorkflowPorts.put(inputProcessorPort, inputWorkflowPort);
+ workflowPortToProcessorPort.put(inputWorkflowPort,
+ inputProcessorPort);
+ processorBinding.getInputPortBindings();
+ }
+
+ NamedSet<OutputWorkflowPort> outputWorkflowPorts = subDataflow
+ .getOutputPorts();
+ for (OutputActivityPort outputActivityPort : activity
+ .getOutputPorts()) {
+ OutputWorkflowPort outputWorkflowPort = outputWorkflowPorts
+ .getByName(outputActivityPort.getName());
+ OutputProcessorPort outputProcessorPort = scufl2Tools
+ .processorPortBindingForPort(outputActivityPort,
+ profile).getBoundProcessorPort();
+ nestedWorkflowPorts
+ .put(outputProcessorPort, outputWorkflowPort);
+ workflowPortToProcessorPort.put(outputWorkflowPort,
+ outputProcessorPort);
+ }
+
+ Graph subGraph = generateGraph(subDataflow, prefix,
+ processor.getName(), depth + 1);
+ // TODO why does this depth matter?
+ if (depth == 0)
+ subGraph.setWorkflowBean(processor);
+ if (interactive)
+ subGraph.setWorkflowBean(processor);
+ node.setGraph(subGraph);
+ node.setExpanded(true);
+
+ workflowToGraph.put(processor, subGraph);
+ } else {
+ graphElementMap.put(node.getId(), node);
+ workflowToGraph.put(processor, node);
+ }
+
+ NamedSet<InputProcessorPort> inputPorts = processor.getInputPorts();
+ if (inputPorts.size() == 0) {
+ GraphNode portNode = createGraphNode();
+ portNode.setShape(Shape.BOX);
+ portNode.setColor(Color.BLACK);
+ portNode.setFillColor(node.getFillColor());
+ portNode.setLineStyle(LineStyle.SOLID);
+ node.addSinkNode(portNode);
+ } else
+ for (InputPort inputPort : inputPorts) {
+ GraphNode portNode = createGraphNode();
+ portNode.setId("i" + inputPort.getName().replaceAll("\\.", ""));
+ portNode.setLabel(inputPort.getName());
+ portNode.setShape(Shape.BOX);
+ portNode.setColor(Color.BLACK);
+ portNode.setFillColor(node.getFillColor());
+ portNode.setLineStyle(LineStyle.SOLID);
+ if (depth == 0)
+ portNode.setWorkflowBean(inputPort);
+ if (interactive)
+ portNode.setWorkflowBean(inputPort);
+ if (!node.isExpanded())
+ workflowToGraph.put(inputPort, portNode);
+ ports.put(inputPort, portNode);
+ node.addSinkNode(portNode);
+ graphElementMap.put(portNode.getId(), portNode);
+ // portToActivity.put(inputPort, activity);
+ portToProcessor.put(inputPort, processor);
+ }
+
+ NamedSet<OutputProcessorPort> outputPorts = processor.getOutputPorts();
+ if (outputPorts.size() == 0) {
+ GraphNode portNode = createGraphNode();
+ portNode.setShape(Shape.BOX);
+ portNode.setColor(Color.BLACK);
+ portNode.setFillColor(node.getFillColor());
+ portNode.setLineStyle(LineStyle.SOLID);
+ node.addSourceNode(portNode);
+ } else
+ for (OutputPort outputPort : outputPorts) {
+ GraphNode portNode = createGraphNode();
+ portNode.setId("o" + outputPort.getName().replaceAll("\\.", ""));
+ portNode.setLabel(outputPort.getName());
+ portNode.setShape(Shape.BOX);
+ portNode.setColor(Color.BLACK);
+ portNode.setFillColor(node.getFillColor());
+ portNode.setLineStyle(LineStyle.SOLID);
+ if (depth == 0)
+ portNode.setWorkflowBean(outputPort);
+ if (interactive)
+ portNode.setWorkflowBean(outputPort);
+ if (!node.isExpanded())
+ workflowToGraph.put(outputPort, portNode);
+ ports.put(outputPort, portNode);
+ node.addSourceNode(portNode);
+ graphElementMap.put(portNode.getId(), portNode);
+ // portToActivity.put(outputPort, activity);
+ portToProcessor.put(outputPort, processor);
+ }
+
+ return node;
+ }
+
+ /**
+ * Returns the dataflow.
+ *
+ * @return the dataflow
+ */
+ public Workflow getWorkflow() {
+ return workflow;
+ }
+
+ public Profile getProfile() {
+ return profile;
+ }
+
+ /**
+ * Returns the dataflowSelectionModel.
+ *
+ * @return the dataflowSelectionModel
+ */
+ public DataflowSelectionModel getDataflowSelectionModel() {
+ return dataflowSelectionModel;
+ }
+
+ /**
+ * Sets the dataflowSelectionModel.
+ *
+ * @param dataflowSelectionModel
+ * the new dataflowSelectionModel
+ */
+ public void setDataflowSelectionModel(
+ DataflowSelectionModel dataflowSelectionModel) {
+ if (this.dataflowSelectionModel != null)
+ this.dataflowSelectionModel.removeObserver(this);
+ this.dataflowSelectionModel = dataflowSelectionModel;
+ this.dataflowSelectionModel.addObserver(this);
+ }
+
+ /**
+ * Sets the proportion of the node's jobs that have been completed.
+ *
+ * @param nodeId
+ * the id of the node
+ * @param complete
+ * the proportion of the nodes's jobs that have been completed, a
+ * value between 0.0 and 1.0
+ */
+ public void setNodeCompleted(String nodeId, float complete) {
+ if (graphElementMap.containsKey(nodeId)) {
+ GraphElement graphElement = graphElementMap.get(nodeId);
+ graphElement.setCompleted(complete);
+ }
+ }
+
+ public void setEdgeActive(String edgeId, boolean active) {
+ }
+
+ /**
+ * Returns the alignment.
+ *
+ * @return the alignment
+ */
+ public Alignment getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Returns the portStyle.
+ *
+ * @return the portStyle
+ */
+ public PortStyle getPortStyle() {
+ return portStyle;
+ }
+
+ /**
+ * Returns the portStyle for a processor.
+ *
+ * @return the portStyle for a processor
+ */
+ public PortStyle getPortStyle(Processor processor) {
+ if (processorPortStyle.containsKey(processor))
+ return processorPortStyle.get(processor);
+ return portStyle;
+ }
+
+ /**
+ * Sets the alignment.
+ *
+ * @param alignment
+ * the new alignment
+ */
+ public void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ }
+
+ /**
+ * Sets the portStyle.
+ *
+ * @param style
+ * the new portStyle
+ */
+ public void setPortStyle(PortStyle portStyle) {
+ this.portStyle = portStyle;
+ processorPortStyle.clear();
+ }
+
+ /**
+ * Sets the portStyle for a processor.
+ *
+ * @param style
+ * the new portStyle for the processor
+ */
+ public void setPortStyle(Processor processor, PortStyle portStyle) {
+ processorPortStyle.put(processor, portStyle);
+ }
+
+ /**
+ * Shut down any processing and update threads related to this controller.
+ *
+ */
+ public void shutdown() {
+ }
+
+ /**
+ * Returns true if the default is to expand nested workflows.
+ *
+ * @return true if the default is to expand nested workflows
+ */
+ public boolean expandNestedDataflows() {
+ return expandNestedDataflows;
+ }
+
+ /**
+ * Returns true if the nested dataflow should be expanded.
+ *
+ * @param dataflow
+ * @return true if the nested dataflow should be expanded
+ */
+ public boolean expandNestedDataflow(Activity dataflow) {
+ if (dataflowExpansion.containsKey(dataflow))
+ return dataflowExpansion.get(dataflow);
+ return expandNestedDataflows;
+ }
+
+ /**
+ * Sets the default for expanding nested workflows.
+ *
+ * @param expand
+ * the default for expanding nested workflows
+ */
+ public void setExpandNestedDataflows(boolean expand) {
+ dataflowExpansion.clear();
+ this.expandNestedDataflows = expand;
+ }
+
+ /**
+ * Sets whether the nested dataflow should be expanded.
+ *
+ * @param expand
+ * whether the nested dataflow should be expanded
+ * @param dataflow
+ * the nested dataflow
+ */
+ public void setExpandNestedDataflow(Activity dataflow, boolean expand) {
+ dataflowExpansion.put(dataflow, expand);
+ }
+
+ private boolean isSingleOutputProcessor(Object dataflowObject) {
+ boolean result = false;
+ if (dataflowObject instanceof Processor) {
+ Processor processor = (Processor) dataflowObject;
+ result = processor.getOutputPorts().size() == 1;
+ }
+ return result;
+ }
+
+ public boolean startEdgeCreation(GraphElement graphElement, Point point) {
+ if (!edgeCreationFromSource && !edgeCreationFromSink) {
+ Object dataflowObject = graphElement.getWorkflowBean();
+ if (dataflowObject instanceof ReceiverPort) {
+ edgeCreationSink = graphElement;
+ edgeCreationFromSink = true;
+ } else if (dataflowObject instanceof SenderPort
+ || isSingleOutputProcessor(dataflowObject)) {
+ edgeCreationSource = graphElement;
+ edgeCreationFromSource = true;
+ } else if (graphElement instanceof GraphEdge) {
+ GraphEdge edge = (GraphEdge) graphElement;
+ edgeCreationSource = edge.getSource();
+ edgeCreationFromSource = true;
+ edgeMoveElement = edge;
+ }
+ }
+ return edgeCreationFromSource || edgeCreationFromSink;
+ }
+
+ public boolean moveEdgeCreationTarget(GraphElement graphElement, Point point) {
+ boolean edgeValid = false;
+ Object dataflowObject = graphElement.getWorkflowBean();
+ if (edgeCreationFromSink) {
+ if (graphElement instanceof GraphNode) {
+ Object sinkObject = edgeCreationSink.getWorkflowBean();
+ if (dataflowObject instanceof OutputPort) {
+ Processor sourceProcessor = portToProcessor
+ .get(dataflowObject);
+ if (sourceProcessor != null) {
+ Processor sinkProcessor = null;
+ if (sinkObject instanceof Processor)
+ sinkProcessor = (Processor) sinkObject;
+ else if (portToProcessor.containsKey(sinkObject))
+ sinkProcessor = portToProcessor.get(sinkObject);
+ if (sinkProcessor != null) {
+ Set<Processor> possibleSinkProcessors = scufl2Tools
+ .possibleDownStreamProcessors(workflow,
+ sourceProcessor);
+ if (possibleSinkProcessors.contains(sinkProcessor)) {
+ edgeCreationSource = graphElement;
+ edgeValid = true;
+ }
+ }
+ if (sinkObject instanceof OutputWorkflowPort) {
+ edgeCreationSource = graphElement;
+ edgeValid = true;
+ }
+ }
+ } else if (dataflowObject instanceof InputWorkflowPort) {
+ edgeCreationSource = graphElement;
+ edgeValid = true;
+ } else if (dataflowObject instanceof Processor) {
+ Processor sourceProcessor = (Processor) dataflowObject;
+ Processor sinkProcessor = null;
+ if (sinkObject instanceof Processor)
+ sinkProcessor = (Processor) sinkObject;
+ else if (portToProcessor.containsKey(sinkObject))
+ sinkProcessor = portToProcessor.get(sinkObject);
+ if (sinkProcessor != null) {
+ Set<Processor> possibleSinkProcessors = scufl2Tools
+ .possibleDownStreamProcessors(workflow,
+ sourceProcessor);
+ if (possibleSinkProcessors.contains(sinkProcessor)) {
+ edgeCreationSource = graphElement;
+ edgeValid = true;
+ }
+ }
+ if (sinkObject instanceof OutputWorkflowPort) {
+ edgeCreationSource = graphElement;
+ edgeValid = true;
+ }
+ }
+ }
+ if (!edgeValid)
+ edgeCreationSource = null;
+ } else if (edgeCreationFromSource) {
+ if (graphElement instanceof GraphNode) {
+ Object sourceObject = edgeCreationSource.getWorkflowBean();
+ if (dataflowObject instanceof InputPort) {
+ Processor sinkProcessor = portToProcessor
+ .get(dataflowObject);
+ if (sinkProcessor != null) {
+ Processor sourceProcessor = null;
+ if (sourceObject instanceof Processor)
+ sourceProcessor = (Processor) sourceObject;
+ else if (portToProcessor.containsKey(sourceObject))
+ sourceProcessor = portToProcessor.get(sourceObject);
+ if (sourceProcessor != null) {
+ Set<Processor> possibleSourceProcessors = scufl2Tools
+ .possibleUpStreamProcessors(workflow,
+ sinkProcessor);
+ if (possibleSourceProcessors
+ .contains(sourceProcessor)) {
+ edgeCreationSink = graphElement;
+ edgeValid = true;
+ }
+ }
+ if (sourceObject instanceof InputWorkflowPort) {
+ edgeCreationSink = graphElement;
+ edgeValid = true;
+ }
+ }
+ } else if (dataflowObject instanceof OutputWorkflowPort) {
+ if (sourceObject != null) {
+ edgeCreationSink = graphElement;
+ edgeValid = true;
+ }
+ } else if (dataflowObject instanceof Processor) {
+ Processor sinkProcessor = (Processor) dataflowObject;
+ Processor sourceProcessor = null;
+ if (sourceObject instanceof Processor)
+ sourceProcessor = (Processor) sourceObject;
+ else if (portToProcessor.containsKey(sourceObject))
+ sourceProcessor = portToProcessor.get(sourceObject);
+ if (sourceProcessor != null) {
+ Set<Processor> possibleSourceProcessors = scufl2Tools
+ .possibleUpStreamProcessors(workflow,
+ sinkProcessor);
+ if (possibleSourceProcessors.contains(sourceProcessor)) {
+ edgeCreationSink = graphElement;
+ edgeValid = true;
+ }
+ }
+ if (sourceObject instanceof InputWorkflowPort) {
+ edgeCreationSink = graphElement;
+ edgeValid = true;
+ }
+ }
+ }
+ if (!edgeValid)
+ edgeCreationSink = null;
+ }
+ return edgeValid;
+ }
+
+ public boolean stopEdgeCreation(GraphElement graphElement, Point point) {
+ boolean edgeCreated = false;
+ if (edgeCreationSource != null && edgeCreationSink != null) {
+ SenderPort source = null;
+ ReceiverPort sink = null;
+ Object sourceDataflowObject = edgeCreationSource.getWorkflowBean();
+ Object sinkDataflowObject = edgeCreationSink.getWorkflowBean();
+ if (sourceDataflowObject instanceof SenderPort)
+ source = (SenderPort) sourceDataflowObject;
+ else if (sourceDataflowObject instanceof Processor) {
+ Processor processor = (Processor) sourceDataflowObject;
+ source = showPortOptions(processor.getOutputPorts(), "output",
+ componentForPopups, point);
+ }
+ if (sinkDataflowObject instanceof ReceiverPort)
+ sink = (ReceiverPort) sinkDataflowObject;
+ else if (sinkDataflowObject instanceof Processor) {
+ Processor processor = (Processor) sinkDataflowObject;
+ sink = showPortOptions(processor.getInputPorts(), "input",
+ componentForPopups, point);
+ }
+ if (source != null && sink != null) {
+ Edit<?> edit = null;
+ if (edgeMoveElement == null) {
+ DataLink dataLink = new DataLink();
+ dataLink.setReceivesFrom(source);
+ dataLink.setSendsTo(sink);
+ edit = new AddDataLinkEdit(workflow, dataLink);
+ } else {
+ Object existingSink = edgeMoveElement.getSink()
+ .getWorkflowBean();
+ if (existingSink != sink) {
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ DataLink existingDataLink = (DataLink) edgeMoveElement
+ .getWorkflowBean();
+ DataLink newDataLink = new DataLink();
+ newDataLink.setReceivesFrom(existingDataLink
+ .getReceivesFrom());
+ newDataLink.setSendsTo(sink);
+ editList.add(new RemoveDataLinkEdit(workflow,
+ existingDataLink));
+ editList.add(new AddDataLinkEdit(workflow, newDataLink));
+ edit = new CompoundEdit(editList);
+ }
+ }
+ try {
+ if (edit != null) {
+ editManager.doDataflowEdit(workflow.getParent(), edit);
+ edgeCreated = true;
+ }
+ } catch (EditException e) {
+ logger.debug("Failed to create datalink from '"
+ + source.getName() + "' to '" + sink.getName()
+ + "'");
+ }
+ }
+ }
+ edgeCreationSource = null;
+ edgeCreationSink = null;
+ edgeMoveElement = null;
+ edgeCreationFromSource = false;
+ edgeCreationFromSink = false;
+
+ return edgeCreated;
+ }
+
+ private <T extends Port> T showPortOptions(NamedSet<T> ports,
+ String portType, Component component, Point point) {
+ T result = null;
+ if (ports.size() == 0) {
+ showMessageDialog(component, "Service has no " + portType
+ + " ports to connect to");
+ } else if (ports.size() == 1)
+ result = ports.first();
+ else {
+ Object[] portNames = ports.getNames().toArray();
+ String portName = (String) showInputDialog(component, "Select an "
+ + portType + " port", "Port Chooser", PLAIN_MESSAGE, null,
+ portNames, portNames[0]);
+ if (portName != null)
+ result = ports.getByName(portName);
+ }
+ return result;
+
+ }
+
+ public void resetSelection() {
+ if (dataflowSelectionModel != null)
+ for (Object dataflowElement : dataflowSelectionModel.getSelection()) {
+ GraphElement graphElement = workflowToGraph
+ .get(dataflowElement);
+ if (graphElement != null)
+ graphElement.setSelected(true);
+ }
+ }
+
+ public void setIteration(String nodeId, int iteration) {
+ if (graphElementMap.containsKey(nodeId)) {
+ GraphElement graphElement = graphElementMap.get(nodeId);
+ graphElement.setIteration(iteration);
+ }
+ }
+
+ public void setErrors(String nodeId, int errors) {
+ if (graphElementMap.containsKey(nodeId)) {
+ GraphElement graphElement = graphElementMap.get(nodeId);
+ graphElement.setErrors(errors);
+ }
+ }
+
+ @Override
+ public void notify(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) throws Exception {
+ GraphElement graphElement = workflowToGraph.get(message.getElement());
+ if (graphElement != null)
+ graphElement.setSelected(message.getType().equals(
+ DataflowSelectionMessage.Type.ADDED));
+ }
+
+ /**
+ * Returns the GraphEventManager.
+ *
+ * @return the GraphEventManager
+ */
+ public GraphEventManager getGraphEventManager() {
+ return graphEventManager;
+ }
+
+ /**
+ * Sets the GraphEventManager.
+ *
+ * @param graphEventManager
+ * the new GraphEventManager
+ */
+ public void setGraphEventManager(GraphEventManager graphEventManager) {
+ this.graphEventManager = graphEventManager;
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphEdge.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphEdge.java
new file mode 100644
index 0000000..d1348d2
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphEdge.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import java.awt.Point;
+import java.util.List;
+
+/**
+ * An edge connecting two nodes in a graph.
+ *
+ * @author David Withers
+ */
+public class GraphEdge extends GraphElement {
+ public enum ArrowStyle {NONE, NORMAL, DOT}
+
+ private GraphNode source;
+ private GraphNode sink;
+ private ArrowStyle arrowHeadStyle = ArrowStyle.NORMAL;
+ private ArrowStyle arrowTailStyle = ArrowStyle.NONE;
+ private List<Point> path;
+
+ /**
+ * Constructs a new instance of Edge.
+ *
+ */
+ public GraphEdge(GraphController graphController) {
+ super(graphController);
+ }
+
+ /**
+ * Returns the source.
+ *
+ * @return the source
+ */
+ public GraphNode getSource() {
+ return source;
+ }
+
+ /**
+ * Sets the source.
+ *
+ * @param source the new source
+ */
+ public void setSource(GraphNode source) {
+ this.source = source;
+ }
+
+ /**
+ * Returns the sink.
+ *
+ * @return the sink
+ */
+ public GraphNode getSink() {
+ return sink;
+ }
+
+ /**
+ * Sets the sink.
+ *
+ * @param sink the new sink
+ */
+ public void setSink(GraphNode sink) {
+ this.sink = sink;
+ }
+
+ /**
+ * Returns the arrowHeadStyle.
+ *
+ * @return the arrowHeadStyle
+ */
+ public ArrowStyle getArrowHeadStyle() {
+ return arrowHeadStyle;
+ }
+
+ /**
+ * Sets the arrowHeadStyle.
+ *
+ * @param arrowHeadStyle the new arrowHeadStyle
+ */
+ public void setArrowHeadStyle(ArrowStyle arrowHeadStyle) {
+ this.arrowHeadStyle = arrowHeadStyle;
+ }
+
+ /**
+ * Returns the arrowTailStyle.
+ *
+ * @return the arrowTailStyle
+ */
+ public ArrowStyle getArrowTailStyle() {
+ return arrowTailStyle;
+ }
+
+ /**
+ * Sets the arrowTailStyle.
+ *
+ * @param arrowTailStyle the new arrowTailStyle
+ */
+ public void setArrowTailStyle(ArrowStyle arrowTailStyle) {
+ this.arrowTailStyle = arrowTailStyle;
+ }
+
+ /**
+ * Returns the path.
+ *
+ * @return the path
+ */
+ public List<Point> getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the path.
+ *
+ * @param path the new path
+ */
+ public void setPath(List<Point> path) {
+ this.path = path;
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphElement.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphElement.java
new file mode 100644
index 0000000..8bb7bc8
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphElement.java
@@ -0,0 +1,430 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import java.awt.Color;
+import java.awt.Point;
+
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * An element of a graph.
+ *
+ * @author David Withers
+ */
+public abstract class GraphElement {
+ public enum LineStyle {
+ NONE, SOLID, DOTTED
+ }
+
+ private String id;
+ private String label;
+ private Point labelPosition;
+ private LineStyle lineStyle = LineStyle.SOLID;
+ private Color color = Color.BLACK;
+ private Color fillColor;
+ private float opacity = 1f;
+ private GraphElement parent;
+ private boolean selected;
+ private boolean active;
+ private boolean interactive;
+ private boolean visible = true;
+ private boolean filtered;
+ private WorkflowBean workflowBean;
+ protected GraphController graphController;
+ protected float completed;
+ protected int iteration;
+ protected int errors;
+
+ protected GraphElement(GraphController graphController) {
+ this.graphController = graphController;
+ }
+
+ /**
+ * Returns the eventManager.
+ *
+ * @return the eventManager
+ */
+ public GraphEventManager getEventManager() {
+ if (graphController != null) {
+ return graphController.getGraphEventManager();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the workflowBean.
+ *
+ * @return the workflowBean
+ */
+ public WorkflowBean getWorkflowBean() {
+ return workflowBean;
+ }
+
+ /**
+ * Sets the workflowBean.
+ *
+ * @param workflowBean
+ * the new workflowBean
+ */
+ public void setWorkflowBean(WorkflowBean workflowBean) {
+ this.workflowBean = workflowBean;
+ }
+
+ /**
+ * Returns the parent.
+ *
+ * @return the parent
+ */
+ public GraphElement getParent() {
+ return parent;
+ }
+
+ /**
+ * Sets the parent.
+ *
+ * @param parent
+ * the new parent
+ */
+ protected void setParent(GraphElement parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * Returns the label.
+ *
+ * @return the label
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Sets the label.
+ *
+ * @param label
+ * the new label
+ */
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Returns the labelPosition.
+ *
+ * @return the labelPosition
+ */
+ public Point getLabelPosition() {
+ return labelPosition;
+ }
+
+ /**
+ * Sets the labelPosition.
+ *
+ * @param labelPosition
+ * the new labelPosition
+ */
+ public void setLabelPosition(Point labelPosition) {
+ this.labelPosition = labelPosition;
+ }
+
+ /**
+ * Returns the id.
+ *
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the id.
+ *
+ * @param id
+ * the new id
+ */
+ public void setId(String id) {
+ if (graphController != null) {
+ graphController.mapElement(id, this);
+ }
+ this.id = id;
+ }
+
+ /**
+ * Returns the colour.
+ *
+ * @return the colour
+ */
+ public Color getColor() {
+ return color;
+ }
+
+ /**
+ * Sets the colour.
+ *
+ * @param color
+ * the new colour
+ */
+ public void setColor(Color color) {
+ this.color = color;
+ }
+
+ /**
+ * Returns the fillColor.
+ *
+ * @return the fillColor
+ */
+ public Color getFillColor() {
+ return fillColor;
+ }
+
+ /**
+ * Sets the fillColor.
+ *
+ * @param fillColor
+ * the new fillColor
+ */
+ public void setFillColor(Color fillColor) {
+ this.fillColor = fillColor;
+ }
+
+ /**
+ * Returns the lineStyle.
+ *
+ * @return the lineStyle
+ */
+ public LineStyle getLineStyle() {
+ return lineStyle;
+ }
+
+ /**
+ * Sets the lineStyle.
+ *
+ * @param lineStyle
+ * the new lineStyle
+ */
+ public void setLineStyle(LineStyle lineStyle) {
+ this.lineStyle = lineStyle;
+ }
+
+ @Override
+ public String toString() {
+ return id + "[" + label + "]";
+ }
+
+ /**
+ * Returns the selected.
+ *
+ * @return the selected
+ */
+ public boolean isSelected() {
+ return selected;
+ }
+
+ /**
+ * Sets the selected.
+ *
+ * @param selected
+ * the new selected
+ */
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * Returns the iteration.
+ *
+ * @return the value of iteration
+ */
+ public int getIteration() {
+ return iteration;
+ }
+
+ /**
+ * Sets the iteration.
+ *
+ * @param iteration
+ * the new value for iteration
+ */
+ public void setIteration(int iteration) {
+ this.iteration = iteration;
+ }
+
+ /**
+ * Returns the errors.
+ *
+ * @return the value of errors
+ */
+ public int getErrors() {
+ return errors;
+ }
+
+ /**
+ * Sets the errors.
+ *
+ * @param errors
+ * the new value for errors
+ */
+ public void setErrors(int errors) {
+ this.errors = errors;
+ }
+
+ /**
+ * Returns the completed.
+ *
+ * @return the value of completed
+ */
+ public float getCompleted() {
+ return completed;
+ }
+
+ /**
+ * Sets the completed value.
+ *
+ * @param completed
+ */
+ public void setCompleted(float completed) {
+ this.completed = completed;
+ }
+
+ /**
+ * Returns <code>true</code> if the element is active. The default value is
+ * <code>false</code>.
+ *
+ * @return <code>true</code> if the element is active
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Sets the value of active.
+ *
+ * @param active
+ * the new active
+ */
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ /**
+ * Returns <code>true</code> if the element is interactive. The default
+ * value is <code>false</code>.
+ *
+ * @return <code>true</code> if the element is interactive
+ */
+ public boolean isInteractive() {
+ return interactive;
+ }
+
+ /**
+ * Sets the value of interactive.
+ *
+ * @param interactive
+ * the new interactive
+ */
+ public void setInteractive(boolean interactive) {
+ this.interactive = interactive;
+ }
+
+ /**
+ * Returns <code>true</code> if the element is visible. The default value is
+ * <code>true</code>.
+ *
+ * @return <code>true</code> if the element is visible
+ */
+ public boolean isVisible() {
+ return visible;
+ }
+
+ /**
+ * Sets whether the element is visible.
+ *
+ * @param visible
+ * the new value for visible
+ */
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ }
+
+ /**
+ * Returns the opacity value. The default value is 1.0
+ *
+ * @return the opacity value
+ */
+ public float getOpacity() {
+ return opacity;
+ }
+
+ /**
+ * Sets the opacity of the element. Must be a value between 0.0 and 1.0.
+ *
+ * @param opacity
+ * the new opacity value
+ */
+ public void setOpacity(float opacity) {
+ this.opacity = opacity;
+ }
+
+ /**
+ * Returns <code>true</code> if the element is filtered.
+ *
+ * @return <code>true</code> if the element is filtered
+ */
+ public boolean isFiltered() {
+ return filtered;
+ }
+
+ /**
+ * Sets the value of filtered.
+ *
+ * @param filtered
+ * the new value for filtered
+ */
+ public void setFiltered(boolean filtered) {
+ this.filtered = filtered;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+
+ // Equality by id
+ GraphElement other = (GraphElement) obj;
+ if (id == null)
+ return (other.id == null);
+ return id.equals(other.id);
+ }
+
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphEventManager.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphEventManager.java
new file mode 100644
index 0000000..a6b9b0e
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphEventManager.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+public interface GraphEventManager {
+ void mouseClicked(GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+ int screenY);
+
+ void mouseDown(GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+ int screenY);
+
+ void mouseUp(GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, final int x, final int y,
+ int screenX, int screenY);
+
+ void mouseMoved(GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+ int screenY);
+
+ void mouseOver(GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+ int screenY);
+
+ void mouseOut(GraphElement graphElement, short button, boolean altKey,
+ boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+ int screenY);
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java
new file mode 100644
index 0000000..3f3f85f
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A node of a graph that can optionally contain other graphs.
+ *
+ * @author David Withers
+ */
+public class GraphNode extends GraphShapeElement {
+ private List<GraphNode> sourceNodes = new ArrayList<>();
+ private List<GraphNode> sinkNodes = new ArrayList<>();
+ private Graph graph;
+ private boolean expanded;
+
+ /**
+ * Constructs a new instance of Node.
+ *
+ */
+ public GraphNode(GraphController graphController) {
+ super(graphController);
+ }
+
+ /**
+ * Adds a sink node.
+ *
+ * @param sinkNode
+ * the sink node to add
+ */
+ public void addSinkNode(GraphNode sinkNode) {
+ sinkNode.setParent(this);
+ sinkNodes.add(sinkNode);
+ }
+
+ /**
+ * Adds a source node.
+ *
+ * @param sourceNode
+ * the source node to add
+ */
+ public void addSourceNode(GraphNode sourceNode) {
+ sourceNode.setParent(this);
+ sourceNodes.add(sourceNode);
+ }
+
+ /**
+ * Returns the graph that this node contains.
+ *
+ * @return the graph that this node contains
+ */
+ public Graph getGraph() {
+ return graph;
+ }
+
+ /**
+ * Returns the sinkNodes.
+ *
+ * @return the sinkNodes
+ */
+ public List<GraphNode> getSinkNodes() {
+ return sinkNodes;
+ }
+
+ /**
+ * Returns the sourceNodes.
+ *
+ * @return the sourceNodes
+ */
+ public List<GraphNode> getSourceNodes() {
+ return sourceNodes;
+ }
+
+ /**
+ * Returns true if this node is expanded to show the contained graph.
+ *
+ * @return true if this node is expanded
+ */
+ public boolean isExpanded() {
+ return expanded;
+ }
+
+ /**
+ * Removes a sink node.
+ *
+ * @param sinkNode
+ * the node to remove
+ * @return true if the node was removed, false otherwise
+ */
+ public boolean removeSinkNode(GraphNode sinkNode) {
+ return sinkNodes.remove(sinkNode);
+ }
+
+ /**
+ * Removes a source node.
+ *
+ * @param sourceNode
+ * the node to remove
+ * @return true if the node was removed, false otherwise
+ */
+ public boolean removeSourceNode(GraphNode sourceNode) {
+ return sourceNodes.remove(sourceNode);
+ }
+
+ /**
+ * Sets whether this node is expanded to show the contained graph.
+ *
+ * @param expanded
+ * true if this node is expanded
+ */
+ public void setExpanded(boolean expanded) {
+ this.expanded = expanded;
+ }
+
+ /**
+ * Sets the graph that this node contains.
+ *
+ * @param graph
+ * the new graph
+ */
+ public void setGraph(Graph graph) {
+ if (graph != null)
+ graph.setParent(this);
+ this.graph = graph;
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ if (isExpanded())
+ getGraph().setSelected(selected);
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java
new file mode 100644
index 0000000..1bb8b6d
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import java.awt.Dimension;
+import java.awt.Point;
+
+/**
+ * A Graph element that has shape, size and position properties.
+ *
+ * @author David Withers
+ */
+public class GraphShapeElement extends GraphElement {
+ public enum Shape {
+ BOX, RECORD, HOUSE, INVHOUSE, DOT, CIRCLE, TRIANGLE, INVTRIANGLE
+ }
+
+ private Shape shape;
+ private int x, y, width, height;
+
+ public GraphShapeElement(GraphController graphController) {
+ super(graphController);
+ }
+
+ /**
+ * Returns the height.
+ *
+ * @return the height
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the position.
+ *
+ * @return the position
+ */
+ public Point getPosition() {
+ return new Point(x, y);
+ }
+
+ /**
+ * Returns the shape of the element.
+ *
+ * @return the shape of the element
+ */
+ public Shape getShape() {
+ return shape;
+ }
+
+ /**
+ * Returns the width.
+ *
+ * @return the width
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Sets the position.
+ *
+ * @param position
+ * the new position
+ */
+ public void setPosition(Point position) {
+ x = position.x;
+ y = position.y;
+ }
+
+ /**
+ * Sets the shape of the element.
+ *
+ * @param shape
+ * the new shape of the element
+ */
+ public void setShape(Shape shape) {
+ this.shape = shape;
+ }
+
+ /**
+ * Returns the size of the element.
+ *
+ * @return the size of the element
+ */
+ public Dimension getSize() {
+ return new Dimension(width, height);
+ }
+
+ /**
+ * Sets the size of the element.
+ *
+ * @param size
+ * the new size of the node
+ */
+ public void setSize(Dimension size) {
+ width = size.width;
+ height = size.height;
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java
new file mode 100644
index 0000000..1205953
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * 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.models.graph.dot;
+
+import static java.lang.Float.parseFloat;
+import static net.sf.taverna.t2.workbench.models.graph.Graph.Alignment.HORIZONTAL;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Lays out a graph from a DOT layout.
+ *
+ * @author David Withers
+ */
+public class GraphLayout implements DOTParserVisitor {
+ private static final Logger logger = Logger.getLogger(GraphLayout.class);
+ private static final int BORDER = 10;
+
+ private Rectangle bounds;
+ private Rectangle requiredBounds;
+ private GraphController graphController;
+ private int xOffset;
+ private int yOffset;
+
+ public Rectangle layoutGraph(GraphController graphController, Graph graph,
+ String laidOutDot, Rectangle requiredBounds) throws ParseException {
+ this.graphController = graphController;
+ this.requiredBounds = requiredBounds;
+
+ bounds = null;
+ xOffset = 0;
+ yOffset = 0;
+
+ logger.debug(laidOutDot);
+ DOTParser parser = new DOTParser(new StringReader(laidOutDot));
+ parser.parse().jjtAccept(this, graph);
+
+ // int xOffset = (bounds.width - bounds.width) / 2;
+ // int yOffset = (bounds.height - bounds.height) / 2;
+
+ return new Rectangle(xOffset, yOffset, bounds.width, bounds.height);
+ }
+
+ @Override
+ public Object visit(SimpleNode node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTParse node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTGraph node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTStatementList node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTStatement node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTAttributeStatement node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTNodeStatement node, Object data) {
+ GraphElement element = graphController.getElement(removeQuotes(node
+ .getName()));
+ if (element != null)
+ return node.childrenAccept(this, element);
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTNodeId node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTPort node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTEdgeStatement node, Object data) {
+ StringBuilder id = new StringBuilder();
+ id.append(removeQuotes(node.getName()));
+ if (node.getPort() != null) {
+ id.append(":");
+ id.append(removeQuotes(node.getPort()));
+ }
+ if (node.children != null)
+ for (Node child : node.children)
+ if (child instanceof ASTEdgeRHS) {
+ NamedNode rhsNode = (NamedNode) child.jjtAccept(this, data);
+ id.append("->");
+ id.append(removeQuotes(rhsNode.getName()));
+ if (rhsNode.getPort() != null) {
+ id.append(":");
+ id.append(removeQuotes(rhsNode.getPort()));
+ }
+ }
+ GraphElement element = graphController.getElement(id.toString());
+ if (element != null)
+ return node.childrenAccept(this, element);
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTSubgraph node, Object data) {
+ GraphElement element = graphController.getElement(removeQuotes(
+ node.getName()).substring("cluster_".length()));
+ if (element != null)
+ return node.childrenAccept(this, element);
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTEdgeRHS node, Object data) {
+ return node;
+ }
+
+ @Override
+ public Object visit(ASTAttributeList node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ public Object visit(ASTAList node, Object data) {
+ if (data instanceof Graph) {
+ Graph graph = (Graph) data;
+ if ("bb".equalsIgnoreCase(node.getName())) {
+ Rectangle rect = getRectangle(node.getValue());
+ if (rect.width == 0 && rect.height == 0) {
+ rect.width = 500;
+ rect.height = 500;
+ }
+ if (bounds == null) {
+ bounds = calculateBounds(rect);
+ rect = bounds;
+ }
+ graph.setSize(rect.getSize());
+ graph.setPosition(rect.getLocation());
+ } else if ("lp".equalsIgnoreCase(node.getName())) {
+ if (bounds != null)
+ graph.setLabelPosition(getPoint(node.getValue()));
+ }
+ } else if (data instanceof GraphNode) {
+ GraphNode graphNode = (GraphNode) data;
+ if ("width".equalsIgnoreCase(node.getName()))
+ graphNode.setSize(new Dimension(getSize(node.getValue()),
+ graphNode.getHeight()));
+ else if ("height".equalsIgnoreCase(node.getName()))
+ graphNode.setSize(new Dimension(graphNode.getWidth(),
+ getSize(node.getValue())));
+ else if ("pos".equalsIgnoreCase(node.getName())) {
+ Point position = getPoint(node.getValue());
+ position.x = position.x - (graphNode.getWidth() / 2);
+ position.y = position.y - (graphNode.getHeight() / 2);
+ graphNode.setPosition(position);
+ } else if ("rects".equalsIgnoreCase(node.getName())) {
+ List<Rectangle> rectangles = getRectangles(node.getValue());
+ List<GraphNode> sinkNodes = graphNode.getSinkNodes();
+ if (graphController.getAlignment().equals(HORIZONTAL)) {
+ Rectangle rect = rectangles.remove(0);
+ graphNode.setSize(rect.getSize());
+ graphNode.setPosition(rect.getLocation());
+ } else {
+ Rectangle rect = rectangles.remove(sinkNodes.size());
+ graphNode.setSize(rect.getSize());
+ graphNode.setPosition(rect.getLocation());
+ }
+ Point origin = graphNode.getPosition();
+ for (GraphNode sinkNode : sinkNodes) {
+ Rectangle rect = rectangles.remove(0);
+ rect.setLocation(rect.x - origin.x, rect.y - origin.y);
+ sinkNode.setSize(rect.getSize());
+ sinkNode.setPosition(rect.getLocation());
+ }
+ for (GraphNode sourceNode : graphNode.getSourceNodes()) {
+ Rectangle rect = rectangles.remove(0);
+ rect.setLocation(rect.x - origin.x, rect.y - origin.y);
+ sourceNode.setSize(rect.getSize());
+ sourceNode.setPosition(rect.getLocation());
+ }
+ }
+ } else if (data instanceof GraphEdge) {
+ GraphEdge graphEdge = (GraphEdge) data;
+ if ("pos".equalsIgnoreCase(node.getName()))
+ graphEdge.setPath(getPath(node.getValue()));
+ }
+ return node.childrenAccept(this, data);
+ }
+
+ private Rectangle calculateBounds(Rectangle bounds) {
+ bounds = new Rectangle(bounds);
+ bounds.width += BORDER;
+ bounds.height += BORDER;
+ Rectangle newBounds = new Rectangle(bounds);
+ double ratio = bounds.width / (float) bounds.height;
+ double requiredRatio = requiredBounds.width
+ / (float) requiredBounds.height;
+ // adjust the bounds so they match the aspect ration of the required bounds
+ if (ratio > requiredRatio)
+ newBounds.height = (int) (ratio / requiredRatio * bounds.height);
+ else if (ratio < requiredRatio)
+ newBounds.width = (int) (requiredRatio / ratio * bounds.width);
+
+ xOffset = (newBounds.width - bounds.width) / 2;
+ yOffset = (newBounds.height - bounds.height) / 2;
+ // adjust the bounds and so they are not less than the required bounds
+ if (newBounds.width < requiredBounds.width) {
+ xOffset += (requiredBounds.width - newBounds.width) / 2;
+ newBounds.width = requiredBounds.width;
+ }
+ if (newBounds.height < requiredBounds.height) {
+ yOffset += (requiredBounds.height - newBounds.height) / 2;
+ newBounds.height = requiredBounds.height;
+ }
+ // adjust the offset for the border
+ xOffset += BORDER / 2;
+ yOffset += BORDER / 2;
+ return newBounds;
+ }
+
+ private List<Point> getPath(String value) {
+ List<Point> path = new ArrayList<>();
+ for (String point : removeQuotes(value).split(" ")) {
+ String[] coords = point.split(",");
+ if (coords.length == 2) {
+ int x = (int) parseFloat(coords[0]) + xOffset;
+ int y = (int) parseFloat(coords[1]) + yOffset;
+ path.add(new Point(x, flipY(y)));
+ }
+ }
+ return path;
+ }
+
+ private int flipY(int y) {
+ return bounds.height - y;
+ }
+
+ private List<Rectangle> getRectangles(String value) {
+ List<Rectangle> rectangles = new ArrayList<>();
+ String[] rects = value.split(" ");
+ for (String rectangle : rects)
+ rectangles.add(getRectangle(rectangle));
+ return rectangles;
+ }
+
+ private Rectangle getRectangle(String value) {
+ String[] coords = removeQuotes(value).split(",");
+ Rectangle rectangle = new Rectangle();
+ rectangle.x = (int) parseFloat(coords[0]);
+ rectangle.y = (int) parseFloat(coords[3]);
+ rectangle.width = (int) parseFloat(coords[2]) - rectangle.x;
+ rectangle.height = rectangle.y - (int) parseFloat(coords[1]);
+ rectangle.x += xOffset;
+ rectangle.y += yOffset;
+ if (bounds != null)
+ rectangle.y = flipY(rectangle.y);
+ else
+ rectangle.y = rectangle.height - rectangle.y;
+ return rectangle;
+ }
+
+ private Point getPoint(String value) {
+ String[] coords = removeQuotes(value).split(",");
+ return new Point(xOffset + (int) parseFloat(coords[0]), flipY(yOffset
+ + (int) parseFloat(coords[1])));
+ }
+
+ private int getSize(String value) {
+ return (int) (parseFloat(removeQuotes(value)) * 72);
+ }
+
+ private String removeQuotes(String value) {
+ String result = value.trim();
+ if (result.startsWith("\""))
+ result = result.substring(1);
+ if (result.endsWith("\""))
+ result = result.substring(0, result.length() - 1);
+ result = result.replaceAll("\\\\", "");
+ return result;
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java
new file mode 100644
index 0000000..be92cdd
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java
@@ -0,0 +1,439 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.COMPLETED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculatePoints;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static org.apache.batik.util.CSSConstants.CSS_BLACK_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TRANSFORM_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_CLICK_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_END_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_FAMILY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_SIZE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_MIDDLE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEMOVE_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEUP_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_TRANSLATE;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseClickEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseMovedEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOutEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOverEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseUpEventListener;
+
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.apache.batik.dom.svg.SVGOMTextElement;
+import org.w3c.dom.Text;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.svg.SVGElement;
+
+/**
+ * SVG representation of a graph.
+ *
+ * @author David Withers
+ */
+public class SVGGraph extends Graph {
+ private SVGGraphController graphController;
+ private SVGGraphElementDelegate delegate;
+ private SVGMouseClickEventListener mouseClickAction;
+ private SVGMouseMovedEventListener mouseMovedAction;
+ private SVGMouseUpEventListener mouseUpAction;
+ @SuppressWarnings("unused")
+ private SVGMouseOverEventListener mouseOverAction;
+ @SuppressWarnings("unused")
+ private SVGMouseOutEventListener mouseOutAction;
+ private SVGOMGElement mainGroup, labelGroup;
+ private SVGOMPolygonElement polygon, completedPolygon;
+ private SVGOMTextElement label, iteration, error;
+ private Text labelText, iterationText, errorsText;
+ private SVGOMAnimationElement animateShape, animatePosition, animateLabel;
+
+ public SVGGraph(SVGGraphController graphController) {
+ super(graphController);
+ this.graphController = graphController;
+
+ mouseClickAction = new SVGMouseClickEventListener(this);
+ mouseMovedAction = new SVGMouseMovedEventListener(this);
+ mouseUpAction = new SVGMouseUpEventListener(this);
+ mouseOverAction = new SVGMouseOverEventListener(this);
+ mouseOutAction = new SVGMouseOutEventListener(this);
+
+ mainGroup = graphController.createGElem();
+ mainGroup.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "10");
+ mainGroup.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "Helvetica");
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, CSS_BLACK_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, CSS_NONE_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+ mainGroup.setAttribute(SVG_FILL_ATTRIBUTE, CSS_NONE_VALUE);
+
+ EventTarget t = (EventTarget) mainGroup;
+ t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+ t.addEventListener(SVG_MOUSEMOVE_EVENT_TYPE, mouseMovedAction, false);
+ t.addEventListener(SVG_MOUSEUP_EVENT_TYPE, mouseUpAction, false);
+ // t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+ // t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+
+ polygon = graphController.createPolygon();
+ mainGroup.appendChild(polygon);
+
+ completedPolygon = graphController.createPolygon();
+ completedPolygon.setAttribute(SVG_POINTS_ATTRIBUTE,
+ calculatePoints(getShape(), 0, 0));
+ completedPolygon.setAttribute(SVG_FILL_ATTRIBUTE, COMPLETED_COLOUR);
+ // completedPolygon.setAttribute(SVGConstants.SVG_FILL_OPACITY_ATTRIBUTE, "0.8");
+ // completedPolygon.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+ mainGroup.appendChild(completedPolygon);
+
+ labelText = graphController.createText("");
+ label = graphController.createText(labelText);
+ label.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_MIDDLE_VALUE);
+ label.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+ label.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+ labelGroup = graphController.createGElem();
+ labelGroup.appendChild(label);
+ mainGroup.appendChild(labelGroup);
+
+ iterationText = graphController.createText("");
+ iteration = graphController.createText(iterationText);
+ iteration.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_END_VALUE);
+ iteration.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "6");
+ iteration.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+ iteration.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+ iteration.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+ polygon.appendChild(iteration);
+
+ errorsText = graphController.createText("");
+ error = graphController.createText(errorsText);
+ error.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_END_VALUE);
+ error.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "6");
+ error.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+ error.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+ error.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+ polygon.appendChild(error);
+
+ animateShape = createAnimationElement(graphController, SVG_ANIMATE_TAG,
+ SVG_POINTS_ATTRIBUTE, null);
+
+ animatePosition = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ animateLabel = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ delegate = new SVGGraphElementDelegate(graphController, this, mainGroup);
+ }
+
+ public SVGElement getSVGElement() {
+ return mainGroup;
+ }
+
+ @Override
+ public void addEdge(GraphEdge edge) {
+ if (edge instanceof SVGGraphEdge) {
+ final SVGGraphEdge svgGraphEdge = (SVGGraphEdge) edge;
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ float opacity = svgGraphEdge.getOpacity();
+ svgGraphEdge.setOpacity(0);
+ mainGroup.appendChild(svgGraphEdge.getSVGElement());
+ svgGraphEdge.setOpacity(opacity);
+ }
+ });
+ }
+ super.addEdge(edge);
+ }
+
+ @Override
+ public void addNode(GraphNode node) {
+ super.addNode(node);
+ if (node instanceof SVGGraphNode) {
+ final SVGGraphNode svgGraphNode = (SVGGraphNode) node;
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ float opacity = svgGraphNode.getOpacity();
+ svgGraphNode.setOpacity(0);
+ mainGroup.appendChild(svgGraphNode.getSVGElement());
+ svgGraphNode.setOpacity(opacity);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void addSubgraph(Graph subgraph) {
+ super.addSubgraph(subgraph);
+ if (subgraph instanceof SVGGraph) {
+ final SVGGraph svgGraph = (SVGGraph) subgraph;
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ float opacity = svgGraph.getOpacity();
+ svgGraph.setOpacity(0);
+ mainGroup.appendChild(svgGraph.getSVGElement());
+ svgGraph.setOpacity(opacity);
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean removeEdge(GraphEdge edge) {
+ if (edge instanceof SVGGraphEdge) {
+ final SVGGraphEdge svgGraphEdge = (SVGGraphEdge) edge;
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.removeChild(svgGraphEdge.getSVGElement());
+ }
+ });
+ }
+ return super.removeEdge(edge);
+ }
+
+ @Override
+ public boolean removeNode(GraphNode node) {
+ if (node instanceof SVGGraphNode) {
+ final SVGGraphNode svgGraphNode = (SVGGraphNode) node;
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.removeChild(svgGraphNode.getSVGElement());
+ }
+ });
+ }
+ return super.removeNode(node);
+ }
+
+ @Override
+ public boolean removeSubgraph(Graph subgraph) {
+ if (subgraph instanceof SVGGraph) {
+ final SVGGraph svgGraph = (SVGGraph) subgraph;
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.removeChild(svgGraph.getSVGElement());
+ }
+ });
+ }
+ return super.removeSubgraph(subgraph);
+ }
+
+ @Override
+ public void setPosition(final Point position) {
+ final Point oldPosition = getPosition();
+ if (position != null && !position.equals(oldPosition)) {
+ super.setPosition(position);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (graphController.isAnimatable())
+ animate(animatePosition, polygon,
+ graphController.getAnimationSpeed(),
+ oldPosition.x + ", " + oldPosition.y,
+ position.x + ", " + position.y);
+ else
+ polygon.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + position.x + " " + position.y
+ + ")");
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setSize(final Dimension size) {
+ final Dimension oldSize = getSize();
+ if (size != null && !size.equals(oldSize)) {
+ super.setSize(size);
+ updateShape(oldSize.width, oldSize.height);
+ }
+ }
+
+ @Override
+ public void setShape(Shape shape) {
+ final Dimension oldSize = getSize();
+ final Shape currentShape = getShape();
+ if (shape != null && !shape.equals(currentShape)) {
+ super.setShape(shape);
+ updateShape(oldSize.width, oldSize.height);
+ }
+ }
+
+ @Override
+ public void setLabel(final String label) {
+ super.setLabel(label);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ labelText.setData(label);
+ }
+ });
+ }
+
+ @Override
+ public void setLabelPosition(final Point labelPosition) {
+ final Point oldLabelPosition = getLabelPosition();
+ if (labelPosition != null && !labelPosition.equals(oldLabelPosition)) {
+ super.setLabelPosition(labelPosition);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (graphController.isAnimatable()
+ && oldLabelPosition != null)
+ animate(animateLabel, labelGroup,
+ graphController.getAnimationSpeed(),
+ oldLabelPosition.x + ", " + oldLabelPosition.y,
+ labelPosition.x + ", " + labelPosition.y);
+ else
+ labelGroup.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + labelPosition.x + " "
+ + labelPosition.y + ")");
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setIteration(final int iteration) {
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (iteration > 0)
+ iterationText.setData(String.valueOf(iteration));
+ else
+ iterationText.setData("");
+ }
+ });
+ }
+
+ @Override
+ public void setCompleted(final float complete) {
+ super.setCompleted(complete);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ Dimension size = getSize();
+ Point position = getPosition();
+ completedPolygon.setAttribute(
+ SVG_POINTS_ATTRIBUTE,
+ calculatePoints(getShape(),
+ (int) (size.width * complete), size.height));
+ completedPolygon.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + position.x + " " + position.y + ")");
+ }
+ });
+ }
+
+ private void updateShape(final int oldWidth, final int oldHeight) {
+ if (getShape() != null && getWidth() > 0f && getHeight() > 0f) {
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (graphController.isAnimatable())
+ animate(animateShape,
+ polygon,
+ graphController.getAnimationSpeed(),
+ calculatePoints(getShape(), oldWidth, oldHeight),
+ calculatePoints(getShape(), getWidth(),
+ getHeight()));
+ else {
+ polygon.setAttribute(
+ SVG_POINTS_ATTRIBUTE,
+ calculatePoints(getShape(), getWidth(),
+ getHeight()));
+ iteration.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + (getWidth() - 1.5) + " 5.5)");
+ error.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + (getWidth() - 1.5) + " "
+ + (getHeight() - 1) + ")");
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setSelected(final boolean selected) {
+ delegate.setSelected(selected);
+ super.setSelected(selected);
+ }
+
+ @Override
+ public void setLineStyle(final LineStyle lineStyle) {
+ delegate.setLineStyle(lineStyle);
+ super.setLineStyle(lineStyle);
+ }
+
+ @Override
+ public void setColor(final Color color) {
+ delegate.setColor(color);
+ super.setColor(color);
+ }
+
+ @Override
+ public void setFillColor(final Color fillColor) {
+ delegate.setFillColor(fillColor);
+ super.setFillColor(fillColor);
+ }
+
+ @Override
+ public void setVisible(final boolean visible) {
+ delegate.setVisible(visible);
+ super.setVisible(visible);
+ }
+
+ @Override
+ public void setFiltered(final boolean filtered) {
+ delegate.setFiltered(filtered);
+ super.setFiltered(filtered);
+ }
+
+ @Override
+ public void setOpacity(final float opacity) {
+ delegate.setOpacity(opacity);
+ super.setOpacity(opacity);
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java
new file mode 100644
index 0000000..a4c8e4c
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java
@@ -0,0 +1,555 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+import static java.awt.Color.BLACK;
+import static java.awt.Color.GREEN;
+import static java.lang.Float.parseFloat;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculateAngle;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createSVGDocument;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getDot;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getHexValue;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.svgNS;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ELLIPSE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_FAMILY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_SIZE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_G_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_LINE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_PATH_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_POLYGON_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_RECT_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_STYLE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TEXT_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_VIEW_BOX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X1_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X2_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y1_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y2_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y_ATTRIBUTE;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.models.graph.DotWriter;
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+import net.sf.taverna.t2.workbench.models.graph.dot.GraphLayout;
+import net.sf.taverna.t2.workbench.models.graph.dot.ParseException;
+
+import org.apache.batik.bridge.UpdateManager;
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMEllipseElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPathElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.apache.batik.dom.svg.SVGOMRectElement;
+import org.apache.batik.dom.svg.SVGOMTextElement;
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
+import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGElement;
+import org.w3c.dom.svg.SVGPoint;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+public class SVGGraphController extends GraphController {
+ private static final Logger logger = Logger.getLogger(SVGGraphController.class);
+ @SuppressWarnings("unused")
+ private static final Timer timer = new Timer("SVG Graph controller timer", true);
+ private static final String dotErrorMessage = "Cannot draw diagram(s)\n" +
+ "\n" +
+ "Install dot as described\n" +
+ "at http://www.taverna.org.uk\n" +
+ "and specify its location\n" +
+ "in the workbench preferences";
+
+ private Map<String, List<SVGGraphEdge>> datalinkMap = new HashMap<>();
+ private final JSVGCanvas svgCanvas;
+ private SVGDocument svgDocument;
+ private GraphLayout graphLayout = new GraphLayout();
+ private EdgeLine edgeLine;
+ private UpdateManager updateManager;
+ private ExecutorService executor = Executors.newFixedThreadPool(1);
+ private boolean drawingDiagram = false;
+ private int animationSpeed;
+ private Rectangle bounds, oldBounds;
+ private SVGOMAnimationElement animateBounds;
+ private boolean dotMissing = false;
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ public SVGGraphController(Workflow dataflow, Profile profile,
+ boolean interactive, JSVGCanvas svgCanvas, EditManager editManager,
+ MenuManager menuManager, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration) {
+ super(dataflow, profile, interactive, svgCanvas, editManager,
+ menuManager, colourManager);
+ this.svgCanvas = svgCanvas;
+ this.workbenchConfiguration = workbenchConfiguration;
+ installUpdateManager();
+ layoutSVGDocument(svgCanvas.getBounds());
+ svgCanvas.setDocument(getSVGDocument());
+ }
+
+ public SVGGraphController(Workflow dataflow, Profile profile,
+ boolean interactive, JSVGCanvas svgCanvas, Alignment alignment,
+ PortStyle portStyle, EditManager editManager,
+ MenuManager menuManager, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration) {
+ super(dataflow, profile, interactive, svgCanvas, alignment, portStyle,
+ editManager, menuManager, colourManager);
+ this.svgCanvas = svgCanvas;
+ this.workbenchConfiguration = workbenchConfiguration;
+ installUpdateManager();
+ layoutSVGDocument(svgCanvas.getBounds());
+ svgCanvas.setDocument(getSVGDocument());
+ }
+
+ private void installUpdateManager() {
+ svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
+ @Override
+ public void gvtRenderingCompleted(GVTTreeRendererEvent ev) {
+ setUpdateManager(svgCanvas.getUpdateManager());
+ }
+ });
+ }
+
+ @Override
+ public GraphEdge createGraphEdge() {
+ return new SVGGraphEdge(this);
+ }
+
+ @Override
+ public Graph createGraph() {
+ return new SVGGraph(this);
+ }
+
+ @Override
+ public GraphNode createGraphNode() {
+ return new SVGGraphNode(this);
+ }
+
+ public JSVGCanvas getSVGCanvas() {
+ return svgCanvas;
+ }
+
+ public synchronized SVGDocument getSVGDocument() {
+ if (svgDocument == null)
+ svgDocument = createSVGDocument();
+ return svgDocument;
+ }
+
+ @Override
+ public void redraw() {
+ Graph graph = generateGraph();
+ Rectangle actualBounds = layoutGraph(graph, svgCanvas.getBounds());
+ setBounds(actualBounds);
+ transformGraph(getGraph(), graph);
+ }
+
+ private void layoutSVGDocument(Rectangle bounds) {
+ animateBounds = createAnimationElement(this, SVG_ANIMATE_TAG,
+ SVG_VIEW_BOX_ATTRIBUTE, null);
+ updateManager = null;
+ datalinkMap.clear();
+
+ Graph graph = getGraph();
+ if (graph instanceof SVGGraph) {
+ SVGGraph svgGraph = (SVGGraph) graph;
+ SVGSVGElement svgElement = getSVGDocument().getRootElement();
+ SVGElement graphElement = svgGraph.getSVGElement();
+ svgElement.appendChild(graphElement);
+
+ setBounds(layoutGraph(graph, bounds));
+
+ edgeLine = EdgeLine.createAndAdd(getSVGDocument(), this);
+ }
+ drawingDiagram = true;
+ }
+
+ public Rectangle layoutGraph(Graph graph, Rectangle bounds) {
+ Rectangle actualBounds = null;
+ bounds = new Rectangle(bounds);
+ StringWriter stringWriter = new StringWriter();
+ DotWriter dotWriter = new DotWriter(stringWriter);
+ try {
+ dotWriter.writeGraph(graph);
+ String layout = getDot(stringWriter.toString(), workbenchConfiguration);
+ if (layout.isEmpty())
+ logger.warn("Invalid dot returned");
+ else
+ actualBounds = graphLayout.layoutGraph(this, graph, layout, bounds);
+ } catch (IOException e) {
+ outputMessage(dotErrorMessage);
+ setDotMissing(true);
+ logger.warn("Couldn't generate dot");
+ } catch (ParseException e) {
+ logger.warn("Couldn't layout graph", e);
+ }
+ return actualBounds;
+ }
+
+ private void setDotMissing(boolean b) {
+ this.dotMissing = b;
+ }
+
+ public boolean isDotMissing() {
+ return dotMissing;
+ }
+
+ public void setBounds(final Rectangle bounds) {
+ oldBounds = this.bounds;
+ this.bounds = bounds;
+ updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ SVGSVGElement svgElement = getSVGDocument().getRootElement();
+ if (isAnimatable() && oldBounds != null) {
+ String from = "0 0 " + oldBounds.width + " "
+ + oldBounds.height;
+ String to = "0 0 " + bounds.width + " " + bounds.height;
+ animate(animateBounds, svgElement, getAnimationSpeed(),
+ from, to);
+ } else if ((svgElement != null) && (bounds != null))
+ svgElement.setAttribute(SVG_VIEW_BOX_ATTRIBUTE,
+ "0 0 " + String.valueOf(bounds.width) + " "
+ + String.valueOf(bounds.height));
+ }
+ });
+ }
+
+ private void outputMessage(final String message) {
+ SVGSVGElement svgElement = getSVGDocument().getRootElement();
+ String[] parts = message.split("\n");
+ int initialPosition = 200;
+ for (int i = 0; i < parts.length; i++) {
+ Text errorsText = createText(parts[i]);
+ SVGOMTextElement error = (SVGOMTextElement) createElement(SVG_TEXT_TAG);
+ error.setAttribute(SVG_Y_ATTRIBUTE,
+ Integer.toString(initialPosition + i * 60));
+ error.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "20");
+ error.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+ error.setAttribute(SVG_FILL_ATTRIBUTE, "red");
+ error.appendChild(errorsText);
+ svgElement.appendChild(error);
+ }
+ bounds = new Rectangle(300, parts.length * 60 + 200);
+ svgCanvas.setDocument(getSVGDocument());
+ }
+
+ public void setUpdateManager(UpdateManager updateManager) {
+ this.updateManager = updateManager;
+ drawingDiagram = false;
+ resetSelection();
+ }
+
+ @Override
+ public boolean startEdgeCreation(GraphElement graphElement, Point point) {
+ boolean alreadyStarted = edgeCreationFromSource || edgeCreationFromSink;
+ boolean started = super.startEdgeCreation(graphElement, point);
+ if (!alreadyStarted && started) {
+ if (edgeMoveElement instanceof SVGGraphEdge) {
+ SVGGraphEdge svgGraphEdge = (SVGGraphEdge) edgeMoveElement;
+ SVGPoint sourcePoint = svgGraphEdge.getPathElement()
+ .getPointAtLength(0f);
+ edgeLine.setSourcePoint(new Point((int) sourcePoint.getX(),
+ (int) sourcePoint.getY()));
+ } else
+ edgeLine.setSourcePoint(point);
+ edgeLine.setTargetPoint(point);
+ edgeLine.setColour(Color.BLACK);
+ // edgeLine.setVisible(true);
+ }
+ return started;
+ }
+
+ @Override
+ public boolean moveEdgeCreationTarget(GraphElement graphElement, Point point) {
+ boolean linkValid = super.moveEdgeCreationTarget(graphElement, point);
+ if (edgeMoveElement instanceof SVGGraphEdge)
+ ((SVGGraphEdge) edgeMoveElement).setVisible(false);
+ if (edgeCreationFromSink) {
+ edgeLine.setSourcePoint(point);
+ if (linkValid)
+ edgeLine.setColour(GREEN);
+ else
+ edgeLine.setColour(BLACK);
+ edgeLine.setVisible(true);
+ } else if (edgeCreationFromSource) {
+ edgeLine.setTargetPoint(point);
+ if (linkValid)
+ edgeLine.setColour(GREEN);
+ else
+ edgeLine.setColour(BLACK);
+ edgeLine.setVisible(true);
+ }
+ return linkValid;
+ }
+
+ @Override
+ public boolean stopEdgeCreation(GraphElement graphElement, Point point) {
+ GraphEdge movedEdge = edgeMoveElement;
+ boolean edgeCreated = super.stopEdgeCreation(graphElement, point);
+ if (!edgeCreated && movedEdge instanceof SVGGraphEdge)
+ ((SVGGraphEdge) movedEdge).setVisible(true);
+ edgeLine.setVisible(false);
+ return edgeCreated;
+ }
+
+ @Override
+ public void setEdgeActive(String edgeId, boolean active) {
+ if (datalinkMap.containsKey(edgeId))
+ for (GraphEdge datalink : datalinkMap.get(edgeId))
+ datalink.setActive(active);
+ }
+
+ public Element createElement(String tag) {
+ return getSVGDocument().createElementNS(svgNS, tag);
+ }
+
+ SVGOMGElement createGElem() {
+ return (SVGOMGElement) createElement(SVG_G_TAG);
+ }
+
+ SVGOMPolygonElement createPolygon() {
+ return (SVGOMPolygonElement) createElement(SVG_POLYGON_TAG);
+ }
+
+ SVGOMEllipseElement createEllipse() {
+ return (SVGOMEllipseElement) createElement(SVG_ELLIPSE_TAG);
+ }
+
+ SVGOMPathElement createPath() {
+ return (SVGOMPathElement) createElement(SVG_PATH_TAG);
+ }
+
+ SVGOMRectElement createRect() {
+ return (SVGOMRectElement) createElement(SVG_RECT_TAG);
+ }
+
+ public Text createText(String text) {
+ return getSVGDocument().createTextNode(text);
+ }
+
+ SVGOMTextElement createText(Text text) {
+ SVGOMTextElement elem = (SVGOMTextElement) createElement(SVG_TEXT_TAG);
+ elem.appendChild(text);
+ return elem;
+ }
+
+ public void updateSVGDocument(final Runnable thread) {
+ if (updateManager == null && !drawingDiagram)
+ thread.run();
+ else if (!executor.isShutdown())
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ waitForUpdateManager();
+ try {
+ updateManager.getUpdateRunnableQueue().invokeLater(
+ thread);
+ } catch (IllegalStateException e) {
+ logger.error("Update of SVG failed", e);
+ }
+ }
+
+ private void waitForUpdateManager() {
+ try {
+ while (updateManager == null)
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ }
+ }
+ });
+// if (updateManager == null)
+// thread.run();
+// else
+// updateManager.getUpdateRunnableQueue().invokeLater(thread);
+ }
+
+ public boolean isAnimatable() {
+ return animationSpeed > 0 && updateManager != null && !drawingDiagram;
+ }
+
+ /**
+ * Returns the animation speed in milliseconds.
+ *
+ * @return the animation speed in milliseconds
+ */
+ public int getAnimationSpeed() {
+ return animationSpeed;
+ }
+
+ /**
+ * Sets the animation speed in milliseconds. A value of 0 turns off animation.
+ *
+ * @param animationSpeed the animation speed in milliseconds
+ */
+ public void setAnimationSpeed(int animationSpeed) {
+ this.animationSpeed = animationSpeed;
+ }
+
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ getSVGCanvas().stopProcessing();
+ executor.shutdown();
+ }
+ });
+ }
+
+}
+
+class EdgeLine {
+ private static final float arrowLength = 10f;
+ private static final float arrowWidth = 3f;
+
+ private Element line;
+ private Element pointer;
+ private SVGGraphController graphController;
+
+ private EdgeLine(SVGGraphController graphController) {
+ this.graphController = graphController;
+ }
+
+ public static EdgeLine createAndAdd(SVGDocument svgDocument, SVGGraphController graphController) {
+ EdgeLine edgeLine = new EdgeLine(graphController);
+ edgeLine.line = svgDocument.createElementNS(svgNS, SVG_LINE_TAG);
+ edgeLine.line.setAttribute(SVG_STYLE_ATTRIBUTE,
+ "fill:none;stroke:black");
+ edgeLine.line.setAttribute("pointer-events", "none");
+ edgeLine.line.setAttribute("visibility", "hidden");
+ edgeLine.line.setAttribute(SVG_X1_ATTRIBUTE, "0");
+ edgeLine.line.setAttribute(SVG_Y1_ATTRIBUTE, "0");
+ edgeLine.line.setAttribute(SVG_X2_ATTRIBUTE, "0");
+ edgeLine.line.setAttribute(SVG_Y2_ATTRIBUTE, "0");
+
+ edgeLine.pointer = svgDocument.createElementNS(svgNS, SVG_POLYGON_TAG);
+ edgeLine.pointer.setAttribute(SVG_STYLE_ATTRIBUTE,
+ "fill:black;stroke:black");
+ edgeLine.pointer.setAttribute(SVG_POINTS_ATTRIBUTE, "0,0 "
+ + -arrowLength + "," + arrowWidth + " " + -arrowLength + ","
+ + -arrowWidth + " 0,0");
+ edgeLine.pointer.setAttribute("pointer-events", "none");
+ edgeLine.pointer.setAttribute("visibility", "hidden");
+
+ Element svgRoot = svgDocument.getDocumentElement();
+ svgRoot.insertBefore(edgeLine.line, null);
+ svgRoot.insertBefore(edgeLine.pointer, null);
+
+ return edgeLine;
+ }
+
+ public void setSourcePoint(final Point point) {
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ line.setAttribute(SVG_X1_ATTRIBUTE,
+ String.valueOf(point.getX()));
+ line.setAttribute(SVG_Y1_ATTRIBUTE,
+ String.valueOf(point.getY()));
+
+ float x = parseFloat(line.getAttribute(SVG_X2_ATTRIBUTE));
+ float y = parseFloat(line.getAttribute(SVG_Y2_ATTRIBUTE));
+ double angle = calculateAngle(line);
+
+ pointer.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate(" + x
+ + " " + y + ") rotate(" + angle + " 0 0) ");
+ }
+ });
+ }
+
+ public void setTargetPoint(final Point point) {
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ line.setAttribute(SVG_X2_ATTRIBUTE,
+ String.valueOf(point.getX()));
+ line.setAttribute(SVG_Y2_ATTRIBUTE,
+ String.valueOf(point.getY()));
+
+ double angle = calculateAngle(line);
+ pointer.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + point.x + " " + point.y + ") rotate(" + angle
+ + " 0 0) ");
+ }
+ });
+ }
+
+ public void setColour(final Color colour) {
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ String hexColour = getHexValue(colour);
+ line.setAttribute(SVG_STYLE_ATTRIBUTE, "fill:none;stroke:"
+ + hexColour + ";");
+ pointer.setAttribute(SVG_STYLE_ATTRIBUTE, "fill:" + hexColour
+ + ";stroke:" + hexColour + ";");
+ }
+ });
+ }
+
+ public void setVisible(final boolean visible) {
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (visible) {
+ line.setAttribute("visibility", "visible");
+ pointer.setAttribute("visibility", "visible");
+ } else {
+ line.setAttribute("visibility", "hidden");
+ pointer.setAttribute("visibility", "hidden");
+ }
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java
new file mode 100644
index 0000000..7884d62
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.SELECTED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.adjustPathLength;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculateAngle;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getHexValue;
+import static org.apache.batik.util.CSSConstants.CSS_BLACK_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_DISPLAY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_INLINE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.SMILConstants.SMIL_ADDITIVE_ATTRIBUTE;
+import static org.apache.batik.util.SMILConstants.SMIL_SUM_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TRANSFORM_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_CLICK_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_CX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_CY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_D_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEDOWN_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_RX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_RY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_ZERO_VALUE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_ROTATE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_TRANSLATE;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseClickEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseDownEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOutEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOverEventListener;
+
+import org.apache.batik.dom.svg.SVGGraphicsElement;
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMEllipseElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPathElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.svg.SVGElement;
+
+/**
+ * SVG representation of a graph edge.
+ *
+ * @author David Withers
+ */
+public class SVGGraphEdge extends GraphEdge {
+ private static final String ARROW_LENGTH = "8.5";
+ private static final String ARROW_WIDTH = "3";
+ private static final String ELLIPSE_RADIUS = "3.5";
+
+ private SVGGraphController graphController;
+ private SVGGraphElementDelegate delegate;
+ private SVGMouseClickEventListener mouseClickAction;
+ private SVGMouseDownEventListener mouseDownAction;
+ @SuppressWarnings("unused")
+ private SVGMouseOverEventListener mouseOverAction;
+ @SuppressWarnings("unused")
+ private SVGMouseOutEventListener mouseOutAction;
+ private SVGOMGElement mainGroup;
+ private SVGOMPathElement path, deleteButton;
+ private SVGOMPolygonElement polygon;
+ private SVGOMEllipseElement ellipse;
+ private SVGGraphicsElement arrowHead;
+ private SVGOMAnimationElement animatePath, animatePosition, animateRotation;
+
+ public SVGGraphEdge(SVGGraphController graphController) {
+ super(graphController);
+ this.graphController = graphController;
+
+ mouseClickAction = new SVGMouseClickEventListener(this);
+ mouseDownAction = new SVGMouseDownEventListener(this);
+ mouseOverAction = new SVGMouseOverEventListener(this);
+ mouseOutAction = new SVGMouseOutEventListener(this);
+
+ mainGroup = graphController.createGElem();
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, CSS_BLACK_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, CSS_NONE_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+
+ path = graphController.createPath();
+ path.setAttribute(SVG_FILL_ATTRIBUTE, SVG_NONE_VALUE);
+ EventTarget t = (EventTarget) path;
+ t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+ // t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+ // t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+ mainGroup.appendChild(path);
+
+ polygon = graphController.createPolygon();
+ polygon.setAttribute(SVG_POINTS_ATTRIBUTE, ARROW_LENGTH + ", 0"
+ + " 0, -" + ARROW_WIDTH + " 0," + ARROW_WIDTH);
+ t = (EventTarget) polygon;
+ t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+ t.addEventListener(SVG_MOUSEDOWN_EVENT_TYPE, mouseDownAction, false);
+ // t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+ // t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+
+ ellipse = graphController.createEllipse();
+ ellipse.setAttribute(SVG_CX_ATTRIBUTE, ELLIPSE_RADIUS);
+ ellipse.setAttribute(SVG_CY_ATTRIBUTE, SVG_ZERO_VALUE);
+ ellipse.setAttribute(SVG_RX_ATTRIBUTE, ELLIPSE_RADIUS);
+ ellipse.setAttribute(SVG_RY_ATTRIBUTE, ELLIPSE_RADIUS);
+
+ arrowHead = polygon;
+ mainGroup.appendChild(arrowHead);
+
+ deleteButton = graphController.createPath();
+ deleteButton.setAttribute(SVG_STROKE_ATTRIBUTE, "red");
+ deleteButton.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "2");
+ deleteButton.setAttribute(SVG_D_ATTRIBUTE,
+ "M-3.5,-7L3.5,0M-3.5,0L3.5,-7");
+ deleteButton.setAttribute(CSS_DISPLAY_PROPERTY, CSS_NONE_VALUE);
+ mainGroup.appendChild(deleteButton);
+
+ animatePath = createAnimationElement(graphController, SVG_ANIMATE_TAG,
+ SVG_D_ATTRIBUTE, null);
+
+ animatePosition = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ animateRotation = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_ROTATE);
+ animateRotation.setAttribute(SMIL_ADDITIVE_ATTRIBUTE, SMIL_SUM_VALUE);
+
+ delegate = new SVGGraphElementDelegate(graphController, this, mainGroup);
+ }
+
+ public SVGElement getSVGElement() {
+ return mainGroup;
+ }
+
+ /**
+ * Returns the path.
+ *
+ * @return the path
+ */
+ public SVGOMPathElement getPathElement() {
+ return path;
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ final String color = selected ? SELECTED_COLOUR
+ : getHexValue(getColor());
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, color);
+ mainGroup.setAttribute(SVG_FILL_ATTRIBUTE, color);
+ }
+ });
+ }
+
+ @Override
+ public void setActive(final boolean active) {
+ super.setActive(active);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (active) {
+ path.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "2");
+ deleteButton.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_INLINE_VALUE);
+ } else {
+ path.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+ deleteButton.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_NONE_VALUE);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setArrowHeadStyle(final ArrowStyle arrowHeadStyle) {
+ super.setArrowHeadStyle(arrowHeadStyle);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (ArrowStyle.NONE.equals(arrowHeadStyle))
+ mainGroup.removeChild(arrowHead);
+ else if (ArrowStyle.NORMAL.equals(arrowHeadStyle)) {
+ mainGroup.removeChild(arrowHead);
+ arrowHead = polygon;
+ mainGroup.appendChild(arrowHead);
+ } else if (ArrowStyle.DOT.equals(arrowHeadStyle)) {
+ mainGroup.removeChild(arrowHead);
+ arrowHead = ellipse;
+ mainGroup.appendChild(arrowHead);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setPath(final List<Point> pointList) {
+ if (pointList == null)
+ return;
+
+ final List<Point> oldPointList = getPath();
+ super.setPath(pointList);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ Point lastPoint = pointList.get(pointList.size() - 1);
+ double angle = calculateAngle(pointList);
+ if (graphController.isAnimatable() && oldPointList != null) {
+ adjustPathLength(oldPointList, pointList.size());
+ Point oldLastPoint = oldPointList.get(oldPointList.size() - 1);
+ double oldAngle = calculateAngle(oldPointList);
+ animate(animatePath, path,
+ graphController.getAnimationSpeed(),
+ SVGUtil.getPath(oldPointList),
+ SVGUtil.getPath(pointList));
+
+ animate(animatePosition, polygon,
+ graphController.getAnimationSpeed(), oldLastPoint.x
+ + ", " + oldLastPoint.y, lastPoint.x + ", "
+ + lastPoint.y);
+
+ animate(animateRotation, polygon,
+ graphController.getAnimationSpeed(), oldAngle
+ + " 0 0", angle + " 0 0");
+
+ ellipse.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + lastPoint.x + " " + lastPoint.y + ") rotate("
+ + angle + " 0 0) ");
+ deleteButton.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + lastPoint.x + " " + lastPoint.y
+ + ")");
+ } else {
+ path.setAttribute(SVG_D_ATTRIBUTE,
+ SVGUtil.getPath(pointList));
+ polygon.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + lastPoint.x + " " + lastPoint.y + ") rotate("
+ + angle + " 0 0) ");
+ ellipse.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + lastPoint.x + " " + lastPoint.y + ") rotate("
+ + angle + " 0 0) ");
+ deleteButton.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + lastPoint.x + " " + lastPoint.y
+ + ")");
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setColor(final Color color) {
+ delegate.setColor(color);
+ super.setColor(color);
+ }
+
+ @Override
+ public void setFillColor(final Color fillColor) {
+ delegate.setFillColor(fillColor);
+ super.setFillColor(fillColor);
+ }
+
+ @Override
+ public void setVisible(final boolean visible) {
+ delegate.setVisible(visible);
+ super.setVisible(visible);
+ }
+
+ @Override
+ public void setFiltered(final boolean filtered) {
+ delegate.setFiltered(filtered);
+ super.setFiltered(filtered);
+ }
+
+ @Override
+ public void setOpacity(final float opacity) {
+ delegate.setOpacity(opacity);
+ super.setOpacity(opacity);
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java
new file mode 100644
index 0000000..cf7f852
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle.NONE;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.SELECTED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getHexValue;
+import static org.apache.batik.util.CSSConstants.CSS_DISPLAY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_INLINE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_OPACITY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_POINTER_EVENTS_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_VISIBLEPAINTED_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+
+import java.awt.Color;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle;
+
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMElement;
+
+/**
+ * Delegate for GraphElements. Logically a superclass of SVGGraph, SVGGraphNode
+ * and SVGGraphEdge (if java had multiple inheritance).
+ *
+ * @author David Withers
+ */
+public class SVGGraphElementDelegate {
+ private SVGGraphController graphController;
+ private GraphElement graphElement;
+ private SVGOMElement mainGroup;
+ private SVGOMAnimationElement animateOpacity;
+
+ public SVGGraphElementDelegate(SVGGraphController graphController,
+ GraphElement graphElement, SVGOMElement mainGroup) {
+ this.graphController = graphController;
+ this.graphElement = graphElement;
+ this.mainGroup = mainGroup;
+
+ animateOpacity = createAnimationElement(graphController,
+ SVG_ANIMATE_TAG, CSS_OPACITY_PROPERTY, null);
+ }
+
+ public void setSelected(final boolean selected) {
+ boolean currentSelected = graphElement.isSelected();
+ if (currentSelected != selected
+ && !LineStyle.NONE.equals(graphElement.getLineStyle()))
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE,
+ selected ? SELECTED_COLOUR
+ : getHexValue(graphElement.getColor()));
+ mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE,
+ selected ? "2" : "1");
+ }
+ });
+ }
+
+ public void setLineStyle(final LineStyle lineStyle) {
+ LineStyle currentLineStyle = graphElement.getLineStyle();
+ if (!currentLineStyle.equals(lineStyle))
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ String stroke = SVG_NONE_VALUE, dash = SVG_NONE_VALUE;
+ switch (lineStyle) {
+ case DOTTED:
+ stroke = getHexValue(graphElement.getColor());
+ dash = "1,5";
+ break;
+ case SOLID:
+ stroke = getHexValue(graphElement.getColor());
+ default:
+ break;
+ }
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, stroke);
+ mainGroup
+ .setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, dash);
+ }
+ });
+ }
+
+ public void setColor(final Color color) {
+ Color currentColor = graphElement.getColor();
+ if (currentColor != color && NONE != graphElement.getLineStyle())
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE,
+ getHexValue(color));
+ }
+ });
+ }
+
+ public void setFillColor(final Color fillColor) {
+ Color currentFillColor = graphElement.getFillColor();
+ if (currentFillColor != fillColor)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.setAttribute(SVG_FILL_ATTRIBUTE,
+ getHexValue(fillColor));
+ }
+ });
+ }
+
+ public void setVisible(final boolean visible) {
+ boolean currentVisible = graphElement.isVisible();
+ if (currentVisible != visible)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.setAttribute(CSS_DISPLAY_PROPERTY,
+ visible ? CSS_INLINE_VALUE : CSS_NONE_VALUE);
+ }
+ });
+ }
+
+ public void setOpacity(final float opacity) {
+ final float currentOpacity = graphElement.getOpacity();
+ if (currentOpacity != opacity)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (graphController.isAnimatable())
+ animate(animateOpacity, mainGroup,
+ graphController.getAnimationSpeed(),
+ String.valueOf(currentOpacity),
+ String.valueOf(opacity));
+ else
+ mainGroup.setAttribute(CSS_OPACITY_PROPERTY,
+ String.valueOf(opacity));
+ }
+ });
+ }
+
+ public void setFiltered(final boolean filtered) {
+ boolean currentFiltered = graphElement.isFiltered();
+ if (currentFiltered != filtered)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.setAttribute(CSS_POINTER_EVENTS_PROPERTY,
+ filtered ? CSS_NONE_VALUE
+ : CSS_VISIBLEPAINTED_VALUE);
+ setOpacity(filtered ? 0.2f : 1f);
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphNode.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphNode.java
new file mode 100644
index 0000000..004b3f6
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphNode.java
@@ -0,0 +1,611 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.COMPLETED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.ERROR_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculatePoints;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static org.apache.batik.util.CSSConstants.CSS_ALL_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_BLACK_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_DISPLAY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_HIDDEN_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_INLINE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_POINTER_EVENTS_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_VISIBILITY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_VISIBLE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TRANSFORM_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_CLICK_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_CX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_CY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_D_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_END_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_OPACITY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_FAMILY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_SIZE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_HEIGHT_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_MIDDLE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEDOWN_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEMOVE_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEOUT_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEOVER_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_RX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_RY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_TRANSLATE;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseClickEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseDownEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseMovedEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOutEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOverEventListener;
+
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMEllipseElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPathElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.apache.batik.dom.svg.SVGOMRectElement;
+import org.apache.batik.dom.svg.SVGOMTextElement;
+import org.apache.batik.util.CSSConstants;
+import org.apache.batik.util.SVGConstants;
+import org.w3c.dom.Text;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.svg.SVGElement;
+
+/**
+ * SVG representation of a graph node.
+ *
+ * @author David Withers
+ */
+public class SVGGraphNode extends GraphNode {
+ private SVGGraphController graphController;
+ private SVGGraphElementDelegate delegate;
+ private SVGMouseClickEventListener mouseClickAction;
+ private SVGMouseMovedEventListener mouseMovedAction;
+ private SVGMouseDownEventListener mouseDownAction;
+ @SuppressWarnings("unused")
+ private SVGMouseOverEventListener mouseOverAction;
+ @SuppressWarnings("unused")
+ private SVGMouseOutEventListener mouseOutAction;
+ private SVGOMGElement mainGroup, labelGroup, portsGroup;
+ private SVGElement expandedElement, contractedElement;
+ private SVGOMPolygonElement polygon, completedPolygon;
+ private SVGOMEllipseElement ellipse;
+ private SVGOMTextElement label, iteration, error;
+ private Text labelText, iterationText, errorsText;
+ private SVGElement deleteButton;
+ private SVGOMAnimationElement animateShape, animatePosition, animateLabel, animateIteration,
+ animateErrors;
+
+ public SVGGraphNode(SVGGraphController graphController) {
+ super(graphController);
+ this.graphController = graphController;
+ mouseClickAction = new SVGMouseClickEventListener(this);
+ mouseDownAction = new SVGMouseDownEventListener(this);
+ mouseMovedAction = new SVGMouseMovedEventListener(this);
+ mouseOverAction = new SVGMouseOverEventListener(this);
+ mouseOutAction = new SVGMouseOutEventListener(this);
+
+ mainGroup = graphController.createGElem();
+ mainGroup.setAttribute("alignment-baseline", SVG_MIDDLE_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, CSS_BLACK_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, CSS_NONE_VALUE);
+ mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+ mainGroup.setAttribute(SVG_FILL_ATTRIBUTE, CSS_NONE_VALUE);
+
+ EventTarget t = (EventTarget) mainGroup;
+ t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+ t.addEventListener(SVG_MOUSEMOVE_EVENT_TYPE, mouseMovedAction, false);
+ t.addEventListener(SVG_MOUSEDOWN_EVENT_TYPE, mouseDownAction, false);
+// t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+// t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+
+ expandedElement = graphController.createGElem();
+ contractedElement = graphController.createGElem();
+
+ portsGroup = graphController.createGElem();
+ portsGroup.setAttribute(CSS_DISPLAY_PROPERTY, CSS_NONE_VALUE);
+ contractedElement.appendChild(portsGroup);
+
+ mainGroup.appendChild(contractedElement);
+
+ polygon = graphController.createPolygon();
+ contractedElement.appendChild(polygon);
+
+ ellipse = graphController.createEllipse();
+ ellipse.setAttribute(CSS_DISPLAY_PROPERTY, CSS_NONE_VALUE);
+ ellipse.setAttribute(SVG_RX_ATTRIBUTE, String.valueOf(2));
+ ellipse.setAttribute(SVG_CX_ATTRIBUTE, String.valueOf(0));
+ ellipse.setAttribute(SVG_RY_ATTRIBUTE, String.valueOf(2));
+ ellipse.setAttribute(SVG_CY_ATTRIBUTE, String.valueOf(0));
+ contractedElement.appendChild(ellipse);
+
+ completedPolygon = graphController.createPolygon();
+ completedPolygon.setAttribute(SVG_POINTS_ATTRIBUTE,
+ calculatePoints(getShape(), 0, 0));
+ completedPolygon.setAttribute(SVG_FILL_ATTRIBUTE, COMPLETED_COLOUR);
+ completedPolygon.setAttribute(SVG_FILL_OPACITY_ATTRIBUTE, "0.8");
+ contractedElement.appendChild(completedPolygon);
+
+ labelText = graphController.createText("");
+ label = graphController.createText(labelText);
+ label.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_MIDDLE_VALUE);
+ label.setAttribute("baseline-shift", "-35%");
+ label.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+ label.setAttribute(SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
+ labelGroup = graphController.createGElem();
+ labelGroup.appendChild(label);
+ contractedElement.appendChild(labelGroup);
+
+ iterationText = graphController.createText("");
+ iteration = graphController.createText(iterationText);
+ iteration.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_END_VALUE);
+ iteration.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "6");
+ iteration.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+ iteration.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+ iteration.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+ contractedElement.appendChild(iteration);
+
+ errorsText = graphController.createText("");
+ error = graphController.createText(errorsText);
+ error.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_END_VALUE);
+ error.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "6");
+ error.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+ error.setAttribute(SVG_FILL_ATTRIBUTE, CSSConstants.CSS_BLACK_VALUE);
+ error.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+ contractedElement.appendChild(error);
+
+ // deleteButton = createDeleteButton();
+ // g.appendChild(deleteButton);
+
+ animateShape = createAnimationElement(graphController, SVG_ANIMATE_TAG,
+ SVG_POINTS_ATTRIBUTE, null);
+
+ animatePosition = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ animateLabel = SVGUtil.createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ animateIteration = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ animateErrors = createAnimationElement(graphController,
+ SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+ TRANSFORM_TRANSLATE);
+
+ delegate = new SVGGraphElementDelegate(graphController, this, mainGroup);
+ }
+
+ @SuppressWarnings("unused")
+ private SVGElement createDeleteButton() {
+ final SVGOMGElement button = graphController.createGElem();
+ button.setAttribute(CSS_VISIBILITY_PROPERTY, CSS_HIDDEN_VALUE);
+ button.setAttribute(CSS_POINTER_EVENTS_PROPERTY, CSS_ALL_VALUE);
+
+ SVGOMRectElement rect = graphController.createRect();
+ rect.setAttribute(SVG_X_ATTRIBUTE, "4");
+ rect.setAttribute(SVG_Y_ATTRIBUTE, "4");
+ rect.setAttribute(SVG_WIDTH_ATTRIBUTE, "13");
+ rect.setAttribute(SVG_HEIGHT_ATTRIBUTE, "13");
+ rect.setAttribute(SVG_FILL_ATTRIBUTE, "none");
+ button.appendChild(rect);
+
+ final SVGOMPathElement path = graphController.createPath();
+ path.setAttribute(SVG_STROKE_ATTRIBUTE, "white");
+ path.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "2");
+ path.setAttribute(SVG_D_ATTRIBUTE, "M5,5L12,12M5,12L12,5");
+ button.appendChild(path);
+
+ EventTarget t = (EventTarget) button;
+ t.addEventListener(SVG_MOUSEOVER_EVENT_TYPE, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ if (isInteractive()) {
+ deleteButton.setAttribute(CSS_VISIBILITY_PROPERTY,
+ CSS_VISIBLE_VALUE);
+ path.setAttribute(SVG_STROKE_ATTRIBUTE, "red");
+ evt.stopPropagation();
+ }
+ }
+ }, false);
+ t.addEventListener(SVG_MOUSEOUT_EVENT_TYPE, new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ if (isInteractive()) {
+ path.setAttribute(SVG_STROKE_ATTRIBUTE, "white");
+ evt.stopPropagation();
+ }
+ }
+ }, false);
+
+ return button;
+ }
+
+ public SVGElement getSVGElement() {
+ return mainGroup;
+ }
+
+ @Override
+ public void setActive(final boolean active) {
+ super.setActive(active);
+ if (isInteractive())
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (active) {
+ deleteButton.setAttribute(CSS_VISIBILITY_PROPERTY,
+ CSS_VISIBLE_VALUE);
+ // deleteButton.setAttribute(CSSConstants.CSS_DISPLAY_PROPERTY,
+ // CSSConstants.CSS_INLINE_VALUE);
+ } else {
+ deleteButton.setAttribute(CSS_VISIBILITY_PROPERTY,
+ CSS_HIDDEN_VALUE);
+ // button.setAttribute(CSSConstants.CSS_DISPLAY_PROPERTY,
+ // CSSConstants.CSS_NONE_VALUE);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setGraph(Graph graph) {
+ super.setGraph(graph);
+ if (graph instanceof SVGGraph) {
+ SVGGraph svgGraph = (SVGGraph) graph;
+ final SVGElement graphElement = svgGraph.getSVGElement();
+ if (isExpanded())
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ mainGroup.replaceChild(expandedElement, graphElement);
+ }
+ });
+ expandedElement = graphElement;
+ }
+ }
+
+ @Override
+ public void setExpanded(final boolean expanded) {
+ if (isExpanded() != expanded)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (expanded)
+ mainGroup.replaceChild(expandedElement, contractedElement);
+ else
+ mainGroup.replaceChild(contractedElement, expandedElement);
+ }
+ });
+ super.setExpanded(expanded);
+ }
+
+ @Override
+ public void addSourceNode(final GraphNode sourceNode) {
+ super.addSourceNode(sourceNode);
+ if (sourceNode instanceof SVGGraphNode)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ SVGGraphNode svgGraphNode = (SVGGraphNode) sourceNode;
+ portsGroup.appendChild(svgGraphNode.getSVGElement());
+ }
+ });
+ }
+
+ @Override
+ public boolean removeSourceNode(final GraphNode sourceNode) {
+ if (sourceNode instanceof SVGGraphNode)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ SVGGraphNode svgGraphNode = (SVGGraphNode) sourceNode;
+ portsGroup.removeChild(svgGraphNode.getSVGElement());
+ }
+ });
+ return super.removeSourceNode(sourceNode);
+ }
+
+ @Override
+ public void addSinkNode(final GraphNode sinkNode) {
+ super.addSinkNode(sinkNode);
+ if (sinkNode instanceof SVGGraphNode)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ SVGGraphNode svgGraphNode = (SVGGraphNode) sinkNode;
+ portsGroup.appendChild(svgGraphNode.getSVGElement());
+ }
+ });
+ }
+
+ @Override
+ public boolean removeSinkNode(final GraphNode sinkNode) {
+ if (sinkNode instanceof SVGGraphNode)
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ SVGGraphNode svgGraphNode = (SVGGraphNode) sinkNode;
+ portsGroup.removeChild(svgGraphNode.getSVGElement());
+ }
+ });
+ return super.removeSinkNode(sinkNode);
+ }
+
+ @Override
+ public void setPosition(final Point position) {
+ final Point oldPosition = getPosition();
+ if (position != null && !position.equals(oldPosition)) {
+ super.setPosition(position);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (graphController.isAnimatable())
+ animate(animatePosition, mainGroup,
+ graphController.getAnimationSpeed(),
+ oldPosition.x + ", " + oldPosition.y,
+ position.x + ", " + position.y);
+ else
+ mainGroup.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + position.x + " " + position.y
+ + ")");
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setSize(final Dimension size) {
+ final Dimension oldSize = getSize();
+ if (size != null && !size.equals(oldSize)) {
+ super.setSize(size);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ adjustSize(size, oldSize);
+ }
+ });
+ }
+ }
+
+ /** core of implementation of {@link #setSize(Dimension)} */
+ private void adjustSize(Dimension size, Dimension oldSize) {
+ int oldWidth = oldSize.width;
+ int oldHeight = oldSize.height;
+ if (graphController.isAnimatable()) {
+ if (Shape.CIRCLE.equals(getShape())) {
+ ellipse.setAttribute(SVG_RX_ATTRIBUTE,
+ String.valueOf(size.width / 2f));
+ ellipse.setAttribute(SVG_CX_ATTRIBUTE,
+ String.valueOf(size.width / 2f));
+ ellipse.setAttribute(SVG_RY_ATTRIBUTE,
+ String.valueOf(size.height / 2f));
+ ellipse.setAttribute(SVG_CY_ATTRIBUTE,
+ String.valueOf(size.height / 2f));
+ } else
+ animate(animateShape, polygon,
+ graphController.getAnimationSpeed(),
+ calculatePoints(getShape(), oldWidth, oldHeight),
+ calculatePoints(getShape(), getWidth(), getHeight()));
+
+ if (getLabel() != null && !getLabel().isEmpty())
+ animate(animateLabel, labelGroup,
+ graphController.getAnimationSpeed(), (oldWidth / 2f)
+ + ", " + (oldHeight / 2f), (getWidth() / 2f)
+ + ", " + (getHeight() / 2f));
+ else
+ labelGroup.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+ "translate(" + getWidth() / 2f + " " + getHeight() / 2f + ")");
+
+ if (getIteration() > 0)
+ animate(animateIteration, iteration,
+ graphController.getAnimationSpeed(), (oldWidth - 1.5)
+ + ", 5.5", (getWidth() - 1.5) + ", 5.5");
+ else
+ iteration.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + (getWidth() - 1.5) + " 5.5)");
+
+ if (getErrors() > 0)
+ animate(animateErrors, error,
+ graphController.getAnimationSpeed(), (oldWidth - 1.5)
+ + ", " + (oldHeight - 1), (getWidth() - 1.5)
+ + ", " + (getHeight() - 1));
+ else
+ error.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + (getWidth() - 1.5) + " " + (getHeight() - 1) + ")");
+ } else {
+ if (Shape.CIRCLE.equals(getShape())) {
+ ellipse.setAttribute(SVG_RX_ATTRIBUTE,
+ String.valueOf(size.width / 2f));
+ ellipse.setAttribute(SVG_CX_ATTRIBUTE,
+ String.valueOf(size.width / 2f));
+ ellipse.setAttribute(SVG_RY_ATTRIBUTE,
+ String.valueOf(size.height / 2f));
+ ellipse.setAttribute(SVG_CY_ATTRIBUTE,
+ String.valueOf(size.height / 2f));
+ } else
+ polygon.setAttribute(SVG_POINTS_ATTRIBUTE,
+ calculatePoints(getShape(), getWidth(), getHeight()));
+
+ labelGroup.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + getWidth() / 2f + " " + getHeight() / 2f + ")");
+ iteration.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + (getWidth() - 1.5) + " 5.5)");
+ error.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+ + (getWidth() - 1.5) + " " + (getHeight() - 1) + ")");
+ }
+ }
+
+ @Override
+ public void setShape(final Shape shape) {
+ final Shape currentShape = getShape();
+ if (shape != null && !shape.equals(currentShape)) {
+ super.setShape(shape);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (Shape.CIRCLE.equals(shape)) {
+ ellipse.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_INLINE_VALUE);
+ polygon.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_NONE_VALUE);
+ } else if (Shape.CIRCLE.equals(currentShape)) {
+ ellipse.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_NONE_VALUE);
+ polygon.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_INLINE_VALUE);
+ }
+ if (Shape.RECORD.equals(shape))
+ portsGroup.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_INLINE_VALUE);
+ else if (Shape.RECORD.equals(currentShape))
+ portsGroup.setAttribute(CSS_DISPLAY_PROPERTY,
+ CSS_NONE_VALUE);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setLabel(final String label) {
+ super.setLabel(label);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ labelText.setData(label);
+ }
+ });
+ }
+
+ @Override
+ public void setIteration(final int iteration) {
+ super.setIteration(iteration);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (iteration > 0)
+ iterationText.setData(String.valueOf(iteration));
+ else
+ iterationText.setData("");
+ }
+ });
+ }
+
+ @Override
+ public void setErrors(final int errors) {
+ super.setErrors(errors);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ if (errors > 0) {
+ errorsText.setData(String.valueOf(errors));
+ completedPolygon.setAttribute(SVG_FILL_ATTRIBUTE,
+ ERROR_COLOUR);
+ } else {
+ errorsText.setData("");
+ completedPolygon.setAttribute(SVG_FILL_ATTRIBUTE,
+ COMPLETED_COLOUR);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setCompleted(final float complete) {
+ super.setCompleted(complete);
+ graphController.updateSVGDocument(new Runnable() {
+ @Override
+ public void run() {
+ completedPolygon.setAttribute(
+ SVG_POINTS_ATTRIBUTE,
+ calculatePoints(getShape(),
+ (int) (getWidth() * complete), getHeight()));
+ }
+ });
+ }
+
+ @Override
+ public void setSelected(final boolean selected) {
+ delegate.setSelected(selected);
+ super.setSelected(selected);
+ }
+
+ @Override
+ public void setLineStyle(final LineStyle lineStyle) {
+ delegate.setLineStyle(lineStyle);
+ super.setLineStyle(lineStyle);
+ }
+
+ @Override
+ public void setColor(final Color color) {
+ delegate.setColor(color);
+ super.setColor(color);
+ }
+
+ @Override
+ public void setFillColor(final Color fillColor) {
+ delegate.setFillColor(fillColor);
+ super.setFillColor(fillColor);
+ }
+
+ @Override
+ public void setVisible(final boolean visible) {
+ delegate.setVisible(visible);
+ super.setVisible(visible);
+ }
+
+ @Override
+ public void setFiltered(final boolean filtered) {
+ delegate.setFiltered(filtered);
+ super.setFiltered(filtered);
+ }
+
+ @Override
+ public void setOpacity(final float opacity) {
+ delegate.setOpacity(opacity);
+ super.setOpacity(opacity);
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphSettings.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphSettings.java
new file mode 100644
index 0000000..777102e
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphSettings.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+public interface SVGGraphSettings {
+ String COMPLETED_COLOUR = "grey";
+ String ERROR_COLOUR = "#dd3131";
+ String SELECTED_COLOUR = "#4377d3";
+ String NORMAL_COLOUR = "black";
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGMonitorShape.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGMonitorShape.java
new file mode 100644
index 0000000..5aab55f
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGMonitorShape.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+
+public interface SVGMonitorShape extends SVGShape {
+ /**
+ * Returns the polygon used to display the completed value.
+ *
+ * @return the polygon used to display the completed value
+ */
+ SVGOMPolygonElement getCompletedPolygon();
+
+ /**
+ * Sets the polygon used to display the completed value.
+ *
+ * @param polygon
+ * the new polygon used to display the completed value
+ */
+ void setCompletedPolygon(SVGOMPolygonElement polygon);
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGShape.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGShape.java
new file mode 100644
index 0000000..8ebc338
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGShape.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+public interface SVGShape {
+ public void setIteration(final int iteration);
+
+ // public void setErrors(final int errors);
+
+ // public void setCompleted(final float complete);
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGUtil.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGUtil.java
new file mode 100644
index 0000000..f2e4247
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGUtil.java
@@ -0,0 +1,477 @@
+/*******************************************************************************
+ * 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.models.graph.svg;
+
+import static java.lang.Float.parseFloat;
+import static java.lang.Math.PI;
+import static java.lang.Math.atan2;
+import static org.apache.batik.dom.svg.SVGDOMImplementation.getDOMImplementation;
+import static org.apache.batik.util.SMILConstants.SMIL_ATTRIBUTE_NAME_ATTRIBUTE;
+import static org.apache.batik.util.SMILConstants.SMIL_DUR_ATTRIBUTE;
+import static org.apache.batik.util.SMILConstants.SMIL_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SMILConstants.SMIL_FREEZE_VALUE;
+import static org.apache.batik.util.SMILConstants.SMIL_FROM_ATTRIBUTE;
+import static org.apache.batik.util.SMILConstants.SMIL_TO_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TYPE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X1_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X2_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y1_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y2_ATTRIBUTE;
+import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.List;
+
+import net.sf.taverna.t2.lang.io.StreamDevourer;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.models.graph.GraphShapeElement.Shape;
+
+import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
+import org.apache.batik.dom.svg.SVGDOMImplementation;
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.apache.log4j.Logger;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGElement;
+import org.w3c.dom.svg.SVGLocatable;
+import org.w3c.dom.svg.SVGMatrix;
+//import org.apache.batik.transcoder.TranscoderException;
+//import org.apache.batik.transcoder.svg2svg.PrettyPrinter;
+
+/**
+ * Utility methods.
+ *
+ * @author David Withers
+ */
+public class SVGUtil {
+ private static final String C = "C";
+ private static final String M = "M";
+ private static final String SPACE = " ";
+ private static final String COMMA = ",";
+ public static final String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
+ private static final String SVG = "svg";
+ private static final Logger logger = Logger.getLogger(SVGUtil.class);
+
+ private static SAXSVGDocumentFactory docFactory;
+
+ static {
+ String parser = getXMLParserClassName();
+ logger.info("Using XML parser " + parser);
+ docFactory = new SAXSVGDocumentFactory(parser);
+ }
+
+ /**
+ * Creates a new SVGDocument.
+ *
+ * @return a new SVGDocument
+ */
+ public static SVGDocument createSVGDocument() {
+ DOMImplementation impl = getDOMImplementation();
+ return (SVGDocument) impl.createDocument(svgNS, SVG, null);
+ }
+
+ /**
+ * Converts a point in screen coordinates to a point in document
+ * coordinates.
+ *
+ * @param locatable
+ * @param screenPoint
+ * the point in screen coordinates
+ * @return the point in document coordinates
+ */
+ public static SVGOMPoint screenToDocument(SVGLocatable locatable,
+ SVGOMPoint screenPoint) {
+ SVGMatrix mat = ((SVGLocatable) locatable.getFarthestViewportElement())
+ .getScreenCTM().inverse();
+ return (SVGOMPoint) screenPoint.matrixTransform(mat);
+ }
+
+ /**
+ * Writes SVG to the console. For debugging only.
+ *
+ * @param svgDocument
+ * the document to output
+ */
+// public static void writeSVG(SVGDocument svgDocument) {
+// writeSVG(svgDocument, new OutputStreamWriter(System.out));
+// }
+
+ /**
+ * Writes SVG to an output stream.
+ *
+ * @param svgDocument
+ * the document to output
+ * @param writer
+ * the stream to write the document to
+ */
+// public static void writeSVG(SVGDocument svgDocument, Writer writer) {
+// StringWriter sw = new StringWriter();
+// try {
+// Transformer transformer = TransformerFactory.newInstance().newTransformer();
+// Source src = new DOMSource(svgDocument.getDocumentElement());
+// transformer.transform(src, new StreamResult(sw));
+//
+// PrettyPrinter pp = new PrettyPrinter();
+// pp.print(new StringReader(sw.toString()), writer);
+// } catch (TransformerException | TranscoderException | IOException e) {
+// e.printStackTrace(new PrintWriter(writer));
+// }
+// }
+
+ /**
+ * Generates an SVGDocument from DOT text by calling out to GraphViz.
+ *
+ * @param dotText
+ * @return an SVGDocument
+ * @throws IOException
+ */
+ public static SVGDocument getSVG(String dotText,
+ WorkbenchConfiguration workbenchConfiguration) throws IOException {
+ String dotLocation = (String) workbenchConfiguration
+ .getProperty("taverna.dotlocation");
+ if (dotLocation == null)
+ dotLocation = "dot";
+ logger.debug("Invoking dot...");
+ Process dotProcess = exec(dotLocation, "-Tsvg");
+ StreamDevourer devourer = new StreamDevourer(
+ dotProcess.getInputStream());
+ devourer.start();
+ try (PrintWriter out = new PrintWriter(dotProcess.getOutputStream(),
+ true)) {
+ out.print(dotText);
+ out.flush();
+ }
+
+ String svgText = devourer.blockOnOutput();
+ /*
+ * Avoid TAV-424, replace buggy SVG outputted by "modern" GraphViz
+ * versions. http://www.graphviz.org/bugs/b1075.html
+ *
+ * Contributed by Marko Ullgren
+ */
+ svgText = svgText.replaceAll("font-weight:regular",
+ "font-weight:normal");
+ logger.info(svgText);
+ // Fake URI, just used for internal references like #fish
+ return docFactory.createSVGDocument(
+ "http://taverna.sf.net/diagram/generated.svg",
+ new StringReader(svgText));
+ }
+
+ /**
+ * Generates DOT text with layout information from DOT text by calling out
+ * to GraphViz.
+ *
+ * @param dotText
+ * dot text
+ * @return dot text with layout information
+ * @throws IOException
+ */
+ public static String getDot(String dotText,
+ WorkbenchConfiguration workbenchConfiguration) throws IOException {
+ String dotLocation = (String) workbenchConfiguration
+ .getProperty("taverna.dotlocation");
+ if (dotLocation == null)
+ dotLocation = "dot";
+ logger.debug("Invoking dot...");
+ Process dotProcess = exec(dotLocation, "-Tdot", "-Glp=0,0");
+ StreamDevourer devourer = new StreamDevourer(
+ dotProcess.getInputStream());
+ devourer.start();
+ try (PrintWriter out = new PrintWriter(dotProcess.getOutputStream(),
+ true)) {
+ out.print(dotText);
+ out.flush();
+ }
+
+ String dot = devourer.blockOnOutput();
+ // logger.info(dot);
+ return dot;
+ }
+
+ private static Process exec(String...args) throws IOException {
+ Process p = Runtime.getRuntime().exec(args);
+ /*
+ * Must create an error devourer otherwise stderr fills up and the
+ * process stalls!
+ */
+ new StreamDevourer(p.getErrorStream()).start();
+ return p;
+ }
+
+ /**
+ * Returns the hex value for a <code>Color</code>. If color is null "none"
+ * is returned.
+ *
+ * @param color
+ * the <code>Color</code> to convert to hex code
+ * @return the hex value
+ */
+ public static String getHexValue(Color color) {
+ if (color == null)
+ return "none";
+
+ return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(),
+ color.getBlue());
+ }
+
+ /**
+ * Calculates the angle to rotate an arrow head to be placed on the end of a
+ * line.
+ *
+ * @param line
+ * the line to calculate the arrow head angle from
+ * @return the angle to rotate an arrow head
+ */
+ public static double calculateAngle(Element line) {
+ float x1 = parseFloat(line.getAttribute(SVG_X1_ATTRIBUTE));
+ float y1 = parseFloat(line.getAttribute(SVG_Y1_ATTRIBUTE));
+ float x2 = parseFloat(line.getAttribute(SVG_X2_ATTRIBUTE));
+ float y2 = parseFloat(line.getAttribute(SVG_Y2_ATTRIBUTE));
+ return calculateAngle(x1, y1, x2, y2);
+ }
+
+ /**
+ * Calculates the angle to rotate an arrow head to be placed on the end of a
+ * line.
+ *
+ * @param pointList
+ * the list of <code>Point</code>s to calculate the arrow head
+ * angle from
+ * @return the angle to rotate an arrow head
+ */
+ public static double calculateAngle(List<Point> pointList) {
+ double angle = 0d;
+ if (pointList.size() > 1) {
+ int listSize = pointList.size();
+ Point a = pointList.get(listSize - 2);
+ Point b = pointList.get(listSize - 1);
+ /*
+ * dot sometimes generates paths with the same point repeated at the
+ * end of the path, so move back along the path until two different
+ * points are found
+ */
+ while (a.equals(b) && listSize > 2) {
+ b = a;
+ a = pointList.get(--listSize - 2);
+ }
+ angle = calculateAngle(a.x, a.y, b.x, b.y);
+ }
+ return angle;
+ }
+
+ /**
+ * Calculates the angle to rotate an arrow head to be placed on the end of a
+ * line.
+ *
+ * @param x1
+ * the x coordinate of the start of the line
+ * @param y1
+ * the y coordinate of the start of the line
+ * @param x2
+ * the x coordinate of the end of the line
+ * @param y2
+ * the y coordinate of the end of the line
+ * @return the angle to rotate an arrow head
+ */
+ public static double calculateAngle(float x1, float y1, float x2, float y2) {
+ return atan2(y2 - y1, x2 - x1) * 180 / PI;
+ }
+
+ /**
+ * Calculates the points that make up the polygon for the specified
+ * {@link Shape}.
+ *
+ * @param shape
+ * the <code>Shape</code> to calculate points for
+ * @param width
+ * the width of the <code>Shape</code>
+ * @param height
+ * the height of the <code>Shape</code>
+ * @return the points that make up the polygon for the specified
+ * <code>Shape</code>
+ */
+ public static String calculatePoints(Shape shape, int width, int height) {
+ StringBuilder sb = new StringBuilder();
+ switch (shape) {
+ case BOX:
+ case RECORD:
+ addPoint(sb, 0, 0);
+ addPoint(sb, width, 0);
+ addPoint(sb, width, height);
+ addPoint(sb, 0, height);
+ break;
+ case HOUSE:
+ addPoint(sb, width / 2f, 0);
+ addPoint(sb, width, height / 3f);
+ addPoint(sb, width, height - 3);
+ addPoint(sb, 0, height - 3);
+ addPoint(sb, 0, height / 3f);
+ break;
+ case INVHOUSE:
+ addPoint(sb, 0, 3);
+ addPoint(sb, width, 3);
+ addPoint(sb, width, height / 3f * 2f);
+ addPoint(sb, width / 2f, height);
+ addPoint(sb, 0, height / 3f * 2f);
+ break;
+ case TRIANGLE:
+ addPoint(sb, width / 2f, 0);
+ addPoint(sb, width, height);
+ addPoint(sb, 0, height);
+ break;
+ case INVTRIANGLE:
+ addPoint(sb, 0, 0);
+ addPoint(sb, width, 0);
+ addPoint(sb, width / 2f, height);
+ break;
+ default:
+ // Nothing to do for the others
+ break;
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Appends x y coordinates to a <code>StringBuilder</code> in the format
+ * "x,y ".
+ *
+ * @param stringBuilder
+ * the <code>StringBuilder</code> to append the point to
+ * @param x
+ * the x coordinate
+ * @param y
+ * the y coordinate
+ */
+ public static void addPoint(StringBuilder stringBuilder, float x, float y) {
+ stringBuilder.append(x).append(COMMA).append(y).append(SPACE);
+ }
+
+ /**
+ * Converts a list of points into a string format for a cubic Bezier curve.
+ *
+ * For example, "M100,200 C100,100 250,100 250,200". See
+ * http://www.w3.org/TR/SVG11/paths.html#PathDataCubicBezierCommands.
+ *
+ * @param pointList
+ * a list of points that describes a cubic Bezier curve
+ * @return a string that describes a cubic Bezier curve
+ */
+ public static String getPath(List<Point> pointList) {
+ StringBuilder sb = new StringBuilder();
+ if (pointList != null && pointList.size() > 1) {
+ Point firstPoint = pointList.get(0);
+ sb.append(M).append(firstPoint.x).append(COMMA)
+ .append(firstPoint.y);
+ sb.append(SPACE);
+ Point secontPoint = pointList.get(1);
+ sb.append(C).append(secontPoint.x).append(COMMA)
+ .append(secontPoint.y);
+ for (int i = 2; i < pointList.size(); i++) {
+ Point point = pointList.get(i);
+ sb.append(SPACE).append(point.x).append(COMMA).append(point.y);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Creates an animation element.
+ *
+ * @param graphController
+ * the SVGGraphController to use to create the animation element
+ * @param elementType
+ * the type of animation element to create
+ * @param attribute
+ * the attribute that the animation should affect
+ * @param transformType
+ * the type of transform - use null not creating a transform
+ * animation
+ * @return an new animation element
+ */
+ public static SVGOMAnimationElement createAnimationElement(
+ SVGGraphController graphController, String elementType,
+ String attribute, String transformType) {
+ SVGOMAnimationElement animationElement = (SVGOMAnimationElement) graphController
+ .createElement(elementType);
+ animationElement.setAttribute(SMIL_ATTRIBUTE_NAME_ATTRIBUTE, attribute);
+ if (transformType != null)
+ animationElement.setAttribute(SVG_TYPE_ATTRIBUTE, transformType);
+ animationElement.setAttribute(SMIL_FILL_ATTRIBUTE, SMIL_FREEZE_VALUE);
+ return animationElement;
+ }
+
+ /**
+ * Adds an animation to the SVG element and starts the animation.
+ *
+ * @param animate
+ * that animation element
+ * @param element
+ * the element to animate
+ * @param duration
+ * the duration of the animation in milliseconds
+ * @param from
+ * the starting point for the animation, can be null
+ * @param to
+ * the end point for the animation, cannot be null
+ */
+ public static void animate(SVGOMAnimationElement animate, SVGElement element, int duration,
+ String from, String to) {
+ animate.setAttribute(SMIL_DUR_ATTRIBUTE, duration + "ms");
+ if (from != null)
+ animate.setAttribute(SMIL_FROM_ATTRIBUTE, from);
+ animate.setAttribute(SMIL_TO_ATTRIBUTE, to);
+ element.appendChild(animate);
+ try {
+ animate.beginElement();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * Adjusts the length of <code>pointList</code> by adding or removing points
+ * to make the length equal to <code>size</code>. If <code>pointList</code>
+ * is shorter than <code>size</code> the last point is repeated. If
+ * <code>pointList</code> is longer than <code>size</code> points at the end
+ * of the list are removed.
+ *
+ * @param pointList
+ * the path to adjust
+ * @param size
+ * the required size for <code>pointList</code>
+ */
+ public static void adjustPathLength(List<Point> pointList, int size) {
+ if (pointList.size() < size) {
+ Point lastPoint = pointList.get(pointList.size() - 1);
+ for (int i = pointList.size(); i < size; i++)
+ pointList.add(lastPoint);
+ } else if (pointList.size() > size) {
+ for (int i = pointList.size(); i > size; i--)
+ pointList.remove(i - 1);
+ }
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGEventListener.java
new file mode 100644
index 0000000..95b4181
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGEventListener.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.screenToDocument;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.MouseEvent;
+import org.w3c.dom.svg.SVGLocatable;
+
+/**
+ * Abstract superclass for SVG event listeners.
+ *
+ * @author David Withers
+ */
+public abstract class SVGEventListener implements EventListener {
+ protected GraphElement graphElement;
+
+ public SVGEventListener(GraphElement graphElement) {
+ this.graphElement = graphElement;
+ }
+
+ protected abstract void event(SVGOMPoint point, MouseEvent evt);
+
+ @Override
+ public final void handleEvent(Event evt) {
+ if (evt instanceof MouseEvent) {
+ MouseEvent me = (MouseEvent) evt;
+ SVGOMPoint point = screenToDocument((SVGLocatable) me.getTarget(),
+ new SVGOMPoint(me.getClientX(), me.getClientY()));
+ event(point, me);
+ evt.stopPropagation();
+ }
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseClickEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseClickEventListener.java
new file mode 100644
index 0000000..0c13be3
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseClickEventListener.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.MouseEvent;
+
+/**
+ * SVG event listener for handling mouse click events.
+ *
+ * @author David Withers
+ */
+public class SVGMouseClickEventListener extends SVGEventListener {
+ public SVGMouseClickEventListener(GraphElement graphElement) {
+ super(graphElement);
+ }
+
+ @Override
+ protected void event(SVGOMPoint point, MouseEvent evt) {
+ graphElement.getEventManager().mouseClicked(graphElement,
+ evt.getButton(), evt.getAltKey(), evt.getCtrlKey(),
+ evt.getMetaKey(), (int) point.getX(), (int) point.getY(),
+ evt.getScreenX(), evt.getScreenY());
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseDownEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseDownEventListener.java
new file mode 100644
index 0000000..bd69506
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseDownEventListener.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.MouseEvent;
+
+/**
+ * SVG event listener for handling mouse button down events.
+ *
+ * @author David Withers
+ */
+public class SVGMouseDownEventListener extends SVGEventListener {
+ public SVGMouseDownEventListener(GraphElement graphElement) {
+ super(graphElement);
+ }
+
+ @Override
+ protected void event(SVGOMPoint point, MouseEvent evt) {
+ graphElement.getEventManager().mouseDown(graphElement, evt.getButton(),
+ evt.getAltKey(), evt.getCtrlKey(), evt.getMetaKey(),
+ (int) point.getX(), (int) point.getY(), evt.getScreenX(),
+ evt.getScreenY());
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseMovedEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseMovedEventListener.java
new file mode 100644
index 0000000..6ae5d50
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseMovedEventListener.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.MouseEvent;
+
+/**
+ * SVG event listener for handling mouse movement events.
+ *
+ * @author David Withers
+ */
+public class SVGMouseMovedEventListener extends SVGEventListener {
+ public SVGMouseMovedEventListener(GraphElement graphElement) {
+ super(graphElement);
+ }
+
+ @Override
+ protected void event(SVGOMPoint point, MouseEvent mouseEvent) {
+ graphElement.getEventManager().mouseMoved(graphElement,
+ mouseEvent.getButton(), mouseEvent.getAltKey(),
+ mouseEvent.getCtrlKey(), mouseEvent.getMetaKey(),
+ (int) point.getX(), (int) point.getY(),
+ mouseEvent.getScreenX(), mouseEvent.getScreenY());
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseOutEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseOutEventListener.java
new file mode 100644
index 0000000..32714a6
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseOutEventListener.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.MouseEvent;
+
+/**
+ * SVG event listener for handling mouse button up events.
+ *
+ * @author David Withers
+ */
+public class SVGMouseOutEventListener extends SVGEventListener {
+ public SVGMouseOutEventListener(GraphElement graphElement) {
+ super(graphElement);
+ }
+
+ @Override
+ protected void event(SVGOMPoint point, MouseEvent mouseEvent) {
+ graphElement.getEventManager().mouseOut(graphElement,
+ mouseEvent.getButton(), mouseEvent.getAltKey(),
+ mouseEvent.getCtrlKey(), mouseEvent.getMetaKey(),
+ (int) point.getX(), (int) point.getY(),
+ mouseEvent.getScreenX(), mouseEvent.getScreenY());
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseOverEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseOverEventListener.java
new file mode 100644
index 0000000..1c5f9a4
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseOverEventListener.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.MouseEvent;
+
+/**
+ * SVG event listener for handling mouse button up events.
+ *
+ * @author David Withers
+ */
+public class SVGMouseOverEventListener extends SVGEventListener {
+ public SVGMouseOverEventListener(GraphElement graphElement) {
+ super(graphElement);
+ }
+
+ @Override
+ protected void event(SVGOMPoint point, MouseEvent mouseEvent) {
+ graphElement.getEventManager().mouseOver(graphElement,
+ mouseEvent.getButton(), mouseEvent.getAltKey(),
+ mouseEvent.getCtrlKey(), mouseEvent.getMetaKey(),
+ (int) point.getX(), (int) point.getY(),
+ mouseEvent.getScreenX(), mouseEvent.getScreenY());
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseUpEventListener.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseUpEventListener.java
new file mode 100644
index 0000000..492ecc2
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/event/SVGMouseUpEventListener.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.models.graph.svg.event;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+
+import org.apache.batik.dom.svg.SVGOMPoint;
+import org.w3c.dom.events.MouseEvent;
+
+/**
+ * SVG event listener for handling mouse button up events.
+ *
+ * @author David Withers
+ */
+public class SVGMouseUpEventListener extends SVGEventListener {
+ public SVGMouseUpEventListener(GraphElement graphElement) {
+ super(graphElement);
+ }
+
+ @Override
+ protected void event(SVGOMPoint point, MouseEvent mouseEvent) {
+ graphElement.getEventManager().mouseUp(graphElement,
+ mouseEvent.getButton(), mouseEvent.getAltKey(),
+ mouseEvent.getCtrlKey(), mouseEvent.getMetaKey(),
+ (int) point.getX(), (int) point.getY(),
+ mouseEvent.getScreenX(), mouseEvent.getScreenY());
+ }
+}
diff --git a/taverna-workbench-graph-model/src/main/jjtree/NamedNode.java b/taverna-workbench-graph-model/src/main/jjtree/NamedNode.java
new file mode 100644
index 0000000..da92a97
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/jjtree/NamedNode.java
@@ -0,0 +1,65 @@
+package net.sf.taverna.t2.workbench.models.graph.dot;
+
+public class NamedNode {
+
+ protected String name, value, port;
+
+ /**
+ * Returns the name.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of name.
+ *
+ * @param name
+ * the new value for name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the value.
+ *
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of value.
+ *
+ * @param value
+ * the new value for value
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the port.
+ *
+ * @return the port
+ */
+ public String getPort() {
+ return port;
+ }
+
+ /**
+ * Sets the value of port.
+ *
+ * @param port
+ * the new value for port
+ */
+ public void setPort(String port) {
+ this.port = port;
+ }
+
+}
+
diff --git a/taverna-workbench-graph-model/src/main/jjtree/dotparser.jjt b/taverna-workbench-graph-model/src/main/jjtree/dotparser.jjt
new file mode 100644
index 0000000..001450b
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/jjtree/dotparser.jjt
@@ -0,0 +1,289 @@
+/**
+ * JavaCC grammar for parsing Graphviz DOT files.
+ * Written by Lynn Monson at lm@lmonson.com
+ * Modified by David Withers
+ */
+options {
+ STATIC = false;
+ UNICODE_INPUT = true;
+ VISITOR = true;
+ MULTI=true;
+ NODE_EXTENDS="NamedNode";
+}
+
+/*===========================================================================================
+ Parser java class
+ ===========================================================================================*/
+PARSER_BEGIN(DOTParser)
+package net.sf.taverna.t2.workbench.models.graph.dot;
+import java.io.*;
+
+class DOTParser {
+
+ public static void parseDot(Reader r) throws IOException, ParseException {
+ new DOTParser(r).parse().dump(" ");
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ parseDot(new FileReader(args[0]));
+ }
+
+}
+PARSER_END(DOTParser)
+
+
+// whitespace
+SKIP : { " " | "\t" | "\n" | "\r" | "\f" }
+
+// comments
+MORE :
+{
+ "//" : IN_SINGLE_LINE_COMMENT
+|
+ "/*" : IN_MULTI_LINE_COMMENT
+}
+
+<IN_SINGLE_LINE_COMMENT>
+SPECIAL_TOKEN :
+{
+ <SINGLE_LINE_COMMENT: "\n" | "\r" | "\r\n" > : DEFAULT
+}
+
+<IN_MULTI_LINE_COMMENT>
+SPECIAL_TOKEN :
+{
+ <MULTI_LINE_COMMENT: "*/" > : DEFAULT
+}
+
+<IN_SINGLE_LINE_COMMENT,IN_MULTI_LINE_COMMENT>
+MORE : { < ~[] > }
+
+// Case insensitive keywords
+TOKEN [IGNORE_CASE] : {
+ <DIGRAPH: ( "digraph" )>
+ | <EDGE: ( "edge" )>
+ | <GRAPH: ( "graph" )>
+ | <NODE: ( "node" )>
+ | <STRICT: ( "strict" )>
+ | <SUBGRAPH: ( "subgraph" )>
+}
+
+// All other tokens
+TOKEN:
+{
+ // -------------------------------------------
+ // Various pieces of syntax as lexical tokens
+ // -------------------------------------------
+ <EQ: "=">
+ | <LBRACE: "{">
+ | <RBRACE: "}">
+ | <EDGE_UNDIRECTED: "--">
+ | <EDGE_DIRECTED: "->">
+ | <LBRACKET: "[">
+ | <RBRACKET: "]">
+ | <COMMA: ",">
+ | <SEMICOLON: ";">
+ | <COLON: ":">
+
+
+ // -------------------------------------------
+ // Identifiers
+ // -------------------------------------------
+ | <ID: <$HtmlString> | <$Number> | <$UnquotedString> | <$QuotedString> >
+ | <HTML: <$HtmlString>>
+ | <#$HtmlString: ( "<" ( "<" (~[">"])* ">" (~["<", ">"])* )* ">" ) >
+
+ // -------------------------------------------
+ // Character definitions (taken from xpath)
+ // -------------------------------------------
+
+ | <#$BaseChar:
+ ["\u0041"-"\u005A"] | ["\u0061"-"\u007A"] | ["\u00C0"-"\u00D6"] | ["\u00D8"-"\u00F6"]
+ | ["\u00F8"-"\u00FF"] | ["\u0100"-"\u0131"] | ["\u0134"-"\u013E"] | ["\u0141"-"\u0148"]
+ | ["\u014A"-"\u017E"] | ["\u0180"-"\u01C3"] | ["\u01CD"-"\u01F0"] | ["\u01F4"-"\u01F5"]
+ | ["\u01FA"-"\u0217"] | ["\u0250"-"\u02A8"] | ["\u02BB"-"\u02C1"] | "\u0386" | ["\u0388"-"\u038A"]
+ | "\u038C" | ["\u038E"-"\u03A1"] | ["\u03A3"-"\u03CE"] | ["\u03D0"-"\u03D6"] | "\u03DA"
+ | "\u03DC" | "\u03DE" | "\u03E0" | ["\u03E2"-"\u03F3"] | ["\u0401"-"\u040C"] | ["\u040E"-"\u044F"]
+ | ["\u0451"-"\u045C"] | ["\u045E"-"\u0481"] | ["\u0490"-"\u04C4"] | ["\u04C7"-"\u04C8"]
+ | ["\u04CB"-"\u04CC"] | ["\u04D0"-"\u04EB"] | ["\u04EE"-"\u04F5"] | ["\u04F8"-"\u04F9"]
+ | ["\u0531"-"\u0556"] | "\u0559" | ["\u0561"-"\u0586"] | ["\u05D0"-"\u05EA"] | ["\u05F0"-"\u05F2"]
+ | ["\u0621"-"\u063A"] | ["\u0641"-"\u064A"] | ["\u0671"-"\u06B7"] | ["\u06BA"-"\u06BE"]
+ | ["\u06C0"-"\u06CE"] | ["\u06D0"-"\u06D3"] | "\u06D5" | ["\u06E5"-"\u06E6"] | ["\u0905"-"\u0939"]
+ | "\u093D" | ["\u0958"-"\u0961"] | ["\u0985"-"\u098C"] | ["\u098F"-"\u0990"] | ["\u0993"-"\u09A8"]
+ | ["\u09AA"-"\u09B0"] | "\u09B2" | ["\u09B6"-"\u09B9"] | ["\u09DC"-"\u09DD"] | ["\u09DF"-"\u09E1"]
+ | ["\u09F0"-"\u09F1"] | ["\u0A05"-"\u0A0A"] | ["\u0A0F"-"\u0A10"] | ["\u0A13"-"\u0A28"]
+ | ["\u0A2A"-"\u0A30"] | ["\u0A32"-"\u0A33"] | ["\u0A35"-"\u0A36"] | ["\u0A38"-"\u0A39"]
+ | ["\u0A59"-"\u0A5C"] | "\u0A5E" | ["\u0A72"-"\u0A74"] | ["\u0A85"-"\u0A8B"] | "\u0A8D"
+ | ["\u0A8F"-"\u0A91"] | ["\u0A93"-"\u0AA8"] | ["\u0AAA"-"\u0AB0"] | ["\u0AB2"-"\u0AB3"]
+ | ["\u0AB5"-"\u0AB9"] | "\u0ABD" | "\u0AE0" | ["\u0B05"-"\u0B0C"] | ["\u0B0F"-"\u0B10"]
+ | ["\u0B13"-"\u0B28"] | ["\u0B2A"-"\u0B30"] | ["\u0B32"-"\u0B33"] | ["\u0B36"-"\u0B39"]
+ | "\u0B3D" | ["\u0B5C"-"\u0B5D"] | ["\u0B5F"-"\u0B61"] | ["\u0B85"-"\u0B8A"]
+ | ["\u0B8E"-"\u0B90"] | ["\u0B92"-"\u0B95"] | ["\u0B99"-"\u0B9A"] | "\u0B9C" | ["\u0B9E"-"\u0B9F"]
+ | ["\u0BA3"-"\u0BA4"] | ["\u0BA8"-"\u0BAA"] | ["\u0BAE"-"\u0BB5"] | ["\u0BB7"-"\u0BB9"]
+ | ["\u0C05"-"\u0C0C"] | ["\u0C0E"-"\u0C10"] | ["\u0C12"-"\u0C28"] | ["\u0C2A"-"\u0C33"]
+ | ["\u0C35"-"\u0C39"] | ["\u0C60"-"\u0C61"] | ["\u0C85"-"\u0C8C"] | ["\u0C8E"-"\u0C90"]
+ | ["\u0C92"-"\u0CA8"] | ["\u0CAA"-"\u0CB3"] | ["\u0CB5"-"\u0CB9"] | "\u0CDE" | ["\u0CE0"-"\u0CE1"]
+ | ["\u0D05"-"\u0D0C"] | ["\u0D0E"-"\u0D10"] | ["\u0D12"-"\u0D28"] | ["\u0D2A"-"\u0D39"]
+ | ["\u0D60"-"\u0D61"] | ["\u0E01"-"\u0E2E"] | "\u0E30" | ["\u0E32"-"\u0E33"] | ["\u0E40"-"\u0E45"]
+ | ["\u0E81"-"\u0E82"] | "\u0E84" | ["\u0E87"-"\u0E88"] | "\u0E8A" | "\u0E8D" | ["\u0E94"-"\u0E97"]
+ | ["\u0E99"-"\u0E9F"] | ["\u0EA1"-"\u0EA3"] | "\u0EA5" | "\u0EA7" | ["\u0EAA"-"\u0EAB"]
+ | ["\u0EAD"-"\u0EAE"] | "\u0EB0" | ["\u0EB2"-"\u0EB3"] | "\u0EBD" | ["\u0EC0"-"\u0EC4"]
+ | ["\u0F40"-"\u0F47"] | ["\u0F49"-"\u0F69"] | ["\u10A0"-"\u10C5"] | ["\u10D0"-"\u10F6"] | "\u1100"
+ | ["\u1102"-"\u1103"] | ["\u1105"-"\u1107"] | "\u1109" | ["\u110B"-"\u110C"] | ["\u110E"-"\u1112"]
+ | "\u113C" | "\u113E" | "\u1140" | "\u114C" | "\u114E" | "\u1150" | ["\u1154"-"\u1155"] | "\u1159"
+ | ["\u115F"-"\u1161"] | "\u1163" | "\u1165" | "\u1167" | "\u1169" | ["\u116D"-"\u116E"]
+ | ["\u1172"-"\u1173"] | "\u1175" | "\u119E" | "\u11A8" | "\u11AB" | ["\u11AE"-"\u11AF"]
+ | ["\u11B7"-"\u11B8"] | "\u11BA" | ["\u11BC"-"\u11C2"] | "\u11EB" | "\u11F0" | "\u11F9"
+ | ["\u1E00"-"\u1E9B"] | ["\u1EA0"-"\u1EF9"] | ["\u1F00"-"\u1F15"] | ["\u1F18"-"\u1F1D"]
+ | ["\u1F20"-"\u1F45"] | ["\u1F48"-"\u1F4D"] | ["\u1F50"-"\u1F57"] | "\u1F59" | "\u1F5B" | "\u1F5D"
+ | ["\u1F5F"-"\u1F7D"] | ["\u1F80"-"\u1FB4"] | ["\u1FB6"-"\u1FBC"] | "\u1FBE" | ["\u1FC2"-"\u1FC4"]
+ | ["\u1FC6"-"\u1FCC"] | ["\u1FD0"-"\u1FD3"] | ["\u1FD6"-"\u1FDB"] | ["\u1FE0"-"\u1FEC"]
+ | ["\u1FF2"-"\u1FF4"] | ["\u1FF6"-"\u1FFC"] | "\u2126" | ["\u212A"-"\u212B"] | "\u212E"
+ | ["\u2180"-"\u2182"] | ["\u3041"-"\u3094"] | ["\u30A1"-"\u30FA"] | ["\u3105"-"\u312C"]
+ | ["\uAC00"-"\uD7A3"] >
+| <#$Ideographic : ["\u4E00"-"\u9FA5"] | "\u3007" | ["\u3021"-"\u3029"] >
+| <#CombiningChar :
+ ["\u0300"-"\u0345"] | ["\u0360"-"\u0361"] | ["\u0483"-"\u0486"] | ["\u0591"-"\u05A1"]
+ | ["\u05A3"-"\u05B9"] | ["\u05BB"-"\u05BD"] | "\u05BF" | ["\u05C1"-"\u05C2"] | "\u05C4"
+ | ["\u064B"-"\u0652"] | "\u0670" | ["\u06D6"-"\u06DC"] | ["\u06DD"-"\u06DF"]
+ | ["\u06E0"-"\u06E4"] | ["\u06E7"-"\u06E8"] | ["\u06EA"-"\u06ED"] | ["\u0901"-"\u0903"]
+ | "\u093C" | ["\u093E"-"\u094C"] | "\u094D" | ["\u0951"-"\u0954"] | ["\u0962"-"\u0963"]
+ | ["\u0981"-"\u0983"] | "\u09BC" | "\u09BE" | "\u09BF" | ["\u09C0"-"\u09C4"] | ["\u09C7"-"\u09C8"]
+ | ["\u09CB"-"\u09CD"] | "\u09D7" | ["\u09E2"-"\u09E3"] | "\u0A02" | "\u0A3C" | "\u0A3E"
+ | "\u0A3F" | ["\u0A40"-"\u0A42"] | ["\u0A47"-"\u0A48"] | ["\u0A4B"-"\u0A4D"] | ["\u0A70"-"\u0A71"]
+ | ["\u0A81"-"\u0A83"] | "\u0ABC" | ["\u0ABE"-"\u0AC5"] | ["\u0AC7"-"\u0AC9"] | ["\u0ACB"-"\u0ACD"]
+ | ["\u0B01"-"\u0B03"] | "\u0B3C" | ["\u0B3E"-"\u0B43"] | ["\u0B47"-"\u0B48"] | ["\u0B4B"-"\u0B4D"]
+ | ["\u0B56"-"\u0B57"] | ["\u0B82"-"\u0B83"] | ["\u0BBE"-"\u0BC2"] | ["\u0BC6"-"\u0BC8"]
+ | ["\u0BCA"-"\u0BCD"] | "\u0BD7" | ["\u0C01"-"\u0C03"] | ["\u0C3E"-"\u0C44"] | ["\u0C46"-"\u0C48"]
+ | ["\u0C4A"-"\u0C4D"] | ["\u0C55"-"\u0C56"] | ["\u0C82"-"\u0C83"] | ["\u0CBE"-"\u0CC4"]
+ | ["\u0CC6"-"\u0CC8"] | ["\u0CCA"-"\u0CCD"] | ["\u0CD5"-"\u0CD6"] | ["\u0D02"-"\u0D03"]
+ | ["\u0D3E"-"\u0D43"] | ["\u0D46"-"\u0D48"] | ["\u0D4A"-"\u0D4D"] | "\u0D57" | "\u0E31"
+ | ["\u0E34"-"\u0E3A"] | ["\u0E47"-"\u0E4E"] | "\u0EB1" | ["\u0EB4"-"\u0EB9"]
+ | ["\u0EBB"-"\u0EBC"] | ["\u0EC8"-"\u0ECD"] | ["\u0F18"-"\u0F19"] | "\u0F35" | "\u0F37" | "\u0F39"
+ | "\u0F3E" | "\u0F3F" | ["\u0F71"-"\u0F84"] | ["\u0F86"-"\u0F8B"] | ["\u0F90"-"\u0F95"] | "\u0F97"
+ | ["\u0F99"-"\u0FAD"] | ["\u0FB1"-"\u0FB7"] | "\u0FB9" | ["\u20D0"-"\u20DC"] | "\u20E1"
+ | ["\u302A"-"\u302F"] | "\u3099" | "\u309A" >
+| <#$Digit:
+ ["\u0030"-"\u0039"] | ["\u0660"-"\u0669"] | ["\u06F0"-"\u06F9"] | ["\u0966"-"\u096F"]
+ | ["\u09E6"-"\u09EF"] | ["\u0A66"-"\u0A6F"] | ["\u0AE6"-"\u0AEF"] | ["\u0B66"-"\u0B6F"]
+ | ["\u0BE7"-"\u0BEF"] | ["\u0C66"-"\u0C6F"] | ["\u0CE6"-"\u0CEF"] | ["\u0D66"-"\u0D6F"]
+ | ["\u0E50"-"\u0E59"] | ["\u0ED0"-"\u0ED9"] | ["\u0F20"-"\u0F29"] >
+| <#Extender :
+ "\u00B7" | "\u02D0" | "\u02D1" | "\u0387" | "\u0640" | "\u0E46" | "\u0EC6" | "\u3005"
+ | ["\u3031"-"\u3035"] | ["\u309D"-"\u309E"] | ["\u30FC"-"\u30FE"] >
+ | <#$EscapedCharacter: "\\" (["\u0020"-"\u007E", "\n", "\r" ] | "\r\n" ) >
+ | <#$NotWhitespaceNotQuoteNotEscape:~["\"","\\","\n","\r"]>
+ | <#$Letter: <$BaseChar> | <$Ideographic> >
+ | <#$Number: ("-")? ("." (<$Digit>)+) | ((<$Digit>)+ ("." (<$Digit>)*)?)>
+ | <#$UnquotedString: (<$Letter>|"_"|<$Digit>)+ >
+ | <#$QuotedString: "\"" (<$NotWhitespaceNotQuoteNotEscape> | <$EscapedCharacter>)* "\"" >
+}
+
+/*===========================================================================================
+ DOT Grammar starts here
+ ===========================================================================================*/
+SimpleNode parse() #Parse :
+{}
+{
+ graph()
+ {
+ return jjtThis;
+ }
+}
+
+void graph() #Graph :
+{Token t;}
+{
+ [<STRICT>] (<GRAPH>|<DIGRAPH>) t=<ID>{jjtThis.setName(t.image);} <LBRACE> stmt_list() <RBRACE> <EOF>
+}
+
+void stmt_list() #StatementList :
+{}
+{
+ stmt() [<SEMICOLON>] [stmt_list()]
+}
+
+
+void stmt() #Statement :
+{}
+{
+ ( LOOKAHEAD(edge_stmt()) edge_stmt() | LOOKAHEAD(2) subgraph() | LOOKAHEAD(2) node_stmt() | LOOKAHEAD(2) attr_stmt() | LOOKAHEAD(2) (<ID> <EQ> <ID>) )
+
+}
+
+/*
+void ideq_stmt() :
+{}
+{
+ <ID> <EQ> <ID>
+}
+*/
+
+void attr_stmt() #AttributeStatement :
+{Token t;}
+{
+ (t=<GRAPH>{jjtThis.setName(t.image);} | t=<NODE>{jjtThis.setName(t.image);} | t=<EDGE>{jjtThis.setName(t.image);}) attr_list()
+}
+
+void node_stmt() #NodeStatement :
+{}
+{
+ node_id(jjtThis) [attr_list()]
+}
+
+void node_id(NamedNode namedNode) #NodeId :
+{Token t;}
+{
+ t=<ID>{namedNode.setName(t.image);} [port(namedNode)]
+}
+
+void port(NamedNode namedNode) #Port :
+{Token t;}
+{
+ <COLON> (LOOKAHEAD(2) t=<ID>{namedNode.setPort(t.image);} <COLON> <ID> | <ID>)
+}
+
+void edge_stmt() #EdgeStatement :
+{}
+{
+ (node_id(jjtThis)| subgraph()) edgeRHS() [attr_list()]
+}
+
+void subgraph() #Subgraph :
+{Token t;}
+{
+ (
+ LOOKAHEAD(2)
+ ([<SUBGRAPH> [t=<ID>{jjtThis.setName(t.image);}]] <LBRACE> stmt_list() <RBRACE>)
+ |
+ (<SUBGRAPH> t=<ID>{jjtThis.setName(t.image);})
+ )
+
+}
+
+void edgeRHS() #EdgeRHS :
+{}
+{
+ edgeop() (node_id(jjtThis) | subgraph()) [ edgeRHS() ]
+}
+
+void edgeop() #void :
+{}
+{
+ <EDGE_UNDIRECTED> | <EDGE_DIRECTED>
+}
+
+
+void attr_list() #AttributeList :
+{}
+{
+ <LBRACKET> a_list() <RBRACKET>
+}
+
+void a_list() #AList :
+{Token t;}
+{
+ t=<ID>{jjtThis.setName(t.image);} [<EQ> t=<ID>{jjtThis.setValue(t.image);}] [<COMMA>] [a_list()]
+}
+
diff --git a/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphControllerTest.java b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphControllerTest.java
new file mode 100644
index 0000000..cb76b97
--- /dev/null
+++ b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphControllerTest.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphController.PortStyle;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class GraphControllerTest {
+
+ Workflow dataflow;
+
+ GraphController graphController;
+
+ @Before
+ public void setUp() throws Exception {
+// System.setProperty("raven.eclipse", "true");
+// setUpRavenRepository();
+// dataflow = WorkflowModelTranslator.doTranslation(loadScufl("nested_iteration.xml"));
+ graphController = new GraphController(dataflow, null, false, null, null, null, null) {
+
+ @Override
+ public GraphEdge createGraphEdge() {
+ return new GraphEdge(this);
+ }
+
+ @Override
+ public Graph createGraph() {
+ return new Graph(this);
+ }
+
+ @Override
+ public GraphNode createGraphNode() {
+ return new GraphNode(this);
+ }
+
+ @Override
+ public void redraw() {
+
+ }
+
+ };
+ graphController.setPortStyle(PortStyle.NONE);
+ }
+
+ @Test
+ @Ignore
+ public void testGenerateGraph() throws IOException, InterruptedException {
+ Graph graph = graphController.generateGraph();
+ assertEquals(5, graph.getNodes().size());
+ assertEquals(9, graph.getEdges().size());
+ assertEquals(1, graph.getSubgraphs().size());
+ }
+
+}
diff --git a/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphEdgeTest.java b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphEdgeTest.java
new file mode 100644
index 0000000..10a3c20
--- /dev/null
+++ b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphEdgeTest.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge.ArrowStyle;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GraphEdgeTest {
+
+ private GraphEdge edge;
+
+ private GraphNode source;
+
+ private GraphNode sink;
+
+ private ArrowStyle arrowHeadStyle;
+
+ private ArrowStyle arrowTailStyle;
+
+ private GraphController graphController;
+
+ @Before
+ public void setUp() throws Exception {
+ source = new GraphNode(graphController);
+ sink = new GraphNode(graphController);
+ arrowHeadStyle = ArrowStyle.DOT;
+ arrowTailStyle = ArrowStyle.NORMAL;
+ edge = new GraphEdge(graphController);
+ edge.setArrowHeadStyle(arrowHeadStyle);
+ edge.setArrowTailStyle(arrowTailStyle);
+ edge.setSink(sink);
+ edge.setSource(source);
+ }
+
+ @Test
+ public void testEdge() {
+ edge = new GraphEdge(graphController);
+ assertNull(edge.getSource());
+ assertNull(edge.getSink());
+ assertNull(edge.getLabel());
+ }
+
+ @Test
+ public void testEdgeNodeNode() {
+ edge = new GraphEdge(graphController);
+ edge.setSource(source);
+ edge.setSink(sink);
+ assertEquals(source, edge.getSource());
+ assertEquals(sink, edge.getSink());
+ assertNull(edge.getLabel());
+ }
+
+ @Test
+ public void testGetSource() {
+ assertEquals(source, edge.getSource());
+ }
+
+ @Test
+ public void testSetSource() {
+ GraphNode node = new GraphNode(graphController);
+ edge.setSource(node);
+ assertEquals(node, edge.getSource());
+ edge.setSource(null);
+ assertNull(edge.getSource());
+ }
+
+ @Test
+ public void testGetSink() {
+ assertEquals(sink, edge.getSink());
+ }
+
+ @Test
+ public void testSetSink() {
+ GraphNode node = new GraphNode(graphController);
+ edge.setSink(node);
+ assertEquals(node, edge.getSink());
+ edge.setSink(null);
+ assertNull(edge.getSink());
+ }
+
+ @Test
+ public void testGetArrowHeadStyle() {
+ assertEquals(arrowHeadStyle, edge.getArrowHeadStyle());
+ }
+
+ @Test
+ public void testSetArrowHeadStyle() {
+ edge.setArrowHeadStyle(ArrowStyle.DOT);
+ assertEquals(ArrowStyle.DOT, edge.getArrowHeadStyle());
+ edge.setArrowHeadStyle(null);
+ assertNull(edge.getArrowHeadStyle());
+ }
+
+ @Test
+ public void testGetArrowTailStyle() {
+ assertEquals(arrowTailStyle, edge.getArrowTailStyle());
+ }
+
+ @Test
+ public void testSetArrowTailStyle() {
+ edge.setArrowTailStyle(ArrowStyle.NORMAL);
+ assertEquals(ArrowStyle.NORMAL, edge.getArrowTailStyle());
+ edge.setArrowTailStyle(null);
+ assertNull(edge.getArrowTailStyle());
+ }
+
+}
diff --git a/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphElementTest.java b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphElementTest.java
new file mode 100644
index 0000000..8d6b7f8
--- /dev/null
+++ b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphElementTest.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.awt.Color;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GraphElementTest {
+
+ private GraphElement element;
+
+ private String id;
+
+ private String label;
+
+ private LineStyle lineStyle;
+
+ private Color color;
+
+ private Color fillColor;
+
+ private GraphElement parent;
+
+ private GraphController graphController;
+
+ @Before
+ public void setUp() throws Exception {
+ element = new GraphElement(graphController) {};
+ id = "element-id";
+ label = "element-label";
+ lineStyle = LineStyle.NONE;
+ color = Color.BLUE;
+ fillColor = Color.GREEN;
+ parent = new GraphNode(graphController);
+ element.setId(id);
+ element.setLabel(label);
+ element.setLineStyle(lineStyle);
+ element.setColor(color);
+ element.setFillColor(fillColor);
+ element.setParent(parent);
+ }
+
+ @Test
+ public void testGetParent() {
+ assertEquals(parent, element.getParent());
+ }
+
+ @Test
+ public void testSetParent() {
+ GraphNode newParent = new GraphNode(graphController);
+ element.setParent(newParent);
+ assertEquals(newParent, element.getParent());
+ element.setParent(null);
+ assertNull(element.getParent());
+ }
+
+ @Test
+ public void testGetLabel() {
+ assertEquals(label, element.getLabel());
+ }
+
+ @Test
+ public void testSetLabel() {
+ element.setLabel("new-label");
+ assertEquals("new-label", element.getLabel());
+ element.setLabel(null);
+ assertNull(element.getLabel());
+ }
+
+ @Test
+ public void testGetId() {
+ assertEquals(id, element.getId());
+ }
+
+ @Test
+ public void testSetId() {
+ element.setId("new-id");
+ assertEquals("new-id", element.getId());
+ element.setId(null);
+ assertNull(element.getId());
+ }
+
+ @Test
+ public void testGetColor() {
+ assertEquals(color, element.getColor());
+ }
+
+ @Test
+ public void testSetColor() {
+ element.setColor(Color.RED);
+ assertEquals(Color.RED, element.getColor());
+ element.setColor(null);
+ assertNull(element.getColor());
+ }
+
+ @Test
+ public void testGetFillColor() {
+ assertEquals(fillColor, element.getFillColor());
+ }
+
+ @Test
+ public void testSetFillColor() {
+ element.setFillColor(Color.RED);
+ assertEquals(Color.RED, element.getFillColor());
+ element.setFillColor(null);
+ assertNull(element.getFillColor());
+ }
+
+ @Test
+ public void testGetLineStyle() {
+ assertEquals(lineStyle, element.getLineStyle());
+ }
+
+ @Test
+ public void testSetLineStyle() {
+ element.setLineStyle(LineStyle.DOTTED);
+ assertEquals(LineStyle.DOTTED, element.getLineStyle());
+ element.setLineStyle(null);
+ assertNull(element.getLineStyle());
+ }
+
+}
diff --git a/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphNodeTest.java b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphNodeTest.java
new file mode 100644
index 0000000..c5bcd6c
--- /dev/null
+++ b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphNodeTest.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Dimension;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphShapeElement.Shape;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GraphNodeTest {
+
+ private GraphNode node;
+
+ private Shape shape;
+
+ private Dimension size;
+
+ private Graph graph;
+
+ private boolean expanded;
+
+ private GraphController graphController;
+
+ @Before
+ public void setUp() throws Exception {
+ shape = Shape.HOUSE;
+ size = new Dimension(1, 2);
+ graph = new Graph(graphController);
+ expanded = false;
+ node = new GraphNode(graphController);
+ node.setShape(shape);
+ node.setSize(size);
+ node.setGraph(graph);
+ node.setExpanded(expanded);
+ }
+
+ @Test
+ public void testNode() {
+ assertNotNull(new GraphNode(graphController));
+ }
+
+ @Test
+ public void testAddSinkNode() {
+ GraphNode newNode = new GraphNode(graphController);
+ node.addSinkNode(newNode);
+ assertEquals(1, node.getSinkNodes().size());
+ assertTrue(node.getSinkNodes().contains(newNode));
+ assertEquals(node, newNode.getParent());
+ }
+
+ @Test
+ public void testAddSourceNode() {
+ GraphNode newNode = new GraphNode(graphController);
+ node.addSourceNode(newNode);
+ assertEquals(1, node.getSourceNodes().size());
+ assertTrue(node.getSourceNodes().contains(newNode));
+ assertEquals(node, newNode.getParent());
+ }
+
+ @Test
+ public void testGetGraph() {
+ assertEquals(graph, node.getGraph());
+ }
+
+ @Test
+ public void testGetHeight() {
+ assertEquals(size.height, node.getHeight(), 0);
+ }
+
+ @Test
+ public void testGetShape() {
+ assertEquals(shape, node.getShape());
+ }
+
+ @Test
+ public void testGetSinkNodes() {
+ assertNotNull(node.getSinkNodes());
+ assertEquals(0, node.getSinkNodes().size());
+ }
+
+ @Test
+ public void testGetSize() {
+ assertEquals(size, node.getSize());
+ }
+
+ @Test
+ public void testGetSourceNodes() {
+ assertNotNull(node.getSourceNodes());
+ assertEquals(0, node.getSourceNodes().size());
+ }
+
+ @Test
+ public void testGetWidth() {
+ assertEquals(size.width, node.getWidth(), 0);
+ }
+
+ @Test
+ public void testIsExpanded() {
+ assertEquals(expanded, node.isExpanded());
+ }
+
+ @Test
+ public void testRemoveSinkNode() {
+ GraphNode newNode = new GraphNode(graphController);
+ assertFalse(node.removeSinkNode(newNode));
+ node.addSinkNode(newNode);
+ assertTrue(node.removeSinkNode(newNode));
+ assertFalse(node.getSinkNodes().contains(newNode));
+ }
+
+ @Test
+ public void testRemoveSourceNode() {
+ GraphNode newNode = new GraphNode(graphController);
+ assertFalse(node.removeSourceNode(newNode));
+ node.addSourceNode(newNode);
+ assertTrue(node.removeSourceNode(newNode));
+ assertFalse(node.getSourceNodes().contains(newNode));
+ }
+
+ @Test
+ public void testSetExpanded() {
+ node.setExpanded(true);
+ assertEquals(true, node.isExpanded());
+ node.setExpanded(false);
+ assertEquals(false, node.isExpanded());
+ }
+
+ @Test
+ public void testSetGraph() {
+ Graph newGraph = new Graph(graphController);
+ node.setGraph(newGraph);
+ assertEquals(newGraph, node.getGraph());
+ node.setGraph(null);
+ assertNull(node.getGraph());
+ }
+
+ @Test
+ public void testSetShape() {
+ node.setShape(Shape.INVTRIANGLE);
+ assertEquals(Shape.INVTRIANGLE, node.getShape());
+ node.setShape(Shape.TRIANGLE);
+ assertEquals(Shape.TRIANGLE, node.getShape());
+ }
+
+ @Test
+ public void testSetSize() {
+ node.setSize(new Dimension(23, 6));
+ assertEquals(new Dimension(23, 6), node.getSize());
+ node.setSize(new Dimension(14, 4));
+ assertEquals(new Dimension(14, 4), node.getSize());
+ }
+
+}
diff --git a/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphTest.java b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphTest.java
new file mode 100644
index 0000000..44a5aaf
--- /dev/null
+++ b/taverna-workbench-graph-model/src/test/java/net/sf/taverna/t2/workbench/models/graph/GraphTest.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * 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.models.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GraphTest {
+
+ private Graph graph;
+
+ private Alignment alignment;
+
+ private GraphController graphController;
+
+ @Before
+ public void setUp() throws Exception {
+ alignment = Alignment.VERTICAL;
+ graph = new Graph(graphController);
+ }
+
+ @Test
+ public void testGraph() {
+ assertNotNull(new Graph(graphController));
+ }
+
+ @Test
+ public void testAddEdge() {
+ GraphEdge newEdge = new GraphEdge(graphController);
+ graph.addEdge(newEdge);
+ assertEquals(1, graph.getEdges().size());
+ assertTrue(graph.getEdges().contains(newEdge));
+ }
+
+ @Test
+ public void testAddNode() {
+ GraphNode newNode = new GraphNode(graphController);
+ graph.addNode(newNode);
+ assertEquals(1, graph.getNodes().size());
+ assertTrue(graph.getNodes().contains(newNode));
+ assertEquals(graph, newNode.getParent());
+ }
+
+ @Test
+ public void testAddSubgraph() {
+ Graph newGraph = new Graph(graphController);
+ graph.addSubgraph(newGraph);
+ assertEquals(1, graph.getSubgraphs().size());
+ assertTrue(graph.getSubgraphs().contains(newGraph));
+ assertEquals(graph, newGraph.getParent());
+ }
+
+ @Test
+ public void testGetAlignment() {
+ assertEquals(alignment, graph.getAlignment());
+ }
+
+ @Test
+ public void testGetEdges() {
+ assertNotNull(graph.getNodes());
+ assertEquals(0, graph.getNodes().size());
+ }
+
+ @Test
+ public void testGetNodes() {
+ assertNotNull(graph.getEdges());
+ assertEquals(0, graph.getEdges().size());
+ }
+
+ @Test
+ public void testGetSubgraphs() {
+ assertNotNull(graph.getSubgraphs());
+ assertEquals(0, graph.getSubgraphs().size());
+ }
+
+ @Test
+ public void testRemoveEdge() {
+ GraphEdge newEdge = new GraphEdge(graphController);
+ assertFalse(graph.removeEdge(newEdge));
+ graph.addEdge(newEdge);
+ assertTrue(graph.removeEdge(newEdge));
+ assertFalse(graph.getNodes().contains(newEdge));
+ }
+
+ @Test
+ public void testRemoveNode() {
+ GraphNode newNode = new GraphNode(graphController);
+ assertFalse(graph.removeNode(newNode));
+ graph.addNode(newNode);
+ assertTrue(graph.removeNode(newNode));
+ assertFalse(graph.getNodes().contains(newNode));
+ }
+
+ @Test
+ public void testRemoveSubgraph() {
+ Graph newGraph = new Graph(graphController);
+ assertFalse(graph.removeSubgraph(newGraph));
+ graph.addSubgraph(newGraph);
+ assertTrue(graph.removeSubgraph(newGraph));
+ assertFalse(graph.getSubgraphs().contains(newGraph));
+ }
+
+ @Test
+ public void testSetAlignment() {
+ graph.setAlignment(Alignment.VERTICAL);
+ assertEquals(Alignment.VERTICAL, graph.getAlignment());
+ graph.setAlignment(Alignment.HORIZONTAL);
+ assertEquals(Alignment.HORIZONTAL, graph.getAlignment());
+ }
+
+}
diff --git a/taverna-workbench-graph-model/src/test/resources/nested_iteration.xml b/taverna-workbench-graph-model/src/test/resources/nested_iteration.xml
new file mode 100644
index 0000000..3675361
--- /dev/null
+++ b/taverna-workbench-graph-model/src/test/resources/nested_iteration.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<s:scufl xmlns:s="http://org.embl.ebi.escience/xscufl/0.1alpha" version="0.2" log="0">
+ <s:workflowdescription lsid="urn:lsid:net.sf.taverna:wfDefinition:c7016fc0-c2f4-4171-b6f1-430f408f4822" author="" title="nested_iteration" />
+ <s:processor name="constant" boring="true">
+ <s:stringconstant>constant</s:stringconstant>
+ </s:processor>
+ <s:processor name="generate_list">
+ <s:defaults>
+ <s:default name="prefix">prefix</s:default>
+ </s:defaults>
+ <s:beanshell>
+ <s:scriptvalue>list = new ArrayList();
+for (int i = 0; i < 20; i++) {
+ list.add(prefix + i);
+}</s:scriptvalue>
+ <s:beanshellinputlist>
+ <s:beanshellinput s:syntactictype="'text/plain'">prefix</s:beanshellinput>
+ </s:beanshellinputlist>
+ <s:beanshelloutputlist>
+ <s:beanshelloutput s:syntactictype="l('text/plain')">list</s:beanshelloutput>
+ </s:beanshelloutputlist>
+ <s:dependencies s:classloader="iteration" />
+ </s:beanshell>
+ </s:processor>
+ <s:processor name="merge">
+ <s:workflow>
+ <s:scufl version="0.2" log="0">
+ <s:workflowdescription lsid="urn:lsid:net.sf.taverna:wfDefinition:3368fb8d-ecc7-4fcd-b511-6ace84b13c81" author="" title="Untitled workflow #24" />
+ <s:processor name="Nested_Workflow">
+ <s:workflow>
+ <s:scufl version="0.2" log="0">
+ <s:workflowdescription lsid="urn:lsid:net.sf.taverna:wfDefinition:75b99c76-7a76-4d3c-8d39-8c48df3355ad" author="" title="Untitled workflow #36" />
+ <s:processor name="concat">
+ <s:beanshell>
+ <s:scriptvalue>Thread.sleep(200);
+out = in1 + in2;</s:scriptvalue>
+ <s:beanshellinputlist>
+ <s:beanshellinput s:syntactictype="'text/plain'">in1</s:beanshellinput>
+ <s:beanshellinput s:syntactictype="'text/plain'">in2</s:beanshellinput>
+ </s:beanshellinputlist>
+ <s:beanshelloutputlist>
+ <s:beanshelloutput s:syntactictype="'text/plain'">out</s:beanshelloutput>
+ </s:beanshelloutputlist>
+ <s:dependencies s:classloader="iteration" />
+ </s:beanshell>
+ </s:processor>
+ <s:link source="in1" sink="concat:in1" />
+ <s:link source="in2" sink="concat:in2" />
+ <s:link source="concat:out" sink="out" />
+ <s:source name="in1" />
+ <s:source name="in2" />
+ <s:sink name="out" />
+ </s:scufl>
+ </s:workflow>
+ </s:processor>
+ <s:link source="in1" sink="Nested_Workflow:in1" />
+ <s:link source="in2" sink="Nested_Workflow:in2" />
+ <s:link source="Nested_Workflow:out" sink="out" />
+ <s:source name="in1" />
+ <s:source name="in2" />
+ <s:sink name="out" />
+ </s:scufl>
+ </s:workflow>
+ <s:mergemode input="in2" mode="merge" />
+ </s:processor>
+ <s:link source="constant:value" sink="constant" />
+ <s:link source="constant:value" sink="merge:in1" />
+ <s:link source="generate_list:list" sink="list" />
+ <s:link source="generate_list:list" sink="merge:in2" />
+ <s:link source="generate_list:list" sink="merge:in2" />
+ <s:link source="merge:out" sink="concat" />
+ <s:source name="input" />
+ <s:sink name="concat">
+ <s:metadata>
+ <s:mimeTypes>
+ <s:mimeType>'text/plain'</s:mimeType>
+ </s:mimeTypes>
+ </s:metadata>
+ </s:sink>
+ <s:sink name="list">
+ <s:metadata>
+ <s:mimeTypes>
+ <s:mimeType>l('text/plain')</s:mimeType>
+ </s:mimeTypes>
+ </s:metadata>
+ </s:sink>
+ <s:sink name="constant">
+ <s:metadata>
+ <s:mimeTypes>
+ <s:mimeType>'text/plain'</s:mimeType>
+ </s:mimeTypes>
+ </s:metadata>
+ </s:sink>
+ <s:coordination name="constant_BLOCKON_generate_list">
+ <s:condition>
+ <s:state>Completed</s:state>
+ <s:target>generate_list</s:target>
+ </s:condition>
+ <s:action>
+ <s:target>constant</s:target>
+ <s:statechange>
+ <s:from>Scheduled</s:from>
+ <s:to>Running</s:to>
+ </s:statechange>
+ </s:action>
+ </s:coordination>
+ <s:coordination name="merge_BLOCKON_generate_list">
+ <s:condition>
+ <s:state>Completed</s:state>
+ <s:target>generate_list</s:target>
+ </s:condition>
+ <s:action>
+ <s:target>merge</s:target>
+ <s:statechange>
+ <s:from>Scheduled</s:from>
+ <s:to>Running</s:to>
+ </s:statechange>
+ </s:action>
+ </s:coordination>
+</s:scufl>
+
diff --git a/taverna-workbench-graph-view/pom.xml b/taverna-workbench-graph-view/pom.xml
new file mode 100644
index 0000000..df8cdc9
--- /dev/null
+++ b/taverna-workbench-graph-view/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <packaging>bundle</packaging>
+ <name>Graph View</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>design-ui</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>io</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.batik</groupId>
+ <artifactId>batik-osgi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/AutoScrollInteractor.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/AutoScrollInteractor.java
new file mode 100644
index 0000000..65a4aa5
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/AutoScrollInteractor.java
@@ -0,0 +1,181 @@
+package net.sf.taverna.t2.workbench.views.graph;
+
+import static java.awt.event.InputEvent.BUTTON1_DOWN_MASK;
+import static java.awt.event.InputEvent.BUTTON1_MASK;
+import static java.awt.event.MouseEvent.BUTTON1;
+import static java.awt.event.MouseEvent.MOUSE_DRAGGED;
+import static java.awt.event.MouseEvent.MOUSE_PRESSED;
+import static java.lang.System.currentTimeMillis;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.batik.swing.gvt.InteractorAdapter;
+import org.apache.batik.swing.gvt.JGVTComponent;
+
+/**
+ * An interactor that scrolls the canvas view if the mouse is dragged to the
+ * edge of the canvas.
+ *
+ * @author David Withers
+ */
+public class AutoScrollInteractor extends InteractorAdapter {
+ /**
+ * Defines the border around the canvas in which the auto scroll will become
+ * active.
+ */
+ private static final int BORDER = 25;
+ /**
+ * The interval, in milliseconds, between scroll events.
+ */
+ private static final long SCROLL_INTERVAL = 100;
+
+ private JSVGCanvas svgCanvas;
+ private Dimension canvasSize;
+ private int scrollX;
+ private int scrollY;
+ private int mouseX;
+ private int mouseY;
+
+ /**
+ * Component used to identify mouse events generated by this class
+ */
+ private Component eventIdentifier = new Component() {
+ private static final long serialVersionUID = -295542754718804222L;
+ };
+
+ private static Timer timer = new Timer("GraphAutoScrollTimer", true);
+
+ private TimerTask task;
+
+ /**
+ * Whether the interactor has finished.
+ */
+ protected boolean finished = true;
+
+ public AutoScrollInteractor(JSVGCanvas svgCanvas) {
+ this.svgCanvas = svgCanvas;
+ }
+
+ @Override
+ public boolean startInteraction(InputEvent ie) {
+ int mods = ie.getModifiers();
+ if (ie.getID() == MOUSE_PRESSED && (mods & BUTTON1_MASK) != 0) {
+ AffineTransform transform = svgCanvas.getRenderingTransform();
+ // check if we're zoomed in
+ if (transform.getScaleX() > 1d || transform.getScaleY() > 1d) {
+ canvasSize = svgCanvas.getSize();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean endInteraction() {
+ return finished;
+ }
+
+ @Override
+ public void mousePressed(final MouseEvent e) {
+ if (startInteraction(e)) {
+ finished = false;
+ task = new TimerTask() {
+ @Override
+ public void run() {
+ scrollTimerCallback(e);
+ }
+ };
+ timer.schedule(task, 0, SCROLL_INTERVAL);
+ }
+ }
+
+ /**
+ * Dispatches a mouse drag event that updates the mouse location by the
+ * amount that the canvas has been scrolled.
+ *
+ * @param dragX
+ * @param dragY
+ */
+ private void dispatchDragEvent(double dragX, double dragY) {
+ int x = (int) (mouseX + dragX);
+ int y = (int) (mouseY + dragY);
+ MouseEvent mouseDragEvent = new MouseEvent(eventIdentifier,
+ MOUSE_DRAGGED, currentTimeMillis(), BUTTON1_DOWN_MASK, x, y, 1,
+ false, BUTTON1);
+ svgCanvas.dispatchEvent(mouseDragEvent);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (!finished) {
+ finished = true;
+ scrollX = 0;
+ scrollY = 0;
+ if (task != null)
+ task.cancel();
+ }
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ // ignore events generated by this class
+ if (!finished && e.getSource() != eventIdentifier) {
+ mouseX = e.getX();
+ mouseY = e.getY();
+ int minX = BORDER;
+ int maxX = canvasSize.width - BORDER;
+ int minY = BORDER;
+ int maxY = canvasSize.height - BORDER;
+
+ scrollX = (mouseX < minX) ? (minX - mouseX)
+ : (mouseX > maxX) ? (maxX - mouseX) : 0;
+ scrollY = (mouseY < minY) ? (minY - mouseY)
+ : (mouseY > maxY) ? (maxY - mouseY) : 0;
+ }
+ }
+
+ private void scrollTimerCallback(MouseEvent e) {
+ double x = scrollX;
+ double y = scrollY;
+ if (x == 0 && y == 0)
+ return;
+
+ JGVTComponent c = (JGVTComponent) e.getSource();
+ AffineTransform rt = (AffineTransform) c.getRenderingTransform()
+ .clone();
+ double currentTranslateX = rt.getTranslateX();
+ double currentTranslateY = rt.getTranslateY();
+ // the tranlation that will show the east edge
+ double maxTranslateX = -((canvasSize.width * rt.getScaleX()) - canvasSize.width);
+ // the translation that will show the south
+ double maxTranslateY = -((canvasSize.height * rt.getScaleY()) - canvasSize.height);
+
+ if (x > 0 && currentTranslateX + x > 0)
+ // scroll left && not at west edge
+ x = -currentTranslateX;
+ else if (x < 0 && currentTranslateX + x < maxTranslateX)
+ // scroll right && not at east edge
+ x = maxTranslateX - currentTranslateX;
+
+ if (y > 0 && currentTranslateY + y > 0)
+ // scroll up && not at north edge
+ y = -currentTranslateY;
+ else if (y < 0 && currentTranslateY + y < maxTranslateY)
+ // scroll down && not at south edge
+ y = maxTranslateY - currentTranslateY;
+
+ if (x != 0d || y != 0d) {
+ AffineTransform at = AffineTransform.getTranslateInstance(x, y);
+ rt.preConcatenate(at);
+ c.setRenderingTransform(rt);
+ dispatchDragEvent(x, y);
+ }
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/GraphViewComponent.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/GraphViewComponent.java
new file mode 100644
index 0000000..55bcf3f
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/GraphViewComponent.java
@@ -0,0 +1,548 @@
+/*******************************************************************************
+ * 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.graph;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static javax.swing.Action.SHORT_DESCRIPTION;
+import static javax.swing.Action.SMALL_ICON;
+import static javax.swing.BoxLayout.PAGE_AXIS;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.allportIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.blobIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.expandNestedIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.horizontalIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.noportIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.refreshIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.verticalIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.zoomInIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.zoomOutIcon;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.ALIGNMENT;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.ANIMATION_ENABLED;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.ANIMATION_SPEED;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.PORT_STYLE;
+import static org.apache.batik.swing.svg.AbstractJSVGComponent.ALWAYS_DYNAMIC;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.Timer;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.models.graph.GraphController.PortStyle;
+import net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphController;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.dndhandler.ServiceTransferHandler;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration;
+import net.sf.taverna.t2.workbench.views.graph.menu.ResetDiagramAction;
+import net.sf.taverna.t2.workbench.views.graph.menu.ZoomInAction;
+import net.sf.taverna.t2.workbench.views.graph.menu.ZoomOutAction;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.batik.swing.JSVGScrollPane;
+import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
+import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author David Withers
+ * @author Alex Nenadic
+ * @author Tom Oinn
+ */
+public class GraphViewComponent extends JPanel implements UIComponentSPI {
+ private static final long serialVersionUID = 7404937056378331528L;
+ private static final Logger logger = Logger.getLogger(GraphViewComponent.class);
+
+ private Workflow workflow;
+ private SVGGraphController graphController;
+ private JPanel diagramPanel;
+
+ private Map<WorkflowBundle, Set<Workflow>> workflowsMap = new IdentityHashMap<>();
+
+ private Map<Workflow, SVGGraphController> graphControllerMap = new IdentityHashMap<>();
+ private Map<Workflow, JPanel> diagramPanelMap = new IdentityHashMap<>();
+ private Map<Workflow, Action[]> diagramActionsMap = new IdentityHashMap<>();
+
+ private Timer timer;
+
+ private CardLayout cardLayout;
+
+ private final ColourManager colourManager;
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+ private final GraphViewConfiguration graphViewConfiguration;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public GraphViewComponent(ColourManager colourManager,
+ EditManager editManager, FileManager fileManager,
+ MenuManager menuManager,
+ GraphViewConfiguration graphViewConfiguration,
+ WorkbenchConfiguration workbenchConfiguration,
+ SelectionManager selectionManager, ServiceRegistry serviceRegistry) {
+ this.colourManager = colourManager;
+ this.editManager = editManager;
+ this.menuManager = menuManager;
+ this.graphViewConfiguration = graphViewConfiguration;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+
+ cardLayout = new CardLayout();
+ setLayout(cardLayout);
+
+ ActionListener taskPerformer = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ if (graphController != null)
+ graphController.redraw();
+ timer.stop();
+ }
+ };
+ timer = new Timer(100, taskPerformer);
+
+ addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ if (timer.isRunning())
+ timer.restart();
+ else
+ timer.start();
+ }
+ });
+
+ editManager.addObserver(new EditManagerObserver());
+ selectionManager.addObserver(new SelectionManagerObserver());
+ fileManager.addObserver(new FileManagerObserver());
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (timer != null)
+ timer.stop();
+ }
+
+ @Override
+ public String getName() {
+ return "Graph View Component";
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public void onDisplay() {
+ }
+
+ @Override
+ public void onDispose() {
+ if (timer != null)
+ timer.stop();
+ }
+
+ private JPanel createDiagramPanel(Workflow workflow) {
+ final JPanel diagramPanel = new JPanel(new BorderLayout());
+
+ // get the default diagram settings
+ Alignment alignment = Alignment.valueOf(graphViewConfiguration
+ .getProperty(ALIGNMENT));
+ PortStyle portStyle = PortStyle.valueOf(graphViewConfiguration
+ .getProperty(PORT_STYLE));
+ boolean animationEnabled = Boolean.parseBoolean(graphViewConfiguration
+ .getProperty(ANIMATION_ENABLED));
+ int animationSpeed = Integer.parseInt(graphViewConfiguration
+ .getProperty(ANIMATION_SPEED));
+
+ // create an SVG canvas
+ final JSVGCanvas svgCanvas = new JSVGCanvas(null, true, false);
+ svgCanvas.setEnableZoomInteractor(false);
+ svgCanvas.setEnableRotateInteractor(false);
+ svgCanvas.setDocumentState(ALWAYS_DYNAMIC);
+ svgCanvas.setTransferHandler(new ServiceTransferHandler(editManager,
+ menuManager, selectionManager, serviceRegistry));
+
+ AutoScrollInteractor asi = new AutoScrollInteractor(svgCanvas);
+ svgCanvas.addMouseListener(asi);
+ svgCanvas.addMouseMotionListener(asi);
+
+ final JSVGScrollPane svgScrollPane = new MySvgScrollPane(svgCanvas);
+
+ GVTTreeRendererAdapter gvtTreeRendererAdapter = new GVTTreeRendererAdapter() {
+ @Override
+ public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
+ logger.info("Rendered svg");
+ svgScrollPane.reset();
+ diagramPanel.revalidate();
+ }
+ };
+ svgCanvas.addGVTTreeRendererListener(gvtTreeRendererAdapter);
+
+ // create a graph controller
+ SVGGraphController svgGraphController = new SVGGraphController(
+ workflow, selectionManager.getSelectedProfile(), false,
+ svgCanvas, alignment, portStyle, editManager, menuManager,
+ colourManager, workbenchConfiguration);
+ svgGraphController.setDataflowSelectionModel(selectionManager
+ .getDataflowSelectionModel(workflow.getParent()));
+ svgGraphController.setAnimationSpeed(animationEnabled ? animationSpeed
+ : 0);
+
+ graphControllerMap.put(workflow, svgGraphController);
+
+ // Toolbar with actions related to graph
+ JToolBar graphActionsToolbar = graphActionsToolbar(workflow,
+ svgGraphController, svgCanvas, alignment, portStyle);
+ graphActionsToolbar.setAlignmentX(LEFT_ALIGNMENT);
+ graphActionsToolbar.setFloatable(false);
+
+ // Panel to hold the toolbars
+ JPanel toolbarPanel = new JPanel();
+ toolbarPanel.setLayout(new BoxLayout(toolbarPanel, PAGE_AXIS));
+ toolbarPanel.add(graphActionsToolbar);
+
+ diagramPanel.add(toolbarPanel, NORTH);
+ diagramPanel.add(svgScrollPane, CENTER);
+
+ // JTextField workflowHierarchy = new JTextField(workflow.getName());
+ // diagramPanel.add(workflowHierarchy, BorderLayout.SOUTH);
+
+ return diagramPanel;
+ }
+
+ @SuppressWarnings("serial")
+ private JToolBar graphActionsToolbar(Workflow workflow,
+ final SVGGraphController graphController, JSVGCanvas svgCanvas,
+ Alignment alignment, PortStyle portStyle) {
+ JToolBar toolBar = new JToolBar();
+
+ JButton resetDiagramButton = new JButton();
+ resetDiagramButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+ JButton zoomInButton = new JButton();
+ zoomInButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+ JButton zoomOutButton = new JButton();
+ zoomOutButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+
+ Action resetDiagramAction = svgCanvas.new ResetTransformAction();
+ ResetDiagramAction.setDesignAction(resetDiagramAction);
+ resetDiagramAction.putValue(SHORT_DESCRIPTION, "Reset Diagram");
+ resetDiagramAction.putValue(SMALL_ICON, refreshIcon);
+ resetDiagramButton.setAction(resetDiagramAction);
+
+ Action zoomInAction = svgCanvas.new ZoomAction(1.2);
+ ZoomInAction.setDesignAction(zoomInAction);
+ zoomInAction.putValue(SHORT_DESCRIPTION, "Zoom In");
+ zoomInAction.putValue(SMALL_ICON, zoomInIcon);
+ zoomInButton.setAction(zoomInAction);
+
+ Action zoomOutAction = svgCanvas.new ZoomAction(1 / 1.2);
+ ZoomOutAction.setDesignAction(zoomOutAction);
+ zoomOutAction.putValue(SHORT_DESCRIPTION, "Zoom Out");
+ zoomOutAction.putValue(SMALL_ICON, zoomOutIcon);
+ zoomOutButton.setAction(zoomOutAction);
+
+ diagramActionsMap.put(workflow, new Action[] { resetDiagramAction,
+ zoomInAction, zoomOutAction });
+
+ toolBar.add(resetDiagramButton);
+ toolBar.add(zoomInButton);
+ toolBar.add(zoomOutButton);
+
+ toolBar.addSeparator();
+
+ ButtonGroup nodeTypeGroup = new ButtonGroup();
+
+ JToggleButton noPorts = new JToggleButton();
+ JToggleButton allPorts = new JToggleButton();
+ JToggleButton blobs = new JToggleButton();
+ nodeTypeGroup.add(noPorts);
+ nodeTypeGroup.add(allPorts);
+ nodeTypeGroup.add(blobs);
+
+ if (portStyle.equals(PortStyle.NONE))
+ noPorts.setSelected(true);
+ else if (portStyle.equals(PortStyle.ALL))
+ allPorts.setSelected(true);
+ else
+ blobs.setSelected(true);
+
+ noPorts.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setPortStyle(PortStyle.NONE);
+ graphController.redraw();
+ }
+ });
+ noPorts.getAction().putValue(SHORT_DESCRIPTION,
+ "Display no service ports");
+ noPorts.getAction().putValue(SMALL_ICON, noportIcon);
+ noPorts.setFocusPainted(false);
+
+ allPorts.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setPortStyle(PortStyle.ALL);
+ graphController.redraw();
+ }
+ });
+ allPorts.getAction().putValue(SHORT_DESCRIPTION,
+ "Display all service ports");
+ allPorts.getAction().putValue(SMALL_ICON, allportIcon);
+ allPorts.setFocusPainted(false);
+
+ blobs.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setPortStyle(PortStyle.BLOB);
+ graphController.redraw();
+ }
+ });
+ blobs.getAction().putValue(SHORT_DESCRIPTION,
+ "Display services as circles");
+ blobs.getAction().putValue(SMALL_ICON, blobIcon);
+ blobs.setFocusPainted(false);
+
+ toolBar.add(noPorts);
+ toolBar.add(allPorts);
+ toolBar.add(blobs);
+
+ toolBar.addSeparator();
+
+ ButtonGroup alignmentGroup = new ButtonGroup();
+
+ JToggleButton vertical = new JToggleButton();
+ JToggleButton horizontal = new JToggleButton();
+ alignmentGroup.add(vertical);
+ alignmentGroup.add(horizontal);
+
+ if (alignment.equals(Alignment.VERTICAL)) {
+ vertical.setSelected(true);
+ } else {
+ horizontal.setSelected(true);
+ }
+
+ vertical.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setAlignment(Alignment.VERTICAL);
+ graphController.redraw();
+ }
+ });
+ vertical.getAction().putValue(SHORT_DESCRIPTION,
+ "Align services vertically");
+ vertical.getAction().putValue(SMALL_ICON, verticalIcon);
+ vertical.setFocusPainted(false);
+
+ horizontal.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setAlignment(Alignment.HORIZONTAL);
+ graphController.redraw();
+ }
+
+ });
+ horizontal.getAction().putValue(SHORT_DESCRIPTION,
+ "Align services horizontally");
+ horizontal.getAction().putValue(SMALL_ICON, horizontalIcon);
+ horizontal.setFocusPainted(false);
+
+ toolBar.add(vertical);
+ toolBar.add(horizontal);
+
+ toolBar.addSeparator();
+
+ JToggleButton expandNested = new JToggleButton();
+ expandNested.setSelected(true);
+
+ expandNested.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ graphController.setExpandNestedDataflows(!graphController
+ .expandNestedDataflows());
+ graphController.redraw();
+ }
+ });
+ expandNested.getAction().putValue(SHORT_DESCRIPTION,
+ "Expand Nested Workflows");
+ expandNested.getAction().putValue(SMALL_ICON, expandNestedIcon);
+ expandNested.setFocusPainted(false);
+ toolBar.add(expandNested);
+
+ return toolBar;
+ }
+
+ /**
+ * Sets the Workflow to display in the graph view.
+ *
+ * @param workflow
+ */
+ private void setWorkflow(Workflow workflow) {
+ this.workflow = workflow;
+ if (!diagramPanelMap.containsKey(workflow))
+ addWorkflow(workflow);
+ graphController = graphControllerMap.get(workflow);
+ diagramPanel = diagramPanelMap.get(workflow);
+ Action[] actions = diagramActionsMap.get(workflow);
+ if (actions != null && actions.length == 3) {
+ ResetDiagramAction.setDesignAction(actions[0]);
+ ZoomInAction.setDesignAction(actions[1]);
+ ZoomOutAction.setDesignAction(actions[2]);
+ }
+ cardLayout.show(this, String.valueOf(diagramPanel.hashCode()));
+ graphController.redraw();
+ }
+
+ private void addWorkflow(Workflow workflow) {
+ JPanel newDiagramPanel = createDiagramPanel(workflow);
+ add(newDiagramPanel, String.valueOf(newDiagramPanel.hashCode()));
+ diagramPanelMap.put(workflow, newDiagramPanel);
+ if (!workflowsMap.containsKey(workflow.getParent()))
+ workflowsMap.put(workflow.getParent(), new HashSet<Workflow>());
+ workflowsMap.get(workflow.getParent()).add(workflow);
+ }
+
+ private void removeWorkflow(Workflow workflow) {
+ JPanel panel = diagramPanelMap.remove(workflow);
+ if (panel != null)
+ remove(panel);
+ SVGGraphController removedController = graphControllerMap.remove(workflow);
+ if (removedController != null)
+ removedController.shutdown();
+ diagramActionsMap.remove(workflow);
+ Set<Workflow> workflows = workflowsMap.get(workflow.getParent());
+ if (workflows != null)
+ workflows.remove(workflow);
+ }
+
+ public GraphController getGraphController(Workflow workflow) {
+ return graphControllerMap.get(workflow);
+ }
+
+ private class EditManagerObserver extends
+ SwingAwareObserver<EditManagerEvent> {
+ @Override
+ public void notifySwing(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) {
+ if (!(message instanceof AbstractDataflowEditEvent))
+ return;
+ AbstractDataflowEditEvent dataflowEditEvent = (AbstractDataflowEditEvent) message;
+ if (dataflowEditEvent.getDataFlow() != workflow.getParent())
+ return;
+
+ boolean animationEnabled = Boolean
+ .parseBoolean(graphViewConfiguration
+ .getProperty(ANIMATION_ENABLED));
+ int animationSpeed = (animationEnabled ? Integer
+ .parseInt(graphViewConfiguration
+ .getProperty(ANIMATION_SPEED)) : 0);
+ boolean animationSettingChanged = (animationEnabled != (graphController
+ .getAnimationSpeed() != 0));
+
+ if (graphController.isDotMissing() || animationSettingChanged) {
+ removeWorkflow(workflow);
+ setWorkflow(workflow);
+ } else {
+ if (animationSpeed != graphController.getAnimationSpeed())
+ graphController.setAnimationSpeed(animationSpeed);
+ graphController.redraw();
+ }
+ }
+ }
+
+ private class FileManagerObserver extends SwingAwareObserver<FileManagerEvent> {
+ @Override
+ public void notifySwing(Observable<FileManagerEvent> sender, final FileManagerEvent message) {
+ if (!(message instanceof ClosedDataflowEvent))
+ return;
+ ClosedDataflowEvent closedDataflowEvent = (ClosedDataflowEvent) message;
+
+ WorkflowBundle workflowBundle = closedDataflowEvent.getDataflow();
+ if (workflowsMap.containsKey(workflowBundle))
+ for (Workflow workflow : workflowsMap.remove(workflowBundle))
+ removeWorkflow(workflow);
+ }
+ }
+
+ private class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowSelectionEvent)
+ setWorkflow(selectionManager.getSelectedWorkflow());
+ else if (message instanceof WorkflowBundleSelectionEvent)
+ setWorkflow(selectionManager.getSelectedWorkflow());
+ }
+ }
+
+ private class MySvgScrollPane extends JSVGScrollPane {
+ private static final long serialVersionUID = -1539947450704269879L;
+
+ public MySvgScrollPane(JSVGCanvas canvas) {
+ super(canvas);
+ }
+
+ @Override
+ public void reset() {
+ super.resizeScrollBars();
+ super.reset();
+ }
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/GraphViewComponentFactory.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/GraphViewComponentFactory.java
new file mode 100644
index 0000000..85f2929
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/GraphViewComponentFactory.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.graph;
+
+import javax.swing.ImageIcon;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration;
+
+/**
+ * @author David Withers
+ */
+public class GraphViewComponentFactory implements UIComponentFactorySPI {
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ColourManager colourManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private GraphViewConfiguration graphViewConfiguration;
+ private ServiceRegistry serviceRegistry;
+
+ @Override
+ public UIComponentSPI getComponent() {
+ return new GraphViewComponent(colourManager, editManager, fileManager,
+ menuManager, graphViewConfiguration, workbenchConfiguration,
+ selectionManager, serviceRegistry);
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Graph View";
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setWorkbenchConfiguration(
+ WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setGraphViewConfiguration(
+ GraphViewConfiguration graphViewConfiguration) {
+ this.graphViewConfiguration = graphViewConfiguration;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/AddWFInputAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/AddWFInputAction.java
new file mode 100644
index 0000000..8c16e4a
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/AddWFInputAction.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.graph.actions;
+
+import static java.awt.event.InputEvent.ALT_DOWN_MASK;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_I;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.inputIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowInputAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * An action that adds a workflow input.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class AddWFInputAction extends AbstractAction implements
+ DesignOnlyAction {
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public AddWFInputAction(EditManager editManager,
+ SelectionManager selectionManager) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ putValue(SMALL_ICON, inputIcon);
+ putValue(NAME, "Workflow input port");
+ putValue(SHORT_DESCRIPTION, "Workflow input port");
+ putValue(ACCELERATOR_KEY,
+ getKeyStroke(VK_I, SHIFT_DOWN_MASK | ALT_DOWN_MASK));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Workflow workflow = selectionManager.getSelectedWorkflow();
+ new AddDataflowInputAction(workflow, null, editManager,
+ selectionManager).actionPerformed(e);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/AddWFOutputAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/AddWFOutputAction.java
new file mode 100644
index 0000000..4027773
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/AddWFOutputAction.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.graph.actions;
+
+import static java.awt.event.InputEvent.ALT_DOWN_MASK;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_O;
+import static javax.swing.KeyStroke.getKeyStroke;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowOutputAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * An action that adds a workflow output.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class AddWFOutputAction extends AbstractAction implements
+ DesignOnlyAction {
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public AddWFOutputAction(EditManager editManager,
+ SelectionManager selectionManager) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ putValue(SMALL_ICON, WorkbenchIcons.outputIcon);
+ putValue(NAME, "Workflow output port");
+ putValue(SHORT_DESCRIPTION, "Workflow output port");
+ putValue(ACCELERATOR_KEY,
+ getKeyStroke(VK_O, SHIFT_DOWN_MASK | ALT_DOWN_MASK));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Workflow workflow = selectionManager.getSelectedWorkflow();
+ new AddDataflowOutputAction(workflow, null, editManager,
+ selectionManager).actionPerformed(e);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/DeleteGraphComponentAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/DeleteGraphComponentAction.java
new file mode 100644
index 0000000..86849b6
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/DeleteGraphComponentAction.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * 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.graph.actions;
+
+import static java.awt.event.KeyEvent.VK_DELETE;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.deleteIcon;
+
+import java.awt.event.ActionEvent;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveConditionAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveDataflowInputPortAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveDataflowOutputPortAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveDatalinkAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveProcessorAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * An action that deletes the selected graph component.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class DeleteGraphComponentAction extends AbstractAction implements DesignOnlyAction {
+ /** Current workflow's selection model event observer.*/
+ private Observer<DataflowSelectionMessage> workflowSelectionObserver = new DataflowSelectionObserver();
+
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public DeleteGraphComponentAction(EditManager editManager, final SelectionManager selectionManager) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ putValue(SMALL_ICON, deleteIcon);
+ putValue(NAME, "Delete");
+ putValue(SHORT_DESCRIPTION, "Delete selected component");
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_DELETE, 0));
+ setEnabled(false);
+
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ WorkflowBundle workflowBundle = selectionManager
+ .getSelectedWorkflowBundle();
+ DataflowSelectionModel dataFlowSelectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+ // Get all selected components
+ Set<Object> selectedWFComponents = dataFlowSelectionModel.getSelection();
+ for (Object selectedWFComponent : selectedWFComponents)
+ if (selectedWFComponent instanceof Processor) {
+ Processor processor = (Processor) selectedWFComponent;
+ new RemoveProcessorAction(processor.getParent(), processor,
+ null, editManager, selectionManager).actionPerformed(e);
+ } else if (selectedWFComponent instanceof DataLink) {
+ DataLink dataLink = (DataLink) selectedWFComponent;
+ new RemoveDatalinkAction(dataLink.getParent(), dataLink, null,
+ editManager, selectionManager).actionPerformed(e);
+ } else if (selectedWFComponent instanceof InputWorkflowPort) {
+ InputWorkflowPort port = (InputWorkflowPort) selectedWFComponent;
+ new RemoveDataflowInputPortAction(port.getParent(), port, null,
+ editManager, selectionManager).actionPerformed(e);
+ } else if (selectedWFComponent instanceof OutputWorkflowPort) {
+ OutputWorkflowPort port = (OutputWorkflowPort) selectedWFComponent;
+ new RemoveDataflowOutputPortAction(port.getParent(), port,
+ null, editManager, selectionManager).actionPerformed(e);
+ } else if (selectedWFComponent instanceof ControlLink) {
+ ControlLink controlLink = (ControlLink) selectedWFComponent;
+ new RemoveConditionAction(controlLink.getParent(), controlLink,
+ null, editManager, selectionManager).actionPerformed(e);
+ }
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus(WorkflowBundle selectionWorkflowBundle) {
+ if (selectionWorkflowBundle != null) {
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(selectionWorkflowBundle);
+ Set<Object> selection = selectionModel.getSelection();
+ if (!selection.isEmpty()) {
+ // Take the first selected item - we only support single selections anyway
+ Object selected = selection.toArray()[0];
+ if ((selected instanceof Processor)
+ || (selected instanceof InputWorkflowPort)
+ || (selected instanceof OutputWorkflowPort)
+ || (selected instanceof DataLink)
+ || (selected instanceof ControlLink)) {
+ setEnabled(true);
+ return;
+ }
+ }
+ }
+ setEnabled(false);
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow node
+ * is selected in the graph view, and enables/disables this action
+ * accordingly.
+ */
+ private final class DataflowSelectionObserver extends
+ SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ updateStatus(selectionManager.getSelectedWorkflowBundle());
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (!(message instanceof WorkflowBundleSelectionEvent))
+ return;
+ WorkflowBundleSelectionEvent workflowBundleSelectionEvent = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle oldFlow = workflowBundleSelectionEvent
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newFlow = workflowBundleSelectionEvent
+ .getSelectedWorkflowBundle();
+
+ /*
+ * Remove the workflow selection model listener from the previous
+ * (if any) and add to the new workflow (if any)
+ */
+ if (oldFlow != null)
+ selectionManager.getDataflowSelectionModel(oldFlow)
+ .removeObserver(workflowSelectionObserver);
+
+ // Update the buttons status as current dataflow has changed
+ updateStatus(newFlow);
+
+ if (newFlow != null)
+ selectionManager.getDataflowSelectionModel(newFlow)
+ .addObserver(workflowSelectionObserver);
+ }
+ }
+
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/RenameWFInputOutputProcessorAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/RenameWFInputOutputProcessorAction.java
new file mode 100644
index 0000000..f56a0e0
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/actions/RenameWFInputOutputProcessorAction.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * 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.graph.actions;
+
+import static java.awt.event.KeyEvent.VK_F2;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+
+import java.awt.event.ActionEvent;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.design.actions.EditDataflowInputPortAction;
+import net.sf.taverna.t2.workbench.design.actions.EditDataflowOutputPortAction;
+import net.sf.taverna.t2.workbench.design.actions.RenameProcessorAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * An action that allows user to rename workflow input, output or
+ * processor, in case one of these is currently selected in the Graph View.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class RenameWFInputOutputProcessorAction extends AbstractAction implements DesignOnlyAction {
+ /** Current workflow's selection model event observer.*/
+ private Observer<DataflowSelectionMessage> workflowSelectionObserver = new DataflowSelectionObserver();
+
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public RenameWFInputOutputProcessorAction(EditManager editManager,
+ final SelectionManager selectionManager) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ putValue(SMALL_ICON, WorkbenchIcons.renameIcon);
+ putValue(NAME, "Rename");
+ putValue(SHORT_DESCRIPTION, "Rename inputs, outputs or services");
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F2, 0));
+ setEnabled(false);
+
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ WorkflowBundle workflowBundle = selectionManager
+ .getSelectedWorkflowBundle();
+ DataflowSelectionModel dataFlowSelectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+ // Get selected port
+ Set<Object> selectedWFComponents = dataFlowSelectionModel
+ .getSelection();
+ if (selectedWFComponents.size() > 1) {
+ showMessageDialog(
+ null,
+ "Only one workflow component should be selected for this action.",
+ "Warning", WARNING_MESSAGE);
+ } else {
+ Object selectedWFComponent = selectedWFComponents.toArray()[0];
+ if (selectedWFComponent instanceof InputWorkflowPort) {
+ InputWorkflowPort port = (InputWorkflowPort) selectedWFComponent;
+ new EditDataflowInputPortAction(port.getParent(), port, null,
+ editManager, selectionManager).actionPerformed(e);
+ } else if (selectedWFComponent instanceof OutputWorkflowPort) {
+ OutputWorkflowPort port = (OutputWorkflowPort) selectedWFComponent;
+ new EditDataflowOutputPortAction(port.getParent(), port, null,
+ editManager, selectionManager).actionPerformed(e);
+ } else if (selectedWFComponent instanceof Processor) {
+ Processor processor = (Processor) selectedWFComponent;
+ new RenameProcessorAction(processor.getParent(), processor,
+ null, editManager, selectionManager).actionPerformed(e);
+ } else { // should not happen as the button will be disabled otherwise, but ...
+ showMessageDialog(
+ null,
+ "This action does not apply for the selected component.",
+ "Warning", WARNING_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus() {
+ WorkflowBundle workflowBundle = selectionManager
+ .getSelectedWorkflowBundle();
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+
+ // List of all selected objects in the graph view
+ Set<Object> selection = selectionModel.getSelection();
+
+ if (!selection.isEmpty()) {
+ // Take the first selected item - we only support single selections anyway
+ Object selected = selection.toArray()[0];
+ if ((selected instanceof Processor)
+ || (selected instanceof InputWorkflowPort)
+ || (selected instanceof OutputWorkflowPort)) {
+ setEnabled(true);
+ return;
+ }
+ }
+ setEnabled(false);
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow node
+ * is selected in the graph view, and enables/disables this action
+ * accordingly.
+ */
+ private final class DataflowSelectionObserver extends
+ SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ updateStatus();
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (!(message instanceof WorkflowBundleSelectionEvent))
+ return;
+ WorkflowBundleSelectionEvent workflowBundleSelectionEvent = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle oldFlow = workflowBundleSelectionEvent
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newFlow = workflowBundleSelectionEvent
+ .getSelectedWorkflowBundle();
+ // Update the buttons status as current dataflow has changed
+ updateStatus();
+
+ /*
+ * Remove the workflow selection model listener from the previous
+ * (if any) and add to the new workflow (if any)
+ */
+ if (oldFlow != null)
+ selectionManager.getDataflowSelectionModel(oldFlow)
+ .removeObserver(workflowSelectionObserver);
+
+ if (newFlow != null)
+ selectionManager.getDataflowSelectionModel(newFlow)
+ .addObserver(workflowSelectionObserver);
+ }
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfiguration.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfiguration.java
new file mode 100644
index 0000000..b1c3265
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfiguration.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.graph.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+import net.sf.taverna.t2.workbench.models.graph.GraphController.PortStyle;
+
+/**
+ * Configuration for the GraphViewComponent.
+ *
+ * @author David Withers
+ */
+public class GraphViewConfiguration extends AbstractConfigurable {
+ public static final String PORT_STYLE = "portStyle";
+ public static final String ALIGNMENT = "alignment";
+ public static final String ANIMATION_ENABLED = "animationEnabled";
+ public static final String ANIMATION_SPEED = "animationSpeed";
+
+ private Map<String, String> defaultPropertyMap;
+
+ public GraphViewConfiguration(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return "general";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<>();
+ defaultPropertyMap.put(PORT_STYLE, PortStyle.NONE.toString());
+ defaultPropertyMap.put(ALIGNMENT, Alignment.VERTICAL.toString());
+ defaultPropertyMap.put(ANIMATION_ENABLED, "false");
+ defaultPropertyMap.put(ANIMATION_SPEED, "800");
+ }
+ return defaultPropertyMap;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Diagram";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "Diagram";
+ }
+
+ @Override
+ public String getUUID() {
+ return "3686BA31-449F-4147-A8AC-0C3F63AFC68F";
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfigurationPanel.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfigurationPanel.java
new file mode 100644
index 0000000..397ad1b
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfigurationPanel.java
@@ -0,0 +1,360 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.graph.config;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NORTHWEST;
+import static java.awt.GridBagConstraints.RELATIVE;
+import static java.awt.GridBagConstraints.REMAINDER;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.SwingConstants.LEFT;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.allportIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.blobIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.horizontalIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.noportIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.verticalIcon;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.ALIGNMENT;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.ANIMATION_ENABLED;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.ANIMATION_SPEED;
+import static net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration.PORT_STYLE;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Hashtable;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSlider;
+import javax.swing.JTextArea;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+import net.sf.taverna.t2.workbench.models.graph.GraphController.PortStyle;
+
+/**
+ * UI for GraphViewConfiguration.
+ *
+ * @author David Withers
+ */
+public class GraphViewConfigurationPanel extends JPanel {
+ private static final long serialVersionUID = 3779779432230124131L;
+ private static final int ANIMATION_SPEED_MIN = 100;
+ private static final int ANIMATION_SPEED_MAX = 3100;
+
+ private GraphViewConfiguration configuration;
+ private JRadioButton noPorts;
+ private JRadioButton allPorts;
+ private JRadioButton blobs;
+ private JRadioButton vertical;
+ private JRadioButton horizontal;
+ private JCheckBox animation;
+ private JLabel animationSpeedLabel;
+ private JSlider animationSpeedSlider;
+
+ public GraphViewConfigurationPanel(GraphViewConfiguration configuration) {
+ this.configuration = configuration;
+ GridBagLayout gridbag = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+ setLayout(gridbag);
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea(
+ "Default settings for the workflow diagram");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ JLabel defaultLayoutLabel = new JLabel("Service display");
+
+ noPorts = new JRadioButton();
+ allPorts = new JRadioButton();
+ blobs = new JRadioButton();
+
+ JLabel noPortsLabel = new JLabel("Name only", noportIcon, LEFT);
+ JLabel allPortsLabel = new JLabel("Name and ports", allportIcon, LEFT);
+ JLabel blobsLabel = new JLabel("No text", blobIcon, LEFT);
+
+ ButtonGroup buttonGroup = new ButtonGroup();
+ buttonGroup.add(noPorts);
+ buttonGroup.add(allPorts);
+ buttonGroup.add(blobs);
+
+ JLabel defaultAlignmentLabel = new JLabel("Diagram alignment");
+
+ vertical = new JRadioButton();
+ horizontal = new JRadioButton();
+
+ JLabel verticalLabel = new JLabel("Vertical", verticalIcon, LEFT);
+ JLabel horizontalLabel = new JLabel("Horizontal", horizontalIcon, LEFT);
+
+ ButtonGroup alignmentButtonGroup = new ButtonGroup();
+ alignmentButtonGroup.add(horizontal);
+ alignmentButtonGroup.add(vertical);
+
+ animation = new JCheckBox("Enable animation");
+
+ animationSpeedLabel = new JLabel("Animation speed");
+
+ animationSpeedSlider = new JSlider(ANIMATION_SPEED_MIN,
+ ANIMATION_SPEED_MAX);
+ animationSpeedSlider.setMajorTickSpacing(500);
+ animationSpeedSlider.setMinorTickSpacing(100);
+ animationSpeedSlider.setPaintTicks(true);
+ animationSpeedSlider.setPaintLabels(true);
+ animationSpeedSlider.setInverted(true);
+ animationSpeedSlider.setSnapToTicks(true);
+
+ Hashtable<Integer, JLabel> labelTable = new Hashtable<>();
+ labelTable.put(new Integer(ANIMATION_SPEED_MIN), new JLabel("Fast"));
+ labelTable.put(new Integer(
+ ((ANIMATION_SPEED_MAX - ANIMATION_SPEED_MIN) / 2)
+ + ANIMATION_SPEED_MIN), new JLabel("Medium"));
+ labelTable.put(new Integer(ANIMATION_SPEED_MAX), new JLabel("Slow"));
+ animationSpeedSlider.setLabelTable(labelTable);
+
+ animation.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ boolean animationEnabled = animation.isSelected();
+ animationSpeedLabel.setEnabled(animationEnabled);
+ animationSpeedSlider.setEnabled(animationEnabled);
+ }
+ });
+
+ // Set current configuration values
+ setFields(configuration);
+
+ c.anchor = WEST;
+ c.gridx = 0;
+ c.gridwidth = REMAINDER;
+ c.weightx = 1d;
+ c.weighty = 0d;
+ c.fill = HORIZONTAL;
+
+ add(descriptionText, c);
+
+ c.insets = new Insets(10, 0, 10, 0);
+ add(defaultLayoutLabel, c);
+
+ c.insets = new Insets(0, 20, 0, 0);
+ c.gridwidth = 1;
+ c.weightx = 0d;
+ add(noPorts, c);
+ c.insets = new Insets(0, 5, 0, 0);
+ c.gridx = RELATIVE;
+ add(noPortsLabel, c);
+
+ c.insets = new Insets(0, 10, 0, 0);
+ add(allPorts, c);
+ c.insets = new Insets(0, 5, 0, 0);
+ add(allPortsLabel, c);
+
+ c.insets = new Insets(0, 10, 0, 0);
+ add(blobs, c);
+ c.insets = new Insets(0, 5, 0, 0);
+ c.gridwidth = REMAINDER;
+ c.weightx = 1d;
+ add(blobsLabel, c);
+
+ // alignment
+ c.insets = new Insets(20, 0, 10, 0);
+ c.gridx = 0;
+ add(defaultAlignmentLabel, c);
+
+ c.insets = new Insets(0, 20, 0, 0);
+ c.gridx = 0;
+ c.gridwidth = 1;
+ c.weightx = 0d;
+ add(vertical, c);
+ c.insets = new Insets(0, 5, 0, 0);
+ c.gridx = RELATIVE;
+ add(verticalLabel, c);
+
+ c.insets = new Insets(0, 10, 0, 0);
+ add(horizontal, c);
+ c.insets = new Insets(0, 5, 0, 0);
+ c.gridwidth = REMAINDER;
+ c.weightx = 1d;
+ add(horizontalLabel, c);
+
+ // animation
+ c.gridx = 0;
+ c.gridwidth = REMAINDER;
+ c.insets = new Insets(20, 0, 10, 0);
+ add(animation, c);
+
+ c.insets = new Insets(0, 20, 0, 0);
+ add(animationSpeedLabel, c);
+
+ c.insets = new Insets(0, 20, 10, 30);
+ c.anchor = NORTHWEST;
+ c.weighty = 0d;
+ add(animationSpeedSlider, c);
+
+ // Buttons
+ c.gridx = 0;
+ c.insets = new Insets(0, 20, 10, 30);
+ c.anchor = NORTHWEST;
+ c.weighty = 1d;
+ add(createButtonPanel(), c);
+ }
+
+ /**
+ * Create the panel with the buttons.
+ */
+ @SuppressWarnings("serial")
+ private JPanel createButtonPanel() {
+ final JPanel panel = new JPanel();
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ setFields(configuration);
+ }
+ });
+ panel.add(resetButton);
+
+ /**
+ * The applyButton applies the shown field values to the
+ * {@link HttpProxyConfiguration} and saves them for future.
+ */
+ JButton applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ applySettings();
+ setFields(configuration);
+ }
+ });
+ panel.add(applyButton);
+
+ return panel;
+ }
+
+ /**
+ * Save the currently set field values to the {@link GraphViewConfiguration}
+ * . Also apply those values to the currently running Taverna.
+ */
+ private void applySettings() {
+ // Service display
+ if (noPorts.isSelected()) {
+ configuration.setProperty(PORT_STYLE, PortStyle.NONE.toString());
+ } else if (allPorts.isSelected()) {
+ configuration.setProperty(PORT_STYLE, PortStyle.ALL.toString());
+ } else if (blobs.isSelected()) {
+ configuration.setProperty(PORT_STYLE, PortStyle.BLOB.toString());
+ }
+
+ // Diagram alignment
+ if (vertical.isSelected()) {
+ configuration.setProperty(ALIGNMENT, Alignment.VERTICAL.toString());
+ } else if (horizontal.isSelected()) {
+ configuration.setProperty(ALIGNMENT,
+ Alignment.HORIZONTAL.toString());
+ }
+
+ // Animation and its speed
+ if (animation.isSelected()) {
+ configuration.setProperty(ANIMATION_ENABLED, String.valueOf(true));
+ } else {
+ configuration.setProperty(ANIMATION_ENABLED, String.valueOf(false));
+ }
+ int speed = animationSpeedSlider.getValue();
+ configuration.setProperty(ANIMATION_SPEED, String.valueOf(speed));
+ }
+
+ /**
+ * Set the shown configuration field values to those currently in use (i.e.
+ * last saved configuration).
+ */
+ private void setFields(GraphViewConfiguration configurable) {
+ PortStyle portStyle = PortStyle.valueOf(configurable
+ .getProperty(PORT_STYLE));
+ if (portStyle.equals(PortStyle.NONE)) {
+ noPorts.setSelected(true);
+ } else if (portStyle.equals(PortStyle.ALL)) {
+ allPorts.setSelected(true);
+ } else {
+ blobs.setSelected(true);
+ }
+
+ Alignment alignment = Alignment.valueOf(configurable
+ .getProperty(ALIGNMENT));
+ if (alignment.equals(Alignment.VERTICAL)) {
+ vertical.setSelected(true);
+ } else {
+ horizontal.setSelected(true);
+ }
+
+ boolean animationEnabled = Boolean.parseBoolean(configurable
+ .getProperty(ANIMATION_ENABLED));
+ animation.setSelected(animationEnabled);
+
+ Integer animationSpeed = Integer.valueOf(configurable
+ .getProperty(ANIMATION_SPEED));
+ if (animationSpeed > ANIMATION_SPEED_MAX) {
+ animationSpeed = ANIMATION_SPEED_MAX;
+ } else if (animationSpeed < ANIMATION_SPEED_MIN) {
+ animationSpeed = ANIMATION_SPEED_MIN;
+ }
+ animationSpeedSlider.setValue(animationSpeed);
+ animationSpeedSlider.setEnabled(animationEnabled);
+
+ animationSpeedLabel.setEnabled(animationEnabled);
+ }
+
+ // for testing only
+ public static void main(String[] args) {
+ JDialog dialog = new JDialog();
+ dialog.add(new GraphViewConfigurationPanel(null));
+ dialog.setModal(true);
+ dialog.setSize(500, 400);
+ dialog.setVisible(true);
+ System.exit(0);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfigurationUIFactory.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfigurationUIFactory.java
new file mode 100644
index 0000000..959b598
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/config/GraphViewConfigurationUIFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.graph.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+/**
+ * ConfigurationFactory for the GraphViewConfiguration.
+ *
+ * @author David Withers
+ */
+public class GraphViewConfigurationUIFactory implements ConfigurationUIFactory {
+ private GraphViewConfiguration graphViewConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new GraphViewConfigurationPanel(graphViewConfiguration);
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return graphViewConfiguration;
+ }
+
+ public void setGraphViewConfiguration(
+ GraphViewConfiguration graphViewConfiguration) {
+ this.graphViewConfiguration = graphViewConfiguration;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/AddWFInputMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/AddWFInputMenuAction.java
new file mode 100644
index 0000000..65448c3
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/AddWFInputMenuAction.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.InsertMenu.INSERT;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.AddWFInputAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class AddWFInputMenuAction extends AbstractMenuAction {
+ private static final URI ADD_WF_INPUT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuAddWFInput");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public AddWFInputMenuAction() {
+ super(INSERT, 10, ADD_WF_INPUT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddWFInputAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/AddWFOutputMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/AddWFOutputMenuAction.java
new file mode 100644
index 0000000..522c841
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/AddWFOutputMenuAction.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.InsertMenu.INSERT;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.AddWFOutputAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class AddWFOutputMenuAction extends AbstractMenuAction {
+ private static final URI ADD_WF_OUTPUT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuAddWFOutput");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public AddWFOutputMenuAction() {
+ super(INSERT, 20, ADD_WF_OUTPUT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddWFOutputAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DeleteGraphComponentMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DeleteGraphComponentMenuAction.java
new file mode 100644
index 0000000..654078f
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DeleteGraphComponentMenuAction.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.GraphDeleteMenuSection.GRAPH_DELETE_MENU_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.DeleteGraphComponentAction;
+
+/**
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class DeleteGraphComponentMenuAction extends AbstractMenuAction {
+ private static final URI DELETE_GRAPH_COMPONENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuDeleteGraphComponent");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public DeleteGraphComponentMenuAction() {
+ super(GRAPH_DELETE_MENU_SECTION, 10, DELETE_GRAPH_COMPONENT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new DeleteGraphComponentAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramMenu.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramMenu.java
new file mode 100644
index 0000000..02c71d8
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static java.awt.event.KeyEvent.VK_V;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class DiagramMenu extends AbstractMenu {
+ public static final URI DIAGRAM = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagram");
+
+ public DiagramMenu() {
+ super(DEFAULT_MENU_BAR, 65, DIAGRAM, "View");
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("View");
+ action.putValue(MNEMONIC_KEY, VK_V);
+ return action;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramSaveMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramSaveMenuSection.java
new file mode 100644
index 0000000..5ebb770
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramSaveMenuSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.DiagramMenu.DIAGRAM;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ */
+public class DiagramSaveMenuSection extends AbstractMenuSection {
+ public static final URI DIAGRAM_SAVE_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagramSaveMenuSection");
+
+ public DiagramSaveMenuSection() {
+ super(DIAGRAM, 40, DIAGRAM_SAVE_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramZoomMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramZoomMenuSection.java
new file mode 100644
index 0000000..639deee
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/DiagramZoomMenuSection.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.DiagramMenu.DIAGRAM;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class DiagramZoomMenuSection extends AbstractMenuSection {
+ public static final URI DIAGRAM_ZOOM_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagramZoomMenuSection");
+
+ public DiagramZoomMenuSection() {
+ super(DIAGRAM, 20, DIAGRAM_ZOOM_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphCopyMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphCopyMenuSection.java
new file mode 100644
index 0000000..70cc462
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphCopyMenuSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.GraphMenuSection.GRAPH_MENU_SECTION;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * ???
+ */
+public class GraphCopyMenuSection extends AbstractMenuSection {
+ public static final URI GRAPH_COPY_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphCopyMenuSection");
+
+ public GraphCopyMenuSection() {
+ super(GRAPH_MENU_SECTION, 15, GRAPH_COPY_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphDeleteMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphDeleteMenuSection.java
new file mode 100644
index 0000000..28d2144
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphDeleteMenuSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.GraphMenuSection.GRAPH_MENU_SECTION;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ */
+public class GraphDeleteMenuSection extends AbstractMenuSection {
+ public static final URI GRAPH_DELETE_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphDeleteMenuSection");
+
+ public GraphDeleteMenuSection() {
+ super(GRAPH_MENU_SECTION, 30, GRAPH_DELETE_MENU_SECTION);
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphDetailsMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphDetailsMenuSection.java
new file mode 100644
index 0000000..f2b6af1
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphDetailsMenuSection.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.GraphMenuSection.GRAPH_MENU_SECTION;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class GraphDetailsMenuSection extends AbstractMenuSection {
+ public static final URI GRAPH_DETAILS_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphDetailsMenuSection");
+
+ public GraphDetailsMenuSection() {
+ super(GRAPH_MENU_SECTION, 25, GRAPH_DETAILS_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphEditMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphEditMenuSection.java
new file mode 100644
index 0000000..1a487b1
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphEditMenuSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.GraphMenuSection.GRAPH_MENU_SECTION;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ */
+public class GraphEditMenuSection extends AbstractMenuSection {
+ public static final URI GRAPH_EDIT_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphEditMenuSection");
+
+ public GraphEditMenuSection() {
+ super(GRAPH_MENU_SECTION, 20, GRAPH_EDIT_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphMenuSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphMenuSection.java
new file mode 100644
index 0000000..4030d34
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/GraphMenuSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ */
+public class GraphMenuSection extends AbstractMenuSection {
+ public static final URI GRAPH_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuSection");
+ public static final URI EDIT_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#edit");
+
+ public GraphMenuSection() {
+ super(EDIT_MENU_URI, 20, GRAPH_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/InsertMenu.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/InsertMenu.java
new file mode 100644
index 0000000..9b498e5
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/InsertMenu.java
@@ -0,0 +1,30 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.views.graph.menu;
+
+import static java.awt.event.KeyEvent.VK_I;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+/**
+ * @author alanrw
+ */
+public class InsertMenu extends AbstractMenu {
+ public static final URI INSERT = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#insert");
+
+ public InsertMenu() {
+ super(DEFAULT_MENU_BAR, 64, INSERT, makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Insert");
+ action.putValue(MNEMONIC_KEY, VK_I);
+ return action;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/RenameWFInputOutputProcessorMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/RenameWFInputOutputProcessorMenuAction.java
new file mode 100644
index 0000000..3cf9f66
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/RenameWFInputOutputProcessorMenuAction.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.GraphDetailsMenuSection.GRAPH_DETAILS_MENU_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.RenameWFInputOutputProcessorAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class RenameWFInputOutputProcessorMenuAction extends AbstractMenuAction {
+ private static final URI RENAME_WF_INPUT_OUTPUT_PROCESSOR_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuRenameWFInputOutputProcessor");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RenameWFInputOutputProcessorMenuAction() {
+ super(GRAPH_DETAILS_MENU_SECTION, 30,
+ RENAME_WF_INPUT_OUTPUT_PROCESSOR_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new RenameWFInputOutputProcessorAction(editManager,
+ selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ResetDiagramAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ResetDiagramAction.java
new file mode 100644
index 0000000..9fbd452
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ResetDiagramAction.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_0;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.refreshIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.DesignOrResultsAction;
+
+@SuppressWarnings("serial")
+public class ResetDiagramAction extends AbstractAction implements
+ DesignOrResultsAction {
+ private static Action designAction = null;
+ @SuppressWarnings("unused")
+ private static Action resultsAction = null;
+
+ public static void setResultsAction(Action resultsAction) {
+ ResetDiagramAction.resultsAction = resultsAction;
+ }
+
+ public static void setDesignAction(Action designAction) {
+ ResetDiagramAction.designAction = designAction;
+ }
+
+ public ResetDiagramAction() {
+ super("Reset diagram", refreshIcon);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_0, getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+// if (isWorkflowPerspective() && (designAction != null))
+ designAction.actionPerformed(e);
+// else if (isResultsPerspective() && (resultsAction != null))
+// resultsAction.actionPerformed(e);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ResetDiagramMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ResetDiagramMenuAction.java
new file mode 100644
index 0000000..c4b402e
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ResetDiagramMenuAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.DiagramZoomMenuSection.DIAGRAM_ZOOM_MENU_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+/**
+ * An action that zooms a diagram image
+ *
+ * @author Alex Nenadic
+ * @author Tom Oinn
+ * @author Alan R Williams
+ */
+public class ResetDiagramMenuAction extends AbstractMenuAction {
+ public static final URI RESET_DIAGRAM_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagramMenuResetDiagram");
+
+ public ResetDiagramMenuAction() {
+ super(DIAGRAM_ZOOM_MENU_SECTION, 5, RESET_DIAGRAM_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new ResetDiagramAction();
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/SaveGraphImageSubMenu.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/SaveGraphImageSubMenu.java
new file mode 100644
index 0000000..49f948a
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/SaveGraphImageSubMenu.java
@@ -0,0 +1,315 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.views.graph.menu.DiagramSaveMenuSection.DIAGRAM_SAVE_MENU_SECTION;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URL;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JFileChooser;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.lang.io.StreamCopier;
+import net.sf.taverna.t2.lang.io.StreamDevourer;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ExtensionFileFilter;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.models.graph.DotWriter;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.views.graph.GraphViewComponent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * An action that saves graph diagram image.
+ *
+ * @author Alex Nenadic
+ * @author Tom Oinn
+ */
+public class SaveGraphImageSubMenu extends AbstractMenuCustom {
+ private static final Logger logger = Logger
+ .getLogger(SaveGraphImageSubMenu.class);
+ private static final String[] saveTypes = { "dot", "png", "svg", "ps",
+ "ps2" };
+ private static final String[] saveExtensions = { "dot", "png", "svg", "ps",
+ "ps" };
+ private static final String[] saveTypeNames = { "dot text", "PNG bitmap",
+ "scalable vector graphics", "postscript", "postscript for PDF" };
+ public static final URI SAVE_GRAPH_IMAGE_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuSaveGraphImage");
+
+ private JMenu saveDiagramMenu;
+ private FileManager fileManager;
+ private SelectionManager selectionManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private GraphViewComponent graphViewComponent;
+
+ public SaveGraphImageSubMenu() {
+ super(DIAGRAM_SAVE_MENU_SECTION, 70, SAVE_GRAPH_IMAGE_MENU_URI);
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ saveDiagramMenu = new JMenu("Export diagram");
+ saveDiagramMenu
+ .setToolTipText("Open this menu to export the diagram in various formats");
+ for (int i = 0; i < saveTypes.length; i++) {
+ String type = saveTypes[i];
+ String extension = saveExtensions[i];
+ ImageIcon icon = new ImageIcon(
+ WorkbenchIcons.class.getResource("graph/saveAs"
+ + type.toUpperCase() + ".png"));
+ JMenuItem item = new JMenuItem(new DotInvoker("Export as "
+ + saveTypeNames[i], icon, type, extension));
+ saveDiagramMenu.add(item);
+ }
+ return saveDiagramMenu;
+ }
+
+ @SuppressWarnings("serial")
+ class DotInvoker extends AbstractAction implements DesignOnlyAction {
+ String type = "dot";
+ String extension = "dot";
+
+ public DotInvoker(String name, ImageIcon icon, String type,
+ String extension) {
+ super(name, icon);
+ this.type = type;
+ this.extension = extension;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Workflow workflow = selectionManager.getSelectedWorkflow();
+ if (workflow == null) {
+ showMessageDialog(null, "Cannot export an empty diagram.",
+ "Warning", WARNING_MESSAGE);
+ return;
+ }
+
+ File file = saveDialogue(null, workflow, extension,
+ "Export workflow diagram");
+ if (file == null)
+ // User cancelled
+ return;
+
+ try {
+ GraphController graphController = graphViewComponent
+ .getGraphController(workflow);
+
+ if (type.equals("dot")) {
+ // Just write out the dot text, no processing required
+ PrintWriter out = new PrintWriter(new FileWriter(file));
+ DotWriter dotWriter = new DotWriter(out);
+ dotWriter.writeGraph(graphController.generateGraph());
+ out.flush();
+ out.close();
+ } else {
+ String dotLocation = (String) workbenchConfiguration
+ .getProperty("taverna.dotlocation");
+ if (dotLocation == null)
+ dotLocation = "dot";
+ logger.debug("GraphViewComponent: Invoking dot...");
+ Process dotProcess = Runtime.getRuntime().exec(
+ new String[] { dotLocation, "-T" + type });
+
+ FileOutputStream fos = new FileOutputStream(file);
+
+ StringWriter stringWriter = new StringWriter();
+ DotWriter dotWriter = new DotWriter(stringWriter);
+ dotWriter.writeGraph(graphController.generateGraph());
+
+ OutputStream dotOut = dotProcess.getOutputStream();
+ dotOut.write(SVGUtil.getDot(stringWriter.toString(),
+ workbenchConfiguration).getBytes());
+ dotOut.flush();
+ dotOut.close();
+ new StreamDevourer(dotProcess.getErrorStream()).start();
+ new StreamCopier(dotProcess.getInputStream(), fos).start();
+ }
+ } catch (Exception ex) {
+ logger.warn("GraphViewComponent: Could not export diagram to " + file, ex);
+ showMessageDialog(null,
+ "Problem saving diagram : \n" + ex.getMessage(),
+ "Error!", ERROR_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Pop up a save dialogue relating to the given workflow. This method can be
+ * used, for example, for saving the workflow diagram as .png, and will use
+ * the existing workflow title as a base for suggesting a filename.
+ *
+ * @param parentComponent
+ * Parent component for dialogue window
+ * @param model
+ * Workflow to save
+ * @param extension
+ * Extension for filename, such as "jpg"
+ * @param windowTitle
+ * Title for dialogue box, such as "Save workflow diagram"
+ * @return File instance for the selected abstract filename, or null if the
+ * dialogue was cancelled.
+ */
+ private File saveDialogue(Component parentComponent, Workflow workflow,
+ String extension, String windowTitle) {
+ JFileChooser fc = new JFileChooser();
+ Preferences prefs = Preferences
+ .userNodeForPackage(SaveGraphImageSubMenu.class);
+ String curDir = prefs
+ .get("currentDir", System.getProperty("user.home"));
+ String suggestedFileName = "";
+ // Get the source the workflow was loaded from - can be File, URL, or InputStream
+ Object source = fileManager.getDataflowSource(workflow.getParent());
+ if (source instanceof File) {
+ suggestedFileName = ((File) source).getName();
+ // remove the file extension
+ suggestedFileName = suggestedFileName.substring(0,
+ suggestedFileName.lastIndexOf("."));
+ } else if (source instanceof URL) {
+ suggestedFileName = ((URL) source).getPath();
+ // remove the file extension
+ suggestedFileName = suggestedFileName.substring(0,
+ suggestedFileName.lastIndexOf("."));
+ } else {
+ // We cannot suggest the file name if workflow was read from an InputStream
+ }
+
+ fc.setDialogTitle(windowTitle);
+ fc.resetChoosableFileFilters();
+ fc.setFileFilter(new ExtensionFileFilter(new String[] { extension }));
+ if (suggestedFileName.isEmpty())
+ // No file suggestion, just the directory
+ fc.setCurrentDirectory(new File(curDir));
+ else
+ // Suggest a filename from the workflow file name
+ fc.setSelectedFile(new File(curDir, suggestedFileName + "." + extension));
+
+ while (true) {
+ if (fc.showSaveDialog(parentComponent) != APPROVE_OPTION) {
+ logger.info("GraphViewComponent: Aborting diagram export to "
+ + suggestedFileName);
+ return null;
+ }
+
+ File file = fixExtension(fc.getSelectedFile(), extension);
+ logger.debug("GraphViewComponent: Selected " + file + " as export target");
+ prefs.put("currentDir", fc.getCurrentDirectory().toString());
+
+ // If file doesn't exist, we may write it! (Well, probably...)
+ if (!file.exists())
+ return file;
+
+ // Ask the user if they want to overwrite the file
+ String msg = file.getAbsolutePath()
+ + " already exists. Do you want to overwrite it?";
+ if (showConfirmDialog(null, msg, "File already exists",
+ YES_NO_OPTION) == JOptionPane.YES_OPTION)
+ return file;
+ }
+ }
+
+ /**
+ * Make sure given File has the given extension. If it has no extension,
+ * a new File instance will be returned. Otherwise, the passed instance is
+ * returned unchanged.
+ *
+ * @param file
+ * File which extension is to be checked
+ * @param extension
+ * Extension desired, example: "xml"
+ * @return file parameter if the extension was OK, or a new File instance
+ * with the correct extension
+ */
+ private File fixExtension(File file, String extension) {
+ if (file.getName().endsWith("." + extension))
+ return file;
+ // Append the extension (keep the existing one)
+ String name = file.getName();
+ return new File(file.getParent(), name + "." + extension);
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setWorkbenchConfiguration(
+ WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setGraphViewComponent(GraphViewComponent graphViewComponent) {
+ this.graphViewComponent = graphViewComponent;
+ }
+
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+ @SuppressWarnings("unused")
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (!(message instanceof PerspectiveSelectionEvent))
+ return;
+ PerspectiveSelectionEvent event = (PerspectiveSelectionEvent) message;
+
+ saveDiagramMenu.setEnabled((DESIGN_PERSPECTIVE_ID.equals(event
+ .getSelectedPerspective().getID())));
+ }
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomInAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomInAction.java
new file mode 100644
index 0000000..b8735c9
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomInAction.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_EQUALS;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.zoomInIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.DesignOrResultsAction;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ZoomInAction extends AbstractAction implements
+ DesignOrResultsAction {
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(ZoomInAction.class);
+ private static Action designAction = null;
+ @SuppressWarnings("unused")
+ private static Action resultsAction = null;
+
+ public static void setResultsAction(Action resultsAction) {
+ ZoomInAction.resultsAction = resultsAction;
+ }
+
+ public static void setDesignAction(Action designAction) {
+ ZoomInAction.designAction = designAction;
+ }
+
+ ZoomInAction() {
+ super("Zoom in", zoomInIcon);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_EQUALS, getDefaultToolkit()
+ .getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+// if (isWorkflowPerspective()) {
+// if (designAction != null)
+ designAction.actionPerformed(e);
+// else
+// logger.error("ZoomInAction.designAction is null");
+// } else if (isResultsPerspective() && (resultsAction != null))
+// resultsAction.actionPerformed(e);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomInMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomInMenuAction.java
new file mode 100644
index 0000000..89eea7d
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomInMenuAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.DiagramZoomMenuSection.DIAGRAM_ZOOM_MENU_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+/**
+ * An action that zooms a diagram image
+ *
+ * @author Alex Nenadic
+ * @author Tom Oinn
+ * @author Alan R Williams
+ */
+public class ZoomInMenuAction extends AbstractMenuAction {
+ public static final URI ZOOM_IN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagramMenuZoomIn");
+
+ public ZoomInMenuAction() {
+ super(DIAGRAM_ZOOM_MENU_SECTION, 10, ZOOM_IN_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new ZoomInAction();
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomOutAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomOutAction.java
new file mode 100644
index 0000000..bd2a2b9
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomOutAction.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_MINUS;
+import static javax.swing.KeyStroke.getKeyStroke;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.DesignOrResultsAction;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+@SuppressWarnings("serial")
+public class ZoomOutAction extends AbstractAction implements
+ DesignOrResultsAction {
+ private static Action designAction = null;
+ @SuppressWarnings("unused")
+ private static Action resultsAction = null;
+
+ public static void setResultsAction(Action resultsAction) {
+ ZoomOutAction.resultsAction = resultsAction;
+ }
+
+ public static void setDesignAction(Action designAction) {
+ ZoomOutAction.designAction = designAction;
+ }
+
+ ZoomOutAction() {
+ super("Zoom out", WorkbenchIcons.zoomOutIcon);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_MINUS, getDefaultToolkit()
+ .getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+// if (isWorkflowPerspective() && (designAction != null))
+ designAction.actionPerformed(e);
+// else if (isResultsPerspective() && (resultsAction != null))
+// resultsAction.actionPerformed(e);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomOutMenuAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomOutMenuAction.java
new file mode 100644
index 0000000..bc34252
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/menu/ZoomOutMenuAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.graph.menu;
+
+import static net.sf.taverna.t2.workbench.views.graph.menu.DiagramZoomMenuSection.DIAGRAM_ZOOM_MENU_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+/**
+ * An action that zooms a diagram image
+ *
+ * @author Alex Nenadic
+ * @author Tom Oinn
+ * @author Alan R Williams
+ */
+public class ZoomOutMenuAction extends AbstractMenuAction {
+ public static final URI ZOOM_OUT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagramMenuZoomOut");
+
+ public ZoomOutMenuAction() {
+ super(DIAGRAM_ZOOM_MENU_SECTION, 20, ZOOM_OUT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new ZoomOutAction();
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/AddWFInputToolbarAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/AddWFInputToolbarAction.java
new file mode 100644
index 0000000..736ba8d
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/AddWFInputToolbarAction.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.graph.toolbar;
+
+import static net.sf.taverna.t2.workbench.views.graph.toolbar.GraphEditToolbarSection.GRAPH_EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.AddWFInputAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class AddWFInputToolbarAction extends AbstractMenuAction {
+ private static final URI ADD_WF_INPUT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphToolbarAddWFInput");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public AddWFInputToolbarAction() {
+ super(GRAPH_EDIT_TOOLBAR_SECTION, 10, ADD_WF_INPUT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddWFInputAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/AddWFOutputToolbarAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/AddWFOutputToolbarAction.java
new file mode 100644
index 0000000..ae7d5d0
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/AddWFOutputToolbarAction.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.graph.toolbar;
+
+import static net.sf.taverna.t2.workbench.views.graph.toolbar.GraphEditToolbarSection.GRAPH_EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.AddWFOutputAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class AddWFOutputToolbarAction extends AbstractMenuAction {
+ private static final URI ADD_WF_OUTPUT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphToolbarAddWFOutput");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public AddWFOutputToolbarAction() {
+ super(GRAPH_EDIT_TOOLBAR_SECTION, 20, ADD_WF_OUTPUT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddWFOutputAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/DeleteGraphComponentToolbarAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/DeleteGraphComponentToolbarAction.java
new file mode 100644
index 0000000..068c530
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/DeleteGraphComponentToolbarAction.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.graph.toolbar;
+
+import static net.sf.taverna.t2.workbench.views.graph.toolbar.GraphDeleteToolbarSection.GRAPH_DELETE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.DeleteGraphComponentAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class DeleteGraphComponentToolbarAction extends AbstractMenuAction {
+ private static final URI DELETE_GRAPH_COMPONENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphToolbarDeleteGraphComponent");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public DeleteGraphComponentToolbarAction() {
+ super(GRAPH_DELETE_TOOLBAR_SECTION, 10, DELETE_GRAPH_COMPONENT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new DeleteGraphComponentAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/GraphDeleteToolbarSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/GraphDeleteToolbarSection.java
new file mode 100644
index 0000000..794cf1f
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/GraphDeleteToolbarSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ */
+public class GraphDeleteToolbarSection extends AbstractMenuSection {
+ public static final URI GRAPH_DELETE_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphDeleteToolbarSection");
+
+ public GraphDeleteToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 80, GRAPH_DELETE_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/GraphEditToolbarSection.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/GraphEditToolbarSection.java
new file mode 100644
index 0000000..a61259a
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/GraphEditToolbarSection.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.graph.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ */
+public class GraphEditToolbarSection extends AbstractMenuSection {
+ public static final URI GRAPH_EDIT_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphEditToolbarSection");
+
+ public GraphEditToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 30, GRAPH_EDIT_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/RenameWFInputOutputProcessorToolbarAction.java b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/RenameWFInputOutputProcessorToolbarAction.java
new file mode 100644
index 0000000..1794e8f
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/java/net/sf/taverna/t2/workbench/views/graph/toolbar/RenameWFInputOutputProcessorToolbarAction.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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.graph.toolbar;
+
+import static net.sf.taverna.t2.workbench.views.graph.toolbar.GraphEditToolbarSection.GRAPH_EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.actions.RenameWFInputOutputProcessorAction;
+
+/**
+ * @author Alex Nenadic
+ */
+public class RenameWFInputOutputProcessorToolbarAction extends
+ AbstractMenuAction {
+ private static final URI RENAME_WF_INPUT_OUTPUT_PROCESSOR_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphToolbarRenameWFInputOutputProcessor");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RenameWFInputOutputProcessorToolbarAction() {
+ super(GRAPH_EDIT_TOOLBAR_SECTION, 30,
+ RENAME_WF_INPUT_OUTPUT_PROCESSOR_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new RenameWFInputOutputProcessorAction(editManager,
+ selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..226078d
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,29 @@
+net.sf.taverna.t2.workbench.views.graph.toolbar.GraphEditToolbarSection
+net.sf.taverna.t2.workbench.views.graph.toolbar.GraphDeleteToolbarSection
+net.sf.taverna.t2.workbench.views.graph.toolbar.GraphSaveToolbarSection
+net.sf.taverna.t2.workbench.views.graph.toolbar.AddWFInputToolbarAction
+net.sf.taverna.t2.workbench.views.graph.toolbar.AddWFOutputToolbarAction
+net.sf.taverna.t2.workbench.views.graph.toolbar.RenameWFInputOutputProcessorToolbarAction
+net.sf.taverna.t2.workbench.views.graph.toolbar.DeleteGraphComponentToolbarAction
+net.sf.taverna.t2.workbench.views.graph.toolbar.SaveGraphImageToolbarAction
+
+net.sf.taverna.t2.workbench.views.graph.menu.DiagramMenu
+net.sf.taverna.t2.workbench.views.graph.menu.DiagramSaveMenuSection
+net.sf.taverna.t2.workbench.views.graph.menu.DiagramZoomMenuSection
+
+net.sf.taverna.t2.workbench.views.graph.menu.GraphMenuSection
+net.sf.taverna.t2.workbench.views.graph.menu.GraphCopyMenuSection
+net.sf.taverna.t2.workbench.views.graph.menu.GraphEditMenuSection
+net.sf.taverna.t2.workbench.views.graph.menu.GraphDeleteMenuSection
+net.sf.taverna.t2.workbench.views.graph.menu.GraphDetailsMenuSection
+
+net.sf.taverna.t2.workbench.views.graph.menu.InsertMenu
+
+net.sf.taverna.t2.workbench.views.graph.menu.AddWFInputMenuAction
+net.sf.taverna.t2.workbench.views.graph.menu.AddWFOutputMenuAction
+net.sf.taverna.t2.workbench.views.graph.menu.RenameWFInputOutputProcessorMenuAction
+net.sf.taverna.t2.workbench.views.graph.menu.DeleteGraphComponentMenuAction
+net.sf.taverna.t2.workbench.views.graph.menu.SaveGraphImageSubMenu
+net.sf.taverna.t2.workbench.views.graph.menu.ZoomInMenuAction
+net.sf.taverna.t2.workbench.views.graph.menu.ZoomOutMenuAction
+net.sf.taverna.t2.workbench.views.graph.menu.ResetDiagramMenuAction
\ No newline at end of file
diff --git a/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..70830ec
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfigurationUIFactory
diff --git a/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..8086a8d
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.graph.GraphViewComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..563c21d
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.graph.GraphViewComponent
diff --git a/taverna-workbench-graph-view/src/main/resources/META-INF/spring/graph-view-context-osgi.xml b/taverna-workbench-graph-view/src/main/resources/META-INF/spring/graph-view-context-osgi.xml
new file mode 100644
index 0000000..c7adec0
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/resources/META-INF/spring/graph-view-context-osgi.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="GraphViewConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <service ref="GraphViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <service ref="GraphEditToolbarSection" auto-export="interfaces" />
+ <service ref="GraphDeleteToolbarSection" auto-export="interfaces" />
+ <service ref="AddWFInputToolbarAction" auto-export="interfaces" />
+ <service ref="AddWFOutputToolbarAction" auto-export="interfaces" />
+ <service ref="RenameWFInputOutputProcessorToolbarAction" auto-export="interfaces" />
+ <service ref="DeleteGraphComponentToolbarAction" auto-export="interfaces" />
+ <service ref="DiagramMenu" auto-export="interfaces" />
+ <service ref="DiagramSaveMenuSection" auto-export="interfaces" />
+ <service ref="DiagramZoomMenuSection" auto-export="interfaces" />
+ <service ref="GraphMenuSection" auto-export="interfaces" />
+ <service ref="GraphCopyMenuSection" auto-export="interfaces" />
+ <service ref="GraphEditMenuSection" auto-export="interfaces" />
+ <service ref="GraphDeleteMenuSection" auto-export="interfaces" />
+ <service ref="GraphDetailsMenuSection" auto-export="interfaces" />
+ <service ref="InsertMenu" auto-export="interfaces" />
+ <service ref="AddWFInputMenuAction" auto-export="interfaces" />
+ <service ref="AddWFOutputMenuAction" auto-export="interfaces" />
+ <service ref="RenameWFInputOutputProcessorMenuAction" auto-export="interfaces" />
+ <service ref="DeleteGraphComponentMenuAction" auto-export="interfaces" />
+ <!-- <service ref="SaveGraphImageSubMenu" auto-export="interfaces" /> -->
+ <service ref="ZoomInMenuAction" auto-export="interfaces" />
+ <service ref="ZoomOutMenuAction" auto-export="interfaces" />
+ <service ref="ResetDiagramMenuAction" auto-export="interfaces" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+ <reference id="serviceRegistry" interface="uk.org.taverna.commons.services.ServiceRegistry" />
+
+</beans:beans>
diff --git a/taverna-workbench-graph-view/src/main/resources/META-INF/spring/graph-view-context.xml b/taverna-workbench-graph-view/src/main/resources/META-INF/spring/graph-view-context.xml
new file mode 100644
index 0000000..9968805
--- /dev/null
+++ b/taverna-workbench-graph-view/src/main/resources/META-INF/spring/graph-view-context.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="graphViewConfiguration"
+ class="net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfiguration">
+ <constructor-arg name="configurationManager" ref="configurationManager" />
+ </bean>
+
+ <bean id="GraphViewConfigurationUIFactory"
+ class="net.sf.taverna.t2.workbench.views.graph.config.GraphViewConfigurationUIFactory">
+ <property name="graphViewConfiguration">
+ <ref local="graphViewConfiguration" />
+ </property>
+ </bean>
+
+ <bean id="GraphViewComponentFactory" class="net.sf.taverna.t2.workbench.views.graph.GraphViewComponentFactory">
+ <property name="colourManager" ref="colourManager" />
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="graphViewConfiguration">
+ <ref local="graphViewConfiguration" />
+ </property>
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+
+ <bean id="GraphEditToolbarSection"
+ class="net.sf.taverna.t2.workbench.views.graph.toolbar.GraphEditToolbarSection" />
+ <bean id="GraphDeleteToolbarSection"
+ class="net.sf.taverna.t2.workbench.views.graph.toolbar.GraphDeleteToolbarSection" />
+ <bean id="AddWFInputToolbarAction"
+ class="net.sf.taverna.t2.workbench.views.graph.toolbar.AddWFInputToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="AddWFOutputToolbarAction"
+ class="net.sf.taverna.t2.workbench.views.graph.toolbar.AddWFOutputToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RenameWFInputOutputProcessorToolbarAction"
+ class="net.sf.taverna.t2.workbench.views.graph.toolbar.RenameWFInputOutputProcessorToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="DeleteGraphComponentToolbarAction"
+ class="net.sf.taverna.t2.workbench.views.graph.toolbar.DeleteGraphComponentToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="DiagramMenu"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.DiagramMenu" />
+ <bean id="DiagramSaveMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.DiagramSaveMenuSection" />
+ <bean id="DiagramZoomMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.DiagramZoomMenuSection" />
+ <bean id="GraphMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.GraphMenuSection" />
+ <bean id="GraphCopyMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.GraphCopyMenuSection" />
+ <bean id="GraphEditMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.GraphEditMenuSection" />
+ <bean id="GraphDeleteMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.GraphDeleteMenuSection" />
+ <bean id="GraphDetailsMenuSection"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.GraphDetailsMenuSection" />
+ <bean id="InsertMenu" class="net.sf.taverna.t2.workbench.views.graph.menu.InsertMenu" />
+ <bean id="AddWFInputMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.AddWFInputMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="AddWFOutputMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.AddWFOutputMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RenameWFInputOutputProcessorMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.RenameWFInputOutputProcessorMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="DeleteGraphComponentMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.DeleteGraphComponentMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <!-- <bean id="SaveGraphImageSubMenu"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.SaveGraphImageSubMenu">
+ <property name="fileManager" ref="fileManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="graphViewComponent" ref="GraphViewComponent" />
+ </bean> -->
+ <bean id="ZoomInMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.ZoomInMenuAction" />
+ <bean id="ZoomOutMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.ZoomOutMenuAction" />
+ <bean id="ResetDiagramMenuAction"
+ class="net.sf.taverna.t2.workbench.views.graph.menu.ResetDiagramMenuAction" />
+
+</beans>
diff --git a/taverna-workbench-graph-view/src/test/resources/nested_iteration.t2flow b/taverna-workbench-graph-view/src/test/resources/nested_iteration.t2flow
new file mode 100644
index 0000000..9b50c7f
--- /dev/null
+++ b/taverna-workbench-graph-view/src/test/resources/nested_iteration.t2flow
@@ -0,0 +1,111 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow"><dataflow id="23f84bb1-4a04-47fa-8150-7063310db697" role="top"><name>nested_iteration</name><inputPorts /><outputPorts><port><name>concat</name></port><port><name>list</name></port><port><name>constant</name></port></outputPorts><processors><processor><name>constant</name><inputPorts /><outputPorts><port><name>value</name><depth>0</depth><granularDepth>0</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2</group><artifact>stringconstant-activity</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.activities.stringconstant.StringConstantActivity</class><inputMap /><outputMap><map from="value" to="value" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.stringconstant.StringConstantConfigurationBean xmlns="">
+ <value>constant</value>
+</net.sf.taverna.t2.activities.stringconstant.StringConstantConfigurationBean></configBean></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross /></strategy></iteration></iterationStrategyStack></processor><processor><name>generate_list</name><inputPorts><port><name>prefix</name><depth>0</depth></port></inputPorts><outputPorts><port><name>list</name><depth>1</depth><granularDepth>1</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2</group><artifact>beanshell-activity</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.activities.beanshell.BeanshellActivity</class><inputMap><map from="prefix" to="prefix" /></inputMap><outputMap><map from="list" to="list" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean xmlns="">
+ <script>list = new ArrayList();
+for (int i = 0; i < 20; i++) {
+ list.add(prefix + i);
+}</script>
+ <dependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>prefix</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>1</granularDepth>
+ <name>list</name>
+ <depth>1</depth>
+ <mimeTypes>
+ <string>l('text/plain')</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean></configBean></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><port name="prefix" depth="0" /></strategy></iteration></iterationStrategyStack></processor><processor><name>merge</name><inputPorts><port><name>in1</name><depth>0</depth></port><port><name>in2</name><depth>0</depth></port></inputPorts><outputPorts><port><name>out</name><depth>0</depth><granularDepth>0</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2</group><artifact>dataflow-activity</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.activities.dataflow.DataflowActivity</class><inputMap><map from="in2" to="in2" /><map from="in1" to="in1" /></inputMap><outputMap><map from="out" to="out" /></outputMap><configBean encoding="dataflow"><dataflow ref="79ad4092-abcb-42bf-ac98-d66dfac67dff" /></configBean></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="in1" depth="0" /><port name="in2" depth="0" /></cross></strategy></iteration></iterationStrategyStack></processor><processor><name>generate_list_prefix_defaultValue</name><inputPorts /><outputPorts><port><name>value</name><depth>0</depth><granularDepth>0</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2</group><artifact>stringconstant-activity</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.activities.stringconstant.StringConstantActivity</class><inputMap /><outputMap><map from="value" to="value" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.stringconstant.StringConstantConfigurationBean xmlns="">
+ <value>prefix</value>
+</net.sf.taverna.t2.activities.stringconstant.StringConstantConfigurationBean></configBean></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross /></strategy></iteration></iterationStrategyStack></processor></processors><conditions><condition control="generate_list" target="constant" /></conditions><datalinks><datalink><sink type="processor"><processor>generate_list</processor><port>prefix</port></sink><source type="processor"><processor>generate_list_prefix_defaultValue</processor><port>value</port></source></datalink><datalink><sink type="processor"><processor>merge</processor><port>in1</port></sink><source type="processor"><processor>constant</processor><port>value</port></source></datalink><datalink><sink type="merge"><processor>merge</processor><port>in2</port></sink><source type="processor"><processor>generate_list</processor><port>list</port></source></datalink><datalink><sink type="merge"><processor>merge</processor><port>in2</port></sink><source type="processor"><processor>generate_list</processor><port>list</port></source></datalink><datalink><sink type="dataflow"><port>concat</port></sink><source type="processor"><processor>merge</processor><port>out</port></source></datalink><datalink><sink type="dataflow"><port>list</port></sink><source type="processor"><processor>generate_list</processor><port>list</port></source></datalink><datalink><sink type="dataflow"><port>constant</port></sink><source type="processor"><processor>constant</processor><port>value</port></source></datalink></datalinks></dataflow><dataflow id="79ad4092-abcb-42bf-ac98-d66dfac67dff" role="nested"><name>Untitled workflow #24</name><inputPorts><port><name>in1</name><depth>0</depth><granularDepth>0</granularDepth></port><port><name>in2</name><depth>0</depth><granularDepth>0</granularDepth></port></inputPorts><outputPorts><port><name>out</name></port></outputPorts><processors><processor><name>Nested_Workflow</name><inputPorts><port><name>in2</name><depth>0</depth></port><port><name>in1</name><depth>0</depth></port></inputPorts><outputPorts><port><name>out</name><depth>0</depth><granularDepth>0</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2</group><artifact>dataflow-activity</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.activities.dataflow.DataflowActivity</class><inputMap><map from="in2" to="in2" /><map from="in1" to="in1" /></inputMap><outputMap><map from="out" to="out" /></outputMap><configBean encoding="dataflow"><dataflow ref="ebd93027-c046-4a04-befa-c5715e8ba3da" /></configBean></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="in1" depth="0" /><port name="in2" depth="0" /></cross></strategy></iteration></iterationStrategyStack></processor></processors><conditions /><datalinks><datalink><sink type="processor"><processor>Nested_Workflow</processor><port>in2</port></sink><source type="dataflow"><port>in2</port></source></datalink><datalink><sink type="processor"><processor>Nested_Workflow</processor><port>in1</port></sink><source type="dataflow"><port>in1</port></source></datalink><datalink><sink type="dataflow"><port>out</port></sink><source type="processor"><processor>Nested_Workflow</processor><port>out</port></source></datalink></datalinks></dataflow><dataflow id="ebd93027-c046-4a04-befa-c5715e8ba3da" role="nested"><name>Untitled workflow #36</name><inputPorts><port><name>in1</name><depth>0</depth><granularDepth>0</granularDepth></port><port><name>in2</name><depth>0</depth><granularDepth>0</granularDepth></port></inputPorts><outputPorts><port><name>out</name></port></outputPorts><processors><processor><name>concat</name><inputPorts><port><name>in1</name><depth>0</depth></port><port><name>in2</name><depth>0</depth></port></inputPorts><outputPorts><port><name>out</name><depth>0</depth><granularDepth>0</granularDepth></port></outputPorts><annotations /><activities><activity><raven><group>net.sf.taverna.t2</group><artifact>beanshell-activity</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.activities.beanshell.BeanshellActivity</class><inputMap><map from="in2" to="in2" /><map from="in1" to="in1" /></inputMap><outputMap><map from="out" to="out" /></outputMap><configBean encoding="xstream"><net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean xmlns="">
+ <script>Thread.sleep(200);
+out = in1 + in2;</script>
+ <dependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>in1</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>in2</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>0</granularDepth>
+ <name>out</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+</net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean></configBean></activity></activities><dispatchStack><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig xmlns="">
+ <maxJobs>1</maxJobs>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry</class><configBean encoding="xstream"><net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig></configBean></dispatchLayer><dispatchLayer><raven><group>net.sf.taverna.t2</group><artifact>workflowmodel-impl</artifact><version>0.3-SNAPSHOT</version></raven><class>net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke</class><configBean encoding="xstream"><null xmlns="" /></configBean></dispatchLayer></dispatchStack><iterationStrategyStack><iteration><strategy><cross><port name="in1" depth="0" /><port name="in2" depth="0" /></cross></strategy></iteration></iterationStrategyStack></processor></processors><conditions /><datalinks><datalink><sink type="processor"><processor>concat</processor><port>in1</port></sink><source type="dataflow"><port>in1</port></source></datalink><datalink><sink type="processor"><processor>concat</processor><port>in2</port></sink><source type="dataflow"><port>in2</port></source></datalink><datalink><sink type="dataflow"><port>out</port></sink><source type="processor"><processor>concat</processor><port>out</port></source></datalink></datalinks></dataflow></workflow>
\ No newline at end of file
diff --git a/taverna-workbench-graph-view/src/test/resources/nested_iteration.xml b/taverna-workbench-graph-view/src/test/resources/nested_iteration.xml
new file mode 100644
index 0000000..3a547bb
--- /dev/null
+++ b/taverna-workbench-graph-view/src/test/resources/nested_iteration.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<s:scufl xmlns:s="http://org.embl.ebi.escience/xscufl/0.1alpha" version="0.2" log="0">
+ <s:workflowdescription lsid="urn:lsid:net.sf.taverna:wfDefinition:c7016fc0-c2f4-4171-b6f1-430f408f4822" author="" title="nested_iteration" />
+ <s:processor name="generate_list">
+ <s:defaults>
+ <s:default name="prefix">prefix</s:default>
+ </s:defaults>
+ <s:beanshell>
+ <s:scriptvalue>list = new ArrayList();
+for (int i = 0; i < 20; i++) {
+ list.add(prefix + i);
+}</s:scriptvalue>
+ <s:beanshellinputlist>
+ <s:beanshellinput s:syntactictype="'text/plain'">prefix</s:beanshellinput>
+ </s:beanshellinputlist>
+ <s:beanshelloutputlist>
+ <s:beanshelloutput s:syntactictype="l('text/plain')">list</s:beanshelloutput>
+ </s:beanshelloutputlist>
+ <s:dependencies s:classloader="iteration" />
+ </s:beanshell>
+ </s:processor>
+ <s:processor name="constant" boring="true">
+ <s:stringconstant>constant</s:stringconstant>
+ </s:processor>
+ <s:processor name="merge">
+ <s:workflow>
+ <s:scufl version="0.2" log="0">
+ <s:workflowdescription lsid="urn:lsid:net.sf.taverna:wfDefinition:3368fb8d-ecc7-4fcd-b511-6ace84b13c81" author="" title="Untitled workflow #24" />
+ <s:processor name="Nested_Workflow">
+ <s:workflow>
+ <s:scufl version="0.2" log="0">
+ <s:workflowdescription lsid="urn:lsid:net.sf.taverna:wfDefinition:75b99c76-7a76-4d3c-8d39-8c48df3355ad" author="" title="Untitled workflow #36" />
+ <s:processor name="concat">
+ <s:beanshell>
+ <s:scriptvalue>Thread.sleep(200);
+out = in1 + in2;</s:scriptvalue>
+ <s:beanshellinputlist>
+ <s:beanshellinput s:syntactictype="'text/plain'">in1</s:beanshellinput>
+ <s:beanshellinput s:syntactictype="'text/plain'">in2</s:beanshellinput>
+ </s:beanshellinputlist>
+ <s:beanshelloutputlist>
+ <s:beanshelloutput s:syntactictype="'text/plain'">out</s:beanshelloutput>
+ </s:beanshelloutputlist>
+ <s:dependencies s:classloader="iteration" />
+ </s:beanshell>
+ </s:processor>
+ <s:link source="in1" sink="concat:in1" />
+ <s:link source="in2" sink="concat:in2" />
+ <s:link source="concat:out" sink="out" />
+ <s:source name="in1" />
+ <s:source name="in2" />
+ <s:sink name="out" />
+ </s:scufl>
+ </s:workflow>
+ </s:processor>
+ <s:link source="in1" sink="Nested_Workflow:in1" />
+ <s:link source="in2" sink="Nested_Workflow:in2" />
+ <s:link source="Nested_Workflow:out" sink="out" />
+ <s:source name="in1" />
+ <s:source name="in2" />
+ <s:sink name="out" />
+ </s:scufl>
+ </s:workflow>
+ <s:mergemode input="in2" mode="merge" />
+ </s:processor>
+ <s:link source="constant:value" sink="merge:in1" />
+ <s:link source="generate_list:list" sink="merge:in2" />
+ <s:link source="generate_list:list" sink="merge:in2" />
+ <s:link source="constant:value" sink="constant" />
+ <s:link source="generate_list:list" sink="list" />
+ <s:link source="merge:out" sink="concat" />
+ <s:sink name="concat">
+ <s:metadata>
+ <s:mimeTypes>
+ <s:mimeType>'text/plain'</s:mimeType>
+ </s:mimeTypes>
+ </s:metadata>
+ </s:sink>
+ <s:sink name="list">
+ <s:metadata>
+ <s:mimeTypes>
+ <s:mimeType>l('text/plain')</s:mimeType>
+ </s:mimeTypes>
+ </s:metadata>
+ </s:sink>
+ <s:sink name="constant">
+ <s:metadata>
+ <s:mimeTypes>
+ <s:mimeType>'text/plain'</s:mimeType>
+ </s:mimeTypes>
+ </s:metadata>
+ </s:sink>
+ <s:coordination name="constant_BLOCKON_generate_list">
+ <s:condition>
+ <s:state>Completed</s:state>
+ <s:target>generate_list</s:target>
+ </s:condition>
+ <s:action>
+ <s:target>constant</s:target>
+ <s:statechange>
+ <s:from>Scheduled</s:from>
+ <s:to>Running</s:to>
+ </s:statechange>
+ </s:action>
+ </s:coordination>
+ <s:coordination name="merge_BLOCKON_generate_list">
+ <s:condition>
+ <s:state>Completed</s:state>
+ <s:target>generate_list</s:target>
+ </s:condition>
+ <s:action>
+ <s:target>merge</s:target>
+ <s:statechange>
+ <s:from>Scheduled</s:from>
+ <s:to>Running</s:to>
+ </s:statechange>
+ </s:action>
+ </s:coordination>
+</s:scufl>
+
diff --git a/taverna-workbench-helper-api/pom.xml b/taverna-workbench-helper-api/pom.xml
new file mode 100644
index 0000000..9f3945f
--- /dev/null
+++ b/taverna-workbench-helper-api/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Help System</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Embed-Dependency>javahelp</Embed-Dependency>
+ <Import-Package>org.jdesktop.jdic.browser;resolution:=optional,*</Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Required by javahelp -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>com.springsource.javax.servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>com.springsource.javax.servlet.jsp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.el</groupId>
+ <artifactId>com.springsource.javax.el</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/HelpCollator.java b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/HelpCollator.java
new file mode 100644
index 0000000..8b19b69
--- /dev/null
+++ b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/HelpCollator.java
@@ -0,0 +1,307 @@
+package net.sf.taverna.t2.workbench.helper;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.help.BadIDException;
+import javax.help.HelpSet;
+import javax.help.HelpSetException;
+import javax.help.Map.ID;
+import javax.help.TryMap;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import org.apache.log4j.Logger;
+
+/**
+ * This class loads the {@link HelpSet} and also deals with the registration of
+ * ids and the decoding from a {@link Component} to the corresponding id. These
+ * two sets of functionality should possibly be separated.
+ *
+ * @author alanrw
+ */
+// TODO Convert to a bean
+public final class HelpCollator {
+ private static Logger logger = Logger.getLogger(HelpCollator.class);
+ /**
+ * The HelpSet that is being used.
+ */
+ private static HelpSet hs = null;
+ /**
+ * The mapping from components to ids. This is used because of problems with
+ * CSH throwing exceptions because it tried to use ids that were not in the
+ * map.
+ */
+ private static Map<Component, String> idMap;
+ /**
+ * Indicates whether the HelpCollator has been initialized.
+ */
+ private static boolean initialized = false;
+ /**
+ * A Pattern for normalizing the ids.
+ */
+ private static Pattern nonAlphanumeric;
+ /**
+ * The emptyHelp is set if the HelpCollator was unable to read the
+ */
+ private static boolean emptyHelp = true;
+ private static int TIMEOUT = 5000;
+
+ private static String externalHelpSetURL = "http://www.mygrid.org.uk/taverna/helpset/"
+ + version() + "/helpset.hs";
+
+ // private static Profile profile = ProfileFactory.getInstance().getProfile();
+ private static String version() {
+ return "NO-VERSION";//profile.getVersion();
+ // TODO find a better way to find the version
+ }
+
+ /**
+ * Attempt to read the up-to-date HelpSet from the web
+ */
+ private static void readExternalHelpSet() {
+ try {
+ URL url = new URL(externalHelpSetURL);
+ checkConnection(url);
+ hs = new HelpSet(null, url);
+ if (hs.getLocalMap() == null) {
+ hs = null;
+ logger.error("Helpset from " + externalHelpSetURL
+ + " local map was null");
+ } else
+ logger.info("Read external help set from " + externalHelpSetURL);
+ } catch (MissingResourceException e) {
+ logger.error("No external HelpSet URL specified", e);
+ } catch (MalformedURLException e) {
+ logger.error("External HelpSet URL is malformed", e);
+ } catch (HelpSetException e) {
+ logger.error("External HelpSet could not be read", e);
+ } catch (IOException e) {
+ logger.error("IOException reading External HelpSet", e);
+ }
+ }
+
+ private static void checkConnection(URL url) throws IOException {
+ if (!url.getProtocol().startsWith("http"))
+ return;
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setReadTimeout(TIMEOUT);
+ connection.setConnectTimeout(TIMEOUT);
+ connection.setRequestMethod("HEAD");
+ connection.getInputStream().close();
+ connection.disconnect();
+ }
+
+ /**
+ * This methods creates a HelpSet based upon, in priority, the external
+ * HelpSet, then a newly created empty HelpSet.
+ */
+ private static void initialize() {
+ if (initialized)
+ return;
+ readExternalHelpSet();
+ if (hs == null) {
+ hs = new HelpSet();
+ hs.setLocalMap(new TryMap());
+ } else {
+ logger.trace("EmptyHelp set to false");
+ emptyHelp = false;
+ }
+ idMap = new HashMap<>();
+ nonAlphanumeric = Pattern.compile("[^a-z0-9\\.]");
+ initialized = true;
+ }
+
+ /**
+ * Indicates if an empty HelpSet is being used
+ *
+ * @return
+ */
+ public static boolean isEmptyHelp() {
+ return emptyHelp;
+ }
+
+ public static URL getURLFromID(String id) throws BadIDException,
+ MalformedURLException {
+ initialize();
+ logger.trace("Looking for id: " + id);
+ ID theId = ID.create(id, hs);
+ if (theId == null)
+ return null;
+ return hs.getCombinedMap().getURLFromID(theId);
+ }
+
+ /**
+ * Register a component under the specified id. The method checks that the
+ * id is known to the HelpSet's map.
+ *
+ * @param component
+ * @param id
+ */
+ public static void registerComponent(Component component, String id) {
+ logger.trace("Attempting to register " + id);
+ initialize();
+ String normalizedId = normalizeString(id.toLowerCase());
+ if (idMap.containsKey(component)) {
+ logger.info("Registered " + normalizedId);
+ return;
+ }
+
+ /*
+ * If Workbench is started up while there is no network connection -
+ * hs.getLocalMap() is null for some reason
+ */
+ if (hs != null && hs.getLocalMap() != null
+ && hs.getLocalMap().isValidID(normalizedId, hs)) {
+ idMap.put(component, normalizedId);
+ logger.info("Registered " + normalizedId);
+ } else
+ logger.warn("Refused to register component as " + normalizedId
+ + " not in map");
+ }
+
+ /**
+ * Register a component. Since no id is specified, the HelpCollator takes
+ * the canonical name of the component's class. This is useful when an
+ * explicit hierarchy-based approach has been taken.
+ *
+ * @param component
+ */
+ public static void registerComponent(Component component) {
+ String canonicalName = component.getClass().getCanonicalName();
+ if (canonicalName != null)
+ registerComponent(component, canonicalName);
+ }
+
+ /**
+ * Register a component based upon its parent's class and a suffix
+ * indicating the component's purpose in the parent.
+ *
+ * @param component
+ * @param parent
+ * @param suffix
+ */
+ public static void registerComponent(Component component, Object parent,
+ String suffix) {
+ String canonicalName = parent.getClass().getCanonicalName();
+ if (canonicalName != null)
+ registerComponent(component, canonicalName + "-" + suffix);
+ }
+
+ /**
+ * Try to find an id for the Component. This code should be re-written when
+ * we have more experience in how to couple the UI and HelpSets.
+ *
+ * @param c
+ * @return
+ */
+ static String getHelpID(Component c) {
+ initialize();
+ boolean found = false;
+ String result = null;
+ if (c instanceof JTree) {
+ String idInTree = getHelpIDInTree((JTree) c);
+ if (idInTree != null) {
+ found = true;
+ result = idInTree;
+ }
+ }
+ Component working = c;
+ if (c != null)
+ logger.trace("Starting at a " + working.getClass());
+ while (!found && (working != null)) {
+ if (idMap.containsKey(working)) {
+ result = idMap.get(working);
+ found = true;
+ logger.trace("Found component id " + result);
+ } else {
+ String className = working.getClass().getCanonicalName();
+ if (hs.getLocalMap().isValidID(className, hs)) {
+ result = className;
+ found = true;
+ logger.trace("Found class name " + result);
+ }
+ }
+ if (!found) {
+ working = working.getParent();
+ if (working != null)
+ logger.trace("Moved up to a " + working.getClass());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Change the input String into an id that contains only alphanumeric
+ * characters or hyphens.
+ *
+ * @param input
+ * @return
+ */
+ private static String normalizeString(String input) {
+ Matcher m = nonAlphanumeric.matcher(input);
+ return m.replaceAll("-");
+ }
+
+ /**
+ * If help is sought on part of a JTree, then this method attempts to find a
+ * node of the tree that can be mapped to an id. The possibilities are ad
+ * hoc and should be re-examined when more experience is gained.
+ *
+ * @param c
+ * @return
+ */
+ private static String getHelpIDInTree(JTree c) {
+ initialize();
+
+ TreePath tp = c.getSelectionPath();
+ if (tp == null)
+ return null;
+
+ Object o = tp.getLastPathComponent();
+ if (o == null)
+ return null;
+
+ if (o instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) o;
+ if (dmtn.getUserObject() != null)
+ o = dmtn.getUserObject();
+ }
+
+ String className = o.getClass().getCanonicalName();
+
+ logger.trace("Tree node as a string is " + o);
+
+ String possibility = normalizeString(o.toString().toLowerCase());
+
+ logger.trace("Normalized is " + possibility);
+ logger.trace("Tree node class name is " + className);
+
+ possibility = className + "-" + possibility;
+
+ logger.trace("Possibility is " + possibility);
+
+ String result;
+ if (hs.getLocalMap().isValidID(possibility, hs)) {
+ result = possibility;
+ logger.trace("Accepted tree node " + result);
+ } else if (hs.getLocalMap().isValidID(className, hs)) {
+ result = className;
+ logger.trace("Found tree node class name " + result);
+ } else {
+ result = null;
+ }
+
+ logger.debug("Tree node is a " + o.getClass());
+ return result;
+ }
+}
diff --git a/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/HelpEnabledDialog.java b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/HelpEnabledDialog.java
new file mode 100644
index 0000000..ec17171
--- /dev/null
+++ b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/HelpEnabledDialog.java
@@ -0,0 +1,101 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.helper;
+
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.helper.HelpCollator.registerComponent;
+import static net.sf.taverna.t2.workbench.helper.Helper.setKeyCatcher;
+
+import java.awt.Dialog;
+import java.awt.Frame;
+import java.awt.HeadlessException;
+
+import javax.swing.JDialog;
+
+/**
+ * This class extends JDialog to register the dialog and also attach a key
+ * catcher so that F1 is interpreted as help
+ *
+ * @author alanrw
+ */
+public class HelpEnabledDialog extends JDialog {
+ private static final long serialVersionUID = -5068807887477419800L;
+
+ /**
+ * Create a HelpEnabledDialog, register it (if possible) with the
+ * HelpCollator and attach a keycatcher.
+ *
+ * @param owner
+ * @param title
+ * @param modal
+ * @param id
+ * @throws HeadlessException
+ */
+ public HelpEnabledDialog(Frame owner, String title, boolean modal, String id)
+ throws HeadlessException {
+ super(owner == null ? getMainWindow() : owner, title, modal);
+
+ if (id != null)
+ registerComponent(this, id);
+ else if (owner != null)
+ registerComponent(this, owner.getClass().getCanonicalName()
+ + "-dialog");
+ else if (title != null && !title.isEmpty())
+ registerComponent(this, title);
+ setKeyCatcher(this);
+ }
+
+ /**
+ * Create a HelpEnabledDialog, register it (if possible) with the
+ * HelpCollator and attach a keycatcher.
+ *
+ * @param owner
+ * @param title
+ * @param modal
+ * @param id
+ * @throws HeadlessException
+ */
+ public HelpEnabledDialog(Dialog owner, String title, boolean modal,
+ String id) throws HeadlessException {
+ super(owner, title, modal);
+ if (id != null)
+ registerComponent(this, id);
+ else if (owner != null)
+ registerComponent(this, owner.getClass().getCanonicalName()
+ + "-dialog");
+ setKeyCatcher(this);
+ }
+
+ /**
+ * Create a HelpEnabledDialog, register it (if possible) with the
+ * HelpCollator and attach a keycatcher.
+ *
+ * @param owner
+ * @param title
+ * @param modal
+ * @throws HeadlessException
+ */
+ public HelpEnabledDialog(Frame parent, String title, boolean modal) {
+ this(parent, title, modal, null);
+ }
+
+ /**
+ * Create a HelpEnabledDialog, register it (if possible) with the
+ * HelpCollator and attach a keycatcher.
+ *
+ * @param owner
+ * @param title
+ * @param modal
+ * @throws HeadlessException
+ */
+ public HelpEnabledDialog(Dialog parent, String title, boolean modal) {
+ this(parent, title, modal, null);
+ }
+
+ @Override
+ public void setVisible(boolean b) {
+ setLocationRelativeTo(getParent());
+ super.setVisible(b);
+ }
+}
diff --git a/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/Helper.java b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/Helper.java
new file mode 100644
index 0000000..21b0f75
--- /dev/null
+++ b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/Helper.java
@@ -0,0 +1,187 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.helper;
+
+import static java.awt.Desktop.getDesktop;
+import static java.awt.MouseInfo.getPointerInfo;
+import static javax.swing.JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.helper.HelpCollator.getHelpID;
+import static net.sf.taverna.t2.workbench.helper.HelpCollator.getURLFromID;
+
+import java.awt.AWTEvent;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.help.BadIDException;
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JRootPane;
+import javax.swing.RootPaneContainer;
+
+import org.apache.log4j.Logger;
+
+/**
+ * This class creates the dialogs for the presentation of the HelpSet held by
+ * the HelpCollator.
+ *
+ * @author alanrw
+ */
+public final class Helper {
+ private static Helper instance;
+ private static Logger logger = Logger.getLogger(Helper.class);
+
+ /**
+ * Create a Helper and initialize the static variables.
+ */
+ private Helper() {
+ }
+
+ /**
+ * Get the singleton instance of Helper. In theory there could be more than
+ * one.
+ *
+ * @return
+ */
+ private static Helper getInstance() {
+ if (instance == null)
+ instance = new Helper();
+ return instance;
+ }
+
+ /**
+ * Show in the current dialog the entry (if any) corresponding to the
+ * specified id.
+ *
+ * @param id
+ */
+ private static void showID(String id) {
+ getInstance();
+ try {
+ URL result = getURLFromID(id);
+ if (result == null)
+ result = getURLFromID("home");
+ getDesktop().browse(result.toURI());
+ } catch (BadIDException | IOException | URISyntaxException e) {
+ logger.error(e);
+ }
+ }
+
+ /**
+ * Show the most suitable help for the specified component.
+ *
+ * @param c
+ */
+ public static void showHelp(Component c) {
+ showID(getHelpID(c));
+ }
+
+ /**
+ * Display the default home page help.
+ *
+ * @param e
+ */
+ public static void displayDefaultHelp(AWTEvent e) {
+ showID("home");
+ }
+
+ public static void displayFieldLevelHelp(ActionEvent e) {
+ //
+ }
+
+ private static final String HELP_KEY = "F1";
+
+ /**
+ * Associated the specified action with key presses in the specified
+ * component.
+ *
+ * @param component
+ * @param theAction
+ */
+ public static void setKeyCatcher(final JComponent component,
+ final AbstractAction theAction) {
+ InputMap oldInputMap = component
+ .getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ InputMap newInputMap = new InputMap();
+ newInputMap.setParent(oldInputMap);
+ newInputMap.put(getKeyStroke(HELP_KEY), "doSomething");
+ component.setInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, newInputMap);
+ ActionMap oldActionMap = component.getActionMap();
+ ActionMap newActionMap = new ActionMap();
+ newActionMap.setParent(oldActionMap);
+ newActionMap.put("doSomething", theAction);
+ component.setActionMap(newActionMap);
+ }
+
+ /**
+ * Set up a key-press catcher for the specified component such that when F1
+ * is pressed it should help for the component where the cursor is.
+ *
+ * @param rootpanecontainer
+ */
+ public static void setKeyCatcher(final RootPaneContainer rootpanecontainer) {
+ @SuppressWarnings("serial")
+ AbstractAction theAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ Component component = (Component) rootpanecontainer;
+ Container container = (Container) rootpanecontainer;
+ logger.info("frame action F1 pressed with source "
+ + evt.getSource().getClass().getName());
+ Point mousePosition = getPointerInfo().getLocation();
+ Point framePosition = component.getLocation();
+ Point relativePosition = (Point) mousePosition.clone();
+ relativePosition.translate(-framePosition.x, -framePosition.y);
+ Component c = container.findComponentAt(relativePosition);
+ if (c != null)
+ logger.info("F1 pressed in a " + c.getClass().getName());
+ showHelpWithinContainer(rootpanecontainer, c);
+ }
+ };
+
+ JRootPane pane = rootpanecontainer.getRootPane();
+ setKeyCatcher(pane, theAction);
+ }
+
+ /**
+ * Show the help most associated with the specific component within the container.
+ *
+ * @param root
+ * @param c
+ */
+ static void showHelpWithinContainer(RootPaneContainer root, Component c) {
+ getInstance();
+ showHelp(c);
+ }
+
+ /**
+ * Register a component with the {@link HelpCollator} under the specified
+ * id.
+ *
+ * @param component
+ * @param id
+ */
+ public static void registerComponent(Component component, final String id) {
+ HelpCollator.registerComponent(component, id);
+ }
+
+ /**
+ * Register a component with the {@link HelpCollator}.
+ *
+ * @param component
+ * @param parent
+ * @param suffix
+ */
+ public static void registerComponent(Component component, Object parent,
+ String suffix) {
+ HelpCollator.registerComponent(component, parent, suffix);
+ }
+}
diff --git a/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/NonBlockedHelpEnabledDialog.java b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/NonBlockedHelpEnabledDialog.java
new file mode 100644
index 0000000..67e6bc5
--- /dev/null
+++ b/taverna-workbench-helper-api/src/main/java/net/sf/taverna/t2/workbench/helper/NonBlockedHelpEnabledDialog.java
@@ -0,0 +1,40 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.helper;
+
+import static java.awt.Dialog.ModalExclusionType.APPLICATION_EXCLUDE;
+
+import java.awt.Dialog;
+import java.awt.Frame;
+import java.awt.HeadlessException;
+
+/**
+ * @author alanrw
+ */
+public class NonBlockedHelpEnabledDialog extends HelpEnabledDialog {
+ private static final long serialVersionUID = -2455471377333940417L;
+
+ public NonBlockedHelpEnabledDialog(Dialog owner, String title,
+ boolean modal, String id) throws HeadlessException {
+ super(owner, title, modal, id);
+ this.setModalExclusionType(APPLICATION_EXCLUDE);
+ }
+
+ public NonBlockedHelpEnabledDialog(Frame owner, String title,
+ boolean modal, String id) throws HeadlessException {
+ super(owner, title, modal, id);
+ this.setModalExclusionType(APPLICATION_EXCLUDE);
+ }
+
+ public NonBlockedHelpEnabledDialog(Frame parent, String title, boolean modal) {
+ super(parent, title, modal, null);
+ this.setModalExclusionType(APPLICATION_EXCLUDE);
+ }
+
+ public NonBlockedHelpEnabledDialog(Dialog parent, String title,
+ boolean modal) {
+ super(parent, title, modal, null);
+ this.setModalExclusionType(APPLICATION_EXCLUDE);
+ }
+}
diff --git a/taverna-workbench-helper/pom.xml b/taverna-workbench-helper/pom.xml
new file mode 100644
index 0000000..70c0621
--- /dev/null
+++ b/taverna-workbench-helper/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>helper</artifactId>
+ <name>Help System (legacy dependency)</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-httpproxy-config/pom.xml b/taverna-workbench-httpproxy-config/pom.xml
new file mode 100644
index 0000000..f1a8328
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>httpproxy-config</artifactId>
+ <packaging>bundle</packaging>
+ <name>HTTP Proxy configuration</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java
new file mode 100644
index 0000000..1229d57
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java
@@ -0,0 +1,582 @@
+/*******************************************************************************
+ * 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.httpproxy.config;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.PROXY_USE_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_NON_PROXY_HOSTS;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_HOST;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_PASSWORD;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_PORT;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_USER;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_NON_PROXY_HOSTS;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_HOST;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_PASSWORD;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_PORT;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_USER;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_NO_PROXY_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_SPECIFIED_VALUES_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_SYSTEM_PROPERTIES_OPTION;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import uk.org.taverna.configuration.proxy.HttpProxyConfiguration;
+
+/**
+ * The HttpProxyConfigurationPanel provides the user interface to a
+ * {@link HttpProxyConfiguration} to determine how HTTP Connections are made by
+ * Taverna.
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public class HttpProxyConfigurationPanel extends JPanel {
+ static final long serialVersionUID = 3668473431971125038L;
+ /**
+ * The size of the field for the JTextFields.
+ */
+ private static int TEXTFIELD_SIZE = 25;
+
+ private final HttpProxyConfiguration httpProxyConfiguration;
+ /**
+ * RadioButtons that are in a common ButtonGroup. Selecting one of them
+ * indicates whether the system http proxy settings, the ad hoc specified
+ * values or no proxy settings at all should be used.
+ */
+ private JRadioButton useSystemProperties;
+ private JRadioButton useSpecifiedValues;
+ private JRadioButton useNoProxy;
+ /**
+ * JTextFields and one DialogTextArea to hold the settings for the HTTP
+ * proxy properties. The values are only editable if the user picks
+ * useSpecifiedValues.
+ */
+ private JTextField proxyHostField;
+ private JTextField proxyPortField;
+ private JTextField proxyUserField;
+ private JTextField proxyPasswordField;
+ private DialogTextArea nonProxyHostsArea;
+ private JScrollPane nonProxyScrollPane;
+ /**
+ * A string that indicates which HTTP setting option the user has currently
+ * picked. This does not necesarily match that which has been applied.
+ */
+ private String shownOption = USE_SYSTEM_PROPERTIES_OPTION;
+
+ /**
+ * The HttpProxyConfigurationPanel consists of a set of properties where the
+ * configuration values for HTTP can be specified and a set of buttons where
+ * the more general apply, help etc. appear.
+ */
+ public HttpProxyConfigurationPanel(
+ HttpProxyConfiguration httpProxyConfiguration) {
+ this.httpProxyConfiguration = httpProxyConfiguration;
+ initComponents();
+ }
+
+ /**
+ * Populates the panel with a representation of the current HTTP proxy
+ * settings for the specified {@link HttpProxyConfiguration} and also the
+ * capability to alter them.
+ */
+ private void initComponents() {
+ shownOption = httpProxyConfiguration.getProperty(PROXY_USE_OPTION);
+
+ this.setLayout(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea("HTTP proxy configuration");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ gbc.anchor = WEST;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.fill = HORIZONTAL;
+ this.add(descriptionText, gbc);
+
+ /**
+ * Generate the three radio buttons and put them in a group. Each button
+ * is bound to an action that alters the shownOption and re-populates
+ * the shown HTTP property fields.
+ */
+ useNoProxy = new JRadioButton("Do not use a proxy");
+ useNoProxy.setAlignmentX(LEFT_ALIGNMENT);
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(useNoProxy, gbc);
+ ActionListener useNoProxyListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ shownOption = USE_NO_PROXY_OPTION;
+ populateFields();
+ }
+ };
+ useNoProxy.addActionListener(useNoProxyListener);
+
+ useSystemProperties = new JRadioButton("Use system properties");
+ useSystemProperties.setAlignmentX(LEFT_ALIGNMENT);
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ this.add(useSystemProperties, gbc);
+ ActionListener systemPropertiesListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ shownOption = USE_SYSTEM_PROPERTIES_OPTION;
+ populateFields();
+ }
+ };
+ useSystemProperties.addActionListener(systemPropertiesListener);
+
+ useSpecifiedValues = new JRadioButton("Use specified values");
+ useSpecifiedValues.setAlignmentX(LEFT_ALIGNMENT);
+ gbc.gridx = 0;
+ gbc.gridy = 3;
+ this.add(useSpecifiedValues, gbc);
+ ActionListener specifiedValuesListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ shownOption = USE_SPECIFIED_VALUES_OPTION;
+ populateFields();
+ }
+ };
+ useSpecifiedValues.addActionListener(specifiedValuesListener);
+
+ ButtonGroup bg = new ButtonGroup();
+ bg.add(useSystemProperties);
+ bg.add(useSpecifiedValues);
+ bg.add(useNoProxy);
+
+ /**
+ * Create the fields to show the HTTP proxy property values. These
+ * become editable if the shown option is to use specified values.
+ */
+ proxyHostField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 4;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(new JLabel("Proxy host"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 4;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyHostField, gbc);
+
+ proxyPortField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 5;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ this.add(new JLabel("Proxy port"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 5;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyPortField, gbc);
+
+ proxyUserField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 6;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ this.add(new JLabel("Proxy user"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 6;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyUserField, gbc);
+
+ proxyPasswordField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 7;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ this.add(new JLabel("Proxy password"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 7;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyPasswordField, gbc);
+
+ nonProxyHostsArea = new DialogTextArea(10, 40);
+ nonProxyScrollPane = new JScrollPane(nonProxyHostsArea);
+ nonProxyScrollPane
+ .setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ nonProxyScrollPane
+ .setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+ // nonProxyScrollPane.setPreferredSize(new Dimension(300, 500));
+ gbc.gridx = 0;
+ gbc.gridy = 8;
+ gbc.gridwidth = 2;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(new JLabel("Non-proxy hosts"), gbc);
+ gbc.gridx = 0;
+ gbc.gridy = 9;
+ gbc.weightx = 1.0;
+ gbc.weighty = 1.0;
+ gbc.gridwidth = 2;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ gbc.fill = BOTH;
+ this.add(nonProxyScrollPane, gbc);
+
+ // Add buttons panel
+ gbc.gridx = 0;
+ gbc.gridy = 10;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridwidth = 2;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = CENTER;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(createButtonPanel(), gbc);
+
+ setFields();
+ }
+
+ /**
+ * Populate the fields in the property panel according to which option is
+ * being shown and the stored values within the
+ * {@link HttpProxyConfiguration}.
+ */
+ private void populateFields() {
+ /**
+ * Editing of the property fields is only available when the option is
+ * to use the specified values.
+ */
+ boolean editingEnabled = shownOption
+ .equals(USE_SPECIFIED_VALUES_OPTION);
+
+ if (shownOption.equals(USE_SYSTEM_PROPERTIES_OPTION)) {
+ proxyHostField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_HOST));
+ proxyPortField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_PORT));
+ proxyUserField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_USER));
+ proxyPasswordField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_PASSWORD));
+ nonProxyHostsArea.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_NON_PROXY_HOSTS));
+ } else if (shownOption.equals(USE_SPECIFIED_VALUES_OPTION)) {
+ proxyHostField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_HOST));
+ proxyPortField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_PORT));
+ proxyUserField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_USER));
+ proxyPasswordField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_PASSWORD));
+ nonProxyHostsArea.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_NON_PROXY_HOSTS));
+ } else {
+ proxyHostField.setText(null);
+ proxyPortField.setText(null);
+ proxyUserField.setText(null);
+ proxyPasswordField.setText(null);
+ nonProxyHostsArea.setText(null);
+ }
+
+ proxyHostField.setEnabled(editingEnabled);
+ proxyPortField.setEnabled(editingEnabled);
+ proxyUserField.setEnabled(editingEnabled);
+ proxyPasswordField.setEnabled(editingEnabled);
+ nonProxyHostsArea.setEnabled(editingEnabled);
+ nonProxyHostsArea.setEditable(editingEnabled);
+ nonProxyScrollPane.setEnabled(editingEnabled);
+ }
+
+ /**
+ * Create the panel to contain the buttons
+ *
+ * @return
+ */
+ @SuppressWarnings("serial")
+ private JPanel createButtonPanel() {
+ final JPanel panel = new JPanel();
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ setFields();
+ }
+ });
+ panel.add(resetButton);
+
+ /**
+ * The applyButton applies the shown field values to the
+ * {@link HttpProxyConfiguration} and saves them for future.
+ */
+ JButton applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ applySettings();
+ setFields();
+ }
+ });
+ panel.add(applyButton);
+
+ return panel;
+ }
+
+ /**
+ * Checks that the specified values for the HTTP properties are a valid
+ * combination and, if so, saves them for future use. It does not apply them
+ * to the currently executing Taverna.
+ */
+ private void saveSettings() {
+ if (useSystemProperties.isSelected()) {
+ httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+ USE_SYSTEM_PROPERTIES_OPTION);
+ } else if (useNoProxy.isSelected()) {
+ httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+ USE_NO_PROXY_OPTION);
+ } else {
+ if (validateFields()) {
+ httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+ USE_SPECIFIED_VALUES_OPTION);
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_HOST,
+ proxyHostField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_PORT,
+ proxyPortField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_USER,
+ proxyUserField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_PASSWORD,
+ proxyPasswordField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_NON_PROXY_HOSTS,
+ nonProxyHostsArea.getText());
+ }
+ }
+ }
+
+ /**
+ * Validates and, where appropriate formats, the properties values specified
+ * for HTTP Proxy configuration.
+ *
+ * @return
+ */
+ private boolean validateFields() {
+ boolean result = true;
+ result = result && validateHostField();
+ result = result && validatePortField();
+ result = result && validateUserField();
+ result = result && validatePasswordField();
+ result = result && validateNonProxyHostsArea();
+ return result;
+ }
+
+ /**
+ * Checks that, if a value is specified for non-proxy hosts then a proxy
+ * host has also been specified. Formats the non-proxy hosts string so that
+ * if the user has entered the hosts on separate lines, then the stored
+ * values are separated by bars.
+ *
+ * @return
+ */
+ private boolean validateNonProxyHostsArea() {
+ boolean result = true;
+ String value = nonProxyHostsArea.getText();
+ if ((value != null) && (!value.equals(""))) {
+ value = value.replaceAll("\\n", "|");
+ nonProxyHostsArea.setText(value);
+ result = result
+ && dependsUpon("non-proxy host", "host",
+ proxyHostField.getText());
+ }
+ return result;
+ }
+
+ /**
+ * Checks that, if a password has been specified, then a user has also been
+ * specified.
+ *
+ * @return
+ */
+ private boolean validatePasswordField() {
+ boolean result = true;
+ String value = proxyPasswordField.getText();
+ if ((value != null) && !value.isEmpty())
+ result = result
+ && dependsUpon("password", "user", proxyHostField.getText());
+ return result;
+ }
+
+ /**
+ * Checks that if a user has been specified, then a host has also been
+ * specified.
+ *
+ * @return
+ */
+ private boolean validateUserField() {
+ boolean result = true;
+ String value = proxyUserField.getText();
+ if ((value != null) && !value.isEmpty())
+ result = result
+ && dependsUpon("user", "host", proxyHostField.getText());
+ return result;
+ }
+
+ /**
+ * Checks that if a port has been specified then a host has also been
+ * specified. Checks that the port number is a non-negative integer. If the
+ * port has not been specified, then if a host has been specified, the
+ * default value 80 is used.
+ *
+ * @return
+ */
+ private boolean validatePortField() {
+ boolean result = true;
+ String value = proxyPortField.getText();
+ if ((value != null) && (!value.equals(""))) {
+ result = result
+ && dependsUpon("port", "host", proxyHostField.getText());
+ try {
+ int parsedNumber = Integer.parseInt(value);
+ if (parsedNumber <= 0) {
+ showMessageDialog(this, "The port must be non-negative");
+ result = false;
+ }
+ } catch (NumberFormatException e) {
+ showMessageDialog(this, "The port must be an integer");
+ result = false;
+ }
+ } else {
+ String hostField = proxyHostField.getText();
+ if ((hostField != null) && !hostField.isEmpty())
+ proxyPortField.setText("80");
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the targetValue has been specified. If not then a message is
+ * displayed indicating that the dependent cannot be specified with the
+ * target.
+ *
+ * @param dependent
+ * @param target
+ * @param targetValue
+ * @return
+ */
+ private boolean dependsUpon(String dependent, String target,
+ String targetValue) {
+ boolean result = true;
+ if ((targetValue == null) || target.equals("")) {
+ showMessageDialog(this, "A " + dependent
+ + " cannot be specified without a " + target);
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Could validate the host field e.g. by establishing a connection.
+ * Currently no validation is done.
+ *
+ * @return
+ */
+ private boolean validateHostField() {
+ boolean result = true;
+ // String value = proxyHostField.getText();
+ return result;
+ }
+
+ /**
+ * Save the currently set field values (if valid) to the
+ * {@link HttpProxyConfiguration}. Also applies those values to the
+ * currently running Taverna.
+ */
+ private void applySettings() {
+ if (validateFields()) {
+ saveSettings();
+ httpProxyConfiguration.changeProxySettings();
+ }
+ }
+
+ /**
+ * Set the shown field values to those currently in use (i.e. last saved
+ * configuration).
+ */
+ private void setFields() {
+ shownOption = httpProxyConfiguration.getProperty(PROXY_USE_OPTION);
+ useSystemProperties.setSelected(shownOption
+ .equals(USE_SYSTEM_PROPERTIES_OPTION));
+ useSpecifiedValues.setSelected(shownOption
+ .equals(USE_SPECIFIED_VALUES_OPTION));
+ useNoProxy.setSelected(shownOption.equals(USE_NO_PROXY_OPTION));
+ populateFields();
+ }
+}
diff --git a/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java
new file mode 100644
index 0000000..9f6ac8c
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.httpproxy.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+import uk.org.taverna.configuration.proxy.HttpProxyConfiguration;
+
+/**
+ * A Factory to create a HttpProxyConfiguration
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public class HttpProxyConfigurationUIFactory implements ConfigurationUIFactory {
+ private HttpProxyConfiguration httpProxyConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new HttpProxyConfigurationPanel(httpProxyConfiguration);
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return httpProxyConfiguration;
+ }
+
+ public void setHttpProxyConfiguration(HttpProxyConfiguration httpProxyConfiguration) {
+ this.httpProxyConfiguration = httpProxyConfiguration;
+ }
+}
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..d87772b
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.httpproxy.config.HttpProxyConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml
new file mode 100644
index 0000000..631bdb4
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="HttpProxyConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <reference id="httpProxyConfiguration" interface="uk.org.taverna.configuration.proxy.HttpProxyConfiguration" />
+
+</beans:beans>
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml
new file mode 100644
index 0000000..6d6060f
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="HttpProxyConfigurationUIFactory" class="net.sf.taverna.t2.workbench.httpproxy.config.HttpProxyConfigurationUIFactory">
+ <property name="httpProxyConfiguration" ref="httpProxyConfiguration" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-iteration-strategy-ui/pom.xml b/taverna-workbench-iteration-strategy-ui/pom.xml
new file mode 100644
index 0000000..0b6ff81
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>iteration-strategy-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Menu generation API</name>
+ <description>An SPI system for building UI menues</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-api</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-impl</artifactId>
+ <version>${t2.core.version}</version>
+ <!-- <scope>test</scope> -->
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/IterationStrategyIcons.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/IterationStrategyIcons.java
new file mode 100644
index 0000000..350c0cc
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/IterationStrategyIcons.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.iterationstrategy;
+
+import javax.swing.ImageIcon;
+
+import org.apache.log4j.Logger;
+
+public class IterationStrategyIcons {
+
+ private static Logger logger = Logger
+ .getLogger(IterationStrategyIcons.class);
+
+ public static ImageIcon joinIteratorIcon, lockStepIteratorIcon,
+ leafnodeicon;
+
+ static {
+ try {
+ Class<?> c = IterationStrategyIcons.class;
+ joinIteratorIcon = new ImageIcon(c
+ .getResource("icons/crossproducticon.png"));
+ lockStepIteratorIcon = new ImageIcon(c
+ .getResource("icons/dotproducticon.png"));
+ leafnodeicon = new ImageIcon(c
+ .getResource("icons/leafnodeicon.png"));
+ } catch (Exception ex) {
+ logger.warn("Could not find icon", ex);
+ }
+ }
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyConfigurationDialog.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyConfigurationDialog.java
new file mode 100644
index 0000000..1af83cb
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyConfigurationDialog.java
@@ -0,0 +1,148 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.iterationstrategy.contextview;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.iterationstrategy.editor.IterationStrategyEditorControl;
+import net.sf.taverna.t2.workflowmodel.Edit;
+import net.sf.taverna.t2.workflowmodel.EditException;
+import net.sf.taverna.t2.workflowmodel.Edits;
+import net.sf.taverna.t2.workflowmodel.Processor;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategy;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategyStack;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author alanrw
+ *
+ */
+@SuppressWarnings("serial")
+public class IterationStrategyConfigurationDialog extends HelpEnabledDialog {
+
+ private static Logger logger = Logger
+ .getLogger(IterationStrategyConfigurationDialog.class);
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+
+ private final Frame owner;
+ private final Processor processor;
+ private final IterationStrategyStack originalStack;
+
+ private IterationStrategyStack workingStack;
+
+ public IterationStrategyConfigurationDialog(Frame owner, Processor processor, IterationStrategyStack iStack, EditManager editManager, FileManager fileManager) {
+ super (owner, "List handling for " + processor.getLocalName(), true, null);
+ this.owner = owner;
+ this.processor = processor;
+ this.originalStack = iStack;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.workingStack = IterationStrategyContextualView.copyIterationStrategyStack(originalStack);
+ IterationStrategy iterationStrategy = IterationStrategyContextualView.getIterationStrategy(workingStack);
+ IterationStrategyEditorControl iterationStrategyEditorControl = new IterationStrategyEditorControl(
+ iterationStrategy);
+ this.add(iterationStrategyEditorControl, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout());
+
+ JButton okButton = new JButton(new OKAction(this));
+ buttonPanel.add(okButton);
+
+ JButton resetButton = new JButton(new ResetAction(
+ iterationStrategyEditorControl));
+ buttonPanel.add(resetButton);
+
+ JButton cancelButton = new JButton(new CancelAction(this));
+ buttonPanel.add(cancelButton);
+
+ this.add(buttonPanel, BorderLayout.SOUTH);
+ this.pack();
+ this.setSize(new Dimension(getPreferredSize().width, getPreferredSize().height > 400 ? 400 : getPreferredSize().height));
+ }
+
+ private final class OKAction extends AbstractAction {
+ private final JDialog dialog;
+
+ private OKAction(JDialog dialog) {
+ super("OK");
+ this.dialog = dialog;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ Edits edits = editManager.getEdits();
+ try {
+ Edit<?> edit = edits.getSetIterationStrategyStackEdit(
+ processor,
+ IterationStrategyContextualView.copyIterationStrategyStack(workingStack));
+ editManager.doDataflowEdit(
+ fileManager.getCurrentDataflow(), edit);
+ dialog.setVisible(false);
+ } catch (RuntimeException ex) {
+ logger.warn("Could not set list handling", ex);
+ JOptionPane.showMessageDialog(owner,
+ "Can't set list handling",
+ "An error occured when setting list handling: "
+ + ex.getMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (EditException ex) {
+ logger.warn("Could not set list handling", ex);
+ JOptionPane.showMessageDialog(owner,
+ "Can't set list handling",
+ "An error occured when setting list handling: "
+ + ex.getMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ private final class ResetAction extends AbstractAction {
+ private final IterationStrategyEditorControl strategyEditorControl;
+
+ private ResetAction(
+ IterationStrategyEditorControl strategyEditorControl) {
+ super("Reset");
+ this.strategyEditorControl = strategyEditorControl;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ workingStack = IterationStrategyContextualView.copyIterationStrategyStack(originalStack);
+ strategyEditorControl
+ .setIterationStrategy(IterationStrategyContextualView.getIterationStrategy(workingStack));
+ }
+
+ }
+
+ private final class CancelAction extends AbstractAction {
+ private final JDialog dialog;
+
+ private CancelAction(JDialog dialog) {
+ super("Cancel");
+ this.dialog = dialog;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(false);
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyContextualView.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyContextualView.java
new file mode 100644
index 0000000..369bea4
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyContextualView.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * 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.iterationstrategy.contextview;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.iterationstrategy.editor.IterationStrategyTree;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Processor;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategy;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategyStack;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.impl.IterationStrategyImpl;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.impl.IterationStrategyStackImpl;
+
+import org.apache.log4j.Logger;
+import org.jdom.Content;
+import org.jdom.Element;
+import org.jdom.filter.ElementFilter;
+
+/**
+ * Contextual view of an {@link IterationStrategyStack}.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class IterationStrategyContextualView extends ContextualView {
+
+ private static Logger logger = Logger
+ .getLogger(IterationStrategyContextualView.class);
+
+ private EditManager editManager;
+
+ private FileManager fileManager;
+
+ private IterationStrategyStack iterationStack;
+
+ private final Processor processor;
+
+ private IterationStrategyTree strategyTree = new IterationStrategyTree();
+
+ static {
+
+// This should be enabled and modified for T2-822
+/* editManager.addObserver(new Observer<EditManagerEvent> () {
+
+ private void examineEdit(Edit edit) {
+ if (edit instanceof ConnectDatalinkEdit) {
+ processConnectDatalinkEdit((ConnectDatalinkEdit) edit);
+ }
+ if (edit instanceof CompoundEdit) {
+ processCompoundEdit((CompoundEdit) edit);
+ }
+ }
+
+ private void processConnectDatalinkEdit(ConnectDatalinkEdit edit) {
+ Datalink d = ((ConnectDatalinkEdit) edit).getSubject();
+ EventHandlingInputPort sink = d.getSink();
+ if (sink instanceof ProcessorInputPort) {
+ ProcessorInputPort pip = (ProcessorInputPort) sink;
+ Processor p = pip.getProcessor();
+ final HelpEnabledDialog dialog = new IterationStrategyConfigurationDialog(null, p, copyIterationStrategyStack(p.getIterationStrategy()));
+ dialog.setVisible(true);
+ }
+ }
+
+ private void processCompoundEdit(CompoundEdit edit) {
+ for (Edit e : edit.getChildEdits()) {
+ examineEdit(e);
+ }
+ }
+
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (!(message instanceof DataflowEditEvent)) {
+ return;
+ }
+ examineEdit(message.getEdit());
+ }});*/
+ }
+
+ public IterationStrategyContextualView(Processor processor, EditManager editManager, FileManager fileManager) {
+ if (processor == null || processor.getIterationStrategy() == null) {
+ throw new NullPointerException(
+ "Iteration strategy stack can't be null");
+ }
+ this.processor = processor;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ refreshIterationStrategyStack();
+ initView();
+ }
+
+ @Override
+ public Action getConfigureAction(final Frame owner) {
+ return new ConfigureIterationStrategyAction(owner);
+ }
+
+ public Processor getProcessor() {
+ return processor;
+ }
+
+ @Override
+ public void refreshView() {
+ refreshIterationStrategyStack();
+ strategyTree.setIterationStrategy(getIterationStrategy(iterationStack));
+ }
+
+ public static IterationStrategyStack copyIterationStrategyStack(
+ IterationStrategyStack stack) {
+ Element asXML = ((IterationStrategyStackImpl)stack).asXML();
+ stripEmptyElements(asXML);
+ IterationStrategyStackImpl copyStack = new IterationStrategyStackImpl();
+ copyStack.configureFromElement(asXML);
+ if (copyStack.getStrategies().isEmpty()) {
+ copyStack.addStrategy(new IterationStrategyImpl());
+ }
+ return copyStack;
+ }
+
+ private static void stripEmptyElements(Element asXML) {
+ int childCount = asXML.getContent().size();
+ int index = 0;
+ while (index < childCount) {
+ Content child = asXML.getContent(index);
+ if (child instanceof Element) {
+ Element childElement = (Element) child;
+ if (childElement.getName().equals("port")) {
+ index++;
+ }
+ else if (childElement.getDescendants(new ElementFilter("port")).hasNext()) {
+ stripEmptyElements(childElement);
+ index++;
+ } else {
+ asXML.removeContent(childElement);
+ childCount--;
+ }
+ }
+ }
+ }
+
+ public static IterationStrategy getIterationStrategy(IterationStrategyStack iStack) {
+ List<? extends IterationStrategy> strategies = iStack
+ .getStrategies();
+ if (strategies.isEmpty()) {
+ throw new IllegalStateException("Empty iteration stack");
+ }
+ IterationStrategy strategy = strategies.get(0);
+ if (!(strategy instanceof IterationStrategyImpl)) {
+ throw new IllegalStateException(
+ "Can't edit unknown iteration strategy implementation "
+ + strategy);
+ }
+ return (IterationStrategyImpl) strategy;
+ }
+
+ private void refreshIterationStrategyStack() {
+ IterationStrategyStack originalIterationStrategy = processor
+ .getIterationStrategy();
+ if (!(originalIterationStrategy instanceof IterationStrategyStackImpl)) {
+ throw new IllegalStateException(
+ "Unknown iteration strategy implementation "
+ + originalIterationStrategy);
+ }
+ this.iterationStack = copyIterationStrategyStack((IterationStrategyStackImpl) originalIterationStrategy);
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return strategyTree;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "List handling";
+ }
+
+ private final class ConfigureIterationStrategyAction extends AbstractAction {
+ private final Frame owner;
+
+ private ConfigureIterationStrategyAction(Frame owner) {
+ super("Configure");
+ this.owner = owner;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final HelpEnabledDialog dialog = new IterationStrategyConfigurationDialog(owner, processor, iterationStack, editManager, fileManager);
+ dialog.setVisible(true);
+ refreshView();
+ }
+
+
+
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 200;
+ }
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyContextualViewFactory.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyContextualViewFactory.java
new file mode 100644
index 0000000..a970239
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/contextview/IterationStrategyContextualViewFactory.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.iterationstrategy.contextview;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+public class IterationStrategyContextualViewFactory implements
+ ContextualViewFactory<Processor> {
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+ public boolean canHandle(Object selection) {
+ return selection instanceof Processor;
+ }
+
+ public List<ContextualView> getViews(Processor p) {
+ return Arrays.asList(new ContextualView[] {new IterationStrategyContextualView(p, editManager, fileManager)});
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyCellRenderer.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyCellRenderer.java
new file mode 100644
index 0000000..4c31574
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyCellRenderer.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.iterationstrategy.editor;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.iterationstrategy.IterationStrategyIcons;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.CrossProduct;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.DotProduct;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.NamedInputPortNode;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+final class IterationStrategyCellRenderer extends DefaultTreeCellRenderer {
+
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger
+ .getLogger(IterationStrategyCellRenderer.class);
+
+ @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 CrossProduct) {
+ setIcon(IterationStrategyIcons.joinIteratorIcon);
+ setText("Cross product");
+ } else if (value instanceof DotProduct) {
+ setIcon(IterationStrategyIcons.lockStepIteratorIcon);
+ setText("Dot product");
+ } else if (value instanceof NamedInputPortNode) {
+ setIcon(IterationStrategyIcons.leafnodeicon);
+ NamedInputPortNode namedInput = (NamedInputPortNode) value;
+ setText(namedInput.getPortName());
+ } else {
+ setText("List handling");
+ if (!leaf){
+ if (expanded) {
+ setIcon(WorkbenchIcons.folderOpenIcon);
+ } else {
+ setIcon(WorkbenchIcons.folderClosedIcon);
+ }
+ }
+ }
+ return this;
+ }
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyEditor.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyEditor.java
new file mode 100644
index 0000000..add5201
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyEditor.java
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * 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.iterationstrategy.editor;
+
+import java.awt.GraphicsEnvironment;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+//import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import javax.swing.DropMode;
+import javax.swing.JComponent;
+import javax.swing.JTree;
+import javax.swing.TransferHandler;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.AbstractIterationStrategyNode;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategy;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.NamedInputPortNode;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.TerminalNode;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class IterationStrategyEditor extends IterationStrategyTree implements
+ UIComponentSPI {
+
+ private static Logger logger = Logger
+ .getLogger(IterationStrategyEditor.class);
+
+ //private BufferedImage imgGhost; // The 'drag image'
+
+ // mouse was clicked
+
+ public IterationStrategyEditor() {
+ super();
+ // Make this a drag source
+ if (!GraphicsEnvironment.isHeadless()) {
+ this.setDragEnabled(true);
+ this.setDropMode(DropMode.ON_OR_INSERT);
+ this.setTransferHandler(new TreeTransferHandler());
+ this.getSelectionModel().setSelectionMode(
+ TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
+ expandTree();
+ }
+
+ //
+ }
+
+ public IterationStrategyEditor(IterationStrategy theStrategy) {
+ this();
+ setIterationStrategy(theStrategy);
+ }
+
+ /**
+ *
+ * This code is freely adapted from code derived
+ *
+ */
+ class TreeTransferHandler extends TransferHandler {
+ DataFlavor nodesFlavor;
+ DataFlavor[] flavors = new DataFlavor[1];
+
+ public TreeTransferHandler() {
+ getNodesFlavor();
+ }
+
+ private DataFlavor getNodesFlavor() {
+ if (nodesFlavor == null) {
+ try {
+ nodesFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType +
+ ";class=" + AbstractIterationStrategyNode.class.getName(),
+ "AbstractIterationStrategyNode",
+ this.getClass().getClassLoader());
+ flavors[0] = nodesFlavor;
+ } catch(Exception e) {
+ logger.error("Problem creating nodesFlavor:" + e);
+ }
+ }
+ return nodesFlavor;
+ }
+
+ public boolean canImport(TransferHandler.TransferSupport support) {
+ if(!support.isDrop()) {
+ logger.error("isDrop not supported");
+ return false;
+ }
+
+ if(!support.isDataFlavorSupported(getNodesFlavor())) {
+ logger.info("Not correct flavor");
+ return false;
+ }
+ // Do not allow a drop on the drag source selections.
+ JTree.DropLocation dl =
+ (JTree.DropLocation)support.getDropLocation();
+ TreePath dest = dl.getPath();
+ AbstractIterationStrategyNode destination =
+ (AbstractIterationStrategyNode)dest.getLastPathComponent();
+ Transferable t = support.getTransferable();
+ if (destination instanceof TerminalNode) {
+ return false;
+ }
+ try {
+ AbstractIterationStrategyNode node = (AbstractIterationStrategyNode) t.getTransferData(getNodesFlavor());
+ if (node.isNodeDescendant(destination)) {
+ return false;
+ }
+ } catch (UnsupportedFlavorException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ }
+// JTree tree = (JTree) support.getComponent();
+// int dropRow = tree.getRowForPath(dl.getPath());
+// int selRow = tree.getLeadSelectionRow();
+// if (selRow == dropRow) {
+// logger.info("Dragging to source");
+// return false;
+//
+// }
+ support.setShowDropLocation(true);
+ return true;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ JTree tree = (JTree)c;
+ TreePath[] paths = tree.getSelectionPaths();
+ if(paths != null) {
+ AbstractIterationStrategyNode node =
+ (AbstractIterationStrategyNode)paths[0].getLastPathComponent();
+ return new NodeTransferable(node);
+ }
+ return null;
+ }
+
+ protected void exportDone(JComponent source, Transferable data, int action) {
+ }
+
+ public int getSourceActions(JComponent c) {
+ return MOVE;
+ }
+
+ public boolean importData(TransferHandler.TransferSupport support) {
+ if(!canImport(support)) {
+ logger.info("Cannot import");
+ return false;
+ }
+ // Extract transfer data.
+ AbstractIterationStrategyNode node = null;
+ try {
+ Transferable t = support.getTransferable();
+ node = (AbstractIterationStrategyNode) t.getTransferData(getNodesFlavor());
+ } catch(UnsupportedFlavorException ufe) {
+ logger.error("UnsupportedFlavor", ufe);
+ } catch (Exception e) {
+ logger.error("Problem getting transfer data", e);
+ }
+
+ // Get drop location info.
+ JTree.DropLocation dl =
+ (JTree.DropLocation)support.getDropLocation();
+ int childIndex = dl.getChildIndex();
+ TreePath dest = dl.getPath();
+ AbstractIterationStrategyNode parent =
+ (AbstractIterationStrategyNode)dest.getLastPathComponent();
+ int index = childIndex;
+ logger.info ("parent is a " + parent.getClass().getName());
+ if (parent instanceof NamedInputPortNode) {
+ AbstractIterationStrategyNode sibling = parent;
+ parent = (AbstractIterationStrategyNode) sibling.getParent();
+ index = parent.getIndex(sibling);
+ } else if (index == -1) {
+ index = parent.getChildCount();
+ }
+ if (parent instanceof TerminalNode) {
+ if (parent.getChildCount() > 0) {
+ parent = (AbstractIterationStrategyNode) parent.getChildAt(0);
+ index = parent.getChildCount();
+ }
+
+ }
+ logger.info("parent is a " + parent.getClass().getName());
+
+ try {
+ // The parent insert removes from the oldParent
+ parent.insert(node, index++);
+ DefaultTreeModel model = IterationStrategyEditor.this
+ .getModel();
+ refreshModel();
+ } catch (IllegalStateException e) {
+ logger.error(e);
+ } catch (IllegalArgumentException e) {
+ logger.error(e);
+ }
+ return true;
+ }
+
+ public String toString() {
+ return getClass().getName();
+ }
+
+ public class NodeTransferable implements Transferable {
+ AbstractIterationStrategyNode node;
+
+ public NodeTransferable(AbstractIterationStrategyNode node) {
+ this.node = node;
+ }
+
+ public AbstractIterationStrategyNode getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException {
+ if(!isDataFlavorSupported(flavor))
+ throw new UnsupportedFlavorException(flavor);
+ return node;
+ }
+
+ public DataFlavor[] getTransferDataFlavors() {
+ return flavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return getNodesFlavor().equals(flavor);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyEditorControl.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyEditorControl.java
new file mode 100644
index 0000000..c745283
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyEditorControl.java
@@ -0,0 +1,439 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/**
+ * This file is a component of the Taverna project,
+ * and is licensed under the GNU LGPL.
+ * Copyright Tom Oinn, EMBL-EBI
+ */
+package net.sf.taverna.t2.workbench.iterationstrategy.editor;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.SwingConstants;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.iterationstrategy.IterationStrategyIcons;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.CrossProduct;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.DotProduct;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategy;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategyNode;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.TerminalNode;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A control panel for the iteration tree editor allowing the user to manipulate
+ * the tree, removing and adding nodes into the tree based on the context.
+ *
+ * @author Tom Oinn
+ * @author Stian Soiland-Reyes
+ *
+ */
+@SuppressWarnings("serial")
+public class IterationStrategyEditorControl extends JPanel {
+
+ protected static Set<IterationStrategyNode> descendentsOfNode(
+ IterationStrategyNode node) {
+ Set<IterationStrategyNode> descendants = new HashSet<IterationStrategyNode>();
+ Set<IterationStrategyNode> nodesToVisit = new HashSet<IterationStrategyNode>();
+ Set<IterationStrategyNode> visitedNodes = new HashSet<IterationStrategyNode>();
+
+ // Note: Not added to descendants
+ nodesToVisit.add(node);
+ while (!nodesToVisit.isEmpty()) {
+ // pick the first one
+ IterationStrategyNode visiting = nodesToVisit.iterator().next();
+ visitedNodes.add(visiting);
+ nodesToVisit.remove(visiting);
+
+ // Find new and interesting children
+ List<IterationStrategyNode> children = visiting.getChildren();
+ Set<IterationStrategyNode> newNodes = new HashSet<IterationStrategyNode>(
+ children);
+ newNodes.removeAll(visitedNodes);
+
+ descendants.addAll(newNodes);
+ nodesToVisit.addAll(newNodes);
+ }
+ return descendants;
+ }
+
+ private static Logger logger = Logger
+ .getLogger(IterationStrategyEditorControl.class);
+
+ private IterationStrategyNode selectedNode = null;
+
+ private IterationStrategyTree tree;
+
+ protected AddCrossAction addCross = new AddCrossAction();
+ protected AddDotAction addDot = new AddDotAction();
+ protected ChangeAction change = new ChangeAction();
+ protected NormalizeAction normalize = new NormalizeAction();
+ protected RemoveAction remove = new RemoveAction();
+ protected MoveUpAction moveUp = new MoveUpAction();
+
+ //private static final int ICON_SIZE = 15;
+
+ protected ImageIcon arrowUpIcon = WorkbenchIcons.upArrowIcon;
+ protected ImageIcon arrowDownIcon = WorkbenchIcons.downArrowIcon;
+ //protected ImageIcon arrowLeft = WorkbenchIcons.leftArrowIcon;
+ //protected ImageIcon arrowRight = WorkbenchIcons.rightArrowIcon;
+ protected ImageIcon normalizeIcon = WorkbenchIcons.normalizeIcon;
+
+ private final IterationStrategy strategy;
+
+ /**
+ * Create a new panel from the supplied iteration strategy
+ */
+ public IterationStrategyEditorControl(IterationStrategy strategy) {
+
+ this.strategy = strategy;
+ setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+
+ // Create the components
+ tree = new IterationStrategyEditor(strategy);
+
+ JButton addCrossButton = new JButton(addCross);
+ addCrossButton.setHorizontalAlignment(SwingConstants.LEFT);
+ JButton addDotButton = new JButton(addDot);
+ addDotButton.setHorizontalAlignment(SwingConstants.LEFT);
+ JButton normalizeButton = new JButton(normalize);
+ normalizeButton.setHorizontalAlignment(SwingConstants.LEFT);
+ normalizeButton.setIcon(normalizeIcon);
+ JButton removeButton = new JButton(remove);
+ removeButton.setHorizontalAlignment(SwingConstants.LEFT);
+ JButton changeButton = new JButton(change);
+ changeButton.setHorizontalAlignment(SwingConstants.LEFT);
+
+ JButton moveUpButton = new JButton(moveUp);
+ moveUpButton.setIcon(arrowUpIcon);
+ moveUpButton.setHorizontalAlignment(SwingConstants.LEFT);
+
+ // Set the default enabled state to off on all buttons other than the
+ // normalizeButton
+ // one.
+ disableButtons();
+
+ // Create a layout with the tree on the right and the buttons in a grid
+ // layout on the left
+ JToolBar toolbar = new JToolBar();
+ toolbar.setFloatable(false);
+ toolbar.setRollover(true);
+ // toolbar.setLayout(new GridLayout(2,2));
+ toolbar.add(normalizeButton);
+ toolbar.add(addCrossButton);
+ toolbar.add(addDotButton);
+ toolbar.add(removeButton);
+ toolbar.add(changeButton);
+ toolbar.add(moveUpButton);
+
+ toolbar.setAlignmentX(LEFT_ALIGNMENT);
+
+ // Listen to tree selection events and enable buttons appropriately
+ tree.addTreeSelectionListener(new ButtonEnabler());
+
+ // Add components to the control panel
+ add(toolbar);
+ JScrollPane treePane = new JScrollPane(tree);
+ //treePane.setPreferredSize(new Dimension(0, 0));
+ add(treePane);
+ }
+
+ public void setIterationStrategy(IterationStrategy iterationStrategy) {
+ tree.setIterationStrategy(iterationStrategy);
+ disableButtons();
+ selectNode(null);
+ }
+
+ private void disableButtons() {
+ remove.setEnabled(false);
+ addCross.setEnabled(false);
+ addDot.setEnabled(false);
+ change.setEnabled(false);
+ }
+
+ private IterationStrategyNode findRoot() {
+ IterationStrategyNode root = (IterationStrategyNode) tree.getModel()
+ .getRoot();
+ if (root.getChildCount() > 0) {
+ return root.getChildAt(0);
+ }
+ return root;
+ }
+
+ protected void selectNode(TreeNode newNode) {
+ DefaultTreeModel model = tree.getModel();
+ if (newNode == null) {
+ newNode = (TreeNode) model.getRoot();
+ }
+ TreeNode[] pathToRoot = model.getPathToRoot(newNode);
+ tree.setSelectionPath(new TreePath(pathToRoot));
+ }
+
+ /**
+ * Add a cross product node as a child of the selected node
+ */
+ protected class AddCrossAction extends AbstractAction {
+
+ public AddCrossAction() {
+ super("Add Cross", IterationStrategyIcons.joinIteratorIcon);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ CrossProduct newNode = new CrossProduct();
+ newNode.setParent(selectedNode);
+ tree.refreshModel();
+ }
+ }
+
+ /**
+ * Add a dot product node as a child of the selected node
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ protected class AddDotAction extends AbstractAction {
+
+ public AddDotAction() {
+ super("Add Dot", IterationStrategyIcons.lockStepIteratorIcon);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ DotProduct newNode = new DotProduct();
+ newNode.setParent(selectedNode);
+ tree.refreshModel();
+ }
+ }
+
+ protected class ButtonEnabler implements TreeSelectionListener {
+ public void valueChanged(TreeSelectionEvent e) {
+ TreePath selectedPath = e.getPath();
+ IterationStrategyNode selectedObject = (IterationStrategyNode) selectedPath
+ .getLastPathComponent();
+ selectedNode = selectedObject;
+ if (selectedObject instanceof CrossProduct
+ || selectedObject instanceof DotProduct) {
+ if ((selectedObject.getParent() == null) || (selectedObject.getParent() instanceof TerminalNode)) {
+ remove.setEnabled(false);
+ } else {
+ remove.setEnabled(true);
+ }
+ if (selectedObject instanceof CrossProduct) {
+ change.putValue(Action.NAME, "Change to Dot Product");
+ change.putValue(Action.SMALL_ICON,
+ IterationStrategyIcons.lockStepIteratorIcon);
+ } else {
+ change.putValue(Action.NAME, "Change to Cross Product");
+ change.putValue(Action.SMALL_ICON,
+ IterationStrategyIcons.joinIteratorIcon);
+ }
+ addCross.setEnabled(true);
+ addDot.setEnabled(true);
+ change.setEnabled(true);
+ } else {
+ // Top- or leaf node
+ remove.setEnabled(false);
+ addCross.setEnabled(false);
+ addDot.setEnabled(false);
+ change.setEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * Add a cross product node as a child of the selected node
+ */
+ protected class ChangeAction extends AbstractAction {
+
+ public ChangeAction() {
+ super("Switch to...", IterationStrategyIcons.joinIteratorIcon);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ IterationStrategyNode newNode;
+ if (selectedNode instanceof CrossProduct) {
+ newNode = new DotProduct();
+ } else {
+ newNode = new CrossProduct();
+ }
+
+ List<IterationStrategyNode> children = new ArrayList<IterationStrategyNode>(
+ selectedNode.getChildren());
+ for (IterationStrategyNode child : children) {
+ child.setParent(newNode);
+ }
+
+ DefaultTreeModel model = tree.getModel();
+ if (selectedNode.getParent() == null) {
+ model.setRoot(newNode);
+ tree.refreshModel();
+ newNode.setParent(null);
+ } else {
+ IterationStrategyNode parent = selectedNode.getParent();
+ int index = parent.getIndex(selectedNode);
+ selectedNode.setParent(null);
+ parent.insert(newNode, index);
+ tree.refreshModel();
+ }
+
+ selectNode(newNode);
+ }
+
+ }
+
+ /**
+ * Normalize the tree when the button is pressed
+ *
+ */
+ protected class NormalizeAction extends AbstractAction {
+ public NormalizeAction() {
+ super("Normalize", normalizeIcon);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ strategy.normalize();
+ // Expand all the nodes in the tree
+ //DefaultTreeModel model = tree.getModel();
+ tree.refreshModel();
+ }
+ }
+
+ /**
+ * Remove the selected node, moving any descendant leaf nodes to the parent
+ * to prevent them getting lost
+ */
+ protected class RemoveAction extends AbstractAction {
+ public RemoveAction() {
+ super("Remove node", WorkbenchIcons.deleteIcon);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ IterationStrategyNode nodeToBeRemoved = selectedNode;
+
+ //DefaultTreeModel model = tree.getModel();
+
+ // Now removeButton the candidate nodes from their parents and
+ // put them back into the root node
+ IterationStrategyNode root = findRoot();
+ if (root == selectedNode) {
+ return;
+ }
+ IterationStrategyNode oldParent = nodeToBeRemoved.getParent();
+
+ for (IterationStrategyNode nodeToMove : descendentsOfNode(nodeToBeRemoved)) {
+ nodeToMove.setParent(oldParent);
+ }
+ nodeToBeRemoved.setParent(null);
+ tree.refreshModel();
+ // Disable the various buttons, as the current selection
+ // is now invalid.
+ remove.setEnabled(false);
+ addCross.setEnabled(false);
+ addDot.setEnabled(false);
+ change.setEnabled(false);
+ selectNode(oldParent);
+ }
+ }
+
+ protected class MoveUpAction extends AbstractAction {
+
+ public MoveUpAction() {
+ super("Move up", arrowUpIcon);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ //DefaultTreeModel model = tree.getModel();
+
+ IterationStrategyNode aboveNode = aboveSelectedNode();
+ if ((aboveNode == null) || ((aboveNode instanceof TerminalNode) && (aboveNode.getChildCount() > 0))) {
+ logger.warn("Can't move above top");
+ return;
+ }
+ IterationStrategyNode selectedParent = selectedNode.getParent();
+ IterationStrategyNode aboveParent = aboveNode.getParent();
+ if (selectedParent != null && selectedParent.equals(aboveParent)) {
+ // Siblings
+ int aboveChildIndex = selectedParent.getIndex(aboveNode);
+ selectedParent.insert(selectedNode, aboveChildIndex);
+ tree.refreshModel();
+ selectNode(selectedNode);
+ } else if (aboveNode.equals(selectedParent)) {
+ if (aboveParent instanceof TerminalNode
+ && selectedNode.getAllowsChildren()) {
+ aboveNode.setParent(selectedNode);
+ selectedNode.setParent(aboveParent);
+ tree.refreshModel();
+ selectNode(selectedNode);
+ } else if (!(aboveParent instanceof TerminalNode)){
+ int aboveChildIndex = aboveParent.getIndex(aboveNode);
+ aboveParent.insert(selectedNode, aboveChildIndex);
+ tree.refreshModel();
+ selectNode(selectedNode);
+ }
+ } else {
+
+ }
+
+ }
+
+ }
+
+ protected IterationStrategyNode belowSelectedNode() {
+ return offsetFromSelectedNode(1);
+ }
+
+ protected IterationStrategyNode offsetFromSelectedNode(int offset) {
+ int currentRow = tree.getRowForPath(tree.getSelectionPath());
+ int offsetRow = currentRow + offset;
+ TreePath offsetPath = tree.getPathForRow(offsetRow);
+ if (offsetPath == null) {
+ return null;
+ }
+ IterationStrategyNode offsetNode = (IterationStrategyNode) offsetPath
+ .getLastPathComponent();
+ if (offsetNode == tree.getModel().getRoot()) {
+ return null;
+ }
+ return offsetNode;
+ }
+
+ protected IterationStrategyNode aboveSelectedNode() {
+ return offsetFromSelectedNode(-1);
+ }
+
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyTree.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyTree.java
new file mode 100644
index 0000000..c4665c6
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/IterationStrategyTree.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * 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.iterationstrategy.editor;
+
+import java.util.Enumeration;
+
+import javax.swing.ImageIcon;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import net.sf.taverna.t2.workbench.iterationstrategy.IterationStrategyIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.IterationStrategy;
+
+@SuppressWarnings("serial")
+public class IterationStrategyTree extends JTree implements UIComponentSPI {
+
+ private IterationStrategy strategy = null;
+
+ public IterationStrategyTree() {
+ super();
+ setCellRenderer(new IterationStrategyCellRenderer());
+ }
+
+ public ImageIcon getIcon() {
+ return IterationStrategyIcons.leafnodeicon;
+ }
+
+ public void onDisplay() {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onDispose() {
+ this.strategy = null;
+ setModel(null);
+ }
+
+ public synchronized void setIterationStrategy(
+ IterationStrategy theStrategy) {
+ if (theStrategy != this.strategy) {
+ this.strategy = theStrategy;
+ TreeNode terminal = theStrategy.getTerminalNode();
+ setModel(new DefaultTreeModel(terminal));
+ this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ expandTree();
+ revalidate();
+ }
+ }
+
+ protected synchronized void refreshModel() {
+ this.getModel().nodeStructureChanged(strategy.getTerminalNode());
+ expandTree();
+ }
+
+ @Override
+ public DefaultTreeModel getModel() {
+ return (DefaultTreeModel) super.getModel();
+ }
+
+ @Override
+ public void setModel(TreeModel newModel) {
+ if (newModel != null && !(newModel instanceof DefaultTreeModel)) {
+ throw new IllegalArgumentException(
+ "Model must be a DefaultTreeModel");
+ }
+ super.setModel(newModel);
+ }
+
+ protected void expandTree() {
+ DefaultMutableTreeNode root =
+ (DefaultMutableTreeNode)this.getModel().getRoot();
+ Enumeration e = root.breadthFirstEnumeration();
+ while(e.hasMoreElements()) {
+ DefaultMutableTreeNode node =
+ (DefaultMutableTreeNode)e.nextElement();
+ if(node.isLeaf()) continue;
+ int row = this.getRowForPath(new TreePath(node.getPath()));
+ this.expandRow(row);
+ }
+ }
+
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/menu/IterationStrategyConfigureMenuAction.java b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/menu/IterationStrategyConfigureMenuAction.java
new file mode 100644
index 0000000..f8c7f73
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/java/net/sf/taverna/t2/workbench/iterationstrategy/menu/IterationStrategyConfigureMenuAction.java
@@ -0,0 +1,65 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.iterationstrategy.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.iterationstrategy.contextview.IterationStrategyConfigurationDialog;
+import net.sf.taverna.t2.workbench.iterationstrategy.contextview.IterationStrategyContextualView;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+public class IterationStrategyConfigureMenuAction extends AbstractContextualMenuAction {
+
+
+
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+
+ private static final URI ITERATION_STRATEGY_CONFIGURE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/iterationStrategyConfigure");
+
+ public IterationStrategyConfigureMenuAction() {
+ super(configureRunningSection, 40, ITERATION_STRATEGY_CONFIGURE_URI);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("List handling...") {
+ public void actionPerformed(ActionEvent e) {
+ Processor p = (Processor) getContextualSelection().getSelection();
+ final HelpEnabledDialog dialog = new IterationStrategyConfigurationDialog(null, p, IterationStrategyContextualView.copyIterationStrategyStack(p.getIterationStrategy()));
+ dialog.setVisible(true);
+ }
+ };
+ }
+
+ public boolean isEnabled() {
+ return super.isEnabled() && (getContextualSelection().getSelection() instanceof Processor);
+ }
+
+}
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6f25f4e
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.iterationstrategy.menu.IterationStrategyConfigureMenuAction
\ No newline at end of file
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..a6a27b0
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.iterationstrategy.contextview.IterationStrategyContextualViewFactory
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/spring/iteration-strategy-ui-context-osgi.xml b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/spring/iteration-strategy-ui-context-osgi.xml
new file mode 100644
index 0000000..e3db399
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/spring/iteration-strategy-ui-context-osgi.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="IterationStrategyContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/spring/iteration-strategy-ui-context.xml b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/spring/iteration-strategy-ui-context.xml
new file mode 100644
index 0000000..f0bc464
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/META-INF/spring/iteration-strategy-ui-context.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="IterationStrategyContextualViewFactory" class="net.sf.taverna.t2.workbench.iterationstrategy.contextview.IterationStrategyContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/crossproducticon.png b/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/crossproducticon.png
new file mode 100644
index 0000000..4627fc8
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/crossproducticon.png
Binary files differ
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/dotproducticon.png b/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/dotproducticon.png
new file mode 100644
index 0000000..c269883
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/dotproducticon.png
Binary files differ
diff --git a/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/leafnodeicon.png b/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/leafnodeicon.png
new file mode 100644
index 0000000..36808c6
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/main/resources/net/sf/taverna/t2/workbench/iterationstrategy/icons/leafnodeicon.png
Binary files differ
diff --git a/taverna-workbench-iteration-strategy-ui/src/test/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/RunIterationStrategyEditor.java b/taverna-workbench-iteration-strategy-ui/src/test/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/RunIterationStrategyEditor.java
new file mode 100644
index 0000000..1862233
--- /dev/null
+++ b/taverna-workbench-iteration-strategy-ui/src/test/java/net/sf/taverna/t2/workbench/iterationstrategy/editor/RunIterationStrategyEditor.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.iterationstrategy.editor;
+
+import javax.swing.JFrame;
+
+import net.sf.taverna.t2.workflowmodel.processor.iteration.NamedInputPortNode;
+import net.sf.taverna.t2.workflowmodel.processor.iteration.impl.IterationStrategyImpl;
+
+public class RunIterationStrategyEditor {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ IterationStrategyImpl iterationStrategyImpl = new IterationStrategyImpl();
+ NamedInputPortNode fishPort = new NamedInputPortNode("fish", 2);
+ NamedInputPortNode otherPort = new NamedInputPortNode("other", 0);
+ NamedInputPortNode soupPort = new NamedInputPortNode("soup", 1);
+ iterationStrategyImpl.addInput(fishPort);
+ iterationStrategyImpl.addInput(soupPort);
+ iterationStrategyImpl.addInput(otherPort);
+
+ iterationStrategyImpl.connectDefault(otherPort);
+ iterationStrategyImpl.connectDefault(fishPort);
+ iterationStrategyImpl.connectDefault(soupPort);
+
+ IterationStrategyEditorControl editorControl = new IterationStrategyEditorControl(iterationStrategyImpl);
+
+ JFrame frame = new JFrame("List handling editor");
+ frame.add(editorControl);
+ frame.setSize(500,400);
+ frame.setVisible(true);
+
+
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/pom.xml b/taverna-workbench-loop-ui/pom.xml
new file mode 100644
index 0000000..f639bea
--- /dev/null
+++ b/taverna-workbench-loop-ui/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>loop-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Loop layer contextual view</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>beanshell-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>edits-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>file-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>selection-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/ActivityGenerator.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/ActivityGenerator.java
new file mode 100644
index 0000000..9bd68e8
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/ActivityGenerator.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * 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.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import net.sf.taverna.t2.workbench.loop.comparisons.Comparison;
+import net.sf.taverna.t2.workbench.loop.comparisons.EqualTo;
+import net.sf.taverna.t2.workbench.loop.comparisons.IsGreaterThan;
+import net.sf.taverna.t2.workbench.loop.comparisons.IsLessThan;
+import net.sf.taverna.t2.workbench.loop.comparisons.Matches;
+import net.sf.taverna.t2.workbench.loop.comparisons.NotEqualTo;
+import net.sf.taverna.t2.workbench.loop.comparisons.NotMatches;
+
+import org.apache.log4j.Logger;
+
+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.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class ActivityGenerator {
+
+ private static final String LOOP_PORT = "loop";
+
+ private static final String SCRIPT = "script";
+
+ public static URI BEANSHELL_ACTIVITY = URI
+ .create("http://ns.taverna.org.uk/2010/activity/beanshell");
+
+ public static URI BEANSHELL_CONFIG = BEANSHELL_ACTIVITY.resolve("#Config");
+
+
+ public static final double DEFAULT_DELAY_S = 0.2;
+ public static final String COMPARE_PORT = "comparePort";
+ public static final String COMPARISON = "comparison";
+ public static final String CUSTOM_COMPARISON = "custom";
+ public static final String COMPARE_VALUE = "compareValue";
+ public static final String IS_FEED_BACK = "isFeedBack";
+ public static final String DELAY = "delay";
+
+ private static Logger logger = Logger.getLogger(ActivityGenerator.class);
+ private final ObjectNode loopProperties;
+ private final Processor processorToCompare;
+ private static Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public ActivityGenerator(ObjectNode configuration,
+ Processor processorToCompare) {
+ this.loopProperties = configuration;
+ this.processorToCompare = processorToCompare;
+ }
+
+ protected Activity generateActivity() {
+ Activity beanshell = new Activity();
+ beanshell.setType(BEANSHELL_ACTIVITY);
+ Configuration config = generateBeanshellConfig(beanshell);
+ // TODO: Where to put the config?
+ return beanshell;
+ }
+
+ private Configuration generateBeanshellConfig(Activity beanshell) {
+ Configuration config = scufl2Tools.createConfigurationFor(beanshell, BEANSHELL_CONFIG);
+ generateInputPorts(beanshell);
+ generateOutputPorts(beanshell);
+ config.getJsonAsObjectNode().put(SCRIPT, generateScript());
+ return config;
+ }
+
+ protected static List<Comparison> comparisons = Arrays.asList(
+ new EqualTo(), new NotEqualTo(), new Matches(), new NotMatches(),
+ new IsGreaterThan(), new IsLessThan());
+
+ protected static Comparison getComparisonById(String id) {
+ if (id == null || id.isEmpty()) {
+ return comparisons.get(0);
+ }
+ for (Comparison potentialComparison : comparisons) {
+ if (potentialComparison.getId().equals(id)) {
+ return potentialComparison;
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("boxing")
+ private String generateScript() {
+ Map<String, String> replacements = new HashMap<String, String>();
+ replacements.put("${loopPort}", LOOP_PORT);
+ replacements.put("${port}", loopProperties.findValue(COMPARE_PORT).asText());
+ replacements.put("${value}", beanshellString(loopProperties
+ .findValue(COMPARE_VALUE).asText()));
+
+
+ // as seconds
+ Double delay = loopProperties.findPath(DELAY).asDouble(DEFAULT_DELAY_S);
+ // as milliseconds
+ delay = Math.max(0.0, delay) * 1000;
+ // as integer (for Thread.sleep)
+ replacements.put("${delay}", Integer.toString(delay.intValue()));
+
+ String template = getComparisonById(
+ loopProperties.findValue(COMPARISON).asText()).getScriptTemplate();
+
+ if (delay > 0.0) {
+ template += "\nif (\"true\".matches(${loopPort})) {\n";
+ template += " Thread.sleep(${delay});\n";
+ template += "}";
+ }
+
+ String script = template;
+ for (Entry<String, String> mapping : replacements.entrySet()) {
+ script = script.replace(mapping.getKey(), mapping.getValue());
+ }
+ return script;
+ }
+
+ private String beanshellString(String value) {
+ value = value.replace("\\", "\\\\");
+ value = value.replace("\n", "\\n");
+ value = value.replace("\"", "\\\"");
+ return '"' + value + '"';
+ }
+
+ private void generateInputPorts(Activity beanshell) {
+ if (processorToCompare == null) {
+ return;
+ }
+ for (OutputProcessorPort procOut : processorToCompare.getOutputPorts()) {
+ // Any of the outputs are available to the script, giving
+ // a custom script that compares multiple outputs a better
+ // starting point.
+ String portName = procOut.getName();
+ if (portName.equals(loopProperties.findValue(COMPARE_PORT).asText()) ||
+ (loopProperties.findValue(IS_FEED_BACK).asBoolean())) {
+ InputActivityPort input = new InputActivityPort(beanshell, portName);
+ input.setDepth(procOut.getDepth());
+ input.setParent(beanshell);
+ }
+ }
+ }
+
+ private void generateOutputPorts(Activity beanshell) {
+ OutputActivityPort loopPort = new OutputActivityPort(beanshell, LOOP_PORT);
+ loopPort.setDepth(0);
+ loopPort.setGranularDepth(0);
+ if (processorToCompare == null) {
+ return;
+ }
+ if (! loopProperties.findValue(IS_FEED_BACK).asBoolean()) {
+ return;
+ }
+ for (InputProcessorPort procIn : processorToCompare.getInputPorts()) {
+ String portName = procIn.getName();
+ if (processorToCompare.getOutputPorts().containsName(portName)) {
+ OutputActivityPort actOut = new OutputActivityPort(beanshell, portName);
+ actOut.setDepth(procIn.getDepth());
+ actOut.setGranularDepth(procIn.getDepth());
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/AddLoopFactory.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/AddLoopFactory.java
new file mode 100644
index 0000000..f23650c
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/AddLoopFactory.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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.event.ActionEvent;
+import java.net.URI;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.AddLayerFactorySPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+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 com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class AddLoopFactory implements AddLayerFactorySPI {
+
+ private static final URI LOOP_TYPE = URI.create("http://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Loop");
+
+
+ private static Logger logger = Logger.getLogger(AddLoopFactory.class);
+ private static final JsonNodeFactory JSON_NODE_FACTORY = JsonNodeFactory.instance;
+ private static Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private SelectionManager selectionManager;
+ private ApplicationConfiguration applicationConfig;
+
+ public boolean canAddLayerFor(Processor processor) {
+ return findLoopLayer(processor) == null;
+ }
+
+
+ public ObjectNode findLoopLayer(Processor processor) {
+ List<Configuration> configs = scufl2Tools.configurationsFor(processor, selectionManager.getSelectedProfile());
+ for (Configuration config : configs) {
+ if (config.getJson().has("loop")) {
+ return (ObjectNode) config.getJson().get("loop");
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("serial")
+ public Action getAddLayerActionFor(final Processor processor) {
+ return new AbstractAction("Add looping") {
+
+ public void actionPerformed(ActionEvent e) {
+ ObjectNode loopLayer = findLoopLayer(processor);
+ if (loopLayer == null) {
+ loopLayer = JSON_NODE_FACTORY.objectNode();
+ }
+ // Pop up the configure loop dialog
+ LoopConfigureAction loopConfigureAction = new LoopConfigureAction(
+ MainWindow.getMainWindow(), null, processor, loopLayer,
+ selectionManager.getSelectedProfile(), editManager,
+ fileManager, getApplicationConfig());
+ loopConfigureAction.actionPerformed(e);
+ }
+ };
+ }
+
+ @Override
+ public boolean canCreateLayerClass(URI dispatchLayerType) {
+ return dispatchLayerType.equals(LOOP_TYPE);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public SelectionManager getSelectionManager() {
+ return selectionManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+
+ public ApplicationConfiguration getApplicationConfig() {
+ return applicationConfig;
+ }
+
+
+ public void setApplicationConfig(ApplicationConfiguration applicationConfig) {
+ this.applicationConfig = applicationConfig;
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopAddMenuAction.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopAddMenuAction.java
new file mode 100644
index 0000000..e7b6e87
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopAddMenuAction.java
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+
+public class LoopAddMenuAction extends AbstractContextualMenuAction {
+
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+
+ private static final URI LOOP_ADD_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/loopAdd");
+
+ private static final String LOOP_ADD = "Loop add";
+
+ public LoopAddMenuAction() {
+ super(configureRunningSection, 20, LOOP_ADD_URI);
+ }
+
+ private AddLoopFactory addLoopFactory;
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Looping...") {
+ public void actionPerformed(ActionEvent e) {
+ //Loop loopLayer = null;
+ Processor p = (Processor) getContextualSelection().getSelection();
+ addLoopFactory.getAddLayerActionFor(p).actionPerformed(e);
+ //LoopConfigureMenuAction.configureLoopLayer(p, e); // Configuration dialog pop up is now done from getAddLayerActionFor()
+ }
+ };
+ }
+
+ public boolean isEnabled() {
+ Object selection = getContextualSelection().getSelection();
+ return (super.isEnabled() && (selection instanceof Processor) && (LoopConfigureMenuAction.getLoopLayer((Processor)selection) == null));
+ }
+
+ public void setAddLoopFactory(AddLoopFactory addLoopFactory) {
+ this.addLoopFactory = addLoopFactory;
+ }
+
+
+
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigurationPanel.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigurationPanel.java
new file mode 100644
index 0000000..88efb1a
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigurationPanel.java
@@ -0,0 +1,588 @@
+/*******************************************************************************
+ * 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));
+ }
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigureAction.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigureAction.java
new file mode 100644
index 0000000..0b7ad8f
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigureAction.java
@@ -0,0 +1,262 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.loop;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * @author Alan R Williams
+ * @author Stian Soiland-Reyes
+ *
+ */
+@SuppressWarnings("serial")
+public class LoopConfigureAction extends AbstractAction {
+
+ private static Logger logger = Logger.getLogger(LoopConfigureAction.class);
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ private final Frame owner;
+ private final ObjectNode loopLayer;
+ private final LoopContextualView contextualView;
+ private final Processor processor;
+ private final Profile profile;
+
+ private ApplicationConfiguration applicationConfig;
+
+
+ protected LoopConfigureAction(Frame owner,
+ LoopContextualView contextualView, Processor processor,
+ ObjectNode loopLayer, Profile profile, EditManager editManager,
+ FileManager fileManager, ApplicationConfiguration applicationConfig) {
+ super("Configure");
+ this.owner = owner;
+ this.contextualView = contextualView;
+ this.loopLayer = loopLayer;
+ this.profile = profile;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.processor = processor;
+ this.applicationConfig = applicationConfig;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String title = "Looping for service " + processor.getName();
+ final JDialog dialog = new HelpEnabledDialog(owner, title, true);
+ LoopConfigurationPanel loopConfigurationPanel = new LoopConfigurationPanel(
+ processor, loopLayer, profile, applicationConfig);
+ dialog.add(loopConfigurationPanel, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout());
+
+ JButton okButton = new JButton(new OKAction(dialog, loopConfigurationPanel));
+ buttonPanel.add(okButton);
+
+ JButton resetButton = new JButton(new ResetAction(loopConfigurationPanel));
+ buttonPanel.add(resetButton);
+
+ JButton cancelButton = new JButton(new CancelAction(dialog));
+ buttonPanel.add(cancelButton);
+
+ dialog.add(buttonPanel, BorderLayout.SOUTH);
+ dialog.pack();
+ dialog.setSize(650, 430);
+ dialog.setLocationRelativeTo(null);
+ dialog.setVisible(true);
+ }
+
+ protected class CancelAction extends AbstractAction {
+ private final JDialog dialog;
+
+ protected CancelAction(JDialog dialog) {
+ super("Cancel");
+ this.dialog = dialog;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(false);
+ if (contextualView != null) {
+ contextualView.refreshView();
+ }
+ }
+
+ }
+
+ protected class OKAction extends AbstractAction {
+ private final JDialog dialog;
+ private final LoopConfigurationPanel loopConfigurationPanel;
+
+ protected OKAction(JDialog dialog, LoopConfigurationPanel loopConfigurationPanel) {
+ super("OK");
+ this.dialog = dialog;
+ this.loopConfigurationPanel = loopConfigurationPanel;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ try {
+
+ List<Edit<?>> compoundEdit = new ArrayList<Edit<?>>();
+ LoopConfiguration configuration = loopConfigurationPanel.getConfiguration();
+ compoundEdit.add(edits.getConfigureEdit(loopLayer, configuration));
+ compoundEdit.addAll(checkPortMappings(configuration.getCondition()));
+
+ editManager.doDataflowEdit(fileManager.getCurrentDataflow(), new CompoundEdit(
+ compoundEdit));
+ dialog.setVisible(false);
+ if (contextualView != null) {
+ contextualView.refreshView();
+ }
+ } catch (RuntimeException ex) {
+ logger.warn("Could not configure looping", ex);
+ JOptionPane.showMessageDialog(owner, "Could not configure looping",
+ "An error occured when configuring looping: " + ex.getMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (EditException ex) {
+ logger.warn("Could not configure looping", ex);
+ JOptionPane.showMessageDialog(owner, "Could not configure looping",
+ "An error occured when configuring looping: " + ex.getMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ protected List<Edit<?>> checkPortMappings(Activity<?> conditionActivity) {
+
+ List<Edit<?>> compoundEdit = new ArrayList<Edit<?>>();
+ if (processor.getActivityList().isEmpty()) {
+ return compoundEdit;
+ }
+ Set<String> newInputs = new HashSet<String>();
+ Set<String> newOutputs = new HashSet<String>();
+
+ Activity<?> firstProcessorActivity;
+ firstProcessorActivity = processor.getActivityList().get(0);
+ if (conditionActivity != null) {
+ for (OutputPort condOutPort : conditionActivity.getOutputPorts()) {
+ String portName = condOutPort.getName();
+ Map<String, String> mapping = firstProcessorActivity.getInputPortMapping();
+ if (!mapping.containsKey(portName)) {
+ if (mapping.containsKey(portName)) {
+ logger.warn("Can't re-map input for " + "conditional output "
+ + portName);
+ }
+ for (InputPort inputPort : firstProcessorActivity.getInputPorts()) {
+ if (inputPort.equals(portName)) {
+ Edit<Activity<?>> edit = edits.getAddActivityInputPortMappingEdit(
+ firstProcessorActivity, portName, portName);
+ compoundEdit.add(edit);
+ newInputs.add(portName);
+ }
+ }
+ }
+ }
+ for (InputPort condInPort : conditionActivity.getInputPorts()) {
+ String portName = condInPort.getName();
+ Map<String, String> mapping = firstProcessorActivity.getOutputPortMapping();
+ if (!mapping.containsValue(portName)) {
+ for (OutputPort outputPort : firstProcessorActivity.getOutputPorts()) {
+ if (outputPort.equals(portName)) {
+ if (mapping.containsKey(portName)) {
+ logger.warn("Can't re-map output for " + "conditional input "
+ + portName);
+ }
+ Edit<Activity<?>> edit = edits.getAddActivityOutputPortMappingEdit(
+ firstProcessorActivity, portName, portName);
+ logger.info("Mapping for conditional non-outgoing activity port binding "
+ + portName);
+ compoundEdit.add(edit);
+ newOutputs.add(portName);
+ }
+ }
+ }
+ }
+ }
+ // Remove any stale bindings that no longer match neither
+ // conditional activity or the processor output ports
+ for (String processorIn : firstProcessorActivity.getInputPortMapping().keySet()) {
+ if (newInputs.contains(processorIn)) {
+ continue;
+ }
+ boolean foundMatch = false;
+ for (InputPort processorPort : processor.getInputPorts()) {
+ if (processorPort.getName().equals(processorIn)) {
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ Edit<Activity<?>> edit = edits.getRemoveActivityInputPortMappingEdit(
+ firstProcessorActivity, processorIn);
+ logger.info("Removing stale input port binding " + processorIn);
+ compoundEdit.add(edit);
+ }
+ }
+ for (String processorOut : firstProcessorActivity.getOutputPortMapping().keySet()) {
+ if (newInputs.contains(processorOut)) {
+ continue;
+ }
+ boolean foundMatch = false;
+ for (OutputPort processorPort : processor.getOutputPorts()) {
+ if (processorPort.getName().equals(processorOut)) {
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ Edit<Activity<?>> edit = edits.getRemoveActivityOutputPortMappingEdit(
+ firstProcessorActivity, processorOut);
+ logger.info("Removing stale output port binding " + processorOut);
+ compoundEdit.add(edit);
+ }
+ }
+
+ return compoundEdit;
+ }
+ }
+
+ protected class ResetAction extends AbstractAction {
+ private LoopConfigurationPanel loopConfigurationPanel;
+
+ protected ResetAction(LoopConfigurationPanel loopConfigurationPanel) {
+ super("Reset");
+ this.loopConfigurationPanel = loopConfigurationPanel;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (contextualView != null) {
+ contextualView.refreshView();
+ }
+ loopConfigurationPanel.setConfiguration(loopLayer.getConfiguration());
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigureMenuAction.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigureMenuAction.java
new file mode 100644
index 0000000..3d6700d
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopConfigureMenuAction.java
@@ -0,0 +1,97 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+public class LoopConfigureMenuAction extends AbstractContextualMenuAction {
+
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+
+ private static final URI LOOP_CONFIGURE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/loopConfigure");
+
+ private static final String LOOP_CONFIGURE = "Loop configure";
+
+ private EditManager editManager;
+
+ private FileManager fileManager;
+
+ public LoopConfigureMenuAction() {
+ super(configureRunningSection, 20, LOOP_CONFIGURE_URI);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Looping...") {
+ public void actionPerformed(ActionEvent e) {
+ Processor p = (Processor) getContextualSelection().getSelection();
+ configureLoopLayer(p, e);
+ }
+ };
+ }
+
+ public void configureLoopLayer(Processor p, ActionEvent e) {
+ ObjectNode loopLayer = getLoopLayer(p);
+ if (loopLayer != null) {
+ LoopConfigureAction loopConfigureAction = new LoopConfigureAction(null, null, loopLayer, editManager, fileManager);
+ loopConfigureAction.actionPerformed(e);
+ }
+ }
+
+ public static ObjectNode getLoopLayer(Processor p) {
+ for (DispatchLayer dl : p.getDispatchStack().getLayers()) {
+ if (dl instanceof Loop) {
+ result = (Loop) dl;
+ break;
+ }
+ }
+ return result;
+ }
+
+ public boolean isEnabled() {
+ Object selection = getContextualSelection().getSelection();
+ return (super.isEnabled() && (selection instanceof Processor) && (getLoopLayer((Processor)selection) != null));
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopContextualView.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopContextualView.java
new file mode 100644
index 0000000..c22f376
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopContextualView.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * 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.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.Properties;
+
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.loop.comparisons.Comparison;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+/**
+ * View of a processor, including it's iteration stack, activities, etc.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class LoopContextualView extends ContextualView {
+
+ private static final long serialVersionUID = 1L;
+
+ private static Logger logger = Logger.getLogger(LoopContextualView.class);
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+ private Loop loopLayer;
+
+ private JPanel panel;
+
+ private Processor processor;
+
+ public LoopContextualView(Processor processor, EditManager editManager, FileManager fileManager) {
+ super();
+ this.loopLayer = loopLayer;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.processor = processor;
+ initialise();
+ initView();
+ }
+
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new LoopConfigureAction(owner, this, processor, editManager, fileManager);
+ }
+
+ @Override
+ public void refreshView() {
+ initialise();
+ }
+
+ private void initialise() {
+ if (panel == null) {
+ panel = new JPanel();
+ } else {
+ panel.removeAll();
+ }
+ panel.setLayout(new GridBagLayout());
+ updateUIByConfig();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ return panel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Loop of " + processor.getLocalName();
+ }
+
+ protected void updateUIByConfig() {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.weightx = 0.1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+
+ StringBuilder description = new StringBuilder("<html><body>");
+ Properties properties = loopLayer.getConfiguration().getProperties();
+ if (properties.getProperty(ActivityGenerator.COMPARISON,
+ ActivityGenerator.CUSTOM_COMPARISON).equals(
+ ActivityGenerator.CUSTOM_COMPARISON)) {
+ Activity<?> condition = loopLayer.getConfiguration().getCondition();
+ if (condition != null) {
+ description.append("Looping using custom conditional ");
+ if (condition instanceof BeanshellActivity) {
+ String script = ((BeanshellActivity)condition).getConfiguration().getScript();
+ if (script != null) {
+ if (script.length() <= 100) {
+ description.append("<pre>\n");
+ description.append(script);
+ description.append("</pre>\n");
+ }
+ }
+ }
+ } else {
+ description.append("<i>Unconfigured, will not loop</i>");
+ }
+ } else {
+ description.append("The service will be invoked repeatedly ");
+ description.append("until<br> its output <strong>");
+ description.append(properties
+ .getProperty(ActivityGenerator.COMPARE_PORT));
+ description.append("</strong> ");
+
+ Comparison comparison = ActivityGenerator
+ .getComparisonById(properties
+ .getProperty(ActivityGenerator.COMPARISON));
+ description.append(comparison.getName());
+
+ description.append(" the " + comparison.getValueType() + ": <pre>");
+ description.append(properties
+ .getProperty(ActivityGenerator.COMPARE_VALUE));
+ description.append("</pre>");
+
+ String delay = properties.getProperty(ActivityGenerator.DELAY, "");
+ try {
+ if (Double.parseDouble(delay) > 0) {
+ description.append("adding a delay of " + delay
+ + " seconds between loops.");
+ }
+ } catch (NumberFormatException ex) {
+ }
+ }
+ description.append("</body></html>");
+
+ panel.add(new JLabel(description.toString()), gbc);
+ gbc.gridy++;
+
+ revalidate();
+ }
+
+
+
+ @Override
+ public int getPreferredPosition() {
+ return 400;
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopContextualViewFactory.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopContextualViewFactory.java
new file mode 100644
index 0000000..d222849
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopContextualViewFactory.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * 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.util.Arrays;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+
+public class LoopContextualViewFactory implements ContextualViewFactory<Processor> {
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+ public boolean canHandle(Object selection) {
+ return selection instanceof Processor;
+ }
+
+ public List<ContextualView> getViews(Processor selection) {
+ return Arrays.asList(new ContextualView[] {new LoopContextualView(selection, editManager, fileManager)});
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopRemoveMenuAction.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopRemoveMenuAction.java
new file mode 100644
index 0000000..12a9592
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/LoopRemoveMenuAction.java
@@ -0,0 +1,92 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+public class LoopRemoveMenuAction extends AbstractContextualMenuAction {
+
+ private static Logger logger = Logger
+ .getLogger(LoopRemoveMenuAction.class);
+
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+
+ private static final URI LOOP_REMOVE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/loopRemove");
+
+ private static final String LOOP_REMOVE = "Loop remove";
+
+ public LoopRemoveMenuAction() {
+ super(configureRunningSection, 25, LOOP_REMOVE_URI);
+ }
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Disable looping") {
+ public void actionPerformed(ActionEvent e) {
+ Processor p = (Processor) getContextualSelection().getSelection();
+ Loop loopLayer = LoopConfigureMenuAction.getLoopLayer(p);
+ Edit<DispatchStack> deleteEdit = editManager.getEdits().getDeleteDispatchLayerEdit(
+ p.getDispatchStack(), loopLayer);
+ // TODO: Should warn before removing "essential" layers
+ try {
+ editManager.doDataflowEdit(fileManager.getCurrentDataflow(),
+ deleteEdit);
+ } catch (EditException ex) {
+ logger.warn("Could not remove layer " + loopLayer, ex);
+ }
+
+ }
+ };
+ }
+
+ public boolean isEnabled() {
+ Object selection = getContextualSelection().getSelection();
+ return (super.isEnabled() && (selection instanceof Processor) && (LoopConfigureMenuAction.getLoopLayer((Processor)selection) != null));
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/Comparison.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/Comparison.java
new file mode 100644
index 0000000..608030c
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/Comparison.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+import net.sf.taverna.t2.workbench.loop.LoopConfigurationPanel;
+
+/**
+ * A comparison beanshell template for {@link LoopConfigurationPanel}.
+ * <p>
+ * A comparison is a template for generating a beanshell that can be used for
+ * comparisons in say the {@link Loop} layer.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public abstract class Comparison {
+
+ public String toString() {
+ return getName();
+ }
+
+ public abstract String getId();
+
+ public abstract String getName();
+
+ public abstract String getValueType();
+
+ public abstract String getScriptTemplate();
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/EqualTo.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/EqualTo.java
new file mode 100644
index 0000000..dbfb8f5
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/EqualTo.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+public class EqualTo extends Comparison {
+
+ public String getId() {
+ return "EqualTo";
+ }
+
+ public String getName() {
+ return "is equal to";
+ }
+
+ public String getScriptTemplate() {
+ return "${loopPort} = \"\" + ! ${port}.equals(${value}); ";
+ }
+
+ public String getValueType() {
+ return "string";
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/IsGreaterThan.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/IsGreaterThan.java
new file mode 100644
index 0000000..fc6f56b
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/IsGreaterThan.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+public class IsGreaterThan extends Comparison {
+
+ public String getId() {
+ return "IsGreaterThan";
+ }
+
+ public String getName() {
+ return "is greater than";
+ }
+
+ public String getScriptTemplate() {
+ return "${loopPort} = \"\" + (! (Double.parseDouble(${port}) > Double.parseDouble(${value})));";
+ }
+
+ public String getValueType() {
+ return "number";
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/IsLessThan.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/IsLessThan.java
new file mode 100644
index 0000000..d5fe38c
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/IsLessThan.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+public class IsLessThan extends Comparison {
+
+ public String getId() {
+ return "IsLessThan";
+ }
+
+ public String getName() {
+ return "is less than";
+ }
+
+ public String getScriptTemplate() {
+ return "${loopPort} = \"\" + (! (Double.parseDouble(${port}) < Double.parseDouble(${value})));";
+ }
+
+ public String getValueType() {
+ return "number";
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/Matches.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/Matches.java
new file mode 100644
index 0000000..fa84aeb
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/Matches.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+public class Matches extends Comparison {
+
+ public String getId() {
+ return "Matches";
+ }
+
+ public String getName() {
+ return "matches";
+ }
+
+ public String getScriptTemplate() {
+ return "${loopPort} = \"\" + ! ${port}.matches(${value});";
+ }
+
+ public String getValueType() {
+ return "regular expression";
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/NotEqualTo.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/NotEqualTo.java
new file mode 100644
index 0000000..9c73835
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/NotEqualTo.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+public class NotEqualTo extends Comparison {
+
+ public String getId() {
+ return "NotEqualTo";
+ }
+
+ public String getName() {
+ return "is not equal to";
+ }
+
+ public String getScriptTemplate() {
+ return "${loopPort} = \"\" + ${port}.equals(${value});";
+ }
+
+ public String getValueType() {
+ return "string";
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/NotMatches.java b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/NotMatches.java
new file mode 100644
index 0000000..803d5d7
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/java/net/sf/taverna/t2/workbench/loop/comparisons/NotMatches.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.comparisons;
+
+public class NotMatches extends Comparison {
+
+ public String getId() {
+ return "NotMatches";
+ }
+
+ public String getName() {
+ return "does not match";
+ }
+
+ public String getScriptTemplate() {
+ return "${loopPort} = \"\" + ${port}.matches(${value});";
+ }
+
+ public String getValueType() {
+ return "regular expression";
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..1956a3f
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.workbench.loop.LoopConfigureMenuAction
+net.sf.taverna.t2.workbench.loop.LoopAddMenuAction
+net.sf.taverna.t2.workbench.loop.LoopRemoveMenuAction
diff --git a/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.AddLayerFactorySPI b/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.AddLayerFactorySPI
new file mode 100644
index 0000000..52eafc4
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.AddLayerFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.loop.AddLoopFactory
diff --git a/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..9150066
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.loop.LoopContextualViewFactory
diff --git a/taverna-workbench-loop-ui/src/main/resources/META-INF/spring/loop-ui-context-osgi.xml b/taverna-workbench-loop-ui/src/main/resources/META-INF/spring/loop-ui-context-osgi.xml
new file mode 100644
index 0000000..4abb75f
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/resources/META-INF/spring/loop-ui-context-osgi.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="AddLoopFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.AddLayerFactorySPI" />
+
+ <service ref="LoopConfigureMenuAction" auto-export="interfaces" />
+ <service ref="LoopAddMenuAction" auto-export="interfaces" />
+ <service ref="LoopRemoveMenuAction" auto-export="interfaces" />
+
+ <service ref="LoopContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="applicationConfig" interface="uk.org.taverna.configuration.app.ApplicationConfiguration"/>
+</beans:beans>
diff --git a/taverna-workbench-loop-ui/src/main/resources/META-INF/spring/loop-ui-context.xml b/taverna-workbench-loop-ui/src/main/resources/META-INF/spring/loop-ui-context.xml
new file mode 100644
index 0000000..4c2133c
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/main/resources/META-INF/spring/loop-ui-context.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="AddLoopFactory" class="net.sf.taverna.t2.workbench.loop.AddLoopFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="applicationConfig" ref="applicationConfig" />
+ </bean>
+
+ <bean id="LoopConfigureMenuAction" class="net.sf.taverna.t2.workbench.loop.LoopConfigureMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="LoopAddMenuAction" class="net.sf.taverna.t2.workbench.loop.LoopAddMenuAction">
+ <property name="addLoopFactory">
+ <ref local="AddLoopFactory"/>
+ </property>
+ </bean>
+ <bean id="LoopRemoveMenuAction" class="net.sf.taverna.t2.workbench.loop.LoopRemoveMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+
+ <bean id="LoopContextualViewFactory" class="net.sf.taverna.t2.workbench.loop.LoopContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-loop-ui/src/test/java/net/sf/taverna/t2/workbench/loop/ShowContextualView.java b/taverna-workbench-loop-ui/src/test/java/net/sf/taverna/t2/workbench/loop/ShowContextualView.java
new file mode 100644
index 0000000..3e3d121
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/test/java/net/sf/taverna/t2/workbench/loop/ShowContextualView.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * 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.Component;
+import java.awt.event.ActionEvent;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.FileManagerImpl;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.impl.SelectionManagerImpl;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponent;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl.ContextualViewFactoryRegistryImpl;
+
+/**
+ * A standalone application to show contextual views
+ * <p>
+ * The application shows a JFrame containing a contextual view, together with
+ * buttons which will select items in the {@link SelectionManager} for a
+ * (rather) empty current dataflow.
+ *
+ * @author Stian Soiland-Reyes.
+ *
+ */
+public class ShowContextualView {
+
+ public static void main(String[] args) throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ FileManager fileManager = new FileManagerImpl(editManager);
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry = new ContextualViewFactoryRegistryImpl();
+ SelectionManagerImpl selectionMan = new SelectionManagerImpl();
+ selectionMan.setFileManager(fileManager);
+ selectionMan.setEditManager(editManager);
+ new ShowContextualView(editManager, fileManager,selectionMan, contextualViewFactoryRegistry).showFrame();
+ }
+
+ private SelectionManager selectionManager;
+ private FileManager fileManager;
+ private EditManager editManager;
+ private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+
+ private uk.org.taverna.scufl2.api.core.Processor processor;
+
+ private WorkflowBundle currentDataflow;
+
+ public ShowContextualView(EditManager editManager, FileManager fileManager, final SelectionManager selectionManager, ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.selectionManager = selectionManager;
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ currentDataflow = fileManager.newDataflow();
+ makeProcessor();
+
+ }
+
+ private void makeProcessor() {
+ processor = new Processor(currentDataflow.getMainWorkflow(), "Hello");
+ }
+
+ private List getSelections() {
+ return Arrays.asList(processor, currentDataflow);
+ }
+
+ private Component makeSelectionButtons() {
+ JPanel buttons = new JPanel();
+ for (final Object selection : getSelections()) {
+ buttons.add(new JButton(new AbstractAction("" + selection) {
+ public void actionPerformed(ActionEvent e) {
+ selectionManager.getDataflowSelectionModel(
+ currentDataflow).setSelection(
+ Collections.<Object> singleton(selection));
+ }
+ }));
+ }
+ return buttons;
+ }
+
+ protected void showFrame() {
+ JFrame frame = new JFrame(getClass().getName());
+ ContextualViewComponent contextualViewComponent = new ContextualViewComponent(editManager, selectionManager, contextualViewFactoryRegistry);
+ frame.add(contextualViewComponent, BorderLayout.CENTER);
+
+ frame.add(makeSelectionButtons(), BorderLayout.NORTH);
+ frame.setSize(400, 400);
+ frame.setVisible(true);
+ }
+
+}
diff --git a/taverna-workbench-loop-ui/src/test/resources/log4j.properties b/taverna-workbench-loop-ui/src/test/resources/log4j.properties
new file mode 100644
index 0000000..850ede3
--- /dev/null
+++ b/taverna-workbench-loop-ui/src/test/resources/log4j.properties
@@ -0,0 +1,10 @@
+log4j.rootLogger=WARN, CONSOLE
+log4j.logger.net.sf.taverna.t2=INFO
+#log4j.logger.net.sf.taverna.t2.ui=DEBUG
+
+#log4j.logger.org.apache.commons.httpclient=ERROR
+
+# Default output to console
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%-5p %d{ISO8601} (%c:%L) - %m%n
\ No newline at end of file
diff --git a/taverna-workbench-menu-api/pom.xml b/taverna-workbench-menu-api/pom.xml
new file mode 100644
index 0000000..247cd41
--- /dev/null
+++ b/taverna-workbench-menu-api/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Menu generation API</name>
+ <description>An SPI system for building UI menus</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractContextualMenuAction.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractContextualMenuAction.java
new file mode 100644
index 0000000..7209cae
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractContextualMenuAction.java
@@ -0,0 +1,64 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu;
+
+import java.net.URI;
+
+/**
+ * An {@link AbstractMenuAction} that is {@link ContextualMenuComponent} aware.
+ * The contextual selection can be retrieved from
+ * {@link #getContextualSelection()}.
+ * <p>
+ * The cached action will be flushed everytime the contextual selection changes,
+ * forcing a new call to {@link #createAction()} - given that
+ * {@link #isEnabled()} returns true.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractContextualMenuAction extends AbstractMenuAction
+ implements ContextualMenuComponent {
+
+ private ContextualSelection contextualSelection;
+
+ public AbstractContextualMenuAction(URI parentId, int positionHint) {
+ super(parentId, positionHint);
+ }
+
+ public AbstractContextualMenuAction(URI parentId, int positionHint, URI id) {
+ super(parentId, positionHint, id);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return contextualSelection != null;
+ }
+
+ @Override
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ // Force new createAction() call
+ action = null;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenu.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenu.java
new file mode 100644
index 0000000..07eb8d2
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenu.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import static net.sf.taverna.t2.ui.menu.MenuComponent.MenuType.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#menu menu}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of a
+ * menu. The definition of "menu" includes both the menu bar and sub menus. A
+ * menu can contain {@linkplain AbstractMenuAction actions},
+ * {@linkplain AbstractMenuToggle toggles} or {@linkplain AbstractMenuCustom
+ * custom components}, or any of the above grouped in a
+ * {@linkplain AbstractMenuSection section},
+ * {@linkplain AbstractMenuOptionGroup option group} or a
+ * {@linkplain AbstractMenu submenu}.
+ * <p>
+ * Menu components are linked together using URIs, avoiding the need for compile
+ * time dependencies between SPI implementations. To add components to a menu,
+ * use the {@link URI} identifying this menu as their parent id.
+ * <p>
+ * <strong>Note:</strong> To avoid conflicts with other plugins, use a unique
+ * URI root that is related to the Java package name, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu</code>, and use hash
+ * identifiers for each menu item, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu#run</code> for a "Run"
+ * item. Use flat URI namespaces, don't base a child's URI on the parent's URI,
+ * as this might make it difficult to relocate the parent menu.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenu</code>) of the menu
+ * implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractMenu extends AbstractMenuItem {
+ /**
+ * Construct a menu bar (does not have a parent). This menu bar can be built
+ * and used through {@link MenuManager#createMenuBar(URI)}. There is a
+ * default menu bar implementation in {@link DefaultMenuBar} that can be
+ * built using {@link MenuManager#createMenuBar()}, but in case you need
+ * several menu bars for different windows or modes, use this constructor.
+ *
+ * @param id
+ * The {@link URI} to identify this menu bar. Use this as the
+ * parent ID for menu components to appear in this menu.
+ */
+ public AbstractMenu(URI id) {
+ super(menu, (URI) null, id);
+ }
+
+ /**
+ * Construct a submenu.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu. The parent should be of
+ * type
+ * {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#menu}.
+ * @param positionHint
+ * The position hint to determine the position of this submenu
+ * among its siblings in the parent menu. For extensibility, use
+ * BASIC style numbering such as 10, 20, etc.
+ * @param id
+ * The {@link URI} to identify this menu bar. Use this as the
+ * parent ID for menu components to appear in this submenu.
+ * @param label
+ * The label for presenting this sub-menu in the parent menu.
+ */
+ public AbstractMenu(URI parentId, int positionHint, URI id, String label) {
+ this(parentId, positionHint, id, new DummyAction(label));
+ }
+
+ /**
+ * Construct a submenu.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu. The parent should be of
+ * type
+ * {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#menu}.
+ * @param positionHint
+ * The position hint to determine the position of this submenu
+ * among its siblings in the parent menu. For extensibility, use
+ * BASIC style numbering such as 10, 20, etc.
+ * @param id
+ * The {@link URI} to identify this menu bar. Use this as the
+ * parent ID for menu components to appear in this submenu.
+ * @param action
+ * The action containing a label and icon for presenting this
+ * sub-menu in the parent menu.
+ */
+ public AbstractMenu(URI parentId, int positionHint, URI id, Action action) {
+ super(menu, parentId, id);
+ this.action = action;
+ this.positionHint = positionHint;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuAction.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuAction.java
new file mode 100644
index 0000000..446a2ec
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuAction.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#action action}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of an
+ * action. An action is an item within a menu or toolbar that can be
+ * clicked/selected to invoke some action.
+ * <p>
+ * This action can have as an parent a {@linkplain AbstractMenu menu} or
+ * {@linkplain AbstractToolBar toolbar}, or grouped within an
+ * {@linkplain AbstractMenuSection section} or
+ * {@linkplain AbstractMenuOptionGroup option group}.
+ * <p>
+ * To define the {@link Action}, implement {@link #createAction()}. The action
+ * should provide both the label/icon (representation) and
+ * {@link ActionListener#actionPerformed(ActionEvent)}.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenuAction</code>) of the menu
+ * action implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractMenuAction extends AbstractMenuItem {
+ /**
+ * Construct a menu action to appear within the specified menu component.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The component
+ * should be a {@linkplain MenuComponent.MenuType#isParentType()
+ * parent type} and must have been registered separately as an
+ * SPI.
+ * @param positionHint
+ * The position hint to determine the position of this action
+ * among its siblings in the parent menu, section or toolbar. For
+ * extensibility, use BASIC style numbering such as 10, 20, etc.
+ * (Note that position hints are local to each parent, so each
+ * {@linkplain AbstractMenuSection section} have their own
+ * position hint scheme.)
+ */
+ public AbstractMenuAction(URI parentId, int positionHint) {
+ this(parentId, positionHint, null);
+ }
+
+ /**
+ * Construct a menu action to appear within the specified menu component.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The component
+ * should be a {@linkplain MenuComponent.MenuType#isParentType()
+ * parent type} and must have been registered separately as an
+ * SPI.
+ * @param positionHint
+ * The position hint to determine the position of this action
+ * among its siblings in the parent menu, section or toolbar. For
+ * extensibility, use BASIC style numbering such as 10, 20, etc.
+ * (Note that position hints are local to each parent, so each
+ * {@linkplain AbstractMenuSection section} have their own
+ * position hint scheme.)
+ * @param id
+ * The {@link URI} to identify this action. Although no
+ * components can have an action as their parent, this URI can be
+ * used to retrieve the realisation of this component using
+ * {@link MenuManager#getComponentByURI(URI)}. This ID might also
+ * be registered as a help identifier with the help system.
+ */
+ public AbstractMenuAction(URI parentId, int positionHint, URI id) {
+ super(MenuType.action, parentId, id);
+ this.positionHint = positionHint;
+ }
+
+ /**
+ * Call {@link #createAction()} on first call, after that return cached
+ * action.
+ *
+ * @see #createAction()
+ */
+ @Override
+ public synchronized Action getAction() {
+ if (action == null)
+ action = createAction();
+ return action;
+ }
+
+ /**
+ * Create the {@link Action} that labels this menu action, in addition to
+ * performing the desired action on
+ * {@link ActionListener#actionPerformed(ActionEvent)}.
+ * <p>
+ * This method will be called by {@link #getAction()} on the first call.
+ * Concurrent calls to <tt>getAction()</tt> will return the same action.
+ * <p>
+ * Implementations might use {@link AbstractAction} as a superclass for menu
+ * actions. It is recommended to make the action a top level class so that
+ * it can be used both within an {@link AbstractMenuAction} of a menu bar
+ * and within an {@link AbstractMenuAction} of a tool bar.
+ *
+ * @see #getAction()
+ * @return A configured {@link Action} that should at least have a label or
+ * icon.
+ */
+ protected abstract Action createAction();
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuCustom.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuCustom.java
new file mode 100644
index 0000000..9a64130
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuCustom.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.awt.Component;
+import java.net.URI;
+
+import javax.swing.JButton;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#custom}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of a
+ * custom menu or toolbar {@link Component}, for instance a {@link JMenu},
+ * {@link JMenuItem} or {@link JButton}.
+ * <p>
+ * This type of component can be useful for adding third party components that
+ * are built using other menu systems, or to provide dynamic menus such as a
+ * list of open files. This is the recommended way to customise the menus,
+ * although it is also possible to modify the components returned using
+ * {@link MenuManager#getComponentByURI(URI)}, but as the components built by
+ * the menu manager might be refreshed by various actions forcing an update to
+ * the SPI registry, such as installing a plugin. By using a custom menu
+ * component it is possible to avoid these problems and to provide the
+ * {@link Component} to be inserted into the menu/toolbar as built by the
+ * {@link MenuManager}.
+ * <p>
+ * This component can have as an parent any menu component that
+ * {@linkplain MenuType#isParentType() is a parent type}. Note that although you
+ * can specify an {@link URI} to identify the custom component (to be used with
+ * {@link MenuManager#getComponentByURI(URI)} a custom component can't have
+ * children. Such children would have to be created manually and added to the
+ * component.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenuAction</code>) of the menu
+ * action implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractMenuCustom extends AbstractMenuItem {
+ /**
+ * Construct a menu action to appear within the specified menu component.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The component
+ * should be a {@link MenuType#isParentType() parent type} and
+ * must have been registered separately as an SPI.
+ * @param positionHint
+ * The position hint to determine the position of this action
+ * among its siblings in the parent menu, section or toolbar. For
+ * extensibility, use BASIC style numbering such as 10, 20, etc.
+ * (Note that position hints are local to each parent, so each
+ * {@linkplain AbstractMenuSection section} have their own
+ * position hint scheme.)
+ */
+ public AbstractMenuCustom(URI parentId, int positionHint) {
+ this(parentId, positionHint, null);
+ }
+
+ /**
+ * Construct a menu action to appear within the specified menu component.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The component
+ * should be a {@linkplain MenuType#isParentType() parent type}
+ * and must have been registered separately as an SPI.
+ * @param positionHint
+ * The position hint to determine the position of this action
+ * among its siblings in the parent menu, section or toolbar. For
+ * extensibility, use BASIC style numbering such as 10, 20, etc.
+ * (Note that position hints are local to each parent, so each
+ * {@linkplain AbstractMenuSection section} have their own
+ * position hint scheme.)
+ * @param id
+ * The {@link URI} to identify this action. Although no
+ * components can have an action as their parent, this URI can be
+ * used to retrieve the realisation of this component using
+ * {@link MenuManager#getComponentByURI(URI)}. This ID might also
+ * be registered as a help identifier with the help system.
+ */
+ public AbstractMenuCustom(URI parentId, int positionHint, URI id) {
+ super(MenuType.custom, parentId, id);
+ this.positionHint = positionHint;
+ }
+
+ /**
+ * Create the {@link Component} that is to be added to the parent.
+ * <p>
+ * The component must be compatible with the parent realisation from the
+ * {@link MenuManager}, for instance you can't add {@link JMenuItem}s to a
+ * toolbar.
+ * </p>
+ * <p>
+ * Note that the component might get assigned new parents if the
+ * menues/toolbars are rebuilt by the {@link MenuManager} is refreshed,
+ * although the menu manager will try to avoid a second call to
+ * {@link #createCustomComponent()}.
+ * </p>
+ *
+ * @return A custom {@link Component} such as {@link JMenu},
+ * {@link JMenuItem} or {@link JButton} to be added to the parent
+ * menu component.
+ */
+ protected abstract Component createCustomComponent();
+
+ /**
+ * Return the custom component created using
+ * {@link #createCustomComponent()} on first call, return cached instance on
+ * later calls.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public final synchronized Component getCustomComponent() {
+ if (customComponent == null)
+ customComponent = createCustomComponent();
+ return customComponent;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuItem.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuItem.java
new file mode 100644
index 0000000..63b6c78
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuItem.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.Icon;
+
+/**
+ * An abstract implementation of {@link MenuComponent} that can be used by
+ * convenience to create menu component SPIs for the {@link MenuManager}.
+ * <p>
+ * Abstract subclasses of this class are specialised by their
+ * {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType}. To create a menu,
+ * toolbar, section, action etc, create an SPI implementation by subclassing
+ * depending on the required type:
+ * </p>
+ * <dl>
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#menu}</dt>
+ * <dd>Subclass {@link AbstractMenu}</dd>
+ *
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#toolBar}</dt>
+ * <dd>Subclass {@link AbstractToolBar}</dd>
+ *
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#section}</dt>
+ * <dd>Subclass {@link AbstractMenuSection}</dd>
+ *
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#action}</dt>
+ * <dd>Subclass {@link AbstractMenuAction}</dd>
+ *
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#toggle}</dt>
+ * <dd>Subclass {@link AbstractMenuToggle}</dd>
+ *
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#custom}</dt>
+ * <dd>Subclass {@link AbstractMenuCustom}</dd>
+ *
+ * <dt> {@link net.sf.taverna.t2.ui.menu.MenuComponent.MenuType#optionGroup}</dt>
+ * <dd>Subclass {@link AbstractMenuOptionGroup}</dd>
+ *
+ * </dl>
+ * <p>
+ * Note that you are not required to subclass any of these as long as your SPI
+ * implementations implement the {@link MenuComponent} interface. In all cases
+ * you are still required to list all your implementations, including
+ * intermediate menus and sections, in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code>
+ * </p>
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public abstract class AbstractMenuItem implements MenuComponent {
+ /**
+ * An {@link Action} that does not perform any action, but only contains a
+ * name and icon. Used by {@link AbstractMenu} and others.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ @SuppressWarnings("serial")
+ public static class DummyAction extends AbstractAction {
+ public DummyAction(String name) {
+ super(name);
+ }
+
+ public DummyAction(String name, Icon icon) {
+ super(name, icon);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ }
+ }
+
+ public AbstractMenuItem(MenuType type, URI parentId, URI id) {
+ this.type = type;
+ this.parentId = parentId;
+ this.id = id;
+ }
+
+ private final MenuType type;
+ private final URI parentId;
+ private final URI id;
+ protected int positionHint = 100;
+ protected Action action;
+ protected Component customComponent;
+
+ @Override
+ public Action getAction() {
+ return action;
+ }
+
+ @Override
+ public Component getCustomComponent() {
+ return customComponent;
+ }
+
+ @Override
+ public URI getId() {
+ return id;
+ }
+
+ @Override
+ public URI getParentId() {
+ return parentId;
+ }
+
+ @Override
+ public int getPositionHint() {
+ return positionHint;
+ }
+
+ @Override
+ public MenuType getType() {
+ return type;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuOptionGroup.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuOptionGroup.java
new file mode 100644
index 0000000..091cc23
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuOptionGroup.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#optionGroup}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of an
+ * option group. An option group is similar to a
+ * {@linkplain AbstractMenuSection section}, but enforces that only one of the
+ * children are selected at any time.
+ * <p>
+ * Menu components are linked together using URIs, avoiding the need for compile
+ * time dependencies between SPI implementations. To add actions or toggles to
+ * an option group, use the {@link URI} identifying this section as their parent
+ * id.
+ * <p>
+ * <strong>Note:</strong> To avoid conflicts with other plugins, use a unique
+ * URI root that is related to the Java package name, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu</code>, and use hash
+ * identifiers for each menu item, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu#run</code> for a "Run"
+ * item. Use flat URI namespaces, don't base a child's URI on the parent's URI,
+ * as this might make it difficult to relocate the parent menu.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenu</code>) of the option
+ * group implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractMenuOptionGroup extends AbstractMenuItem {
+ /**
+ * Construct an option group.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The parent
+ * should be of type {@link MenuType#menu} or
+ * {@link MenuType#toolBar}.
+ * @param positionHint
+ * The position hint to determine the position of this option
+ * group among its siblings in the parent menu. For
+ * extensibility, use BASIC style numbering such as 10, 20, etc.
+ * (Note that position hints are local to each parent, so each
+ * option group have their own position hint scheme for their
+ * children.)
+ * @param id
+ * The {@link URI} to identify this option group. Use this as the
+ * parent ID for menu components to appear in this option group.
+ */
+ public AbstractMenuOptionGroup(URI parentId, int positionHint, URI id) {
+ super(MenuType.optionGroup, parentId, id);
+ this.positionHint = positionHint;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuSection.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuSection.java
new file mode 100644
index 0000000..2e649e0
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuSection.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu;
+
+import java.awt.Color;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#section}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of a
+ * section. A section is a part of a {@linkplain AbstractMenu menu} or
+ * {@linkplain AbstractToolBar toolbar} that group together
+ * {@linkplain AbstractMenuAction actions} or {@linkplain AbstractMenuToggle
+ * toggles}, and separates them from siblings using separators if needed.
+ * <p>
+ * Menu components are linked together using URIs, avoiding the need for compile
+ * time dependencies between SPI implementations. To add actions to a section,
+ * use the {@link URI} identifying this section as their parent id.
+ * <p>
+ * <strong>Note:</strong> To avoid conflicts with other plugins, use a unique
+ * URI root that is related to the Java package name, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu</code>, and use hash
+ * identifiers for each menu item, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu#run</code> for a "Run"
+ * item. Use flat URI namespaces, don't base a child's URI on the parent's URI,
+ * as this might make it difficult to relocate the parent menu.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenu</code>) of the section
+ * implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractMenuSection extends AbstractMenuItem {
+ public static final String SECTION_COLOR = "sectionColor";
+
+ /**
+ * Construct a menu section.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The parent
+ * should be of type {@link MenuType#menu} or
+ * {@link MenuType#toolBar}.
+ * @param positionHint
+ * The position hint to determine the position of this section
+ * among its siblings in the parent menu. For extensibility, use
+ * BASIC style numbering such as 10, 20, etc. (Note that position
+ * hints are local to each parent, so each section have their own
+ * position hint scheme for their children.)
+ * @param id
+ * The {@link URI} to identify this menu section. Use this as the
+ * parent ID for menu components to appear in this section.
+ */
+ public AbstractMenuSection(URI parentId, int positionHint, URI id) {
+ super(MenuType.section, parentId, id);
+ this.positionHint = positionHint;
+ }
+
+ @Override
+ public synchronized Action getAction() {
+ if (action == null)
+ action = createAction();
+ return action;
+ }
+
+ /**
+ * (Optionally) Create the {@link Action} that labels this section.
+ * <p>
+ * The actual action will be ignored, but the label and/or icon will be used
+ * as a section header in the menu. If the property {@link #SECTION_COLOR}
+ * has been defined in the action, that {@link Color} will be used to make
+ * the section background.
+ * <p>
+ * The default implementation of this method returns <code>null</code>,
+ * meaning that no section header will be created - instead a simple line
+ * will separate this section from the items above (if needed).
+ * <p>
+ * Implementations might use {@link AbstractAction} as a superclass for menu
+ * actions.
+ *
+ * @return A configured {@link Action} that should at least have a label or
+ * icon.
+ */
+ protected Action createAction() {
+ return null;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuToggle.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuToggle.java
new file mode 100644
index 0000000..97e977d
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractMenuToggle.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#toggle}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of an
+ * toggle action. A toggle is a menu item that can be turned on/off and are
+ * typically represented with a check box when they are enabled.
+ * <p>
+ * This action can have as an parent a {@linkplain AbstractMenu menu} or
+ * {@linkplain AbstractToolBar toolbar}, or grouped within a
+ * {@linkplain AbstractMenuSection section} or
+ * {@linkplain AbstractMenuOptionGroup option group}.
+ * <p>
+ * To define the {@link Action}, implement {@link #createAction()}. The action
+ * should provide both the label/icon (representation) and
+ * {@link ActionListener#actionPerformed(ActionEvent)}.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenuAction</code>) of the menu
+ * action implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractMenuToggle extends AbstractMenuItem {
+ /**
+ * Construct a toggle action to appear within the specified menu component.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The component
+ * should be a {@linkplain MenuType#isParentType() parent type}
+ * and must have been registered separately as an SPI.
+ * @param positionHint
+ * The position hint to determine the position of this toggle
+ * action among its siblings in the parent menu, section or
+ * toolbar. For extensibility, use BASIC style numbering such as
+ * 10, 20, etc. (Note that position hints are local to each
+ * parent, so each {@linkplain AbstractMenuSection section} have
+ * their own position hint scheme.)
+ */
+ public AbstractMenuToggle(URI parentId, int positionHint) {
+ this(parentId, null, positionHint);
+ }
+
+ /**
+ * Construct a toggle action to appear within the specified menu component.
+ *
+ * @param parentId
+ * The {@link URI} of the parent menu component. The component
+ * should be a {@link MenuType#isParentType() parent type} and
+ * must have been registered separately as an SPI.
+ * @param id
+ * The {@link URI} to identify this toggle action. Although no
+ * components can have an action as their parent, this URI can be
+ * used to retrieve the realisation of this component using
+ * {@link MenuManager#getComponentByURI(URI)}. This ID might also
+ * be registered as a help identifier with the help system.
+ * @param positionHint
+ * The position hint to determine the position of this action
+ * among its siblings in the parent menu, section or toolbar. For
+ * extensibility, use BASIC style numbering such as 10, 20, etc.
+ * (Note that position hints are local to each parent, so each
+ * {@linkplain AbstractMenuSection section} have their own
+ * position hint scheme.)
+ */
+ public AbstractMenuToggle(URI parentId, URI id, int positionHint) {
+ super(MenuType.toggle, parentId, id);
+ this.positionHint = positionHint;
+ }
+
+ /**
+ * Call {@link #createAction()} on first call, after that return cached
+ * action.
+ *
+ * @see #createAction()
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized Action getAction() {
+ if (action == null)
+ action = createAction();
+ return action;
+ }
+
+ /**
+ * Create the {@link Action} that labels this toggle action, in addition to
+ * performing the desired action on
+ * {@link ActionListener#actionPerformed(ActionEvent)}.
+ * <p>
+ * Implementations might use {@link AbstractAction} as a superclass for menu
+ * actions. It is recommended to make the action a top level class so that
+ * it can be used both within an {@link AbstractMenuAction} of a menu bar
+ * and within an {@link AbstractMenuAction} of a tool bar.
+ * </p>
+ *
+ * @return A configured {@link Action} that should at least have a label or
+ * icon.
+ */
+ protected abstract Action createAction();
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractToolBar.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractToolBar.java
new file mode 100644
index 0000000..234ea75
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/AbstractToolBar.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+/**
+ * A {@link MenuComponent} of the type {@link MenuType#toolBar}.
+ * <p>
+ * Subclass to create an SPI implementation for the {@link MenuManager} of a
+ * toolbar. A toolbar can contain {@linkplain AbstractMenuAction actions},
+ * {@linkplain AbstractMenuToggle toggles} or {@linkplain AbstractMenuCustom
+ * custom components}, or any of the above grouped in a
+ * {@linkplain AbstractMenuSection section} or an
+ * {@linkplain AbstractMenuOptionGroup option group}.
+ * <p>
+ * The {@link DefaultToolBar default toolbar} can be used as a parent for items
+ * that are to be returned in the toolbar {@link MenuManager#createToolBar()},
+ * while toolbars from other instances of AbstractToolBar can be created using
+ * {@link MenuManager#createToolBar(URI)} specifying the URI of the toolbar's
+ * identifier.
+ * <p>
+ * Menu components are linked together using URIs, avoiding the need for compile
+ * time dependencies between SPI implementations. To add components to a
+ * toolbar, use the {@link URI} identifying this toolbar as their parent id.
+ * <p>
+ * <strong>Note:</strong> To avoid conflicts with other plugins, use a unique
+ * URI root that is related to the Java package name, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu</code>, and use hash
+ * identifiers for each menu item, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu#run</code> for a "Run"
+ * item. Use flat URI namespaces, don't base a child's URI on the parent's URI,
+ * as this might make it difficult to relocate the parent menu.
+ * <p>
+ * You need to list the {@linkplain Class#getName() fully qualified class name}
+ * (for example <code>com.example.t2plugin.menu.MyMenu</code>) of the toolbar
+ * implementation in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code> so
+ * that it can be discovered by the {@link MenuManager}. This requirement also
+ * applies to parent menu components (except {@link DefaultToolBar} and
+ * {@link DefaultMenuBar}, but ensure they are only listed once.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractToolBar extends AbstractMenuItem {
+ /**
+ * Construct a toolbar with the given {@link URI} as identifier.
+ *
+ * @param id
+ * The {@link URI} to identify this toolbar. Use this as the
+ * parent ID for menu components to appear in this toolbar.
+ */
+ public AbstractToolBar(URI id) {
+ super(MenuType.toolBar, null, id);
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/ContextualMenuComponent.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/ContextualMenuComponent.java
new file mode 100644
index 0000000..080beb1
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/ContextualMenuComponent.java
@@ -0,0 +1,35 @@
+package net.sf.taverna.t2.ui.menu;
+
+import java.awt.Component;
+
+/**
+ * A contextual menu component.
+ * <p>
+ * A {@link MenuComponent} that also implements ContextualMenuComponent, when
+ * included in a menu tree rooted in the {@link DefaultContextualMenu} and
+ * retrieved using
+ * {@link MenuManager#createContextMenu(Object, Object, Component)}, will be
+ * {@linkplain #setContextualSelection(ContextualSelection) informed} before
+ * calls to {@link #isEnabled()} or {@link #getAction()}.
+ * <p>
+ * In this way the contextual menu item can be visible for only certain
+ * selections, and its action can be bound to the current selection.
+ * <p>
+ * Contextual menu components can be grouped by {@linkplain AbstractMenuSection
+ * sections} and {@linkplain AbstractMenu sub-menus}, or directly have the
+ * {@link DefaultContextualMenu} as the parent.
+ *
+ * @see ContextualSelection
+ * @see DefaultContextualMenu
+ * @author Stian Soiland-Reyes
+ */
+public interface ContextualMenuComponent extends MenuComponent {
+ /**
+ * Set the contextual selection, or <code>null</code> if there is no current
+ * selection (if the menu item was not included in a contextual menu).
+ *
+ * @param contextualSelection
+ * The contextual selection
+ */
+ void setContextualSelection(ContextualSelection contextualSelection);
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/ContextualSelection.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/ContextualSelection.java
new file mode 100644
index 0000000..e94307e
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/ContextualSelection.java
@@ -0,0 +1,48 @@
+package net.sf.taverna.t2.ui.menu;
+
+import java.awt.Component;
+
+import javax.swing.JPopupMenu;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * A contextual selection as passed to a {@link ContextualMenuComponent}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class ContextualSelection {
+ private final Object parent;
+ private final Object selection;
+ private final Component relativeToComponent;
+
+ public ContextualSelection(Object parent, Object selection,
+ Component relativeToComponent) {
+ this.parent = parent;
+ this.selection = selection;
+ this.relativeToComponent = relativeToComponent;
+ }
+
+ /**
+ * The parent object of the selected object, for instance a {@link Workflow}.
+ */
+ public Object getParent() {
+ return parent;
+ }
+
+ /**
+ * The selected object which actions in the contextual menu relate to, for
+ * instance a Processor.
+ */
+ public Object getSelection() {
+ return selection;
+ }
+
+ /**
+ * A UI component which the returned {@link JPopupMenu} (and it's actions)
+ * is to be relative to, for instance as a parent of pop-up dialogues.
+ */
+ public Component getRelativeToComponent() {
+ return relativeToComponent;
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultContextualMenu.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultContextualMenu.java
new file mode 100644
index 0000000..0db13a7
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultContextualMenu.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu;
+
+import java.net.URI;
+
+/**
+ * The default contextual menu, created using
+ * {@link MenuManager#createContextMenu(Object, Object, java.awt.Component)()}.
+ * <p>
+ * Items that are part of a contextual menu should also implement
+ * {@link ContextualMenuComponent}, the menu manager will then be able to tell
+ * the items what is the current selection for the contextual menu, so that the
+ * items can update their {@link MenuComponent#isEnabled()} (only visible for
+ * some selections) and {@link MenuComponent#getAction()} (the action needs the
+ * selected object).
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class DefaultContextualMenu extends AbstractMenu {
+ /**
+ * The URI of a menu item representing the default menu bar. Menu items who
+ * has this URI as their {@link #getParentId()} will be shown in the top
+ * menu of the main application window.
+ */
+ public static final URI DEFAULT_CONTEXT_MENU = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#defaultContextMenu");
+
+ /**
+ * Construct the default menu bar
+ */
+ public DefaultContextualMenu() {
+ super(DEFAULT_CONTEXT_MENU);
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultMenuBar.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultMenuBar.java
new file mode 100644
index 0000000..8c5eab6
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultMenuBar.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+/**
+ * The default {@link AbstractMenu menu bar} that appears in the main
+ * application window, created using {@link MenuManager#createMenuBar()}.
+ * Alternative menu bars can be created using
+ * {@link MenuManager#createMenuBar(URI)} - referring to the URI of another
+ * instance of {@link AbstractMenu}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class DefaultMenuBar extends AbstractMenu {
+ /**
+ * The URI of a menu item representing the default menu bar. Menu items who
+ * has this URI as their {@link #getParentId()} will be shown in the top
+ * menu of the main application window.
+ */
+ public static final URI DEFAULT_MENU_BAR = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#defaultMenuBar");
+
+ /**
+ * Construct the default menu bar
+ *
+ */
+ public DefaultMenuBar() {
+ super(DEFAULT_MENU_BAR);
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultToolBar.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultToolBar.java
new file mode 100644
index 0000000..302c37a
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DefaultToolBar.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+/**
+ * The default tool bar that will be shown by the main application window. Use
+ * {@link #DEFAULT_TOOL_BAR} as the {@link #getParentId()} for items that should
+ * appear in this toolbar. This toolbar can be created using
+ * {@link MenuManager#createToolBar()}
+ * <p>
+ * Separate toolbars can be made by subclassing {@link AbstractToolBar} and
+ * created by using {@link MenuManager#createToolBar(URI)}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class DefaultToolBar extends AbstractToolBar {
+ /**
+ * The URI of a tool bar item representing the default tool bar. Items who
+ * has this URI as their {@link #getParentId()} will be shown in the default
+ * toolbar of the main application window.
+ */
+ public static final URI DEFAULT_TOOL_BAR = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#defaultToolBar");
+
+ /**
+ * Construct the default toolbar.
+ */
+ public DefaultToolBar() {
+ super(DEFAULT_TOOL_BAR);
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DesignOnlyAction.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DesignOnlyAction.java
new file mode 100644
index 0000000..7260fdf
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DesignOnlyAction.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+/**
+ * Marker interface for actions that are valid only when the design perspective
+ * is selected.
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public interface DesignOnlyAction {
+
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DesignOrResultsAction.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DesignOrResultsAction.java
new file mode 100644
index 0000000..26e6b62
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/DesignOrResultsAction.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+/**
+ * Marker interface for actions that are valid the design or result perspectives
+ * are selected.
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public interface DesignOrResultsAction {
+
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/MenuComponent.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/MenuComponent.java
new file mode 100644
index 0000000..5bc91c4
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/MenuComponent.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JMenu;
+import javax.swing.JToolBar;
+import javax.swing.MenuElement;
+
+/**
+ * A menu component, including sub menus, toolbars, and menu items.
+ * <p>
+ * This is an {@link net.sf.taverna.t2.spi.SPIRegistry SPI}, and implementations
+ * should list their fully qualified classnames in
+ * <tt>META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</tt> to be
+ * discovered by the {@link MenuManager}.
+ *
+ * @author Stian Soiland-Reyes
+ * @author David Withers
+ */
+public interface MenuComponent {
+ /**
+ * The {@link Action} describing this menu item, used for creating the UI
+ * representation of this item.
+ * <p>
+ * As a minimum the action should contain a name, and optionally an icon, a
+ * description and a keyboard shortcut. For {@linkplain MenuType#action
+ * actions} and {@linkplain MenuType#toggle toggles} the {@link Action}'s
+ * {@link ActionListener#actionPerformed(ActionEvent)} method is called when
+ * the item is clicked/selected.
+ * <p>
+ * This action is ignored and should be <code>null</code> for items of type
+ * {@link MenuType#optionGroup} and {@link MenuType#custom}. The action is
+ * optional for {@linkplain MenuType#toolBar toolbars} and
+ * {@linkplain MenuType#section sections}, where the action's name would be
+ * used as a label.
+ *
+ * @return The {@link Action} describing this menu item, or
+ * <code>null</code> if the {@link #getType()} is
+ * {@link MenuType#section}, {@link MenuType#optionGroup} or
+ * {@link MenuType#custom}.
+ */
+ public Action getAction();
+
+ /**
+ * Get a custom {@link Component} to be inserted into the parent
+ * menu/toolbar.
+ * <p>
+ * Used instead of creating menu elements from the {@link #getAction()} if
+ * the {@link #getType()} is {@link MenuType#custom}. This can be used to
+ * include dynamic menus.
+ * <p>
+ * This value is ignored and should be <code>null</code> for all other types
+ * except {@link MenuType#custom}.
+ *
+ * @return A {@link Component} to be inserted into the parent menu/toolbar.
+ */
+ public Component getCustomComponent();
+
+ /**
+ * The {@link URI} to identify this menu item.
+ * <p>
+ * This identifier can be used with other menu item's {@link #getParentId()}
+ * if this item has a {@link #getType()} of {@link MenuType#menu},
+ * {@link MenuType#toolBar}, {@link MenuType#section} or
+ * {@link MenuType#optionGroup}.
+ * <p>
+ * Leaf menu items of {@link #getType()} {@link MenuType#toggle},
+ * {@link MenuType#custom} and {@link MenuType#action} don't need an
+ * identifier as they can't have children, and may return <code>null</code>
+ * instead. However, a valid identifier might be used to look up the
+ * MenuItem with {@link MenuManager#getComponentByURI(URI)}
+ * <p>
+ * <strong>Note:</strong> To avoid conflicts with other plugins, use a
+ * unique URI root that is related to the Java package name, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu</code>, and use hash
+ * identifiers for each menu item, for instance
+ * <code>http://cs.university.ac.uk/myplugin/2008/menu#run</code> for a
+ * "Run" item. Use flat URI namespaces, don't base a child's URI on the
+ * parent's URI, as this might make it difficult to relocate the parent
+ * menu.
+ *
+ * @return The {@link URI} to identify this menu item.
+ */
+ public URI getId();
+
+ /**
+ * The {@link URI} of the parent menu item, as returned by the parent's
+ * {@link #getId()}.
+ * <p>
+ * If this is the {@link DefaultMenuBar#DEFAULT_MENU_BAR}, then this menu
+ * item will be one of the top level menus of the main application window,
+ * like "File" or "Edit", and must have {@link #getType()}
+ * {@link MenuType#menu}.
+ * <p>
+ * This value should be <code>null</code> if this item is of
+ * {@link #getType()} {@link MenuType#toolBar}, and could be
+ * <code>null</code> if this is an independent root menu of type
+ * {@link MenuType#menu} (to be used outside the main window).
+ * <p>
+ * <strong>Note:</strong> To avoid compile time and runtime dependency on
+ * the parent menu item, always construct this URI directly using
+ * {@link URI#create(String)}.
+ *
+ * @return The {@link URI} of the parent menu item.
+ */
+ public URI getParentId();
+
+ /**
+ * A hint on how to position this item below the parent.
+ * <p>
+ * Menu items within the same parent menu/group/toolBar are ordered
+ * according to this position hint. If several items have the same position
+ * hint, their internal order is undefined, although generally it will be
+ * the order in which they were loaded.
+ * <p>
+ * <strong>Tip:</strong> Number the position hints in BASIC style, such as
+ * 10, 20, etc. so that plugins can use position hint such as 19 or 21 to be
+ * immediately before or after your item.
+ *
+ * @return A position hint
+ */
+ public int getPositionHint();
+
+ /**
+ * The {@link MenuType type} of menu item.
+ * <p>
+ * In the simple case of a "File -> New" menu structure, the "File" menu
+ * item has a type of {@link MenuType#menu}, while the "New" has a type of
+ * {@link MenuType#action}.
+ * <p>
+ * The menu item can only have children (i.e., items with
+ * {@link #getParentId()} equalling to this item's {@link #getId()}) if the
+ * type is not a leaf type, i.e., not {@link MenuType#toggle} or
+ * {@link MenuType#action}.
+ *
+ * @return A {@link MenuType} to specify the role of this menu item.
+ */
+ public MenuType getType();
+
+ /**
+ * True if this menu component is to be included in the menu/toolbar.
+ *
+ * @return True is this menu component is to be included
+ */
+ public boolean isEnabled();
+
+ /**
+ * The type of menu item, such as {@link #action}, {@link #menu} or
+ * {@link #toolBar}.
+ * <p>
+ * Some types are {@linkplain #isParentType() parent types} - that means
+ * URIs to menu components of that type can be used as a
+ * {@linkplain MenuComponent#getParentId() parent id}.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ public static enum MenuType {
+ /**
+ * A normal {@link Action} as part of a {@link #menu}, {@link #toolBar},
+ * {@link #section} or {@link #optionGroup}. Such menu items are leaf
+ * nodes, which no {@link MenuComponent}s can have this as it's
+ * {@link MenuComponent#getParentId()}. The action's
+ * {@link ActionListener#actionPerformed(ActionEvent)} will be called
+ * when choosing/clicking the menu item from the menu or toolBar.
+ */
+ action,
+ /**
+ * Provide a customised {@link MenuElement} from
+ * {@link MenuComponent#getCustomComponent()} that is to be used instead
+ * of creating an element from {@link MenuComponent#getAction()}.
+ */
+ custom,
+ /**
+ * A group containing mutually exclusive choices (as {@link #action}s),
+ * to be grouped in a {@link ButtonGroup}, separated using
+ * {@link JMenu#addSeparator()} or {@link JToolBar#addSeparator()} when
+ * needed. The {@link MenuComponent#getAction()} is ignored and should
+ * be <code>null</code>.
+ */
+ optionGroup,
+ /**
+ * A section of menu items within {@link #menu} or {@link #toolBar}.
+ * Sections are separated using {@link JMenu#addSeparator()} or
+ * {@link JToolBar#addSeparator()} when needed. The
+ * {@link MenuComponent#getAction()} is ignored and should be
+ * <code>null</code>.
+ */
+ section,
+ /**
+ * A (sub)menu that contain other menu items, including deeper
+ * {@link #menu}s. The {@link Action} from
+ * {@link MenuComponent#getAction()} is used to find the name, icon,
+ * etc., for the sub-menu, while its
+ * {@link ActionListener#actionPerformed(ActionEvent)} method is
+ * ignored. The {@link DefaultMenuBar} is the default top level menu,
+ * although others can be created with <code>null</code> as their
+ * parent.
+ */
+ menu,
+ /**
+ * A boolean toggle action, the action will be shown as a
+ * {@link JCheckBox} on a menu or toolBar. Such menu items are leaf
+ * nodes, which no {@link MenuComponent}s can have this as it's
+ * {@link MenuComponent#getParentId()}. The action's
+ * {@link ActionListener#actionPerformed(ActionEvent)} will be called
+ * when toggling the action.
+ */
+ toggle,
+ /**
+ * A toolBar containing {@link #optionGroup}s, {@link #toggle}s or
+ * {@link #action}s. The toolBar can be shown as a {@link JToolBar}. The
+ * {@link MenuComponent#getAction()} and
+ * {@link MenuComponent#getParentId()} are ignored and should be
+ * <code>null</code>.
+ */
+ toolBar;
+
+ private static final Set<MenuType> parentTypes = defineParentTypes();
+
+ /**
+ * True if the menu type is a parent type such as {@link #optionGroup},
+ * {@link #section}, {@link #menu} or {@link #toolBar}. If the type of a
+ * menu component is a a parent type it can (should) have children,
+ * i.e., the children has a {@link MenuComponent#getParentId()} that
+ * equals the parent's {@link MenuComponent#getId()}.
+ *
+ * @return True if the menu type is a parent type.
+ */
+ public boolean isParentType() {
+ return parentTypes.contains(this);
+ }
+
+ /**
+ * Create the set of {@link MenuType}s that {@link #isParentType()}
+ * would return <code>true</code> for.
+ *
+ * @return A {@link Set} of {@link MenuType}s.
+ */
+ private static Set<MenuType> defineParentTypes() {
+ HashSet<MenuType> types = new HashSet<>();
+ types.add(optionGroup);
+ types.add(section);
+ types.add(menu);
+ types.add(toolBar);
+ return types;
+ }
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/MenuManager.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/MenuManager.java
new file mode 100644
index 0000000..cca1bf0
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/MenuManager.java
@@ -0,0 +1,339 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.awt.Component;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JToolBar;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.MenuComponent.MenuType;
+import net.sf.taverna.t2.ui.menu.MenuManager.MenuManagerEvent;
+
+/**
+ * Create {@link JMenuBar}s and {@link JToolBar}s based on SPI instances of
+ * {@link MenuComponent}.
+ * <p>
+ * Elements of menus are discovered automatically using an {@link SPIRegistry}.
+ * The elements specify their internal relationship through
+ * {@link MenuComponent#getParentId()} and
+ * {@link MenuComponent#getPositionHint()}. {@link MenuComponent#getType()}
+ * specifies how the component is to be rendered or grouped.
+ * <p>
+ * The menu manager is {@link Observable}, you can
+ * {@linkplain #addObserver(Observer) add an observer} to be notified when the
+ * menus have changed, i.e. when {@link #update()} has been called, for instance
+ * when the {@link SPIRegistry} (which the menu manager observes) has been
+ * updated due to a plugin installation.
+ * <p>
+ * {@link #createMenuBar()} creates the default menu bar, ie. the menu bar
+ * containing all the items with {@link DefaultMenuBar#DEFAULT_MENU_BAR} as
+ * their parent. Alternate menu bars can be created using
+ * {@link #createMenuBar(URI)}.
+ * <p>
+ * Similary {@link #createToolBar()} creates the default tool bar, containing
+ * the items that has {@link DefaultToolBar#DEFAULT_TOOL_BAR} as their parent.
+ * Alternate toolbars can be created using {@link #createToolBar(URI)}.
+ * <p>
+ * The menu manager keeps weak references to the created (published) menu bars
+ * and tool bars, and will attempt to update them when {@link #update()} is
+ * called.
+ * <p>
+ * See the package level documentation for more information about how to specify
+ * menu elements.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public interface MenuManager extends Observable<MenuManagerEvent> {
+ /**
+ * Add the items from the list of menu items to the parent menu with
+ * expansion sub-menus if needed.
+ * <p>
+ * If the list contains more than <tt>maxItemsInMenu</tt> items, a series of
+ * sub-menus will be created and added to the parentMenu instead, each
+ * containing a maximum of <tt>maxItemsInMenu</tt> items. (Note that if
+ * menuItems contains more than <tt>maxItemsInMenu*maxItemsInMenu</tt>
+ * items, there might be more than <tt>maxItemsInMenu</tt> sub-menus added
+ * to the parent).
+ * <p>
+ * The sub-menus are titled according to the {@link JMenuItem#getText()} of
+ * the first and last menu item it contains - assuming that they are already
+ * sorted.
+ * <p>
+ * The optional {@link ComponentFactory} headerItemFactory, if not
+ * <code>null</code>, will be invoked to create a header item that will be
+ * inserted on top of the sub-menus. This item does not count towards
+ * <tt>maxItemsInMenu</tt>.
+ * <p>
+ * Note that this is a utility method that does not mandate the use of the
+ * {@link MenuManager} structure for the menu.
+ *
+ * @param menuItems
+ * {@link JMenuItem}s to be inserted
+ * @param parentMenu
+ * Menu to insert items to
+ * @param maxItemsInMenu
+ * Maximum number of items in parent menu or created sub-menus
+ * @param headerItemFactory
+ * If not <code>null</code>, a {@link ComponentFactory} to create
+ * a header item to insert at top of created sub-menus
+ */
+ abstract void addMenuItemsWithExpansion(List<JMenuItem> menuItems,
+ JMenu parentMenu, int maxItemsInMenu,
+ ComponentFactory headerItemFactory);
+
+ /**
+ * Create a contextual menu for a selected object.
+ * <p>
+ * Items for the contextual menues are discovered in a similar to fashion as
+ * with {@link #createMenuBar()}, but using {@link DefaultContextualMenu} as
+ * the root.
+ * <p>
+ * Additionally, items implementing {@link ContextualMenuComponent} will be
+ * {@linkplain ContextualMenuComponent#setContextualSelection(Object, Object, Component)
+ * informed} about what is the current selection, as passed to this method.
+ * <p>
+ * Thus, the items can choose if they want to be
+ * {@link MenuComponent#isEnabled() visible} or not for a given selection,
+ * and return an action that is bound it to the selection.
+ *
+ * @param parent
+ * The parent object of the selected object, for instance a
+ * {@link Workflow}.
+ * @param selection
+ * The selected object which actions in the contextual menu
+ * relate to, for instance a {@link Processor}
+ * @param relativeToComponent
+ * A UI component which the returned {@link JPopupMenu} (and it's
+ * actions) is to be relative to, for instance as a parent of
+ * pop-up dialogues.
+ * @return An empty or populated {@link JPopupMenu} depending on the
+ * selected objects.
+ */
+ abstract JPopupMenu createContextMenu(Object parent, Object selection,
+ Component relativeToComponent);
+
+ /**
+ * Create the {@link JMenuBar} containing menu elements defining
+ * {@link DefaultMenuBar#DEFAULT_MENU_BAR} as their
+ * {@linkplain MenuComponent#getParentId() parent}.
+ * <p>
+ * A {@linkplain WeakReference weak reference} is kept in the menu manager
+ * to update the menubar if {@link #update()} is called (manually or
+ * automatically when the SPI is updated).
+ *
+ * @return A {@link JMenuBar} populated with the items belonging to the
+ * default menu bar
+ */
+ abstract JMenuBar createMenuBar();
+
+ /**
+ * Create the {@link JMenuBar} containing menu elements defining the given
+ * <code>id</code> as their {@linkplain MenuComponent#getParentId() parent}.
+ * <p>
+ * Note that the parent itself also needs to exist as a registered SPI
+ * instance og {@link MenuComponent#getType()} equal to
+ * {@link MenuType#menu}, for instance by subclassing {@link AbstractMenu}.
+ * <p>
+ * A {@linkplain WeakReference weak reference} is kept in the menu manager
+ * to update the menubar if {@link #update()} is called (manually or
+ * automatically when the SPI is updated).
+ *
+ * @param id
+ * The {@link URI} identifying the menu bar
+ * @return A {@link JMenuBar} populated with the items belonging to the
+ * given parent id.
+ */
+ abstract JMenuBar createMenuBar(URI id);
+
+ /**
+ * Create the {@link JToolBar} containing elements defining
+ * {@link DefaultToolBar#DEFAULT_TOOL_BAR} as their
+ * {@linkplain MenuComponent#getParentId() parent}.
+ * <p>
+ * A {@linkplain WeakReference weak reference} is kept in the menu manager
+ * to update the toolbar if {@link #update()} is called (manually or
+ * automatically when the SPI is updated).
+ *
+ * @return A {@link JToolBar} populated with the items belonging to the
+ * default tool bar
+ */
+ abstract JToolBar createToolBar();
+
+ /**
+ * Create the {@link JToolBar} containing menu elements defining the given
+ * <code>id</code> as their {@linkplain MenuComponent#getParentId() parent}.
+ * <p>
+ * Note that the parent itself also needs to exist as a registered SPI
+ * instance of {@link MenuComponent#getType()} equal to
+ * {@link MenuType#toolBar}, for instance by subclassing
+ * {@link AbstractToolBar}.
+ * <p>
+ * A {@linkplain WeakReference weak reference} is kept in the menu manager
+ * to update the toolbar if {@link #update()} is called (manually or
+ * automatically when the SPI is updated).
+ *
+ * @param id
+ * The {@link URI} identifying the tool bar
+ * @return A {@link JToolBar} populated with the items belonging to the
+ * given parent id.
+ */
+ abstract JToolBar createToolBar(URI id);
+
+ /**
+ * Get a menu item identified by the given URI.
+ * <p>
+ * Return the UI {@link Component} last created for a {@link MenuComponent},
+ * through {@link #createMenuBar()}, {@link #createMenuBar(URI)},
+ * {@link #createToolBar()} or {@link #createToolBar(URI)}.
+ * <p>
+ * For instance, if {@link #createMenuBar()} created a menu bar containing a
+ * "File" menu with {@link MenuComponent#getId() getId()} ==
+ * <code>http://example.com/menu#file</code>, calling:
+ *
+ * <pre>
+ * Component fileMenu = getComponentByURI(URI
+ * .create("http://example.com/menu#file"));
+ * </pre>
+ *
+ * would return the {@link JMenu} last created for "File". Note that "last
+ * created" could mean both the last call to {@link #createMenuBar()} and
+ * last call to {@link #update()} - which could have happened because the
+ * SPI registry was updated. To be notified when
+ * {@link #getComponentByURI(URI)} might return a new Component because the
+ * menues have been reconstructed, {@linkplain #addObserver(Observer) add an
+ * observer} to the MenuManager.
+ * <p>
+ * If the URI is unknown, has not yet been rendered as a {@link Component},
+ * or the Component is no longer in use outside the menu manager's
+ * {@linkplain WeakReference weak references}, <code>null</code> is returned
+ * instead.
+ *
+ * @see #getURIByComponent(Component)
+ * @param id
+ * {@link URI} of menu item as returned by
+ * {@link MenuComponent#getId()}
+ * @return {@link Component} as previously generated by
+ * {@link #createMenuBar()}/{@link #createToolBar()}, or
+ * <code>null</code> if the URI is unknown, or if the
+ * {@link Component} no longer exists.
+ */
+ public abstract Component getComponentByURI(URI id);
+
+ /**
+ * Get the URI of the {@link MenuComponent} this menu/toolbar
+ * {@link Component} was created from.
+ * <p>
+ * If the component was created by the MenuManager, through
+ * {@link #createMenuBar()}, {@link #createMenuBar(URI)},
+ * {@link #createToolBar()} or {@link #createToolBar(URI)}, the URI
+ * identifying the defining {@link MenuComponent} is returned. This will be
+ * the same URI as returned by {@link MenuComponent#getId()}.
+ * <p>
+ * Note that if {@link #update()} has been invoked, the {@link MenuManager}
+ * might have rebuilt the menu structure and replaced the components since
+ * the given <code>component</code> was created. The newest
+ * {@link Component} for the given URI can be retrieved using
+ * {@link #getComponentByURI(URI)}.
+ * <p>
+ * If the component is unknown, <code>null</code> is returned instead.
+ *
+ * @see #getComponentByURI(URI)
+ * @param component
+ * {@link Component} that was previously created by the
+ * {@link MenuManager}
+ * @return {@link URI} identifying the menu component, as returned by
+ * {@link MenuComponent#getId()}, or <code>null</code> if the
+ * component is unknown.
+ */
+ abstract URI getURIByComponent(Component component);
+
+ /**
+ * Update and rebuild the menu structure.
+ * <p>
+ * Rebuild menu structure as defined by the {@link MenuComponent}s retrieved
+ * from the MenuComponent {@link SPIRegistry}.
+ * <p>
+ * Rebuilds previously published menubars and toolbars created with
+ * {@link #createMenuBar()}, {@link #createMenuBar(URI)},
+ * {@link #createToolBar()} and {@link #createToolBar(URI)}. Note that the
+ * rebuild will do a removeAll() on the menubar/toolbar, so all components
+ * will be reconstructed. You can use {@link #getComponentByURI(URI)} to
+ * look up individual components within the menu and toolbars.
+ * <p>
+ * Note that the menu manager is observing the {@link SPIRegistry}, so if a
+ * plugin gets installed and the SPI registry is updated, this update method
+ * will be called by the SPI registry observer.
+ * <p>
+ * If there are several concurrent calls to {@link #update()}, the calls
+ * from the other thread will return immediately, while the first thread to
+ * get the synchronization lock on the menu manager will do the actual
+ * update. If you want to ensure that {@link #update()} does not return
+ * before the update has been performed fully, synchronize on the menu
+ * manager:
+ *
+ * <pre>
+ * MenuManager menuManager = MenuManager.getInstance();
+ * synchronized (menuManager) {
+ * menuManager.update();
+ * }
+ * doSomethingAfterUpdateFinished();
+ * </pre>
+ */
+ abstract void update();
+
+ /**
+ * Abstract class for events sent to {@linkplain Observer observers} of the
+ * menu manager.
+ *
+ * @see UpdatedMenuManagerEvent
+ * @author Stian Soiland-Reyes
+ */
+ static abstract class MenuManagerEvent {
+ }
+
+ /**
+ * Event sent to observers registered by
+ * {@link MenuManager#addObserver(Observer)} when the menus have been
+ * updated, i.e. when {@link MenuManager#update()} has been called.
+ */
+ static class UpdatedMenuManagerEvent extends MenuManagerEvent {
+ }
+
+ /**
+ * A factory for making {@link Component}s, in particular for making headers
+ * (like {@link JLabel}s) for
+ * {@link MenuManager#addMenuItemsWithExpansion(List, JMenu, int, ComponentFactory)}
+ */
+ interface ComponentFactory {
+ public Component makeComponent();
+ }
+}
diff --git a/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/package-info.java b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/package-info.java
new file mode 100644
index 0000000..4c86db5
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/java/net/sf/taverna/t2/ui/menu/package-info.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/**
+ * An {@link net.sf.taverna.t2.spi.SPIRegistry SPI} based system for creating
+ * {@link javax.swing.JMenuBar menues} and {@link javax.swing.JToolBar toolbars}.
+ * <p>
+ * Each element of a menu and/or toolbar is created by making an SPI
+ * implementation class of {@link net.sf.taverna.t2.ui.menu.MenuComponent} and listing the fully qualified
+ * class name in the SPI description resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent</code>
+ * </p>
+ * <p>
+ * The {@link net.sf.taverna.t2.ui.menu.MenuManager} discovers all menu components using an SPI registry,
+ * and builds the {@link javax.swing.JMenuBar menu bar} using
+ * {@link net.sf.taverna.t2.ui.menu.MenuManager#createMenuBar()} or the
+ * {@link javax.swing.JToolBar toolbar} using
+ * {@link net.sf.taverna.t2.ui.menu.MenuManager#createToolBar()}.
+ * </p>
+ * <p>
+ * This allows plugins to provide actions (menu items) and submenues that can be
+ * inserted at any points in the generated menu. All parts of the menues are
+ * described through a parent/child relationship using
+ * {@link net.sf.taverna.t2.ui.menu.MenuComponent#getId()} and {@link net.sf.taverna.t2.ui.menu.MenuComponent#getParentId()}. The
+ * components are identified using {@link java.net.URI}s to avoid compile time
+ * dependencies, so a plugin can for instance add something to the existing
+ * "Edit" menu without depending on the actual implementation of the
+ * {@link net.sf.taverna.t2.ui.menu.MenuComponent} describing "Edit", as long as it refers to the same
+ * URI. The use of URIs instead of pure strings is to encourage the use of
+ * unique identifiers, for instance plugins should use an URI base that is
+ * derived from their package name to avoid collision with other plugins.
+ * </p>
+ * <p>
+ * A set of abstract classes, with a common parent {@link net.sf.taverna.t2.ui.menu.AbstractMenuItem},
+ * make it more convenient to create simple SPI implementations. Two default top
+ * level implementations {@link net.sf.taverna.t2.ui.menu.DefaultMenuBar} and {@link net.sf.taverna.t2.ui.menu.DefaultToolBar} can
+ * be used as parents for items that are to be included in
+ * {@link net.sf.taverna.t2.ui.menu.MenuManager#createMenuBar()} and {@link net.sf.taverna.t2.ui.menu.MenuManager#createToolBar()},
+ * but it's possible to have other parents - such menu trees would have to be
+ * created by providing the URI of the top level parent to
+ * {@link net.sf.taverna.t2.ui.menu.MenuManager#createMenuBar(URI)} or
+ * {@link net.sf.taverna.t2.ui.menu.MenuManager#createToolBar(URI)}.
+ * </p>
+ * <p>
+ * In the simplest form a menu structure can be built by subclassing
+ * {@link net.sf.taverna.t2.ui.menu.AbstractMenu} and {@link net.sf.taverna.t2.ui.menu.AbstractMenuAction}, but more complex menus
+ * can be built by including submenus (AbstractMenu with an AbstractMenu as a
+ * parent), grouping similar actions in a {@link net.sf.taverna.t2.ui.menu.AbstractMenuSection section},
+ * or making {@link net.sf.taverna.t2.ui.menu.AbstractMenuToggle toggle actions} and
+ * {@link net.sf.taverna.t2.ui.menu.AbstractMenuOptionGroup option groups}. You can add arbitrary "real"
+ * {@link javax.swing.JMenuBar} / {@link javax.swing.JToolBar} compatible items
+ * (such as {@link javax.swing.JMenu}s, {@link javax.swing.JMenuItem}s and
+ * {@link javax.swing.JButton}s) using
+ * {@link net.sf.taverna.t2.ui.menu.AbstractMenuCustom custom menu items}.
+ * </p>
+ *
+ * <p>
+ * Example showing how <code>File->Open</code> could be implemented using
+ * two SPI implementations net.sf.taverna.t2.ui.perspectives.hello.FileMenu and
+ * net.sf.taverna.t2.ui.perspectives.hello.FileOpenAction:
+ * </p>
+ *
+ * <pre>
+ * package net.sf.taverna.t2.ui.perspectives.hello;
+ *
+ * import java.net.URI;
+ *
+ * import net.sf.taverna.t2.ui.menu.AbstractMenu;
+ * import net.sf.taverna.t2.ui.menu.DefaultMenuBar;
+ *
+ * public class FileMenu extends AbstractMenu {
+ *
+ * private static final URI FILE_URI = URI
+ * .create("http://taverna.sf.net/2008/t2workbench/test#file");
+ *
+ * public FileMenu() {
+ * super(DefaultMenuBar.DEFAULT_MENU_BAR, 10, FILE_URI, "File");
+ * }
+ *
+ * }
+ * </pre>
+ * <pre>
+ * package net.sf.taverna.t2.ui.perspectives.hello;
+ *
+ * import java.awt.event.ActionEvent;
+ * import java.net.URI;
+ *
+ * import javax.swing.AbstractAction;
+ * import javax.swing.Action;
+ * import javax.swing.JOptionPane;
+ *
+ * import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+ *
+ * public class FileOpenAction extends AbstractMenuAction {
+ * public FileOpenAction() {
+ * super(URI.create("http://taverna.sf.net/2008/t2workbench/test#file"),
+ * 20);
+ * }
+ *
+ * @Override
+ * public Action createAction() {
+ * return new AbstractAction("Open") {
+ * public void actionPerformed(ActionEvent arg0) {
+ * JOptionPane.showMessageDialog(null, "Open");
+ * }
+ * };
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * The implementation of the {@link net.sf.taverna.t2.ui.menu.MenuManager} itself is discovered by an
+ * internal SPI registry through {@link net.sf.taverna.t2.ui.menu.MenuManager#getInstance()}. The menu
+ * manager is observing the SPI registry, so that any updates to the registry
+ * from installing plugins etc. are reflected in an automatic rebuild of the
+ * menus. This update can also be triggered manually by calling
+ * {@link net.sf.taverna.t2.ui.menu.MenuManager#update()}.
+ * </p>
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+package net.sf.taverna.t2.ui.menu;
+
diff --git a/taverna-workbench-menu-api/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-menu-api/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..c137386
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,4 @@
+net.sf.taverna.t2.ui.menu.DefaultMenuBar
+net.sf.taverna.t2.ui.menu.DefaultToolBar
+net.sf.taverna.t2.ui.menu.DefaultContextualMenu
+
diff --git a/taverna-workbench-menu-api/src/main/resources/META-INF/spring/menu-api-context-osgi.xml b/taverna-workbench-menu-api/src/main/resources/META-INF/spring/menu-api-context-osgi.xml
new file mode 100644
index 0000000..b27e860
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/resources/META-INF/spring/menu-api-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="DefaultMenuBar" auto-export="interfaces" />
+ <service ref="DefaultToolBar" auto-export="interfaces" />
+ <service ref="DefaultContextualMenu" auto-export="interfaces" />
+
+</beans:beans>
diff --git a/taverna-workbench-menu-api/src/main/resources/META-INF/spring/menu-api-context.xml b/taverna-workbench-menu-api/src/main/resources/META-INF/spring/menu-api-context.xml
new file mode 100644
index 0000000..734dbbe
--- /dev/null
+++ b/taverna-workbench-menu-api/src/main/resources/META-INF/spring/menu-api-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="DefaultMenuBar" class="net.sf.taverna.t2.ui.menu.DefaultMenuBar" />
+ <bean id="DefaultToolBar" class="net.sf.taverna.t2.ui.menu.DefaultToolBar" />
+ <bean id="DefaultContextualMenu" class="net.sf.taverna.t2.ui.menu.DefaultContextualMenu" />
+
+</beans>
diff --git a/taverna-workbench-menu-impl/pom.xml b/taverna-workbench-menu-impl/pom.xml
new file mode 100644
index 0000000..d95bf49
--- /dev/null
+++ b/taverna-workbench-menu-impl/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>menu-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Menu generation implementation</name>
+ <description>The main workbench ui</description>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Embed-Dependency>javahelp</Embed-Dependency>
+ <Import-Package>org.jdesktop.jdic.browser;resolution:=optional,*</Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java
new file mode 100644
index 0000000..ee1fd3d
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java
@@ -0,0 +1,880 @@
+/*******************************************************************************
+ * 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.ui.menu.impl;
+
+import static java.lang.Math.min;
+import static javax.help.CSH.setHelpIDString;
+import static javax.swing.Action.NAME;
+import static javax.swing.Action.SHORT_DESCRIPTION;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.ui.menu.AbstractMenuSection.SECTION_COLOR;
+import static net.sf.taverna.t2.ui.menu.DefaultContextualMenu.DEFAULT_CONTEXT_MENU;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.WeakHashMap;
+
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.AbstractMenuOptionGroup;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.ui.menu.DesignOrResultsAction;
+import net.sf.taverna.t2.ui.menu.MenuComponent;
+import net.sf.taverna.t2.ui.menu.MenuComponent.MenuType;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementation of {@link MenuManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class MenuManagerImpl implements MenuManager {
+ private static Logger logger = Logger.getLogger(MenuManagerImpl.class);
+
+ private boolean needsUpdate;
+ /**
+ * Cache used by {@link #getURIByComponent(Component)}
+ */
+ private WeakHashMap<Component, URI> componentToUri;
+ /**
+ * {@link MenuElementComparator} used for sorting menu components from the
+ * SPI registry.
+ */
+ private MenuElementComparator menuElementComparator = new MenuElementComparator();
+ /**
+ * Map of {@link URI} to it's discovered children. Populated by
+ * {@link #findChildren()}.
+ */
+ private HashMap<URI, List<MenuComponent>> menuElementTree;
+ /**
+ * Multicaster to distribute messages to {@link Observer}s of this menu
+ * manager.
+ */
+ private MultiCaster<MenuManagerEvent> multiCaster;
+ /**
+ * Lock for {@link #update()}
+ */
+ private final Object updateLock = new Object();
+ /**
+ * True if {@link #doUpdate()} is running, subsequents call to
+ * {@link #update()} will return immediately.
+ */
+ private boolean updating;
+ /**
+ * Cache used by {@link #getComponentByURI(URI)}
+ */
+ private Map<URI, WeakReference<Component>> uriToComponent;
+ /**
+ * Map from {@link URI} to defining {@link MenuComponent}. Children are in
+ * {@link #menuElementTree}.
+ */
+ private Map<URI, MenuComponent> uriToMenuElement;
+ // Note: Not reset by #resetCollections()
+ private Map<URI, List<WeakReference<Component>>> uriToPublishedComponents = new HashMap<>();
+ private List<MenuComponent> menuComponents = new ArrayList<>();
+
+ /**
+ * Construct the MenuManagerImpl. Observes the SPI registry and does an
+ * initial {@link #update()}.
+ */
+ public MenuManagerImpl() {
+ multiCaster = new MultiCaster<>(this);
+ needsUpdate = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addMenuItemsWithExpansion(List<JMenuItem> menuItems,
+ JMenu parentMenu, int maxItemsInMenu,
+ ComponentFactory headerItemFactory) {
+ if (menuItems.size() <= maxItemsInMenu) {
+ // Just add them directly
+ for (JMenuItem menuItem : menuItems)
+ parentMenu.add(menuItem);
+ return;
+ }
+ int index = 0;
+ while (index < menuItems.size()) {
+ int toIndex = min(menuItems.size(), index + maxItemsInMenu);
+ if (toIndex == menuItems.size() - 1)
+ // Don't leave a single item left for the last subMenu
+ toIndex--;
+ List<JMenuItem> subList = menuItems.subList(index, toIndex);
+ JMenuItem firstItem = subList.get(0);
+ JMenuItem lastItem = subList.get(subList.size() - 1);
+ JMenu subMenu = new JMenu(firstItem.getText() + " ... "
+ + lastItem.getText());
+ if (headerItemFactory != null)
+ subMenu.add(headerItemFactory.makeComponent());
+ for (JMenuItem menuItem : subList)
+ subMenu.add(menuItem);
+ parentMenu.add(subMenu);
+ index = toIndex;
+ }
+ }
+
+ @Override
+ public void addObserver(Observer<MenuManagerEvent> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ @Override
+ public JPopupMenu createContextMenu(Object parent, Object selection,
+ Component relativeToComponent) {
+ ContextualSelection contextualSelection = new ContextualSelection(
+ parent, selection, relativeToComponent);
+ JPopupMenu popupMenu = new JPopupMenu();
+ populateContextMenu(popupMenu, DEFAULT_CONTEXT_MENU,
+ contextualSelection);
+ registerComponent(DEFAULT_CONTEXT_MENU, popupMenu, true);
+ return popupMenu;
+ }
+
+ @Override
+ public JMenuBar createMenuBar() {
+ return createMenuBar(DEFAULT_MENU_BAR);
+ }
+
+ @Override
+ public JMenuBar createMenuBar(URI id) {
+ JMenuBar menuBar = new JMenuBar();
+ if (needsUpdate)
+ update();
+ populateMenuBar(menuBar, id);
+ registerComponent(id, menuBar, true);
+ return menuBar;
+ }
+
+ @Override
+ public JToolBar createToolBar() {
+ return createToolBar(DEFAULT_TOOL_BAR);
+ }
+
+ @Override
+ public JToolBar createToolBar(URI id) {
+ JToolBar toolbar = new JToolBar();
+ if (needsUpdate)
+ update();
+ populateToolBar(toolbar, id);
+ registerComponent(id, toolbar, true);
+ return toolbar;
+ }
+
+ @Override
+ public synchronized Component getComponentByURI(URI id) {
+ WeakReference<Component> componentRef = uriToComponent.get(id);
+ if (componentRef == null)
+ return null;
+ // Might also be null it reference has gone dead
+ return componentRef.get();
+ }
+
+ @Override
+ public List<Observer<MenuManagerEvent>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ @Override
+ public synchronized URI getURIByComponent(Component component) {
+ return componentToUri.get(component);
+ }
+
+ @Override
+ public void removeObserver(Observer<MenuManagerEvent> observer) {
+ multiCaster.removeObserver(observer);
+ }
+
+ @Override
+ public void update() {
+ synchronized (updateLock) {
+ if (updating && !needsUpdate)
+ return;
+ updating = true;
+ }
+ try {
+ doUpdate();
+ } finally {
+ synchronized (updateLock) {
+ updating = false;
+ needsUpdate = false;
+ }
+ }
+ }
+
+ public void update(Object service, Map<?, ?> properties) {
+ needsUpdate = true;
+ update();
+ }
+
+ /**
+ * Add a {@link JMenu} to the list of components as described by the menu
+ * component. If there are no children, the menu is not added.
+ *
+ * @param components
+ * List of components where to add the created {@link JMenu}
+ * @param menuComponent
+ * The {@link MenuComponent} definition for this menu
+ * @param isToolbar
+ * True if the list of components is to be added to a toolbar
+ */
+ private void addMenu(List<Component> components,
+ MenuComponent menuComponent, MenuOptions menuOptions) {
+ URI menuId = menuComponent.getId();
+ if (menuOptions.isToolbar()) {
+ logger.warn("Can't have menu " + menuComponent
+ + " within toolBar element");
+ return;
+ }
+ MenuOptions childOptions = new MenuOptions(menuOptions);
+ List<Component> subComponents = makeComponents(menuId, childOptions);
+ if (subComponents.isEmpty()) {
+ logger.warn("No sub components found for menu " + menuId);
+ return;
+ }
+
+ JMenu menu = new JMenu(menuComponent.getAction());
+ for (Component menuItem : subComponents)
+ if (menuItem == null)
+ menu.addSeparator();
+ else
+ menu.add(menuItem);
+ registerComponent(menuId, menu);
+ components.add(menu);
+ }
+
+ /**
+ * Add <code>null</code> to the list of components, meaning that a separator
+ * is to be created. Subsequent separators are ignored, and if there are no
+ * components on the list already no separator will be added.
+ *
+ * @param components
+ * List of components
+ */
+ private void addNullSeparator(List<Component> components) {
+ if (components.isEmpty())
+ // Don't start with a separator
+ return;
+ if (components.get(components.size() - 1) == null)
+ // Already a separator in last position
+ return;
+ components.add(null);
+ }
+
+ /**
+ * Add an {@link AbstractMenuOptionGroup option group} to the list of
+ * components
+ *
+ * @param components
+ * List of components where to add the created {@link JMenu}
+ * @param optionGroupId
+ * The {@link URI} identifying the option group
+ * @param isToolbar
+ * True if the option group is to be added to a toolbar
+ */
+ private void addOptionGroup(List<Component> components, URI optionGroupId,
+ MenuOptions menuOptions) {
+ MenuOptions childOptions = new MenuOptions(menuOptions);
+ childOptions.setOptionGroup(true);
+
+ List<Component> buttons = makeComponents(optionGroupId, childOptions);
+ addNullSeparator(components);
+ if (buttons.isEmpty()) {
+ logger.warn("No sub components found for option group "
+ + optionGroupId);
+ return;
+ }
+ ButtonGroup buttonGroup = new ButtonGroup();
+
+ for (Component button : buttons) {
+ if (button instanceof AbstractButton)
+ buttonGroup.add((AbstractButton) button);
+ else
+ logger.warn("Component of button group " + optionGroupId
+ + " is not an AbstractButton: " + button);
+ if (button == null) {
+ logger.warn("Separator found within button group");
+ addNullSeparator(components);
+ } else
+ components.add(button);
+ }
+ addNullSeparator(components);
+ }
+
+ /**
+ * Add a section to a list of components.
+ *
+ * @param components
+ * List of components
+ * @param sectionId
+ * The {@link URI} identifying the section
+ * @param menuOptions
+ * {@link MenuOptions options} for creating the menu
+ */
+ private void addSection(List<Component> components, URI sectionId,
+ MenuOptions menuOptions) {
+ List<Component> childComponents = makeComponents(sectionId, menuOptions);
+
+ MenuComponent sectionDef = uriToMenuElement.get(sectionId);
+ addNullSeparator(components);
+ if (childComponents.isEmpty()) {
+ logger.warn("No sub components found for section " + sectionId);
+ return;
+ }
+ Action sectionAction = sectionDef.getAction();
+ if (sectionAction != null) {
+ String sectionLabel = (String) sectionAction.getValue(NAME);
+ if (sectionLabel != null) {
+ // No separators before the label
+ stripTrailingNullSeparator(components);
+ Color labelColor = (Color) sectionAction.getValue(SECTION_COLOR);
+ if (labelColor == null)
+ labelColor = GREEN;
+ ShadedLabel label = new ShadedLabel(sectionLabel, labelColor);
+ components.add(label);
+ }
+ }
+ for (Component childComponent : childComponents)
+ if (childComponent == null) {
+ logger.warn("Separator found within section " + sectionId);
+ addNullSeparator(components);
+ } else
+ components.add(childComponent);
+ addNullSeparator(components);
+ }
+
+ /**
+ * Remove the last <code>null</code> separator from the list of components
+ * if it's present.
+ *
+ * @param components
+ * List of components
+ */
+ private void stripTrailingNullSeparator(List<Component> components) {
+ if (!components.isEmpty()) {
+ int lastIndex = components.size() - 1;
+ if (components.get(lastIndex) == null)
+ components.remove(lastIndex);
+ }
+ }
+
+ /**
+ * Perform the actual update, called by {@link #update()}. Reset all the
+ * collections, refresh from SPI, modify any previously published components
+ * and notify any observers.
+ */
+ protected synchronized void doUpdate() {
+ resetCollections();
+ findChildren();
+ updatePublishedComponents();
+ multiCaster.notify(new UpdatedMenuManagerEvent());
+ }
+
+ /**
+ * Find all children for all known menu components. Populates
+ * {@link #uriToMenuElement}.
+ *
+ */
+ protected void findChildren() {
+ for (MenuComponent menuElement : menuComponents) {
+ uriToMenuElement.put(menuElement.getId(), menuElement);
+ logger.debug("Found menu element " + menuElement.getId() + " "
+ + menuElement);
+ if (menuElement.getParentId() == null)
+ continue;
+ List<MenuComponent> siblings = menuElementTree.get(menuElement
+ .getParentId());
+ if (siblings == null) {
+ siblings = new ArrayList<>();
+ synchronized (menuElementTree) {
+ menuElementTree.put(menuElement.getParentId(), siblings);
+ }
+ }
+ siblings.add(menuElement);
+ }
+// if (uriToMenuElement.isEmpty()) {
+// logger.error("No menu elements found, check classpath/Raven/SPI");
+// }
+ }
+
+ /**
+ * Get the children which have the given URI specified as their parent, or
+ * an empty list if no children exist.
+ *
+ * @param id
+ * The {@link URI} of the parent
+ * @return The {@link List} of {@link MenuComponent} which have the given
+ * parent
+ */
+ protected List<MenuComponent> getChildren(URI id) {
+ List<MenuComponent> children = null;
+ synchronized (menuElementTree) {
+ children = menuElementTree.get(id);
+ if (children != null)
+ children = new ArrayList<>(children);
+ }
+ if (children == null)
+ children = Collections.<MenuComponent> emptyList();
+ else
+ Collections.sort(children, menuElementComparator);
+ return children;
+ }
+
+ /**
+ * Make the list of Swing {@link Component}s that are the children of the
+ * given {@link URI}.
+ *
+ * @param id
+ * The {@link URI} of the parent which children are to be made
+ * @param menuOptions
+ * Options of the created menu, for instance
+ * {@link MenuOptions#isToolbar()}.
+ * @return A {@link List} of {@link Component}s that can be added to a
+ * {@link JMenuBar}, {@link JMenu} or {@link JToolBar}.
+ */
+ protected List<Component> makeComponents(URI id, MenuOptions menuOptions) {
+ List<Component> components = new ArrayList<>();
+ for (MenuComponent childElement : getChildren(id)) {
+ if (childElement instanceof ContextualMenuComponent)
+ ((ContextualMenuComponent) childElement)
+ .setContextualSelection(menuOptions
+ .getContextualSelection());
+ /*
+ * Important - check this AFTER setContextualSelection so the item
+ * can change it's enabled-state if needed.
+ */
+ if (!childElement.isEnabled())
+ continue;
+ MenuType type = childElement.getType();
+ Action action = childElement.getAction();
+ URI childId = childElement.getId();
+ if (type.equals(MenuType.action)) {
+ if (action == null) {
+ logger.warn("Skipping invalid action " + childId + " for "
+ + id);
+ continue;
+ }
+
+ Component actionComponent;
+ if (menuOptions.isOptionGroup()) {
+ if (menuOptions.isToolbar()) {
+ actionComponent = new JToggleButton(action);
+ toolbarizeButton((AbstractButton) actionComponent);
+ } else
+ actionComponent = new JRadioButtonMenuItem(action);
+ } else {
+ if (menuOptions.isToolbar()) {
+ actionComponent = new JButton(action);
+ toolbarizeButton((AbstractButton) actionComponent);
+ } else
+ actionComponent = new JMenuItem(action);
+ }
+ registerComponent(childId, actionComponent);
+ components.add(actionComponent);
+ } else if (type.equals(MenuType.toggle)) {
+ if (action == null) {
+ logger.warn("Skipping invalid toggle " + childId + " for "
+ + id);
+ continue;
+ }
+ Component toggleComponent;
+ if (menuOptions.isToolbar())
+ toggleComponent = new JToggleButton(action);
+ else
+ toggleComponent = new JCheckBoxMenuItem(action);
+ registerComponent(childId, toggleComponent);
+ components.add(toggleComponent);
+ } else if (type.equals(MenuType.custom)) {
+ Component customComponent = childElement.getCustomComponent();
+ if (customComponent == null) {
+ logger.warn("Skipping null custom component " + childId
+ + " for " + id);
+ continue;
+ }
+ registerComponent(childId, customComponent);
+ components.add(customComponent);
+ } else if (type.equals(MenuType.optionGroup))
+ addOptionGroup(components, childId, menuOptions);
+ else if (type.equals(MenuType.section))
+ addSection(components, childId, menuOptions);
+ else if (type.equals(MenuType.menu))
+ addMenu(components, childElement, menuOptions);
+ else {
+ logger.warn("Skipping invalid/unknown type " + type + " for "
+ + id);
+ continue;
+ }
+ }
+ stripTrailingNullSeparator(components);
+ return components;
+ }
+
+ /**
+ * Fill the specified menu bar with the menu elements that have the given
+ * URI as their parent.
+ * <p>
+ * Existing elements on the menu bar will be removed.
+ *
+ * @param menuBar
+ * The {@link JMenuBar} to update
+ * @param id
+ * The {@link URI} of the menu bar
+ */
+ protected void populateMenuBar(JMenuBar menuBar, URI id) {
+ menuBar.removeAll();
+ MenuComponent menuDef = uriToMenuElement.get(id);
+ if (menuDef == null)
+ throw new IllegalArgumentException("Unknown menuBar " + id);
+ if (!menuDef.getType().equals(MenuType.menu))
+ throw new IllegalArgumentException("Element " + id
+ + " is not a menu, but a " + menuDef.getType());
+ MenuOptions menuOptions = new MenuOptions();
+ for (Component component : makeComponents(id, menuOptions))
+ if (component == null)
+ logger.warn("Ignoring separator in menu bar " + id);
+ else
+ menuBar.add(component);
+ }
+
+ /**
+ * Fill the specified menu bar with the menu elements that have the given
+ * URI as their parent.
+ * <p>
+ * Existing elements on the menu bar will be removed.
+ *
+ * @param popupMenu
+ * The {@link JPopupMenu} to update
+ * @param id
+ * The {@link URI} of the menu bar
+ * @param contextualSelection
+ * The current selection for the context menu
+ */
+ protected void populateContextMenu(JPopupMenu popupMenu, URI id,
+ ContextualSelection contextualSelection) {
+ popupMenu.removeAll();
+ MenuComponent menuDef = uriToMenuElement.get(id);
+ if (menuDef == null)
+ throw new IllegalArgumentException("Unknown menuBar " + id);
+ if (!menuDef.getType().equals(MenuType.menu))
+ throw new IllegalArgumentException("Element " + id
+ + " is not a menu, but a " + menuDef.getType());
+ MenuOptions menuOptions = new MenuOptions();
+ menuOptions.setContextualSelection(contextualSelection);
+ for (Component component : makeComponents(id, menuOptions))
+ if (component == null)
+ popupMenu.addSeparator();
+ else
+ popupMenu.add(component);
+ }
+
+ /**
+ * Fill the specified tool bar with the elements that have the given URI as
+ * their parent.
+ * <p>
+ * Existing elements on the tool bar will be removed.
+ *
+ * @param toolbar
+ * The {@link JToolBar} to update
+ * @param id
+ * The {@link URI} of the tool bar
+ */
+ protected void populateToolBar(JToolBar toolbar, URI id) {
+ toolbar.removeAll();
+ MenuComponent toolbarDef = uriToMenuElement.get(id);
+ if (toolbarDef == null)
+ throw new IllegalArgumentException("Unknown toolBar " + id);
+ if (!toolbarDef.getType().equals(MenuType.toolBar))
+ throw new IllegalArgumentException("Element " + id
+ + " is not a toolBar, but a " + toolbarDef.getType());
+ if (toolbarDef.getAction() != null) {
+ String name = (String) toolbarDef.getAction().getValue(Action.NAME);
+ toolbar.setName(name);
+ } else
+ toolbar.setName("");
+ MenuOptions menuOptions = new MenuOptions();
+ menuOptions.setToolbar(true);
+ for (Component component : makeComponents(id, menuOptions)) {
+ if (component == null) {
+ toolbar.addSeparator();
+ continue;
+ }
+ if (component instanceof JButton) {
+ JButton toolbarButton = (JButton) component;
+ toolbarButton.putClientProperty("hideActionText", true);
+ }
+ toolbar.add(component);
+ }
+ }
+
+ /**
+ * Register a component that has been created. Such a component can be
+ * resolved through {@link #getComponentByURI(URI)}.
+ *
+ * @param id
+ * The {@link URI} that defined the component
+ * @param component
+ * The {@link Component} that was created.
+ */
+ protected synchronized void registerComponent(URI id, Component component) {
+ registerComponent(id, component, false);
+ }
+
+ /**
+ * Register a component that has been created. Such a component can be
+ * resolved through {@link #getComponentByURI(URI)}.
+ *
+ * @param id
+ * The {@link URI} that defined the component
+ * @param component
+ * The {@link Component} that was created.
+ * @param published
+ * <code>true</code> if the component has been published through
+ * {@link #createMenuBar()} or similar, and is to be
+ * automatically updated by later calls to {@link #update()}.
+ */
+ protected synchronized void registerComponent(URI id, Component component,
+ boolean published) {
+ uriToComponent.put(id, new WeakReference<>(component));
+ componentToUri.put(component, id);
+ if (published) {
+ List<WeakReference<Component>> publishedComponents = uriToPublishedComponents
+ .get(id);
+ if (publishedComponents == null) {
+ publishedComponents = new ArrayList<>();
+ uriToPublishedComponents.put(id, publishedComponents);
+ }
+ publishedComponents.add(new WeakReference<>(component));
+ }
+ setHelpStringForComponent(component, id);
+ }
+
+ /**
+ * Reset all collections
+ *
+ */
+ protected synchronized void resetCollections() {
+ menuElementTree = new HashMap<>();
+ componentToUri = new WeakHashMap<>();
+ uriToMenuElement = new HashMap<>();
+ uriToComponent = new HashMap<>();
+ }
+
+ /**
+ * Set javax.help string to identify the component for later references to
+ * the help document. Note that the component (ie. the
+ * {@link AbstractMenuAction} must have an ID for an registration to take
+ * place.
+ *
+ * @param component
+ * The {@link Component} to set help string for
+ * @param componentId
+ * The {@link URI} to be used as identifier
+ */
+ protected void setHelpStringForComponent(Component component,
+ URI componentId) {
+ if (componentId != null) {
+ String helpId = componentId.toASCIIString();
+ setHelpIDString(component, helpId);
+ }
+ }
+
+ /**
+ * Make an {@link AbstractButton} be configured in a "toolbar-like" way, for
+ * instance showing only the icon.
+ *
+ * @param actionButton
+ * Button to toolbarise
+ */
+ protected void toolbarizeButton(AbstractButton actionButton) {
+ Action action = actionButton.getAction();
+ if (action.getValue(SHORT_DESCRIPTION) == null)
+ action.putValue(SHORT_DESCRIPTION, action.getValue(NAME));
+ actionButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+ // actionButton.setHorizontalTextPosition(JButton.CENTER);
+ // actionButton.setVerticalTextPosition(JButton.BOTTOM);
+ if (action.getValue(Action.SMALL_ICON) != null) {
+ // Don't show the text
+ actionButton.putClientProperty("hideActionText", true);
+ // Since hideActionText seems to be broken in Java 5 and/or OS X
+ actionButton.setText(null);
+ }
+ }
+
+ /**
+ * Update all components that have been published using
+ * {@link #createMenuBar()} and similar. Content of such components will be
+ * removed and replaced by fresh components.
+ */
+ protected void updatePublishedComponents() {
+ for (Entry<URI, List<WeakReference<Component>>> entry : uriToPublishedComponents
+ .entrySet())
+ for (WeakReference<Component> reference : entry.getValue()) {
+ URI id = entry.getKey();
+ Component component = reference.get();
+ if (component == null)
+ continue;
+ if (component instanceof JToolBar)
+ populateToolBar((JToolBar) component, id);
+ else if (component instanceof JMenuBar)
+ populateMenuBar((JMenuBar) component, id);
+ else
+ logger.warn("Could not update published component " + id
+ + ": " + component.getClass());
+ }
+ }
+
+ public void setMenuComponents(List<MenuComponent> menuComponents) {
+ this.menuComponents = menuComponents;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ /**
+ * {@link Comparator} that can order {@link MenuComponent}s by their
+ * {@link MenuComponent#getPositionHint()}.
+ */
+ protected static class MenuElementComparator implements
+ Comparator<MenuComponent> {
+ @Override
+ public int compare(MenuComponent a, MenuComponent b) {
+ return a.getPositionHint() - b.getPositionHint();
+ }
+ }
+
+ /**
+ * Various options for
+ * {@link MenuManagerImpl#makeComponents(URI, MenuOptions)} and friends.
+ *
+ * @author Stian Soiland-Reyes
+ */
+ public static class MenuOptions {
+ private boolean isToolbar = false;
+ private boolean isOptionGroup = false;
+ private ContextualSelection contextualSelection = null;
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ public void setContextualSelection(
+ ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+ public MenuOptions(MenuOptions original) {
+ this.isOptionGroup = original.isOptionGroup();
+ this.isToolbar = original.isToolbar();
+ this.contextualSelection = original.getContextualSelection();
+ }
+
+ public MenuOptions() {
+ }
+
+ @Override
+ protected MenuOptions clone() {
+ return new MenuOptions(this);
+ }
+
+ public boolean isToolbar() {
+ return isToolbar;
+ }
+
+ public void setToolbar(boolean isToolbar) {
+ this.isToolbar = isToolbar;
+ }
+
+ public boolean isOptionGroup() {
+ return isOptionGroup;
+ }
+
+ public void setOptionGroup(boolean isOptionGroup) {
+ this.isOptionGroup = isOptionGroup;
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+ private static final String RESULTS_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective";
+
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (!(message instanceof PerspectiveSelectionEvent))
+ return;
+ handlePerspectiveSelect((PerspectiveSelectionEvent) message);
+ }
+
+ private void handlePerspectiveSelect(PerspectiveSelectionEvent event) {
+ String perspectiveID = event.getSelectedPerspective().getID();
+ boolean isDesign = DESIGN_PERSPECTIVE_ID.equals(perspectiveID);
+ boolean isResults = RESULTS_PERSPECTIVE_ID.equals(perspectiveID);
+
+ for (MenuComponent menuComponent : menuComponents)
+ if (!(menuComponent instanceof ContextualMenuComponent)) {
+ Action action = menuComponent.getAction();
+ if (action instanceof DesignOnlyAction)
+ action.setEnabled(isDesign);
+ else if (action instanceof DesignOrResultsAction)
+ action.setEnabled(isDesign || isResults);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java
new file mode 100644
index 0000000..9a2f37b
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_A;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class AdvancedMenu extends AbstractMenu {
+ public static final URI ADVANCED_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+ public AdvancedMenu() {
+ super(DEFAULT_MENU_BAR, 1000, ADVANCED_URI, makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Advanced");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_A));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java
new file mode 100644
index 0000000..a15237c
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_E;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class EditMenu extends AbstractMenu {
+ public EditMenu() {
+ super(DEFAULT_MENU_BAR, 20, URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#edit"),
+ makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Edit");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_E));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java
new file mode 100644
index 0000000..6b6eb7c
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import static java.awt.Desktop.getDesktop;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu.HELP_URI;
+
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+/**
+ * MenuItem for feedback
+ *
+ * @author alanrw
+ */
+public class FeedbackMenuAction extends AbstractMenuAction {
+ private static Logger logger = Logger.getLogger(FeedbackMenuAction.class);
+
+ private static String FEEDBACK_URL = "http://www.taverna.org.uk/about/contact-us/feedback/";
+
+ public FeedbackMenuAction() {
+ super(HELP_URI, 20);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new FeedbackAction();
+ }
+
+ @SuppressWarnings("serial")
+ private final class FeedbackAction extends AbstractAction {
+ private FeedbackAction() {
+ super("Contact us");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ getDesktop().browse(new URI(FEEDBACK_URL));
+ } catch (IOException e1) {
+ logger.error("Unable to open URL", e1);
+ } catch (URISyntaxException e1) {
+ logger.error("Invalid URL syntax", e1);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java
new file mode 100644
index 0000000..61f963b
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_F;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+/**
+ * File menu
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class FileMenu extends AbstractMenu {
+ public FileMenu() {
+ super(DEFAULT_MENU_BAR, 10, URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file"),
+ makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("File");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_F));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java
new file mode 100644
index 0000000..c4169cc
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_H;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class HelpMenu extends AbstractMenu {
+ public static final URI HELP_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#help");
+
+ public HelpMenu() {
+ super(DEFAULT_MENU_BAR, 1024, HELP_URI, makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Help");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_H));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java
new file mode 100644
index 0000000..d091c8e
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_F1;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.helper.Helper.displayDefaultHelp;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu.HELP_URI;
+
+import java.awt.AWTEvent;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+/**
+ * MenuItem for help
+ *
+ * @author alanrw
+ */
+public class OnlineHelpMenuAction extends AbstractMenuAction {
+ public OnlineHelpMenuAction() {
+ super(HELP_URI, 10);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OnlineHelpAction();
+ }
+
+ @SuppressWarnings("serial")
+ private final class OnlineHelpAction extends AbstractAction {
+ private OnlineHelpAction() {
+ super("Online help");
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F1, 0));
+
+ }
+
+ /**
+ * When selected, use the Helper to display the default help.
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ displayDefaultHelp((AWTEvent) e);
+ // TODO change helper to bean?
+ }
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java
new file mode 100644
index 0000000..308d51d
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.lang.Runtime.getRuntime;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.showInputDialog;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu.ADVANCED_URI;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class ShowLogsAndDataMenuAction extends AbstractMenuAction {
+ private static final String OPEN = "open";
+ private static final String EXPLORER = "explorer";
+ // TODO Consider using xdg-open instead of gnome-open
+ private static final String GNOME_OPEN = "gnome-open";
+ private static final String WINDOWS = "Windows";
+ private static final String MAC_OS_X = "Mac OS X";
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ public ShowLogsAndDataMenuAction() {
+ super(ADVANCED_URI, 200);
+ }
+
+ private static Logger logger = Logger.getLogger(ShowLogsAndDataMenuAction.class);
+
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Show logs and data folder") {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File logsAndDataDir = applicationConfiguration.getApplicationHomeDir();
+ showDirectory(logsAndDataDir, "Taverna logs and data folder");
+ }
+ };
+ }
+
+ public static void showDirectory(File dir, String title) {
+ String path = dir.getAbsolutePath();
+ String os = System.getProperty("os.name");
+ String cmd;
+ boolean isWindows = false;
+ if (os.equals(MAC_OS_X))
+ cmd = OPEN;
+ else if (os.startsWith(WINDOWS)) {
+ cmd = EXPLORER;
+ isWindows = true;
+ } else
+ // Assume Unix - best option is gnome-open
+ cmd = GNOME_OPEN;
+
+ String[] cmdArray = new String[2];
+ cmdArray[0] = cmd;
+ cmdArray[1] = path;
+ try {
+ Process exec = getRuntime().exec(cmdArray);
+ Thread.sleep(300);
+ exec.getErrorStream().close();
+ exec.getInputStream().close();
+ exec.getOutputStream().close();
+ exec.waitFor();
+ if (exec.exitValue() == 0 || isWindows && exec.exitValue() == 1)
+ // explorer.exe thinks 1 means success
+ return;
+ logger.warn("Exit value from " + cmd + " " + path + ": " + exec.exitValue());
+ } catch (Exception ex) {
+ logger.warn("Could not call " + cmd + " " + path, ex);
+ }
+ // Fall-back - just show a dialogue with the path
+ showInputDialog(getMainWindow(), "Copy path from below:", title,
+ INFORMATION_MESSAGE, null, null, path);
+ }
+
+ public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java
new file mode 100644
index 0000000..2df05e5
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class ViewShowMenuSection extends AbstractMenuSection {
+ public static final URI DIAGRAM_MENU = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagram");
+ public static final URI VIEW_SHOW_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#viewShowMenuSection");
+
+ public ViewShowMenuSection() {
+ super(DIAGRAM_MENU, 10, VIEW_SHOW_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager b/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager
new file mode 100644
index 0000000..3b06fd9
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl
\ No newline at end of file
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml
new file mode 100644
index 0000000..3a1eadf
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="MenuManagerImpl" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+
+ <service ref="FileMenu" auto-export="interfaces" />
+ <service ref="EditMenu" auto-export="interfaces" />
+ <service ref="AdvancedMenu" auto-export="interfaces" />
+ <service ref="HelpMenu" auto-export="interfaces" />
+ <service ref="OnlineHelpMenuAction" auto-export="interfaces" />
+ <service ref="FeedbackMenuAction" auto-export="interfaces" />
+ <service ref="ShowLogsAndDataMenuAction" auto-export="interfaces" />
+
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+ <list id="menuComponents" interface="net.sf.taverna.t2.ui.menu.MenuComponent" cardinality="0..N" greedy-proxying="true">
+ <listener ref="MenuManagerImpl" bind-method="update" unbind-method="update" />
+ </list>
+
+</beans:beans>
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml
new file mode 100644
index 0000000..62fd24e
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="MenuManagerImpl" class="net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl">
+ <property name="menuComponents" ref="menuComponents" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+ <bean id="FileMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.FileMenu" />
+ <bean id="EditMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.EditMenu" />
+ <bean id="AdvancedMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu" />
+ <bean id="HelpMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu" />
+ <bean id="OnlineHelpMenuAction"
+ class="net.sf.taverna.t2.workbench.ui.impl.menu.OnlineHelpMenuAction" />
+ <bean id="FeedbackMenuAction"
+ class="net.sf.taverna.t2.workbench.ui.impl.menu.FeedbackMenuAction" />
+ <bean id="ShowLogsAndDataMenuAction"
+ class="net.sf.taverna.t2.workbench.ui.impl.menu.ShowLogsAndDataMenuAction">
+ <property name="applicationConfiguration" ref="applicationConfiguration" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-menu-items/pom.xml b/taverna-workbench-menu-items/pom.xml
new file mode 100644
index 0000000..c7007e3
--- /dev/null
+++ b/taverna-workbench-menu-items/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>menu-items</artifactId>
+ <packaging>bundle</packaging>
+ <name>Additional menu items</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>design-ui</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>stringconstant-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+ <!-- TODO remove dependencies on implementations -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/AbstractConnectPortMenuActions.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/AbstractConnectPortMenuActions.java
new file mode 100644
index 0000000..2bbed5b
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/AbstractConnectPortMenuActions.java
@@ -0,0 +1,268 @@
+package net.sf.taverna.t2.ui.menu.items.activityport;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.Port;
+import uk.org.taverna.scufl2.api.port.ProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+import uk.org.taverna.scufl2.api.port.WorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.ui.menu.MenuManager.ComponentFactory;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+public abstract class AbstractConnectPortMenuActions extends AbstractMenuCustom
+ implements ContextualMenuComponent {
+
+ protected ActivityIconManager activityIconManager;
+ protected ContextualSelection contextualSelection;
+ protected MenuManager menuManager;
+ protected WorkbenchConfiguration workbenchConfiguration;
+ protected ColourManager colourManager;
+ private EditManager editManager;
+
+ public static final String CONNECT_AS_INPUT_TO = "Connect as input to...";
+ public static final String CONNECT_WITH_OUTPUT_FROM = "Connect with output from...";
+
+ public static final String SERVICE_INPUT_PORTS = "Service input ports";
+ public static final String SERVICE_OUTPUT_PORTS = "Service output ports";
+
+ public static final String NEW_WORKFLOW_INPUT_PORT = "New workflow input port...";
+ public static final String NEW_WORKFLOW_OUTPUT_PORT = "New workflow output port...";
+
+ public static final String WORKFLOW_INPUT_PORTS = "Workflow input ports";
+ public static final String WORKFLOW_OUTPUT_PORTS = "Workflow output ports";
+
+ public static final String SERVICES = "Services";
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public AbstractConnectPortMenuActions(URI parentId, int positionHint) {
+ super(parentId, positionHint);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.customComponent = null;
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ Profile profile = workflow.getParent().getMainProfile();
+ Port port = getSelectedPort();
+ // Component component =
+ // getContextualSelection().getRelativeToComponent();
+
+ String label;
+ if (port instanceof ReceiverPort) {
+ label = CONNECT_WITH_OUTPUT_FROM;
+ } else {
+ label = CONNECT_AS_INPUT_TO;
+ }
+ JMenu connectMenu = new JMenu(new DummyAction(label,
+ WorkbenchIcons.datalinkIcon));
+ addPortMenuItems(workflow, port, connectMenu);
+ addProcessorMenuItems(workflow, profile, port, connectMenu);
+ return connectMenu;
+ }
+
+ private Port getSelectedPort() {
+ Port port = (Port) getContextualSelection().getSelection();
+ return port;
+ }
+
+ protected void addPortMenuItems(Workflow workflow, Port port, JMenu connectMenu) {
+ Color workflowPortColour = colourManager.getPreferredColour(WorkflowPort.class.getCanonicalName());
+
+ boolean addedPorts = false;
+ if (port instanceof SenderPort) {
+ connectMenu.add(new ShadedLabel(WORKFLOW_OUTPUT_PORTS, workflowPortColour));
+ for (OutputWorkflowPort outputWorkflowPort : workflow.getOutputPorts()) {
+ ConnectPortsAction connectPortsAction =
+ new ConnectPortsAction(workflow, (SenderPort) port, outputWorkflowPort, editManager);
+ connectPortsAction.putValue(Action.SMALL_ICON, WorkbenchIcons.outputIcon);
+ connectPortsAction.putValue(Action.NAME, outputWorkflowPort.getName());
+ connectMenu.add(new JMenuItem(connectPortsAction));
+ addedPorts = true;
+ }
+ } else if (port instanceof ReceiverPort) {
+ connectMenu.add(new ShadedLabel(WORKFLOW_INPUT_PORTS, workflowPortColour));
+ for (InputWorkflowPort inputWorkflowPort : workflow.getInputPorts()) {
+ ConnectPortsAction connectPortsAction =
+ new ConnectPortsAction(workflow, inputWorkflowPort, (ReceiverPort) port, editManager);
+ connectPortsAction.putValue(Action.SMALL_ICON, WorkbenchIcons.inputIcon);
+ connectPortsAction.putValue(Action.NAME, inputWorkflowPort.getName());
+ connectMenu.add(new JMenuItem(connectPortsAction));
+ addedPorts = true;
+ }
+ }
+ if (addedPorts) {
+ connectMenu.addSeparator();
+ }
+ CreateAndConnectDataflowPortAction newDataflowPortAction = new CreateAndConnectDataflowPortAction(
+ workflow, port, getSuggestedName(port), contextualSelection.getRelativeToComponent(), editManager);
+
+ if (port instanceof ReceiverPort) {
+ newDataflowPortAction.putValue(Action.NAME, NEW_WORKFLOW_INPUT_PORT);
+ } else if (port instanceof SenderPort) {
+ newDataflowPortAction.putValue(Action.NAME, NEW_WORKFLOW_OUTPUT_PORT);
+ }
+ newDataflowPortAction.putValue(Action.SMALL_ICON, WorkbenchIcons.newIcon);
+ connectMenu.add(new JMenuItem(newDataflowPortAction));
+ }
+
+ /**
+ * @param port
+ * @return
+ */
+ private String getSuggestedName(Port port) {
+ String suggestedName;
+ if (port instanceof ProcessorPort) {
+ suggestedName = ((ProcessorPort) port).getParent().getName() + "_" + port.getName();
+ } else {
+ suggestedName = port.getName();
+ }
+ return suggestedName;
+ }
+
+ protected void addProcessorMenuItems(Workflow dataflow, Profile profile,
+ final Port targetPort, JMenu connectMenu) {
+ final Set<Processor> processors = findProcessors(dataflow, targetPort);
+ if (processors.isEmpty()) {
+ return;
+ }
+ connectMenu.add(new ShadedLabel(SERVICES, colourManager.getPreferredColour(Processor.class.getCanonicalName())));
+
+ List<JMenuItem> menuItems = new ArrayList<JMenuItem>();
+ for (Processor processor : processors) {
+ Activity activity = scufl2Tools.processorBindingForProcessor(processor, profile).getBoundActivity();
+ Icon icon = activityIconManager.iconForActivity(activity);
+ final Color processorPortColour = colourManager.getPreferredColour(ProcessorPort.class.getCanonicalName());
+
+ JMenu processorMenu = new JMenu(new DummyAction(processor.getName(), icon));
+ List<JMenuItem> processorMenuItems = new ArrayList<JMenuItem>();
+ if (targetPort instanceof ReceiverPort) {
+ processorMenu.add(new ShadedLabel(SERVICE_OUTPUT_PORTS,
+ processorPortColour));
+ menuItems.add(processorMenu);
+ for (OutputProcessorPort outputProcessorPort : processor.getOutputPorts()) {
+ ConnectPortsAction connectPortsAction = new ConnectPortsAction(dataflow,
+ outputProcessorPort, (ReceiverPort) targetPort, editManager);
+ connectPortsAction.putValue(Action.SMALL_ICON,
+ WorkbenchIcons.outputPortIcon);
+ connectPortsAction.putValue(Action.NAME, outputProcessorPort.getName());
+ processorMenuItems.add(new JMenuItem(connectPortsAction));
+ }
+ } else if (targetPort instanceof SenderPort) {
+ processorMenu.add(new ShadedLabel(SERVICE_INPUT_PORTS,
+ processorPortColour));
+ menuItems.add(processorMenu);
+ for (InputProcessorPort inputProcessorPort : processor.getInputPorts()) {
+ ConnectPortsAction connectPortsAction = new ConnectPortsAction(dataflow,
+ (SenderPort) targetPort, inputProcessorPort, editManager);
+ connectPortsAction.putValue(Action.SMALL_ICON,
+ WorkbenchIcons.inputPortIcon);
+ connectPortsAction.putValue(Action.NAME, inputProcessorPort.getName());
+ processorMenuItems.add(new JMenuItem(connectPortsAction));
+ }
+ }
+
+ menuManager.addMenuItemsWithExpansion(processorMenuItems,
+ processorMenu, workbenchConfiguration.getMaxMenuItems(),
+ new ComponentFactory() {
+ public Component makeComponent() {
+ if (targetPort instanceof InputPort) {
+ return new ShadedLabel(SERVICE_OUTPUT_PORTS, processorPortColour);
+ } else {
+ return new ShadedLabel(SERVICE_INPUT_PORTS, processorPortColour);
+ }
+ }
+ });
+ }
+ menuManager.addMenuItemsWithExpansion(menuItems, connectMenu,
+ workbenchConfiguration.getMaxMenuItems(),
+ new ComponentFactory() {
+ public Component makeComponent() {
+ return new ShadedLabel(SERVICES, colourManager
+ .getPreferredColour(Processor.class
+ .getCanonicalName()));
+ }
+ });
+ }
+
+ protected Set<Processor> findProcessors(Workflow dataflow, Port targetPort) {
+ Set<Processor> possibleProcessors = new HashSet<Processor>();
+ if (targetPort instanceof InputProcessorPort) {
+ InputProcessorPort inputProcessorPort = (InputProcessorPort) targetPort;
+ possibleProcessors = scufl2Tools.possibleUpStreamProcessors(dataflow, inputProcessorPort.getParent());
+ } else if (targetPort instanceof OutputProcessorPort) {
+ OutputProcessorPort outputProcessorPort = (OutputProcessorPort) targetPort;
+ possibleProcessors = scufl2Tools.possibleDownStreamProcessors(dataflow, outputProcessorPort.getParent());
+ } else {
+ // Probably a dataflow port, everything is allowed
+ possibleProcessors = dataflow.getProcessors();
+ }
+ return possibleProcessors;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+}
\ No newline at end of file
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ActivityInputPortSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ActivityInputPortSection.java
new file mode 100644
index 0000000..89414d6
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ActivityInputPortSection.java
@@ -0,0 +1,67 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+
+public class ActivityInputPortSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ private static final String ACTIVITY_INPUT_PORT = "Service input port: ";
+ public static final URI activityInputPortSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/activityInputPort");
+ private ContextualSelection contextualSelection;
+
+ public ActivityInputPortSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10,
+ activityInputPortSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return getContextualSelection().getSelection() instanceof InputProcessorPort;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+ @Override
+ protected Action createAction() {
+ InputProcessorPort port = (InputProcessorPort) getContextualSelection().getSelection();
+ String name = ACTIVITY_INPUT_PORT + port.getName();
+ return new DummyAction(name);
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ActivityOutputPortSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ActivityOutputPortSection.java
new file mode 100644
index 0000000..b26cff9
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ActivityOutputPortSection.java
@@ -0,0 +1,67 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+
+public class ActivityOutputPortSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ private static final String ACTIVITY_OUTPUT_PORT = "Service output port: ";
+ public static final URI activityOutputPortSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/activityOutputPort");
+ private ContextualSelection contextualSelection;
+
+ public ActivityOutputPortSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10,
+ activityOutputPortSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return getContextualSelection().getSelection() instanceof OutputProcessorPort;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+ @Override
+ protected Action createAction() {
+ OutputProcessorPort port = (OutputProcessorPort) getContextualSelection().getSelection();
+ String name = ACTIVITY_OUTPUT_PORT + port.getName();
+ return new DummyAction(name);
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/AddInputPortDefaultValueAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/AddInputPortDefaultValueAction.java
new file mode 100644
index 0000000..414ffac
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/AddInputPortDefaultValueAction.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * 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.ui.menu.items.activityport;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.activities.stringconstant.views.StringConstantConfigView;
+import net.sf.taverna.t2.workbench.design.actions.DataflowEditAction;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.AddDataLinkEdit;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.iterationstrategy.CrossProduct;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Action for adding a default value to an input port of a processor.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class AddInputPortDefaultValueAction extends DataflowEditAction {
+
+ private static Logger logger = Logger.getLogger(AddInputPortDefaultValueAction.class);
+
+ private static final URI STRING_CONSTANT = URI
+ .create("http://ns.taverna.org.uk/2010/activity/constant");
+
+ private InputProcessorPort inputPort;
+
+ private final ServiceRegistry serviceRegistry;
+
+ public AddInputPortDefaultValueAction(Workflow workflow, InputProcessorPort inputPort,
+ Component component, EditManager editManager, SelectionManager selectionManager,
+ ServiceRegistry serviceRegistry) {
+ super(workflow, component, editManager, selectionManager);
+ this.inputPort = inputPort;
+ this.serviceRegistry = serviceRegistry;
+ putValue(SMALL_ICON, WorkbenchIcons.inputValueIcon);
+ putValue(NAME, "Set constant value");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ try {
+ Activity activity = new Activity();
+ activity.setType(STRING_CONSTANT);
+ Configuration configuration = new Configuration();
+ configuration.setType(STRING_CONSTANT.resolve("#Config"));
+ configuration.getJsonAsObjectNode().put("string", "");
+ configuration.setConfigures(activity);
+
+ StringConstantConfigView configView = new StringConstantConfigView(activity,
+ configuration, serviceRegistry);
+
+ int answer = JOptionPane.showConfirmDialog(component, configView,
+ "Text constant value", JOptionPane.OK_CANCEL_OPTION);
+ if (answer != JOptionPane.CANCEL_OPTION) {
+
+ configView.noteConfiguration();
+ configuration.setJson(configView.getJson());
+
+ Profile profile = selectionManager.getSelectedProfile();
+
+ Processor processor = new Processor();
+ processor.setName(inputPort.getName() + "_value");
+
+ CrossProduct crossProduct = new CrossProduct();
+ crossProduct.setParent(processor.getIterationStrategyStack());
+
+ ProcessorBinding processorBinding = new ProcessorBinding();
+ processorBinding.setBoundProcessor(processor);
+ processorBinding.setBoundActivity(activity);
+
+ // create activity port
+ OutputActivityPort activityPort = new OutputActivityPort(activity, "value");
+ activityPort.setDepth(0);
+ activityPort.setGranularDepth(0);
+ // create processor port
+ OutputProcessorPort processorPort = new OutputProcessorPort(processor,
+ activityPort.getName());
+ processorPort.setDepth(0);
+ processorPort.setGranularDepth(0);
+ // add a new port binding
+ new ProcessorOutputPortBinding(processorBinding, activityPort, processorPort);
+
+ // Add a data link between the string constant processor's output port
+ // and the processor containing the passed inputPort.
+ DataLink datalink = new DataLink();
+ datalink.setReceivesFrom(processorPort);
+ datalink.setSendsTo(inputPort);
+
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ editList.add(new AddChildEdit<Profile>(profile, activity));
+ editList.add(new AddChildEdit<Profile>(profile, configuration));
+ editList.add(new AddChildEdit<Profile>(profile, processorBinding));
+ editList.add(new AddProcessorEdit(dataflow, processor));
+ editList.add(new AddDataLinkEdit(dataflow, datalink));
+
+ editManager.doDataflowEdit(dataflow.getParent(), new CompoundEdit(editList));
+
+ }
+ } catch (EditException ex) {
+ logger.error("Adding default value for input port failed", ex);
+ }
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectInputPortMenuActions.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectInputPortMenuActions.java
new file mode 100644
index 0000000..f22ebbc
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectInputPortMenuActions.java
@@ -0,0 +1,41 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+
+public class ConnectInputPortMenuActions extends AbstractConnectPortMenuActions
+ implements ContextualMenuComponent {
+
+ public ConnectInputPortMenuActions() {
+ super(ActivityInputPortSection.activityInputPortSection, 20);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof InputProcessorPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectOutputPortMenuActions.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectOutputPortMenuActions.java
new file mode 100644
index 0000000..2bb3ec0
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectOutputPortMenuActions.java
@@ -0,0 +1,41 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+
+
+public class ConnectOutputPortMenuActions extends AbstractConnectPortMenuActions {
+
+ public ConnectOutputPortMenuActions() {
+ super(ActivityOutputPortSection.activityOutputPortSection, 20);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof OutputProcessorPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectPortsAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectPortsAction.java
new file mode 100644
index 0000000..f10fedf
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/ConnectPortsAction.java
@@ -0,0 +1,68 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workflow.edits.AddDataLinkEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+
+@SuppressWarnings("serial")
+public class ConnectPortsAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(ConnectPortsAction.class);
+ private final Workflow workflow;
+ private final ReceiverPort receiverPort;
+ private final SenderPort senderPort;
+ private final EditManager editManager;
+
+ public ConnectPortsAction(Workflow workflow,
+ SenderPort senderPort, ReceiverPort receiverPort, EditManager editManager) {
+ super("Connect " + senderPort.getName() + " to " + receiverPort.getName());
+ this.workflow = workflow;
+ this.receiverPort = receiverPort;
+ this.senderPort = senderPort;
+ this.editManager = editManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ DataLink dataLink = new DataLink();
+ dataLink.setReceivesFrom(senderPort);
+ dataLink.setSendsTo(receiverPort);
+ Edit<Workflow> edit = new AddDataLinkEdit(workflow, dataLink);
+ try {
+ editManager.doDataflowEdit(workflow.getParent(), edit);
+ } catch (EditException ex) {
+ logger.warn("Can't create connection between " + senderPort
+ + " and " + receiverPort, ex);
+ }
+ }
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/CreateAndConnectDataflowPortAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/CreateAndConnectDataflowPortAction.java
new file mode 100644
index 0000000..534f4ca
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/CreateAndConnectDataflowPortAction.java
@@ -0,0 +1,226 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.workbench.design.ui.DataflowInputPortPanel;
+import net.sf.taverna.t2.workbench.design.ui.DataflowOutputPortPanel;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.AddDataLinkEdit;
+import net.sf.taverna.t2.workflow.edits.AddWorkflowInputPortEdit;
+import net.sf.taverna.t2.workflow.edits.AddWorkflowOutputPortEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.DepthPort;
+import uk.org.taverna.scufl2.api.port.InputPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.Port;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+
+/**
+ * Action to create a dataflow input/output port and connect it to the specified
+ * processor/activity output/input port.
+ * <p>
+ * The created dataflow port name will be taken from the name of the provided
+ * port.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+@SuppressWarnings("serial")
+public class CreateAndConnectDataflowPortAction extends AbstractAction {
+
+ private static final String VALID_PORT_NAME_REGEX = "[\\p{L}\\p{Digit}_.]+";
+ private static final Dimension INPUT_PORT_DIALOGUE_SIZE = new Dimension(400, 250);
+ private static final Dimension OUTPUT_PORT_DIALOGUE_SIZE = new Dimension(400, 200);
+
+ private static final String INVALID_WORKFLOW_OUTPUT_PORT_NAME = "Invalid workflow output port name.";
+ private static final String DUPLICATE_WORKFLOW_OUTPUT_PORT_NAME = "Duplicate workflow output port name.";
+ private static final String SET_THE_WORKFLOW_OUTPUT_PORT_NAME = "Set the workflow output port name.";
+ private static final String ADD_WORKFLOW_OUTPUT_PORT = "Add workflow output port";
+ private static final String SET_THE_INPUT_PORT_LIST_DEPTH = "Set the input port list depth.";
+ private static final String SET_THE_INPUT_PORT_TYPE = "Set the input port type.";
+ private static final String INVALID_WORKFLOW_INPUT_PORT_NAME = "Invalid workflow input port name.";
+ private static final String DUPLICATE_WORKFLOW_INPUT_PORT_NAME = "Duplicate workflow input port name.";
+ private static final String SET_THE_WORKFLOW_INPUT_PORT_NAME = "Set the workflow input port name.";
+ private static final String ADD_WORKFLOW_INPUT_PORT = "Add workflow input port";
+ private static Logger logger = Logger.getLogger(CreateAndConnectDataflowPortAction.class);
+ private final Workflow workflow;
+
+ private final Port port;
+ private final String suggestedName;
+ private final Component parentComponent;
+ private final EditManager editManager;
+
+ /**
+ * Action for creating a Workflow input/output port and linking it to the
+ * specified port.
+ * <p>
+ * If the provided port is an InputPort then a
+ * Workflow OutputPort will be created and linked. Vice versa, if the
+ * provided port is an OutputPort, a Workflow InputPort will be created.
+ *
+ * @param workflow
+ * Workflow where to create the Workflow input/output port
+ * @param port
+ * Existing Processor port to connect to
+ * @param suggestedName
+ * suggested port name
+ * @param parentComponent
+ * Component to be parent of any pop-ups
+ */
+ public CreateAndConnectDataflowPortAction(Workflow workflow, Port port,
+ String suggestedName, Component parentComponent, EditManager editManager) {
+ super("Connect to new workflow port");
+ this.workflow = workflow;
+ this.port = port;
+ this.suggestedName = suggestedName;
+ this.parentComponent = parentComponent;
+ this.editManager = editManager;
+ if (!(port instanceof InputPort || port instanceof OutputPort)) {
+ throw new IllegalArgumentException("Port " + port
+ + " must be either an InputPort or OutputPort");
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (port instanceof ReceiverPort) {
+ InputWorkflowPort inputWorkflowPort = new InputWorkflowPort();
+ inputWorkflowPort.setName(suggestedName);
+ workflow.getInputPorts().addWithUniqueName(inputWorkflowPort);
+ workflow.getInputPorts().remove(inputWorkflowPort);
+ if (port instanceof DepthPort) {
+ inputWorkflowPort.setDepth(((DepthPort) port).getDepth());
+ } else {
+ inputWorkflowPort.setDepth(0);
+ }
+ showDialogue(inputWorkflowPort);
+
+ } else if (port instanceof SenderPort) {
+ OutputWorkflowPort outputWorkflowPort = new OutputWorkflowPort();
+ outputWorkflowPort.setName(suggestedName);
+ workflow.getOutputPorts().addWithUniqueName(outputWorkflowPort);
+ workflow.getOutputPorts().remove(outputWorkflowPort);
+ showDialogue(outputWorkflowPort);
+ } else {
+ throw new IllegalStateException("Port " + port
+ + " must be either an InputPort or OutputPort");
+ }
+
+ }
+
+ protected void showDialogue(InputWorkflowPort portTemplate) {
+ Set<String> usedInputPorts = new HashSet<String>();
+ for (InputWorkflowPort usedInputPort : workflow.getInputPorts()) {
+ usedInputPorts.add(usedInputPort.getName());
+ }
+ DataflowInputPortPanel inputPanel = new DataflowInputPortPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ ADD_WORKFLOW_INPUT_PORT, inputPanel);
+ vuid.addTextComponentValidation(inputPanel.getPortNameField(),
+ SET_THE_WORKFLOW_INPUT_PORT_NAME, usedInputPorts,
+ DUPLICATE_WORKFLOW_INPUT_PORT_NAME, VALID_PORT_NAME_REGEX,
+ INVALID_WORKFLOW_INPUT_PORT_NAME);
+ vuid.addMessageComponent(inputPanel.getSingleValueButton(),
+ SET_THE_INPUT_PORT_TYPE);
+ vuid.addMessageComponent(inputPanel.getListValueButton(),
+ SET_THE_INPUT_PORT_LIST_DEPTH);
+ vuid.setSize(INPUT_PORT_DIALOGUE_SIZE);
+
+ inputPanel.setPortName(portTemplate.getName());
+ inputPanel.setPortDepth(portTemplate.getDepth());
+
+ if (vuid.show(parentComponent)) {
+ InputWorkflowPort inputWorkflowPort = new InputWorkflowPort();
+ inputWorkflowPort.setName(inputPanel.getPortName());
+ inputWorkflowPort.setDepth(inputPanel.getPortDepth());
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ editList.add(new AddWorkflowInputPortEdit(workflow, inputWorkflowPort));
+ DataLink dataLink = new DataLink();
+ dataLink.setReceivesFrom(inputWorkflowPort);
+ dataLink.setSendsTo((ReceiverPort) port);
+ editList.add(new AddDataLinkEdit(workflow, dataLink));
+ try {
+ CompoundEdit compoundEdit = new CompoundEdit(editList);
+ editManager.doDataflowEdit(workflow.getParent(), compoundEdit);
+ } catch (EditException ex) {
+ logger.warn("Can't create or connect new input port", ex);
+ }
+
+ }
+ }
+
+ protected void showDialogue(OutputWorkflowPort portTemplate) {
+ Set<String> usedOutputPorts = new HashSet<String>();
+ for (OutputWorkflowPort usedInputPort : workflow.getOutputPorts()) {
+ usedOutputPorts.add(usedInputPort.getName());
+ }
+ DataflowOutputPortPanel outputPanel = new DataflowOutputPortPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ ADD_WORKFLOW_OUTPUT_PORT, outputPanel);
+ vuid.addTextComponentValidation(outputPanel.getPortNameField(),
+ SET_THE_WORKFLOW_OUTPUT_PORT_NAME, usedOutputPorts,
+ DUPLICATE_WORKFLOW_OUTPUT_PORT_NAME,
+ VALID_PORT_NAME_REGEX, INVALID_WORKFLOW_OUTPUT_PORT_NAME);
+ vuid.setSize(OUTPUT_PORT_DIALOGUE_SIZE);
+ outputPanel.setPortName(portTemplate.getName());
+
+ if (vuid.show(parentComponent)) {
+ OutputWorkflowPort outputWorkflowPort = new OutputWorkflowPort();
+ outputWorkflowPort.setName(outputPanel.getPortName());
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ editList.add(new AddWorkflowOutputPortEdit(workflow, outputWorkflowPort));
+ DataLink dataLink = new DataLink();
+ dataLink.setReceivesFrom((SenderPort) port);
+ dataLink.setSendsTo(outputWorkflowPort);
+ editList.add(new AddDataLinkEdit(workflow, dataLink));
+ try {
+ CompoundEdit compoundEdit = new CompoundEdit(editList);
+ editManager.doDataflowEdit(workflow.getParent(), compoundEdit);
+ } catch (EditException ex) {
+ logger.warn("Can't create or connect new workflow output port", ex);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/SetConstantInputPortValueMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/SetConstantInputPortValueMenuAction.java
new file mode 100644
index 0000000..30a91c6
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/SetConstantInputPortValueMenuAction.java
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.activityport;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+
+public class SetConstantInputPortValueMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private ServiceRegistry serviceRegistry;
+
+ public SetConstantInputPortValueMenuAction() {
+ super(ActivityInputPortSection.activityInputPortSection, 10);
+ }
+
+ @Override
+ public synchronized Action getAction() {
+ SetDefaultInputPortValueAction action = (SetDefaultInputPortValueAction) super.getAction();
+ action.updateStatus();
+ return action;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof InputProcessorPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SetDefaultInputPortValueAction(editManager, selectionManager, serviceRegistry);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/SetDefaultInputPortValueAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/SetDefaultInputPortValueAction.java
new file mode 100644
index 0000000..302aaf6
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/activityport/SetDefaultInputPortValueAction.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * 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.ui.menu.items.activityport;
+
+import java.awt.event.ActionEvent;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+
+/**
+ * An action that sets a default value to a processor's input port, in case
+ * the input port is selected on the Graph View.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class SetDefaultInputPortValueAction extends AbstractAction {
+
+ /* Current workflow's selection model event observer. */
+ private Observer<DataflowSelectionMessage> workflowSelectionObserver = new DataflowSelectionObserver();
+
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public SetDefaultInputPortValueAction(EditManager editManager,
+ SelectionManager selectionManager, ServiceRegistry serviceRegistry) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+ putValue(SMALL_ICON, WorkbenchIcons.inputValueIcon);
+ putValue(NAME, "Constant value");
+ putValue(SHORT_DESCRIPTION, "Add a constant value for an input port");
+ setEnabled(false);
+
+ selectionManager.addObserver(new SelectionManagerObserver());
+
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+ DataflowSelectionModel dataFlowSelectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+ // Get selected port
+ Set<Object> selectedWFComponents = dataFlowSelectionModel.getSelection();
+ if (selectedWFComponents.size() > 1) {
+ JOptionPane.showMessageDialog(null,
+ "Only one workflow component should be selected for this action.", "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ } else {
+ Object selectedWFComponent = selectedWFComponents.toArray()[0];
+ if (selectedWFComponent instanceof InputProcessorPort) {
+ new AddInputPortDefaultValueAction(workflowBundle.getMainWorkflow(),
+ (InputProcessorPort) selectedWFComponent, null, editManager,
+ selectionManager, serviceRegistry).actionPerformed(e);
+ }
+ }
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus() {
+ WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+
+ // List of all selected objects in the graph view
+ Set<Object> selection = selectionModel.getSelection();
+
+ if (selection.isEmpty()) {
+ setEnabled(false);
+ } else {
+ // Take the first selected item - we only support single selections anyway
+ Object selected = selection.toArray()[0];
+ if (selected instanceof InputProcessorPort) {
+ // If this input port is not already connected to something - enable the button
+ setEnabled(scufl2Tools.datalinksTo((InputProcessorPort) selected).isEmpty());
+ }
+ }
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow
+ * node is selected in the graph view, and enables/disables this action accordingly.
+ */
+ private final class DataflowSelectionObserver implements Observer<DataflowSelectionMessage> {
+
+ public void notify(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) throws Exception {
+ updateStatus();
+ }
+ }
+
+ private final class SelectionManagerObserver extends SwingAwareObserver<SelectionManagerEvent> {
+
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent) {
+ WorkflowBundleSelectionEvent workflowBundleSelectionEvent = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle oldFlow = workflowBundleSelectionEvent
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newFlow = workflowBundleSelectionEvent.getSelectedWorkflowBundle();
+ // Update the buttons status as current dataflow has changed
+ updateStatus();
+
+ // Remove the workflow selection model listener from the previous (if any)
+ // and add to the new workflow (if any)
+ if (oldFlow != null) {
+ selectionManager.getDataflowSelectionModel(oldFlow).removeObserver(
+ workflowSelectionObserver);
+ }
+
+ if (newFlow != null) {
+ selectionManager.getDataflowSelectionModel(newFlow).addObserver(
+ workflowSelectionObserver);
+ }
+ } else if (message instanceof PerspectiveSelectionEvent) {
+ PerspectiveSelectionEvent perspectiveSelectionEvent = (PerspectiveSelectionEvent) message;
+ if (DESIGN_PERSPECTIVE_ID.equals(perspectiveSelectionEvent.getSelectedPerspective().getID())) {
+ updateStatus();
+ } else {
+ setEnabled(false);
+ }
+ }
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/annotated/AnnotatedConfigureMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/annotated/AnnotatedConfigureMenuAction.java
new file mode 100644
index 0000000..7933940
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/annotated/AnnotatedConfigureMenuAction.java
@@ -0,0 +1,77 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.annotated;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureSection;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualView;
+
+public class AnnotatedConfigureMenuAction extends AbstractContextualMenuAction {
+ private static final String ANNOTATE = "Annotate...";
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private List<AnnotationBeanSPI> annotationBeans;
+
+ public AnnotatedConfigureMenuAction() {
+ super(ConfigureSection.configureSection, 40);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && (getContextualSelection().getSelection() instanceof Annotated);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction(ANNOTATE) {
+ public void actionPerformed(ActionEvent e) {
+ AnnotatedContextualView view = new AnnotatedContextualView((Annotated) getContextualSelection().getSelection(),
+ editManager, selectionManager, annotationBeans);
+ JOptionPane.showMessageDialog(null, view.getMainFrame(), "Annotation", JOptionPane.PLAIN_MESSAGE);
+ }
+ };
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setAnnotationBeans(List<AnnotationBeanSPI> annotationBeans) {
+ this.annotationBeans = annotationBeans;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ConfigureRunningContextualMenuSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ConfigureRunningContextualMenuSection.java
new file mode 100644
index 0000000..9c459e2
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ConfigureRunningContextualMenuSection.java
@@ -0,0 +1,50 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+
+public class ConfigureRunningContextualMenuSection extends AbstractMenu
+implements ContextualMenuComponent {
+ public static final String CONFIGURE_RUNNING = "Configure running";
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+ private ContextualSelection contextualSelection;
+
+ public ConfigureRunningContextualMenuSection() {
+ super(ConfigureSection.configureSection, 45, configureRunningSection, CONFIGURE_RUNNING);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+// return super.isEnabled() && contextualSelection instanceof Processor;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ConfigureSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ConfigureSection.java
new file mode 100644
index 0000000..0f1a17f
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ConfigureSection.java
@@ -0,0 +1,61 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+
+/**
+ * Menu section containing the actions to add service templates, i.e. activities
+ * than are not readily runnable but need to be configured first. The actual actions that
+ * go into this menu can be found in the ui modules for the activities.
+ *
+ * @author Alex Nenadic
+ *
+ */
+public class ConfigureSection extends AbstractMenuSection
+ implements ContextualMenuComponent {
+
+ public static final URI configureSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configure");
+ private ContextualSelection contextualSelection;
+
+ public ConfigureSection() {
+ super(EditSection.editSection, 100, configureSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ Object selection = getContextualSelection().getSelection();
+ return super.isEnabled();
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/EditSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/EditSection.java
new file mode 100644
index 0000000..d23ec6e
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/EditSection.java
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+
+/**
+ * Menu section containing the actions to add service templates, i.e. activities
+ * than are not readily runnable but need to be configured first. The actual actions that
+ * go into this menu can be found in the ui modules for the activities.
+ *
+ * @author Alex Nenadic
+ *
+ */
+public class EditSection extends AbstractMenuSection
+ implements ContextualMenuComponent {
+
+ private static final String EDIT = "Edit";
+ public static final URI editSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/edit");
+ private ContextualSelection contextualSelection;
+
+ public EditSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10, editSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled();
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+ @Override
+ protected Action createAction() {
+ DummyAction action = new DummyAction(EDIT);
+ // Set the colour for the section
+ action.putValue(AbstractMenuSection.SECTION_COLOR, ShadedLabel.ORANGE);
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/InsertSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/InsertSection.java
new file mode 100644
index 0000000..c1e21f6
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/InsertSection.java
@@ -0,0 +1,63 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class InsertSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ private static final String INSERT = "Insert";
+ public static final URI insertSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/insert");
+ private ContextualSelection contextualSelection;
+
+ public InsertSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 20, insertSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Workflow;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new DummyAction(INSERT);
+ }
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/PasteMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/PasteMenuAction.java
new file mode 100644
index 0000000..8f9b1f9
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/PasteMenuAction.java
@@ -0,0 +1,74 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.PasteGraphComponentAction;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class PasteMenuAction extends AbstractContextualMenuAction {
+
+ private static final URI PASTE_SERVICE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/paste#pasteServiceComponent");
+
+ private EditManager editManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ServiceRegistry serviceRegistry;
+
+ public PasteMenuAction() {
+ super(EditSection.editSection, 20, PASTE_SERVICE_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return PasteGraphComponentAction.getInstance(editManager, menuManager, selectionManager, serviceRegistry);
+ }
+
+ public boolean isEnabled() {
+ return super.isEnabled() && (getContextualSelection().getSelection() instanceof Workflow);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowConfigureMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowConfigureMenuAction.java
new file mode 100644
index 0000000..49c07a2
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowConfigureMenuAction.java
@@ -0,0 +1,165 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.awt.KeyboardFocusManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+import javax.swing.text.JTextComponent;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.design.actions.EditDataflowInputPortAction;
+import net.sf.taverna.t2.workbench.design.actions.EditDataflowOutputPortAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeConfigurationView;
+import net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+public class ShowConfigureMenuAction extends AbstractMenuAction {
+
+ private static Logger logger = Logger.getLogger(ShowConfigureMenuAction.class);
+
+ public static final URI GRAPH_DETAILS_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphDetailsMenuSection");
+
+ private static final URI SHOW_CONFIGURE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuShowConfigureComponent");
+
+ private EditManager editManager;
+
+ private SelectionManager selectionManager;
+
+ private MenuManager menuManager;
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public ShowConfigureMenuAction() {
+ super(GRAPH_DETAILS_MENU_SECTION, 20, SHOW_CONFIGURE_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new ShowConfigureAction();
+ }
+
+ @SuppressWarnings("serial")
+ protected class ShowConfigureAction extends AbstractAction implements DesignOnlyAction {
+
+ private boolean enabled;
+
+ ShowConfigureAction() {
+ super();
+ putValue(NAME, "Configure");
+ putValue(SHORT_DESCRIPTION, "Configure selected component");
+ putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false));
+
+ KeyboardFocusManager focusManager = KeyboardFocusManager
+ .getCurrentKeyboardFocusManager();
+ focusManager.addPropertyChangeListener(new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent e) {
+ String prop = e.getPropertyName();
+ if ("focusOwner".equals(prop)) {
+ if (e.getNewValue() instanceof JTextComponent) {
+ ShowConfigureAction.super.setEnabled(false);
+ } else {
+ ShowConfigureAction.this.setEnabled(enabled);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ super.setEnabled(enabled);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+ DataflowSelectionModel dataFlowSelectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+ // Get selected port
+ Set<Object> selectedWFComponents = dataFlowSelectionModel.getSelection();
+ if (selectedWFComponents.size() > 0) {
+ Object component = selectedWFComponents.iterator().next();
+ if (component instanceof Processor) {
+ Action action = WorkflowView.getConfigureAction((Processor) component,
+ menuManager);
+ if (action != null) {
+ action.actionPerformed(e);
+ }
+ } else if (component instanceof DataLink) {
+ DataLink dataLink = (DataLink) component;
+ if (dataLink.getMergePosition() != null) {
+ List<DataLink> datalinks = scufl2Tools.datalinksTo(dataLink.getSendsTo());
+ MergeConfigurationView mergeConfigurationView = new MergeConfigurationView(
+ datalinks, editManager, selectionManager);
+ mergeConfigurationView.setLocationRelativeTo(null);
+ mergeConfigurationView.setVisible(true);
+ }
+ } else if (component instanceof InputWorkflowPort) {
+ InputWorkflowPort port = (InputWorkflowPort) component;
+ new EditDataflowInputPortAction(port.getParent(), port, null, editManager,
+ selectionManager).actionPerformed(e);
+ } else if (component instanceof OutputWorkflowPort) {
+ OutputWorkflowPort port = (OutputWorkflowPort) component;
+ new EditDataflowOutputPortAction(port.getParent(), port, null, editManager,
+ selectionManager).actionPerformed(e);
+ }
+ }
+ }
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowDetailsContextualMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowDetailsContextualMenuAction.java
new file mode 100644
index 0000000..27a47de
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowDetailsContextualMenuAction.java
@@ -0,0 +1,65 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+import org.apache.log4j.Logger;
+
+public class ShowDetailsContextualMenuAction extends AbstractContextualMenuAction {
+ private static final String SHOW_DETAILS = "Show details";
+ private String namedComponent = "contextualView";
+
+ private static Logger logger = Logger.getLogger(ShowDetailsContextualMenuAction.class);
+ private Workbench workbench;
+
+ public ShowDetailsContextualMenuAction() {
+ super(ConfigureSection.configureSection, 40);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled();
+ // FIXME: Should we list all the applicable types here?
+ // && getContextualSelection().getSelection() instanceof Processor;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction(SHOW_DETAILS) {
+ public void actionPerformed(ActionEvent e) {
+ workbench.makeNamedComponentVisible(namedComponent);
+ }
+ };
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowDetailsMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowDetailsMenuAction.java
new file mode 100644
index 0000000..ae9fee3
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowDetailsMenuAction.java
@@ -0,0 +1,81 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+public class ShowDetailsMenuAction extends AbstractMenuAction {
+ private static final URI SHOW_DETAILS_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuShowDetailsComponent");
+
+ private static final String SHOW_DETAILS = "Details";
+ private String namedComponent = "contextualView";
+
+ private Workbench workbench;
+
+ public ShowDetailsMenuAction() {
+ super(ShowConfigureMenuAction.GRAPH_DETAILS_MENU_SECTION, 20, SHOW_DETAILS_URI);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled();
+ // FIXME: Should we list all the applicable types here?
+ // && getContextualSelection().getSelection() instanceof Processor;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new ShowDetailsAction();
+ }
+
+ protected class ShowDetailsAction extends AbstractAction implements DesignOnlyAction {
+
+ ShowDetailsAction() {
+ super();
+ putValue(NAME, "Show details");
+ putValue(SHORT_DESCRIPTION, "Show details of selected component");
+ putValue(Action.ACCELERATOR_KEY,
+ KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ workbench.makeNamedComponentVisible(namedComponent);
+ }
+
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowReportsContextualMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowReportsContextualMenuAction.java
new file mode 100644
index 0000000..c9d7279
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/contextualviews/ShowReportsContextualMenuAction.java
@@ -0,0 +1,103 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.contextualviews;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.Icon;
+
+import net.sf.taverna.t2.lang.ui.icons.Icons;
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.validation.Status;
+
+public class ShowReportsContextualMenuAction extends AbstractContextualMenuAction {
+
+ private static final String SHOW_REPORTS = "Show validation report";
+ private String namedComponent = "reportView";
+ private ReportManager reportManager;
+ private Workbench workbench;
+ private SelectionManager selectionManager;
+
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(ShowReportsContextualMenuAction.class);
+
+ public ShowReportsContextualMenuAction() {
+ /** Right below ShowDetailsContextualMenuAction
+ */
+ super(ConfigureSection.configureSection, 41);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled();
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ WorkflowBundle parent;
+ if (getContextualSelection().getParent() instanceof Workflow) {
+ parent = ((Workflow)getContextualSelection().getParent()).getParent();
+ } else {
+ parent = selectionManager.getSelectedWorkflowBundle();
+ }
+ Status status = Status.OK;
+ if (reportManager != null) {
+// status = reportManager.getStatus(parent.getMainProfile(), (WorkflowBean) getContextualSelection().getSelection());
+ }
+
+ Icon icon = null;
+ if (status == Status.WARNING) {
+ icon = Icons.warningIcon;
+ } else if (status == Status.SEVERE) {
+ icon = Icons.severeIcon;
+ }
+
+ return new AbstractAction(SHOW_REPORTS, icon) {
+ public void actionPerformed(ActionEvent e) {
+ workbench.makeNamedComponentVisible(namedComponent);
+ }
+ };
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/controllink/ConditionSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/controllink/ConditionSection.java
new file mode 100644
index 0000000..3f67def
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/controllink/ConditionSection.java
@@ -0,0 +1,71 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.controllink;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+
+public class ConditionSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ private static final String CONTROL_LINK = "Control link: ";
+ public static final URI conditionSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/condition");
+ private ContextualSelection contextualSelection;
+
+ public ConditionSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10, conditionSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof BlockingControlLink;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+ @Override
+ protected Action createAction() {
+ BlockingControlLink controllink = (BlockingControlLink) getContextualSelection()
+ .getSelection();
+ String name = CONTROL_LINK + controllink.getBlock().getName()
+ + " RUNS_AFTER " + controllink.getUntilFinished().getName();
+ return new DummyAction(name);
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/controllink/RemoveConditionMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/controllink/RemoveConditionMenuAction.java
new file mode 100644
index 0000000..68e32e2
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/controllink/RemoveConditionMenuAction.java
@@ -0,0 +1,67 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.controllink;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveConditionAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class RemoveConditionMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RemoveConditionMenuAction() {
+ super(ConditionSection.conditionSection, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof ControlLink
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow dataflow = (Workflow) getContextualSelection().getParent();
+ ControlLink controlLink = (ControlLink) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new RemoveConditionAction(dataflow, controlLink, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/datalink/LinkSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/datalink/LinkSection.java
new file mode 100644
index 0000000..aee08ea
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/datalink/LinkSection.java
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.datalink;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+
+public class LinkSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ public static final URI linkSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/link");
+ private ContextualSelection contextualSelection;
+
+ public LinkSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10, linkSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof DataLink;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ DataLink link = (DataLink) getContextualSelection().getSelection();
+ String name = "Data link: " + link.getReceivesFrom().getName() + " -> " + link.getSendsTo().getName();
+ return new AbstractAction(name) {
+ public void actionPerformed(ActionEvent e) {
+ }
+ };
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/datalink/RemoveLinkMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/datalink/RemoveLinkMenuAction.java
new file mode 100644
index 0000000..c672f99
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/datalink/RemoveLinkMenuAction.java
@@ -0,0 +1,66 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.datalink;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveDatalinkAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class RemoveLinkMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RemoveLinkMenuAction() {
+ super(LinkSection.linkSection, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof DataLink
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ DataLink datalink = (DataLink) getContextualSelection().getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new RemoveDatalinkAction(workflow, datalink, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/ConnectDataflowInputPortMenuActions.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/ConnectDataflowInputPortMenuActions.java
new file mode 100644
index 0000000..15e8424
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/ConnectDataflowInputPortMenuActions.java
@@ -0,0 +1,42 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.items.activityport.AbstractConnectPortMenuActions;
+
+public class ConnectDataflowInputPortMenuActions extends
+ AbstractConnectPortMenuActions implements ContextualMenuComponent {
+
+ public ConnectDataflowInputPortMenuActions() {
+ super(WorkflowInputPortSection.inputPort, 20);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof InputWorkflowPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/ConnectDataflowOutputPortMenuActions.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/ConnectDataflowOutputPortMenuActions.java
new file mode 100644
index 0000000..d99a361
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/ConnectDataflowOutputPortMenuActions.java
@@ -0,0 +1,42 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.items.activityport.AbstractConnectPortMenuActions;
+
+public class ConnectDataflowOutputPortMenuActions extends
+ AbstractConnectPortMenuActions implements ContextualMenuComponent {
+
+ public ConnectDataflowOutputPortMenuActions() {
+ super(WorkflowOutputPortSection.outputPort, 20);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof OutputWorkflowPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/EditDataflowInputPortMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/EditDataflowInputPortMenuAction.java
new file mode 100644
index 0000000..77c25f5
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/EditDataflowInputPortMenuAction.java
@@ -0,0 +1,68 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.EditDataflowInputPortAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+public class EditDataflowInputPortMenuAction extends
+ AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public EditDataflowInputPortMenuAction() {
+ super(WorkflowInputPortSection.inputPort, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof InputWorkflowPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ InputWorkflowPort inport = (InputWorkflowPort) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new EditDataflowInputPortAction(workflow, inport, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/EditDataflowOutputPortMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/EditDataflowOutputPortMenuAction.java
new file mode 100644
index 0000000..0f406dd
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/EditDataflowOutputPortMenuAction.java
@@ -0,0 +1,68 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.EditDataflowOutputPortAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+public class EditDataflowOutputPortMenuAction extends
+ AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public EditDataflowOutputPortMenuAction() {
+ super(WorkflowOutputPortSection.outputPort, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof OutputWorkflowPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ OutputWorkflowPort outport = (OutputWorkflowPort) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new EditDataflowOutputPortAction(workflow, outport, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/RemoveDataflowInputPortMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/RemoveDataflowInputPortMenuAction.java
new file mode 100644
index 0000000..f5e2fc1
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/RemoveDataflowInputPortMenuAction.java
@@ -0,0 +1,68 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveDataflowInputPortAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+public class RemoveDataflowInputPortMenuAction extends
+ AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RemoveDataflowInputPortMenuAction() {
+ super(WorkflowInputPortSection.inputPort, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof InputWorkflowPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ InputWorkflowPort inport = (InputWorkflowPort) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new RemoveDataflowInputPortAction(workflow, inport, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/RemoveDataflowOutputPortMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/RemoveDataflowOutputPortMenuAction.java
new file mode 100644
index 0000000..da775a5
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/RemoveDataflowOutputPortMenuAction.java
@@ -0,0 +1,68 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveDataflowOutputPortAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+public class RemoveDataflowOutputPortMenuAction extends
+ AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RemoveDataflowOutputPortMenuAction() {
+ super(WorkflowOutputPortSection.outputPort, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof OutputWorkflowPort
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ OutputWorkflowPort outport = (OutputWorkflowPort) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new RemoveDataflowOutputPortAction(workflow, outport, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/WorkflowInputPortSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/WorkflowInputPortSection.java
new file mode 100644
index 0000000..1a0d8ef
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/WorkflowInputPortSection.java
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+
+public class WorkflowInputPortSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ public static final URI inputPort = URI
+ .create("http://taverna.sf.net/2009/contextMenu/inputPort");
+ private ContextualSelection contextualSelection;
+
+ public WorkflowInputPortSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10, inputPort);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof InputWorkflowPort;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ InputWorkflowPort proc = (InputWorkflowPort) getContextualSelection().getSelection();
+ String name = "Workflow input port: " + proc.getName();
+ return new AbstractAction(name) {
+ public void actionPerformed(ActionEvent e) {
+ }
+ };
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/WorkflowOutputPortSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/WorkflowOutputPortSection.java
new file mode 100644
index 0000000..e387f9e
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/ports/WorkflowOutputPortSection.java
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.ports;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+
+public class WorkflowOutputPortSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ public static final URI outputPort = URI
+ .create("http://taverna.sf.net/2009/contextMenu/outputPort");
+ private ContextualSelection contextualSelection;
+
+ public WorkflowOutputPortSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 10, outputPort);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof OutputWorkflowPort;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ OutputWorkflowPort proc = (OutputWorkflowPort) getContextualSelection().getSelection();
+ String name = "Workflow output port: " + proc.getName();
+ return new AbstractAction(name) {
+ public void actionPerformed(ActionEvent e) {
+ }
+ };
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/ConditionMenuActions.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/ConditionMenuActions.java
new file mode 100644
index 0000000..a3e569b
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/ConditionMenuActions.java
@@ -0,0 +1,118 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.processor;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureSection;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.design.actions.AddConditionAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class ConditionMenuActions extends AbstractMenuCustom implements
+ ContextualMenuComponent {
+
+ private ContextualSelection contextualSelection;
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private ActivityIconManager activityIconManager;
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public ConditionMenuActions() {
+ super(ConfigureSection.configureSection, 80 );
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Processor
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.customComponent = null;
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ Processor processor = (Processor) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+
+ List<AddConditionAction> conditions = getAddConditionActions(workflow,
+ processor, component);
+ if (conditions.isEmpty()) {
+ return null;
+ }
+ JMenu conditionMenu = new JMenu("Run after");
+ conditionMenu.setIcon(WorkbenchIcons.controlLinkIcon);
+ conditionMenu.add(new ShadedLabel("Services:", ShadedLabel.ORANGE));
+ conditionMenu.addSeparator();
+ for (AddConditionAction addConditionAction : conditions) {
+ conditionMenu.add(new JMenuItem(addConditionAction));
+ }
+ return conditionMenu;
+ }
+
+ protected List<AddConditionAction> getAddConditionActions(
+ Workflow workflow, Processor targetProcessor, Component component) {
+ List<AddConditionAction> actions = new ArrayList<AddConditionAction>();
+ for (Processor processor : scufl2Tools.possibleUpStreamProcessors(workflow, targetProcessor)) {
+ actions.add(new AddConditionAction(workflow, processor,
+ targetProcessor, component, editManager, selectionManager, activityIconManager));
+ }
+ return actions;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/ProcessorSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/ProcessorSection.java
new file mode 100644
index 0000000..b2bde61
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/ProcessorSection.java
@@ -0,0 +1,58 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.processor;
+
+import java.net.URI;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.items.contextualviews.EditSection;
+
+public class ProcessorSection extends AbstractMenuSection implements
+ ContextualMenuComponent {
+
+ public static final URI processorSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/processor");
+ private ContextualSelection contextualSelection;
+
+ public ProcessorSection() {
+ super(EditSection.editSection, 200, processorSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Processor;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ this.action = null;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/RemoveProcessorMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/RemoveProcessorMenuAction.java
new file mode 100644
index 0000000..e9c8fb4
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/RemoveProcessorMenuAction.java
@@ -0,0 +1,67 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.processor;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.design.actions.RemoveProcessorAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class RemoveProcessorMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RemoveProcessorMenuAction() {
+ super(ProcessorSection.processorSection, 100);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Processor
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ Processor processor = (Processor) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new RemoveProcessorAction(workflow, processor, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/RenameProcessorMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/RenameProcessorMenuAction.java
new file mode 100644
index 0000000..a077726
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/processor/RenameProcessorMenuAction.java
@@ -0,0 +1,68 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.processor;
+
+import java.awt.Component;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureSection;
+import net.sf.taverna.t2.workbench.design.actions.RenameProcessorAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class RenameProcessorMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public RenameProcessorMenuAction() {
+ super(ConfigureSection.configureSection, 60);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Processor
+ && getContextualSelection().getParent() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow workflow = (Workflow) getContextualSelection().getParent();
+ Processor processor = (Processor) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new RenameProcessorAction(workflow, processor, component, editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/CreateInputMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/CreateInputMenuAction.java
new file mode 100644
index 0000000..6a3b880
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/CreateInputMenuAction.java
@@ -0,0 +1,62 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.workflow;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.items.contextualviews.InsertSection;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowInputAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class CreateInputMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public CreateInputMenuAction() {
+ super(InsertSection.insertSection, 10);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddDataflowInputAction((Workflow) getContextualSelection()
+ .getSelection(), getContextualSelection()
+ .getRelativeToComponent(), editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/CreateOutputMenuAction.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/CreateOutputMenuAction.java
new file mode 100644
index 0000000..226258c
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/CreateOutputMenuAction.java
@@ -0,0 +1,62 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.workflow;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.menu.items.contextualviews.InsertSection;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowOutputAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class CreateOutputMenuAction extends AbstractContextualMenuAction {
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public CreateOutputMenuAction() {
+ super(InsertSection.insertSection, 20);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Workflow;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AddDataflowOutputAction((Workflow) getContextualSelection()
+ .getSelection(), getContextualSelection()
+ .getRelativeToComponent(), editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/WorkflowServiceTemplatesSection.java b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/WorkflowServiceTemplatesSection.java
new file mode 100644
index 0000000..d461b5e
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/java/net/sf/taverna/t2/ui/menu/items/workflow/WorkflowServiceTemplatesSection.java
@@ -0,0 +1,76 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu.items.workflow;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+
+/**
+ * Menu section containing the actions to add service templates, i.e. activities
+ * than are not readily runnable but need to be configured first. The actual actions that
+ * go into this menu can be found in the ui modules for the activities.
+ *
+ * @author Alex Nenadic
+ *
+ */
+public class WorkflowServiceTemplatesSection extends AbstractMenuSection
+ implements ContextualMenuComponent {
+
+ private static final String SERVICE_TEMPLATES = "Service templates";
+ public static final URI serviceTemplatesSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/serviceTemplates");
+ private ContextualSelection contextualSelection;
+
+ public WorkflowServiceTemplatesSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 30, serviceTemplatesSection);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Workflow;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+ @Override
+ protected Action createAction() {
+ DummyAction action = new DummyAction(SERVICE_TEMPLATES);
+ // Set the colour for the section
+ action.putValue(AbstractMenuSection.SECTION_COLOR, ShadedLabel.ORANGE);
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-items/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-menu-items/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..47f3e0e
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,46 @@
+net.sf.taverna.t2.ui.menu.items.activityport.ActivityInputPortSection
+net.sf.taverna.t2.ui.menu.items.activityport.SetConstantInputPortValueMenuAction
+net.sf.taverna.t2.ui.menu.items.activityport.ConnectInputPortMenuActions
+
+net.sf.taverna.t2.ui.menu.items.activityport.ActivityOutputPortSection
+net.sf.taverna.t2.ui.menu.items.activityport.ConnectOutputPortMenuActions
+
+net.sf.taverna.t2.ui.menu.items.controllink.ConditionSection
+net.sf.taverna.t2.ui.menu.items.controllink.RemoveConditionMenuAction
+
+net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureRunningContextualMenuSection
+net.sf.taverna.t2.ui.menu.items.contextualviews.ShowDetailsMenuAction
+net.sf.taverna.t2.ui.menu.items.contextualviews.ShowDetailsContextualMenuAction
+net.sf.taverna.t2.ui.menu.items.contextualviews.ShowConfigureMenuAction
+net.sf.taverna.t2.ui.menu.items.contextualviews.ShowReportsContextualMenuAction
+
+net.sf.taverna.t2.ui.menu.items.workflow.CreateInputMenuAction
+net.sf.taverna.t2.ui.menu.items.workflow.CreateOutputMenuAction
+net.sf.taverna.t2.ui.menu.items.contextualviews.InsertSection
+net.sf.taverna.t2.ui.menu.items.workflow.WorkflowServiceTemplatesSection
+net.sf.taverna.t2.ui.menu.items.contextualviews.EditSection
+net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureSection
+net.sf.taverna.t2.ui.menu.items.contextualviews.PasteMenuAction
+
+net.sf.taverna.t2.ui.menu.items.datalink.LinkSection
+net.sf.taverna.t2.ui.menu.items.datalink.RemoveLinkMenuAction
+
+net.sf.taverna.t2.ui.menu.items.merge.MergeSection
+net.sf.taverna.t2.ui.menu.items.merge.RemoveMergeMenuAction
+
+net.sf.taverna.t2.ui.menu.items.ports.ConnectDataflowInputPortMenuActions
+net.sf.taverna.t2.ui.menu.items.ports.ConnectDataflowOutputPortMenuActions
+net.sf.taverna.t2.ui.menu.items.ports.EditDataflowInputPortMenuAction
+net.sf.taverna.t2.ui.menu.items.ports.EditDataflowOutputPortMenuAction
+net.sf.taverna.t2.ui.menu.items.ports.RemoveDataflowInputPortMenuAction
+net.sf.taverna.t2.ui.menu.items.ports.RemoveDataflowOutputPortMenuAction
+net.sf.taverna.t2.ui.menu.items.ports.WorkflowInputPortSection
+net.sf.taverna.t2.ui.menu.items.ports.WorkflowOutputPortSection
+
+net.sf.taverna.t2.ui.menu.items.processor.ConditionMenuActions
+net.sf.taverna.t2.ui.menu.items.processor.ProcessorSection
+net.sf.taverna.t2.ui.menu.items.processor.RenameProcessorMenuAction
+net.sf.taverna.t2.ui.menu.items.processor.RemoveProcessorMenuAction
+
+net.sf.taverna.t2.ui.menu.items.annotated.AnnotatedConfigureMenuAction
+
diff --git a/taverna-workbench-menu-items/src/main/resources/META-INF/spring/menu-items-context-osgi.xml b/taverna-workbench-menu-items/src/main/resources/META-INF/spring/menu-items-context-osgi.xml
new file mode 100644
index 0000000..b7ba9e4
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/resources/META-INF/spring/menu-items-context-osgi.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ActivityInputPortSection" auto-export="interfaces" />
+ <service ref="ActivityOutputPortSection" auto-export="interfaces" />
+ <service ref="AnnotatedConfigureMenuAction" auto-export="interfaces" />
+ <service ref="ConditionMenuActions" auto-export="interfaces" />
+ <service ref="ConditionSection" auto-export="interfaces" />
+ <service ref="ConfigureRunningContextualMenuSection" auto-export="interfaces" />
+ <service ref="ConfigureSection" auto-export="interfaces" />
+ <service ref="ConnectDataflowInputPortMenuActions" auto-export="interfaces" />
+ <service ref="ConnectDataflowOutputPortMenuActions" auto-export="interfaces" />
+ <service ref="ConnectInputPortMenuActions" auto-export="interfaces" />
+ <service ref="ConnectOutputPortMenuActions" auto-export="interfaces" />
+ <service ref="CreateInputMenuAction" auto-export="interfaces" />
+ <service ref="CreateOutputMenuAction" auto-export="interfaces" />
+ <service ref="EditDataflowInputPortMenuAction" auto-export="interfaces" />
+ <service ref="EditDataflowOutputPortMenuAction" auto-export="interfaces" />
+ <service ref="EditSection" auto-export="interfaces" />
+ <service ref="InsertSection" auto-export="interfaces" />
+ <service ref="LinkSection" auto-export="interfaces" />
+ <service ref="PasteMenuAction" auto-export="interfaces" />
+ <service ref="ProcessorSection" auto-export="interfaces" />
+ <service ref="RemoveConditionMenuAction" auto-export="interfaces" />
+ <service ref="RemoveDataflowInputPortMenuAction" auto-export="interfaces" />
+ <service ref="RemoveDataflowOutputPortMenuAction" auto-export="interfaces" />
+ <service ref="RemoveLinkMenuAction" auto-export="interfaces" />
+ <service ref="RemoveProcessorMenuAction" auto-export="interfaces" />
+ <service ref="RenameProcessorMenuAction" auto-export="interfaces" />
+ <service ref="SetConstantInputPortValueMenuAction" auto-export="interfaces" />
+ <service ref="ShowConfigureMenuAction" auto-export="interfaces" />
+ <service ref="ShowDetailsContextualMenuAction" auto-export="interfaces" />
+ <service ref="ShowDetailsMenuAction" auto-export="interfaces" />
+ <service ref="ShowReportsContextualMenuAction" auto-export="interfaces" />
+ <service ref="WorkflowInputPortSection" auto-export="interfaces" />
+ <service ref="WorkflowOutputPortSection" auto-export="interfaces" />
+ <service ref="WorkflowServiceTemplatesSection" auto-export="interfaces" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="reportManager" interface="net.sf.taverna.t2.workbench.report.ReportManager" cardinality="0..1" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="workbench" interface="net.sf.taverna.t2.workbench.ui.Workbench" cardinality="0..1" />
+ <reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <reference id="serviceRegistry" interface="uk.org.taverna.commons.services.ServiceRegistry" />
+
+ <list id="annotationBeans" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI"/>
+
+</beans:beans>
diff --git a/taverna-workbench-menu-items/src/main/resources/META-INF/spring/menu-items-context.xml b/taverna-workbench-menu-items/src/main/resources/META-INF/spring/menu-items-context.xml
new file mode 100644
index 0000000..6208a72
--- /dev/null
+++ b/taverna-workbench-menu-items/src/main/resources/META-INF/spring/menu-items-context.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ActivityInputPortSection" class="net.sf.taverna.t2.ui.menu.items.activityport.ActivityInputPortSection" />
+ <bean id="ActivityOutputPortSection" class="net.sf.taverna.t2.ui.menu.items.activityport.ActivityOutputPortSection" />
+ <bean id="ConditionMenuActions" class="net.sf.taverna.t2.ui.menu.items.processor.ConditionMenuActions">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ </bean>
+ <bean id="AnnotatedConfigureMenuAction" class="net.sf.taverna.t2.ui.menu.items.annotated.AnnotatedConfigureMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="annotationBeans" ref="annotationBeans" />
+ </bean>
+ <bean id="ConditionSection" class="net.sf.taverna.t2.ui.menu.items.controllink.ConditionSection" />
+ <bean id="ConfigureRunningContextualMenuSection" class="net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureRunningContextualMenuSection" />
+ <bean id="ConfigureSection" class="net.sf.taverna.t2.ui.menu.items.contextualviews.ConfigureSection" />
+ <bean id="ConnectDataflowInputPortMenuActions" class="net.sf.taverna.t2.ui.menu.items.ports.ConnectDataflowInputPortMenuActions">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+ <bean id="ConnectDataflowOutputPortMenuActions" class="net.sf.taverna.t2.ui.menu.items.ports.ConnectDataflowOutputPortMenuActions">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+ <bean id="ConnectInputPortMenuActions" class="net.sf.taverna.t2.ui.menu.items.activityport.ConnectInputPortMenuActions">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+ <bean id="ConnectOutputPortMenuActions" class="net.sf.taverna.t2.ui.menu.items.activityport.ConnectOutputPortMenuActions">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+ <bean id="CreateInputMenuAction" class="net.sf.taverna.t2.ui.menu.items.workflow.CreateInputMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="CreateOutputMenuAction" class="net.sf.taverna.t2.ui.menu.items.workflow.CreateOutputMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="EditDataflowInputPortMenuAction" class="net.sf.taverna.t2.ui.menu.items.ports.EditDataflowInputPortMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="EditDataflowOutputPortMenuAction" class="net.sf.taverna.t2.ui.menu.items.ports.EditDataflowOutputPortMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="EditSection" class="net.sf.taverna.t2.ui.menu.items.contextualviews.EditSection" />
+ <bean id="InsertSection" class="net.sf.taverna.t2.ui.menu.items.contextualviews.InsertSection" />
+ <bean id="LinkSection" class="net.sf.taverna.t2.ui.menu.items.datalink.LinkSection" />
+ <bean id="PasteMenuAction" class="net.sf.taverna.t2.ui.menu.items.contextualviews.PasteMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+ <bean id="ProcessorSection" class="net.sf.taverna.t2.ui.menu.items.processor.ProcessorSection" />
+ <bean id="RemoveConditionMenuAction" class="net.sf.taverna.t2.ui.menu.items.controllink.RemoveConditionMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RemoveDataflowInputPortMenuAction" class="net.sf.taverna.t2.ui.menu.items.ports.RemoveDataflowInputPortMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RemoveDataflowOutputPortMenuAction" class="net.sf.taverna.t2.ui.menu.items.ports.RemoveDataflowOutputPortMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RemoveLinkMenuAction" class="net.sf.taverna.t2.ui.menu.items.datalink.RemoveLinkMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RemoveProcessorMenuAction" class="net.sf.taverna.t2.ui.menu.items.processor.RemoveProcessorMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RenameProcessorMenuAction" class="net.sf.taverna.t2.ui.menu.items.processor.RenameProcessorMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="SetConstantInputPortValueMenuAction" class="net.sf.taverna.t2.ui.menu.items.activityport.SetConstantInputPortValueMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+ <bean id="ShowConfigureMenuAction" class="net.sf.taverna.t2.ui.menu.items.contextualviews.ShowConfigureMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="ShowDetailsContextualMenuAction" class="net.sf.taverna.t2.ui.menu.items.contextualviews.ShowDetailsContextualMenuAction">
+ <property name="workbench" ref="workbench" />
+ </bean>
+ <bean id="ShowDetailsMenuAction" class="net.sf.taverna.t2.ui.menu.items.contextualviews.ShowDetailsMenuAction">
+ <property name="workbench" ref="workbench" />
+ </bean>
+ <bean id="ShowReportsContextualMenuAction" class="net.sf.taverna.t2.ui.menu.items.contextualviews.ShowReportsContextualMenuAction">
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="workbench" ref="workbench" />
+ </bean>
+ <bean id="WorkflowInputPortSection" class="net.sf.taverna.t2.ui.menu.items.ports.WorkflowInputPortSection" />
+ <bean id="WorkflowOutputPortSection" class="net.sf.taverna.t2.ui.menu.items.ports.WorkflowOutputPortSection" />
+ <bean id="WorkflowServiceTemplatesSection" class="net.sf.taverna.t2.ui.menu.items.workflow.WorkflowServiceTemplatesSection" />
+
+</beans>
diff --git a/taverna-workbench-monitor-view/pom.xml b/taverna-workbench-monitor-view/pom.xml
new file mode 100644
index 0000000..b4e102f
--- /dev/null
+++ b/taverna-workbench-monitor-view/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>monitor-view</artifactId>
+ <packaging>bundle</packaging>
+ <name>Monitor View</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>results-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-report-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-run-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>${commons.beanutils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/MonitorViewComponent.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/MonitorViewComponent.java
new file mode 100644
index 0000000..7ebd21f
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/MonitorViewComponent.java
@@ -0,0 +1,168 @@
+
+/*******************************************************************************
+ * 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.monitor;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static org.apache.batik.ext.swing.GridBagConstants.EAST;
+import static org.apache.batik.ext.swing.GridBagConstants.NONE;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.monitor.graph.MonitorGraphComponent;
+import net.sf.taverna.t2.workbench.views.monitor.progressreport.TableMonitorComponent;
+
+/**
+ * Component that shows the progress of a workflow run, either through a graph or
+ * a table shown in separate tabs. For previous runs, it pulls processor and workflow
+ * statuses from provenance.
+ *
+ * Graph and table are interactive, where clicking on them triggers displaying of
+ * workflow results or intermediate results in a separate component.
+ *
+ * It also contains buttons to pause/resume and stop a workflow run.
+ *
+ */
+@SuppressWarnings({"serial","unused"})
+public class MonitorViewComponent extends JPanel implements Updatable {
+ private MonitorGraphComponent monitorGraph;
+ private TableMonitorComponent tableMonitorComponent;
+
+ private JTabbedPane tabbedPane;
+ private JPanel buttonsPanel;
+
+ public MonitorViewComponent() {
+ super(new BorderLayout());
+ tabbedPane = new JTabbedPane();
+ buttonsPanel = new JPanel(new GridBagLayout());
+
+// buttonsPanel.add(new JLabel("Workflow status"));
+//
+// buttonsPanel.add(new JButton("Pause"));
+// buttonsPanel.add(new JButton("Cancel"));
+// buttonsPanel.add(new JButton("Show results"));
+
+ add(tabbedPane, CENTER);
+ add(buttonsPanel, SOUTH);
+ }
+
+ public void setMonitorGraph(MonitorGraphComponent monitorGraph) {
+ this.monitorGraph = monitorGraph;
+ tabbedPane.add("Graph", monitorGraph);
+ }
+
+ public void setTableMonitorComponent(TableMonitorComponent tableMonitorComponent) {
+ this.tableMonitorComponent = tableMonitorComponent;
+
+ JScrollPane scrollPane = new JScrollPane(tableMonitorComponent,
+ VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
+
+ tabbedPane.add("Progress report", scrollPane);
+ }
+
+ public void addWorkflowRunStatusLabel(JLabel statusLabel){
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+
+ gbc.fill = NONE;
+ buttonsPanel.add(statusLabel, gbc);
+ }
+
+ public void addWorkflowPauseButton(JButton workflowRunPauseButton) {
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ buttonsPanel.add(workflowRunPauseButton, gbc);
+ }
+
+ public void addWorkflowCancelButton(JButton workflowRunCancelButton) {
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridx = 2;
+ gbc.gridy = 0;
+
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ buttonsPanel.add(workflowRunCancelButton, gbc);
+ }
+
+ public void addReloadWorkflowButton(JButton reloadWorkflowButton) {
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridx = 3;
+ gbc.gridy = 0;
+
+ gbc.fill = NONE;
+ gbc.weightx = 1.0;
+ gbc.anchor = EAST;
+ buttonsPanel.add(reloadWorkflowButton, gbc);
+ }
+
+ public void addIntermediateValuesButton(JButton intermediateValuesButton) {
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridx = 4;
+ gbc.gridy = 0;
+
+ gbc.fill = NONE;
+ gbc.weightx = 1.0;
+ gbc.anchor = EAST;
+ buttonsPanel.add(intermediateValuesButton, gbc);
+ }
+
+ public void addWorkflowResultsButton(JButton workflowResultsButton) {
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridx = 5;
+ gbc.gridy = 0;
+
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ gbc.anchor = EAST;
+ buttonsPanel.add(workflowResultsButton, gbc);
+ }
+
+ @Override
+ public void update() {
+ Component selectedComponent = tabbedPane.getSelectedComponent();
+ if (selectedComponent instanceof Updatable)
+ ((Updatable) selectedComponent).update();
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/GraphMonitor.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/GraphMonitor.java
new file mode 100644
index 0000000..ecaff3e
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/GraphMonitor.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * 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.monitor.graph;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tickIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import uk.org.taverna.platform.report.ActivityReport;
+import uk.org.taverna.platform.report.ProcessorReport;
+import uk.org.taverna.platform.report.State;
+import uk.org.taverna.platform.report.WorkflowReport;
+
+/**
+ * An implementation of the Updatable interface that updates a Graph.
+ *
+ * @author David Withers
+ */
+public class GraphMonitor implements Updatable {
+ private static final String STATUS_RUNNING = "Running";
+ private static final String STATUS_FINISHED = "Finished";
+ private static final String STATUS_CANCELLED = "Cancelled";
+
+ /**
+ * Workflow run status label - we can only tell of workflow is running or is
+ * finished from inside this monitor. If workfow run is stopped or paused -
+ * this will be updated form the run-ui.
+ */
+ private JLabel workflowRunStatusLabel;
+ /**
+ * Similarly to {@link #workflowRunStatusLabel} - we disable the pause anc
+ * cancel buttons when workflow runs is finished
+ */
+ private JButton workflowRunPauseButton;
+ private JButton workflowRunCancelButton;
+ private GraphController graphController;
+ private Set<GraphMonitorNode> processors = new HashSet<>();
+ private final WorkflowReport workflowReport;
+
+ public GraphMonitor(GraphController graphController,
+ WorkflowReport workflowReport) {
+ this.graphController = graphController;
+ this.workflowReport = workflowReport;
+ createMonitorNodes(workflowReport.getSubject().getName(),
+ workflowReport);
+ redraw();
+ }
+
+ private void createMonitorNodes(String id, WorkflowReport workflowReport) {
+ for (ProcessorReport processorReport : workflowReport
+ .getProcessorReports()) {
+ String processorId = id + processorReport.getSubject().getName();
+ processors.add(new GraphMonitorNode(processorId, processorReport,
+ graphController));
+ for (ActivityReport activityReport : processorReport
+ .getActivityReports()) {
+ WorkflowReport nestedWorkflowReport = activityReport
+ .getNestedWorkflowReport();
+ if (nestedWorkflowReport != null)
+ createMonitorNodes(processorId, nestedWorkflowReport);
+ }
+ }
+ }
+
+ public void redraw() {
+ for (GraphMonitorNode node : processors)
+ node.redraw();
+ }
+
+ @Override
+ public void update() {
+ for (GraphMonitorNode node : processors)
+ node.update();
+ // updateState();
+ }
+
+ @SuppressWarnings("unused")
+ private void updateState() {
+ State state = workflowReport.getState();
+ switch (state) {
+ case COMPLETED:
+ case FAILED:
+ workflowRunStatusLabel.setText(STATUS_FINISHED);
+ workflowRunStatusLabel.setIcon(tickIcon);
+ workflowRunPauseButton.setEnabled(false);
+ workflowRunCancelButton.setEnabled(false);
+ break;
+ case CANCELLED:
+ workflowRunStatusLabel.setText(STATUS_CANCELLED);
+ workflowRunStatusLabel.setIcon(closeIcon);
+ workflowRunPauseButton.setEnabled(false);
+ workflowRunCancelButton.setEnabled(false);
+ break;
+ case RUNNING:
+ workflowRunStatusLabel.setText(STATUS_RUNNING);
+ workflowRunStatusLabel.setIcon(workingIcon);
+ default:
+ break;
+ }
+ }
+
+ // Set the status label that will be updated from this monitor
+ public void setWorkflowRunStatusLabel(JLabel workflowRunStatusLabel) {
+ this.workflowRunStatusLabel = workflowRunStatusLabel;
+ }
+
+ public void setWorkflowRunPauseButton(JButton workflowRunPauseButton) {
+ this.workflowRunPauseButton = workflowRunPauseButton;
+ }
+
+ public void setWorkflowRunCancelButton(JButton workflowRunCancelButton) {
+ this.workflowRunCancelButton = workflowRunCancelButton;
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/GraphMonitorNode.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/GraphMonitorNode.java
new file mode 100644
index 0000000..8e5c441
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/GraphMonitorNode.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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.monitor.graph;
+
+import static java.lang.Math.max;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import uk.org.taverna.platform.report.ProcessorReport;
+
+/**
+ * A <code>MonitorNode</code> that updates a <code>Graph</code> when
+ * <code>ProcessorReport</code> property changes.
+ *
+ * @author David Withers
+ */
+public class GraphMonitorNode {
+ private ProcessorReport processorReport;
+ private GraphController graphController;
+ private String processorId;
+ private int queueSize = 0;
+ private int sentJobs = 0;
+ private int completedJobs = 0;
+ private int errors = 0;
+
+ public GraphMonitorNode(String id, ProcessorReport processorReport,
+ GraphController graphController) {
+ this.processorReport = processorReport;
+ this.graphController = graphController;
+ processorId = id;
+ }
+
+ /**
+ * Updates the <code>Graph</code> when changes to properties are detected.
+ */
+ public void update() {
+ synchronized (graphController) {
+ boolean queueSizeChanged = false;
+ boolean sentJobsChanged = false;
+ boolean completedJobsChanged = false;
+ boolean errorsChanged = false;
+
+ int newQueueSize = processorReport.getJobsQueued();
+ newQueueSize = newQueueSize == -1 ? 0 : newQueueSize;
+ if (queueSize != newQueueSize) {
+ queueSize = newQueueSize;
+ queueSizeChanged = true;
+ }
+
+ int newSentJobs = processorReport.getJobsStarted();
+ if (sentJobs != newSentJobs) {
+ sentJobs = newSentJobs;
+ sentJobsChanged = true;
+ }
+
+ int newCompletedJobs = processorReport.getJobsCompleted();
+ if (completedJobs != newCompletedJobs) {
+ completedJobs = newCompletedJobs;
+ completedJobsChanged = true;
+ }
+
+ int newErrors = processorReport.getJobsCompletedWithErrors();
+ if (errors != newErrors) {
+ errors = newErrors;
+ errorsChanged = true;
+ }
+
+ if (queueSizeChanged || sentJobsChanged || completedJobsChanged
+ || errorsChanged) {
+ if (completedJobsChanged)
+ graphController.setIteration(processorId, completedJobs);
+ if (completedJobs > 0)
+ graphController.setNodeCompleted(processorId,
+ (completedJobs / (float) (sentJobs + queueSize)));
+ if (sentJobsChanged) {
+ // graphController.setEdgeActive(processorId, true);
+ }
+ if (errorsChanged && errors > 0)
+ graphController.setErrors(processorId, errors);
+ }
+ }
+ }
+
+ public void redraw() {
+ synchronized (graphController) {
+ queueSize = max(processorReport.getJobsQueued(), 0);
+ sentJobs = processorReport.getJobsStarted();
+ completedJobs = processorReport.getJobsCompleted();
+ errors = processorReport.getJobsCompletedWithErrors();
+
+ graphController.setIteration(processorId, completedJobs);
+ if (completedJobs > 0)
+ graphController.setNodeCompleted(processorId,
+ (completedJobs / (float) (sentJobs + queueSize)));
+ if (errors > 0)
+ graphController.setErrors(processorId, errors);
+ }
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/MonitorGraphComponent.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/MonitorGraphComponent.java
new file mode 100644
index 0000000..0be66ff
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/graph/MonitorGraphComponent.java
@@ -0,0 +1,378 @@
+/*******************************************************************************
+ * 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.monitor.graph;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static javax.swing.Action.SHORT_DESCRIPTION;
+import static javax.swing.Action.SMALL_ICON;
+import static javax.swing.BoxLayout.PAGE_AXIS;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.refreshIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.zoomInIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.zoomOutIcon;
+import static org.apache.batik.swing.svg.AbstractJSVGComponent.ALWAYS_DYNAMIC;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+import javax.swing.Timer;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphEventManager;
+import net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphController;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.graph.AutoScrollInteractor;
+import net.sf.taverna.t2.workbench.views.graph.menu.ResetDiagramAction;
+import net.sf.taverna.t2.workbench.views.graph.menu.ZoomInAction;
+import net.sf.taverna.t2.workbench.views.graph.menu.ZoomOutAction;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.batik.swing.JSVGScrollPane;
+import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
+import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.WorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Use to display the graph for fresh workflow runs and allow the user to click
+ * on processors to see the intermediate results for processors pulled from
+ * provenance.
+ */
+@SuppressWarnings("serial")
+public class MonitorGraphComponent extends JPanel implements Updatable {
+ private static Logger logger = Logger.getLogger(MonitorGraphComponent.class);
+
+ private SVGGraphController graphController;
+ private JPanel diagramPanel;
+ private GraphMonitor graphMonitor;
+
+ private Map<String, SVGGraphController> graphControllerMap = new HashMap<>();
+ private Map<String, GraphMonitor> graphMonitorMap = new HashMap<>();
+ private Map<String, JPanel> diagramPanelMap = new HashMap<>();
+ private Map<String, Action[]> diagramActionsMap = new HashMap<>();
+
+ @SuppressWarnings("unused")
+ private Timer timer;
+ private CardLayout cardLayout;
+ @SuppressWarnings("unused")
+ private JLabel statusLabel;
+
+ private final RunService runService;
+ private final ColourManager colourManager;
+ private final WorkbenchConfiguration workbenchConfiguration;
+ private final SelectionManager selectionManager;
+
+ public MonitorGraphComponent(RunService runService, ColourManager colourManager,
+ WorkbenchConfiguration workbenchConfiguration, SelectionManager selectionManager) {
+ this.runService = runService;
+ this.colourManager = colourManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+ this.selectionManager = selectionManager;
+
+ cardLayout = new CardLayout();
+ setLayout(cardLayout);
+
+// ActionListener taskPerformer = new ActionListener() {
+// public void actionPerformed(ActionEvent evt) {
+// if (graphController != null) {
+// graphController.redraw();
+// graphMonitor.redraw();
+// }
+// timer.stop();
+// }
+// };
+// timer = new Timer(100, taskPerformer);
+//
+// addComponentListener(new ComponentAdapter() {
+// public void componentResized(ComponentEvent e) {
+// if (timer.isRunning()) {
+// timer.restart();
+// } else {
+// timer.start();
+// }
+// }
+// });
+
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (graphController != null)
+ graphController.shutdown();
+ }
+
+ @Override
+ public void update() {
+ if (graphMonitor != null)
+ graphMonitor.update();
+ }
+
+ private JPanel createDiagramPanel(String workflowRun) {
+ final JPanel diagramPanel = new JPanel(new BorderLayout());
+
+ try {
+ Workflow workflow = runService.getWorkflow(workflowRun);
+ Profile profile = runService.getProfile(workflowRun);
+
+ // get the default diagram settings
+ // Alignment alignment = Alignment.valueOf(graphViewConfiguration
+ // .getProperty(GraphViewConfiguration.ALIGNMENT));
+ // PortStyle portStyle = PortStyle.valueOf(graphViewConfiguration
+ // .getProperty(GraphViewConfiguration.PORT_STYLE));
+
+ // create an SVG canvas
+ final JSVGCanvas svgCanvas = new JSVGCanvas(null, true, false);
+ svgCanvas.setEnableZoomInteractor(false);
+ svgCanvas.setEnableRotateInteractor(false);
+ svgCanvas.setDocumentState(ALWAYS_DYNAMIC);
+
+ AutoScrollInteractor asi = new AutoScrollInteractor(svgCanvas);
+ svgCanvas.addMouseListener(asi);
+ svgCanvas.addMouseMotionListener(asi);
+
+ final JSVGScrollPane svgScrollPane = new MySvgScrollPane(svgCanvas);
+
+ GVTTreeRendererAdapter gvtTreeRendererAdapter = new GVTTreeRendererAdapter() {
+ @Override
+ public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
+ logger.info("Rendered svg");
+// svgScrollPane.reset();
+// diagramPanel.revalidate();
+ }
+ };
+ svgCanvas.addGVTTreeRendererListener(gvtTreeRendererAdapter);
+
+ // create a graph controller
+ SVGGraphController svgGraphController = new SVGGraphController(
+ workflow, profile, true, svgCanvas, null, null,
+ colourManager, workbenchConfiguration);
+ DataflowSelectionModel selectionModel = selectionManager
+ .getWorkflowRunSelectionModel(workflowRun);
+ svgGraphController.setDataflowSelectionModel(selectionModel);
+ svgGraphController
+ .setGraphEventManager(new MonitorGraphEventManager(
+ selectionModel));
+
+ graphControllerMap.put(workflowRun, svgGraphController);
+
+ // Toolbar with actions related to graph
+ JToolBar graphActionsToolbar = graphActionsToolbar(workflowRun, svgCanvas);
+ graphActionsToolbar.setAlignmentX(LEFT_ALIGNMENT);
+ graphActionsToolbar.setFloatable(false);
+
+ // Panel to hold the toolbars
+ JPanel toolbarPanel = new JPanel();
+ toolbarPanel.setLayout(new BoxLayout(toolbarPanel, PAGE_AXIS));
+ toolbarPanel.add(graphActionsToolbar);
+
+ diagramPanel.add(toolbarPanel, NORTH);
+ diagramPanel.add(svgScrollPane, CENTER);
+
+ // JTextField workflowHierarchy = new JTextField(workflow.getName());
+ // diagramPanel.add(workflowHierarchy, BorderLayout.SOUTH);
+ } catch (InvalidRunIdException e) {
+ diagramPanel.add(new JLabel("Workflow run ID invalid", JLabel.CENTER),
+ CENTER);
+ }
+ return diagramPanel;
+ }
+
+ protected JToolBar graphActionsToolbar(String workflowRun, JSVGCanvas svgCanvas) {
+ JToolBar toolBar = new JToolBar();
+ toolBar.setAlignmentX(LEFT_ALIGNMENT);
+ toolBar.setFloatable(false);
+
+ JButton resetDiagramButton = new JButton();
+ resetDiagramButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+ JButton zoomInButton = new JButton();
+ zoomInButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+ JButton zoomOutButton = new JButton();
+ zoomOutButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+
+ Action resetDiagramAction = svgCanvas.new ResetTransformAction();
+ ResetDiagramAction.setResultsAction(resetDiagramAction);
+ resetDiagramAction.putValue(SHORT_DESCRIPTION, "Reset Diagram");
+ resetDiagramAction.putValue(SMALL_ICON, refreshIcon);
+ resetDiagramButton.setAction(resetDiagramAction);
+
+ Action zoomInAction = svgCanvas.new ZoomAction(1.2);
+ ZoomInAction.setResultsAction(zoomInAction);
+ zoomInAction.putValue(SHORT_DESCRIPTION, "Zoom In");
+ zoomInAction.putValue(SMALL_ICON, zoomInIcon);
+ zoomInButton.setAction(zoomInAction);
+
+ Action zoomOutAction = svgCanvas.new ZoomAction(1 / 1.2);
+ ZoomOutAction.setResultsAction(zoomOutAction);
+ zoomOutAction.putValue(SHORT_DESCRIPTION, "Zoom Out");
+ zoomOutAction.putValue(SMALL_ICON, zoomOutIcon);
+ zoomOutButton.setAction(zoomOutAction);
+
+ // diagramActionsMap.put(workflowRun, new Action[] { resetDiagramAction, zoomInAction,
+ // zoomOutAction });
+
+ toolBar.add(resetDiagramButton);
+ toolBar.add(zoomInButton);
+ toolBar.add(zoomOutButton);
+
+ return toolBar;
+ }
+
+ // public void setStatus(Status status) {
+ // switch (status) {
+ // case RUNNING :
+ // statusLabel.setText("Workflow running");
+ // statusLabel.setIcon(WorkbenchIcons.workingIcon);
+ // if (workflow != null){ // should not be null really, workflow should be set before this
+ // method is called
+ // workflow.setIsRunning(true);
+ // }
+ // break;
+ // case FINISHED :
+ // statusLabel.setText("Workflow finished");
+ // statusLabel.setIcon(WorkbenchIcons.tickIcon);
+ // if (workflow != null){// should not be null really, workflow should be set before this method
+ // is called
+ // workflow.setIsRunning(false);
+ // }
+ // break;
+ // }
+ // }
+
+ public void setWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ if (workflowRun != null) {
+ if (!diagramPanelMap.containsKey(workflowRun))
+ addWorkflowRun(workflowRun);
+ graphController = graphControllerMap.get(workflowRun);
+ diagramPanel = diagramPanelMap.get(workflowRun);
+ graphMonitor = graphMonitorMap.get(workflowRun);
+ Action[] actions = diagramActionsMap.get(workflowRun);
+ if (actions != null && actions.length == 3) {
+ ResetDiagramAction.setDesignAction(actions[0]);
+ ZoomInAction.setDesignAction(actions[1]);
+ ZoomOutAction.setDesignAction(actions[2]);
+ }
+ cardLayout.show(this, String.valueOf(diagramPanel.hashCode()));
+ // graphController.redraw();
+ }
+ }
+
+ public void addWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ JPanel newDiagramPanel = createDiagramPanel(workflowRun);
+ add(newDiagramPanel, String.valueOf(newDiagramPanel.hashCode()));
+ diagramPanelMap.put(workflowRun, newDiagramPanel);
+ graphMonitorMap.put(workflowRun,
+ new GraphMonitor(graphControllerMap.get(workflowRun),
+ runService.getWorkflowReport(workflowRun)));
+ }
+
+ public void removeWorkflowRun(String workflowRun) {
+ JPanel removedDiagramPanel = diagramPanelMap.remove(workflowRun);
+ if (removedDiagramPanel != null)
+ remove(removedDiagramPanel);
+ SVGGraphController removedController = graphControllerMap
+ .remove(workflowRun);
+ if (removedController != null)
+ removedController.shutdown();
+ graphMonitorMap.remove(workflowRun);
+ diagramActionsMap.remove(workflowRun);
+ }
+
+ private class MonitorGraphEventManager implements GraphEventManager {
+ private final DataflowSelectionModel selectionModel;
+
+ public MonitorGraphEventManager(DataflowSelectionModel selectionModel) {
+ this.selectionModel = selectionModel;
+ }
+
+ @Override
+ public void mouseClicked(final GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ Object workflowObject = graphElement.getWorkflowBean();
+ if (workflowObject instanceof Processor
+ || workflowObject instanceof WorkflowPort)
+ selectionModel.addSelection(workflowObject);
+ }
+
+ @Override
+ public void mouseDown(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ }
+
+ @Override
+ public void mouseMoved(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ }
+
+ @Override
+ public void mouseUp(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ }
+
+ @Override
+ public void mouseOut(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ }
+
+ @Override
+ public void mouseOver(GraphElement graphElement, short button,
+ boolean altKey, boolean ctrlKey, boolean metaKey, int x, int y,
+ int screenX, int screenY) {
+ }
+ }
+
+ private class MySvgScrollPane extends JSVGScrollPane {
+ private static final long serialVersionUID = 6890422410714378543L;
+
+ public MySvgScrollPane(JSVGCanvas canvas) {
+ super(canvas);
+ }
+
+ @Override
+ public void reset() {
+ super.resizeScrollBars();
+ super.reset();
+ }
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/TableMonitorComponent.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/TableMonitorComponent.java
new file mode 100644
index 0000000..bb82421
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/TableMonitorComponent.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.monitor.progressreport;
+
+import java.awt.CardLayout;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import uk.org.taverna.platform.report.WorkflowReport;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class TableMonitorComponent extends JPanel implements Updatable {
+ private Map<String, WorkflowRunProgressTreeTable> tableMap = new HashMap<>();
+ private Map<String, WorkflowRunProgressTreeTableModel> tableModelMap = new HashMap<>();
+ private WorkflowRunProgressTreeTable table;
+ private WorkflowRunProgressTreeTableModel tableModel;
+ private CardLayout cardLayout;
+
+ private final RunService runService;
+ private final SelectionManager selectionManager;
+ private final ActivityIconManager activityIconManager;
+
+ public TableMonitorComponent(RunService runService,
+ SelectionManager selectionManager,
+ ActivityIconManager activityIconManager) {
+ this.runService = runService;
+ this.selectionManager = selectionManager;
+ this.activityIconManager = activityIconManager;
+
+ cardLayout = new CardLayout();
+ setLayout(cardLayout);
+ }
+
+ public void setWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ if (workflowRun != null) {
+ if (!tableMap.containsKey(workflowRun))
+ addWorkflowRun(workflowRun);
+ table = tableMap.get(workflowRun);
+ tableModel = tableModelMap.get(workflowRun);
+ cardLayout.show(this, String.valueOf(table.hashCode()));
+ }
+ }
+
+ public void addWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ WorkflowReport workflowReport = runService
+ .getWorkflowReport(workflowRun);
+ WorkflowRunProgressTreeTableModel newTableModel = new WorkflowRunProgressTreeTableModel(
+ workflowReport);
+ WorkflowRunProgressTreeTable newTable = new WorkflowRunProgressTreeTable(
+ newTableModel, activityIconManager,
+ selectionManager.getWorkflowRunSelectionModel(workflowRun));
+
+ add(new JScrollPane(newTable), String.valueOf(newTable.hashCode()));
+ tableMap.put(workflowRun, newTable);
+ tableModelMap.put(workflowRun, newTableModel);
+ }
+
+ public void removeWorkflowRun(String workflowRun) {
+ WorkflowRunProgressTreeTable removedTable = tableMap
+ .remove(workflowRun);
+ if (removedTable != null)
+ remove(removedTable);
+ tableModelMap.remove(workflowRun);
+ }
+
+ @Override
+ public void update() {
+ if (tableModel != null)
+ tableModel.update();
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeCellRenderer.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeCellRenderer.java
new file mode 100644
index 0000000..4c4d6ad
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeCellRenderer.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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.monitor.progressreport;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workflowExplorerIcon;
+
+import java.awt.Component;
+import java.util.Set;
+
+import javax.swing.Icon;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import uk.org.taverna.platform.report.ActivityReport;
+import uk.org.taverna.platform.report.ProcessorReport;
+import uk.org.taverna.platform.report.WorkflowReport;
+
+/**
+ * Cell renderer for Workflow Explorer tree.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class WorkflowRunProgressTreeCellRenderer extends DefaultTreeCellRenderer {
+ private ActivityIconManager activityIconManager;
+
+ public WorkflowRunProgressTreeCellRenderer(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean sel, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ Component result = super.getTreeCellRendererComponent(tree, value, sel,
+ expanded, leaf, row, hasFocus);
+
+ Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
+
+ WorkflowRunProgressTreeCellRenderer renderer = (WorkflowRunProgressTreeCellRenderer) result;
+
+ if (userObject instanceof WorkflowReport) // the root node
+ renderWorkflowReport(renderer, (WorkflowReport) userObject);
+ else if (userObject instanceof ProcessorReport)
+ renderProcessorReport(renderer, (ProcessorReport) userObject);
+
+ return result;
+ }
+
+ private void renderWorkflowReport(
+ WorkflowRunProgressTreeCellRenderer renderer,
+ WorkflowReport workflowReport) {
+ renderer.setIcon(workflowExplorerIcon);
+ renderer.setText(workflowReport.getSubject().getName());
+ }
+
+ private void renderProcessorReport(WorkflowRunProgressTreeCellRenderer renderer,
+ ProcessorReport processorReport) {
+ /*
+ * Get the activity associated with the processor - currently only
+ * one gets displayed
+ */
+ Set<ActivityReport> activityReports = processorReport
+ .getActivityReports();
+ String text = processorReport.getSubject().getName();
+ if (!activityReports.isEmpty()) {
+ ActivityReport activityReport = activityReports.iterator()
+ .next();
+ Icon icon = activityIconManager.iconForActivity(activityReport
+ .getSubject());
+ if (icon != null)
+ renderer.setIcon(icon);
+ }
+ renderer.setText(text);
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeTable.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeTable.java
new file mode 100644
index 0000000..f1f031c
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeTable.java
@@ -0,0 +1,112 @@
+package net.sf.taverna.t2.workbench.views.monitor.progressreport;
+
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.treetable.JTreeTable;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import uk.org.taverna.platform.report.StatusReport;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.WorkflowPort;
+
+@SuppressWarnings("serial")
+public class WorkflowRunProgressTreeTable extends JTreeTable {
+ private final WorkflowRunProgressTreeTableModel treeTableModel;
+ private final DataflowSelectionModel selectionModel;
+ private final DataflowSelectionObserver dataflowSelectionObserver;
+
+ public WorkflowRunProgressTreeTable(
+ WorkflowRunProgressTreeTableModel treeTableModel,
+ ActivityIconManager activityIconManager,
+ DataflowSelectionModel selectionModel) {
+ super(treeTableModel);
+
+ this.treeTableModel = treeTableModel;
+ this.selectionModel = selectionModel;
+
+ this.tree.setCellRenderer(new WorkflowRunProgressTreeCellRenderer(
+ activityIconManager));
+ this.tree.setEditable(false);
+ this.tree.setExpandsSelectedPaths(true);
+ this.tree.setDragEnabled(false);
+ this.tree.setScrollsOnExpand(false);
+
+ getTableHeader().setReorderingAllowed(false);
+ getSelectionModel().setSelectionMode(
+ ListSelectionModel.SINGLE_SELECTION);
+ getSelectionModel().addListSelectionListener(
+ new TableSelectionListener());
+
+ dataflowSelectionObserver = new DataflowSelectionObserver();
+ selectionModel.addObserver(dataflowSelectionObserver);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ selectionModel.removeObserver(dataflowSelectionObserver);
+ }
+
+ /**
+ * Return object in the tree part of this JTreeTable that corresponds to
+ * this row. It will either be a workflow (tree root) or a processor.
+ */
+ public Object getTreeObjectForRow(int row) {
+ TreePath path = tree.getPathForRow(row);
+ if (path == null)
+ return null;
+ return ((DefaultMutableTreeNode) path.getLastPathComponent())
+ .getUserObject();
+ }
+
+ public void setSelectedRowForObject(Object workflowObject) {
+ // Find the row for the object in the tree
+ DefaultMutableTreeNode node = treeTableModel.getNodeForObject(workflowObject);
+ if (node != null) {
+ TreeNode[] path = node.getPath();
+ tree.scrollPathToVisible(new TreePath(path));
+ int row = tree.getRowForPath(new TreePath(path));
+ if (row >= 0)
+ // Set selected row on the table
+ setRowSelectionInterval(row, row);
+ }
+ }
+
+ private class DataflowSelectionObserver extends SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ for (Object selection : selectionModel.getSelection()) {
+ if (selection instanceof Processor
+ || selection instanceof Workflow)
+ setSelectedRowForObject(selection);
+ else if (selection instanceof WorkflowPort)
+ setSelectedRowForObject(((WorkflowPort) selection)
+ .getParent());
+ }
+ }
+ }
+
+ private class TableSelectionListener implements ListSelectionListener {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting())
+ return;
+ int selectedRow = getSelectedRow();
+ if (selectedRow < 0)
+ return;
+ Object selection = getTreeObjectForRow(selectedRow);
+ if (selection instanceof StatusReport)
+ selectionModel.addSelection(((StatusReport<?, ?>) selection)
+ .getSubject());
+ }
+ }
+}
diff --git a/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeTableModel.java b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeTableModel.java
new file mode 100644
index 0000000..1e2e6e9
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/java/net/sf/taverna/t2/workbench/views/monitor/progressreport/WorkflowRunProgressTreeTableModel.java
@@ -0,0 +1,279 @@
+/*******************************************************************************
+ * 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.monitor.progressreport;
+
+import static java.util.Collections.nCopies;
+import static net.sf.taverna.t2.workbench.views.monitor.progressreport.WorkflowRunProgressTreeTableModel.Column.values;
+import static net.sf.taverna.t2.workbench.views.results.processor.ProcessorResultsComponent.formatMilliseconds;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+import net.sf.taverna.t2.lang.ui.treetable.AbstractTreeTableModel;
+import net.sf.taverna.t2.lang.ui.treetable.TreeTableModel;
+import uk.org.taverna.platform.report.ActivityReport;
+import uk.org.taverna.platform.report.Invocation;
+import uk.org.taverna.platform.report.ProcessorReport;
+import uk.org.taverna.platform.report.State;
+import uk.org.taverna.platform.report.StatusReport;
+import uk.org.taverna.platform.report.WorkflowReport;
+
+/**
+ * A TreeTableModel used to display the progress of a workfow run. The workflow
+ * and its processors (some of which may be nested) are represented as a tree,
+ * where their properties, such as status, start and finish times, number of
+ * iterations, etc. are represented as table columns.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ * @author David Withers
+ */
+public class WorkflowRunProgressTreeTableModel extends AbstractTreeTableModel {
+ public static final String NAME = "Name";
+ public static final String STATUS = "Status";
+ public static final String AVERAGE_ITERATION_TIME = "Average time per iteration";
+ public static final String ITERATIONS = "Queued iterations";
+ public static final String ITERATIONS_DONE = "Completed iterations";
+ public static final String ITERATIONS_FAILED = "Iterations with errors";
+
+ public enum Column {
+ NAME("Name", TreeTableModel.class), STATUS("Status"), ITERATIONS_QUEUED(
+ "Queued iterations"), ITERATIONS_DONE("Iterations done"), ITERATIONS_FAILED(
+ "Iterations w/errors"), AVERAGE_ITERATION_TIME(
+ "Average time/iteration"), START_TIME(
+ "First iteration started", Date.class), FINISH_TIME(
+ "Last iteration ended", Date.class);
+
+ private final String label;
+ private final Class<?> columnClass;
+
+ Column(String label) {
+ this(label, String.class);
+ }
+
+ Column(String label, Class<?> columnClass) {
+ this.label = label;
+ this.columnClass = columnClass;
+ }
+
+ public Class<?> getColumnClass() {
+ return columnClass;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+ }
+
+ // Table data (maps workflow element nodes to column data associated with them)
+ private final Map<DefaultMutableTreeNode, List<Object>> data = new HashMap<>();
+ private final Map<Object, DefaultMutableTreeNode> nodeForObject = new HashMap<>();
+ private final DefaultMutableTreeNode rootNode;
+
+ public WorkflowRunProgressTreeTableModel(WorkflowReport workflowReport) {
+ super(new DefaultMutableTreeNode(workflowReport));
+ rootNode = (DefaultMutableTreeNode) this.getRoot();
+ createTree(workflowReport, rootNode);
+ }
+
+ private void createTree(WorkflowReport workflowReport, DefaultMutableTreeNode root) {
+ // If this is the root of the tree rather than a root of the nested sub-tree
+ if (root.equals(rootNode)) {
+ List<Object> columnData = new ArrayList<>(nCopies(values().length,
+ null));
+ setColumnValues(workflowReport, columnData);
+ nodeForObject.put(workflowReport, root);
+ nodeForObject.put(workflowReport.getSubject(), root);
+ data.put(root, columnData);
+ }
+ // One row for each processor
+ for (ProcessorReport processorReport : workflowReport.getProcessorReports()) {
+ List<Object> columnData = new ArrayList<>(nCopies(values().length,
+ null));
+ DefaultMutableTreeNode processorNode = new DefaultMutableTreeNode(
+ processorReport);
+ setColumnValues(processorReport, columnData);
+ nodeForObject.put(processorReport, processorNode);
+ nodeForObject.put(processorReport.getSubject(), processorNode);
+ data.put(processorNode, columnData);
+ root.add(processorNode);
+
+ Set<ActivityReport> activityReports = processorReport.getActivityReports();
+ if (activityReports.size() == 1) {
+ WorkflowReport nestedWorkflowReport = activityReports.iterator().next()
+ .getNestedWorkflowReport();
+ if (nestedWorkflowReport != null)
+ // create sub-tree
+ createTree(nestedWorkflowReport, processorNode);
+ }
+ }
+ }
+
+ public DefaultMutableTreeNode getNodeForObject(Object workflowObject) {
+ return nodeForObject.get(workflowObject);
+ }
+
+ public void setColumnValues(StatusReport<?, ?> report, List<Object> columns) {
+ if (report instanceof WorkflowReport) {
+ WorkflowReport workflowReport = (WorkflowReport) report;
+
+ State state = workflowReport.getState();
+ Date startTime = workflowReport.getStartedDate();
+ Date finishTime = workflowReport.getCompletedDate();
+
+ columns.set(Column.NAME.ordinal(), workflowReport.getSubject().getName());
+ columns.set(Column.STATUS.ordinal(), state);
+ columns.set(Column.ITERATIONS_DONE.ordinal(), "-");
+ columns.set(Column.ITERATIONS_FAILED.ordinal(), "-");
+ columns.set(Column.ITERATIONS_QUEUED.ordinal(), "-");
+ columns.set(Column.START_TIME.ordinal(), startTime);
+ columns.set(Column.FINISH_TIME.ordinal(), finishTime);
+ if (startTime != null && finishTime != null)
+ columns.set(Column.AVERAGE_ITERATION_TIME.ordinal(),
+ formatMilliseconds(finishTime.getTime() - finishTime.getTime()));
+ else
+ columns.set(Column.AVERAGE_ITERATION_TIME.ordinal(), "-");
+ } else if (report instanceof ProcessorReport) {
+ ProcessorReport processorReport = (ProcessorReport) report;
+
+ State state = processorReport.getState();
+ SortedSet<Invocation> invocations = processorReport
+ .getInvocations();
+
+ columns.set(Column.NAME.ordinal(), processorReport.getSubject()
+ .getName());
+ columns.set(Column.STATUS.ordinal(), state);
+ columns.set(Column.ITERATIONS_QUEUED.ordinal(),
+ processorReport.getJobsQueued());
+ columns.set(Column.ITERATIONS_DONE.ordinal(),
+ processorReport.getJobsCompleted());
+ columns.set(Column.ITERATIONS_FAILED.ordinal(),
+ processorReport.getJobsCompletedWithErrors());
+
+ if (invocations.isEmpty()) {
+ columns.set(Column.START_TIME.ordinal(), null);
+ columns.set(Column.FINISH_TIME.ordinal(), null);
+ columns.set(Column.AVERAGE_ITERATION_TIME.ordinal(), null); // iteration
+ } else {
+ Date earliestStartTime = invocations.first().getStartedDate();
+ Date latestFinishTime = invocations.first().getCompletedDate();
+ long totalInvocationTime = 0;
+ int finishedInvocations = 0;
+
+ for (Invocation invocation : invocations) {
+ // Get the earliest start time of all invocations
+ Date startTime = invocation.getStartedDate();
+ if (startTime != null) {
+ if (startTime.before(earliestStartTime))
+ earliestStartTime = startTime;
+ // Get the latest finish time of all invocations
+ Date finishTime = invocation.getCompletedDate();
+ if (finishTime != null) {
+ if (finishTime.after(latestFinishTime)) {
+ latestFinishTime = finishTime;
+ totalInvocationTime += finishTime.getTime() - startTime.getTime();
+ }
+ finishedInvocations++;
+ }
+ }
+ }
+
+ columns.set(Column.START_TIME.ordinal(), earliestStartTime);
+ columns.set(Column.FINISH_TIME.ordinal(), latestFinishTime);
+ if (finishedInvocations > 0) {
+ long averageTime = totalInvocationTime / finishedInvocations;
+ columns.set(Column.AVERAGE_ITERATION_TIME.ordinal(),
+ formatMilliseconds(averageTime));
+ } else
+ columns.set(Column.AVERAGE_ITERATION_TIME.ordinal(), "-");
+ }
+ }
+ }
+
+ public void update() {
+ update(rootNode);
+ fireTreeNodesChanged(rootNode, rootNode.getPath(), null, null);
+ }
+
+ private void update(DefaultMutableTreeNode node) {
+ setColumnValues((StatusReport<?, ?>) node.getUserObject(), data.get(node));
+ for (int i = 0; i < node.getChildCount(); i++)
+ update((DefaultMutableTreeNode) node.getChildAt(i));
+ }
+
+ //
+ // The TreeModel interface
+ //
+
+ @Override
+ public int getChildCount(Object node) {
+ return ((TreeNode) node).getChildCount();
+ }
+
+ @Override
+ public Object getChild(Object node, int i) {
+ return ((TreeNode) node).getChildAt(i);
+ }
+
+ //
+ // The TreeTableNode interface.
+ //
+
+ @Override
+ public int getColumnCount() {
+ return values().length;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return values()[column].getLabel();
+ }
+
+ @Override
+ public Class<?> getColumnClass(int column) {
+ return values()[column].getColumnClass();
+ }
+
+ public Object getValueAt(Object node, Column column) {
+ return getValueAt(node, column.ordinal());
+ }
+
+ @Override
+ public Object getValueAt(Object node, int column) {
+ List<Object> columnValues = data.get(node);
+ if (columnValues == null)
+ return null;
+ return columnValues.get(column);
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-monitor-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-monitor-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..cf13a46
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.monitor.graph.MonitorGraphComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-monitor-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-monitor-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..30f1743
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.monitor.graph.MonitorGraphComponent
\ No newline at end of file
diff --git a/taverna-workbench-monitor-view/src/main/resources/META-INF/spring/monitor-view-context-osgi.xml b/taverna-workbench-monitor-view/src/main/resources/META-INF/spring/monitor-view-context-osgi.xml
new file mode 100644
index 0000000..ab22b97
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/resources/META-INF/spring/monitor-view-context-osgi.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+</beans:beans>
diff --git a/taverna-workbench-monitor-view/src/main/resources/META-INF/spring/monitor-view-context.xml b/taverna-workbench-monitor-view/src/main/resources/META-INF/spring/monitor-view-context.xml
new file mode 100644
index 0000000..d662d87
--- /dev/null
+++ b/taverna-workbench-monitor-view/src/main/resources/META-INF/spring/monitor-view-context.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+</beans>
diff --git a/taverna-workbench-parallelize-ui/pom.xml b/taverna-workbench-parallelize-ui/pom.xml
new file mode 100644
index 0000000..6fba337
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>parallelize-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Parallelize layer contextual view</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson-databind.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigurationPanel.java b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigurationPanel.java
new file mode 100644
index 0000000..f951da6
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigurationPanel.java
@@ -0,0 +1,99 @@
+package net.sf.taverna.t2.workbench.parallelize;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+@SuppressWarnings("serial")
+public class ParallelizeConfigurationPanel extends JPanel {
+
+ private ObjectNode json;
+ private JTextField maxJobsField = new JTextField(10);
+ private final String processorName;
+
+ public ParallelizeConfigurationPanel(Configuration configuration, String processorName) {
+ if (configuration.getJson().has("parallelize")) {
+ json = (ObjectNode) configuration.getJson().get("parallelize").deepCopy();
+ } else {
+ json = configuration.getJsonAsObjectNode().objectNode();
+ }
+ this.processorName = processorName;
+ this.setLayout(new GridBagLayout());
+ this.setBorder(new EmptyBorder(10,10,10,10));
+ populate();
+ }
+
+ public void populate() {
+ this.removeAll();
+ GridBagConstraints gbc = new GridBagConstraints();
+ JLabel jobs = new JLabel("<html><body>Maximum numbers of items to process at the same time</body></html>");
+
+ jobs.setBorder(new EmptyBorder(0,0,0,10));
+ gbc.weightx = 0.8;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(jobs, gbc);
+ if (json.has("maximumJobs")) {
+ maxJobsField.setText(json.get("maximumJobs").asText());
+ } else {
+ maxJobsField.setText("1");
+ }
+ gbc.weightx = 0.2;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(maxJobsField, gbc);
+ gbc.weightx = 0.1;
+ this.add(new JPanel(), gbc);
+
+ gbc.gridy=1;
+ gbc.gridx=0;
+ gbc.gridwidth=3;
+ gbc.weightx=0;
+ gbc.anchor = GridBagConstraints.SOUTH;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.weighty = 1.0;
+ JLabel explanationLabel = new JLabel("<html><body><small>" +
+ "The service <b>" + processorName + "</b> will be invoked as soon as the required inputs " +
+ "for an iteration are available, but no more than the maximum number of items " +
+ "will be invoked at the same time."
+ + "</small></body></html>");
+ this.add(explanationLabel, gbc);
+
+ this.setPreferredSize(new Dimension(350, 170));
+ }
+
+ public boolean validateConfig() {
+ String errorText = "";
+ int maxJobs = -1;
+ try {
+ maxJobs = Integer.parseInt(maxJobsField.getText());
+ if (maxJobs < 1) {
+ errorText += "The maximum number of items must be a positive integer.\n";
+ }
+ }
+ catch (NumberFormatException e) {
+ errorText += "The maximum number of items must be an integer.\n";
+ }
+
+ if (errorText.length() > 0) {
+ JOptionPane.showMessageDialog(this, errorText, "", JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public JsonNode getJson() {
+ json.put("maximumJobs", maxJobsField.getText());
+ return json;
+ }
+
+}
diff --git a/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigureAction.java b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigureAction.java
new file mode 100644
index 0000000..6727c59
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigureAction.java
@@ -0,0 +1,185 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.parallelize;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.ChangeJsonEdit;
+
+import org.apache.log4j.Logger;
+
+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.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * @author alanrw
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ParallelizeConfigureAction extends AbstractAction {
+
+ private Frame owner;
+ private final Processor processor;
+ private final ParallelizeContextualView parallelizeContextualView;
+
+ private EditManager editManager;
+
+ private static Logger logger = Logger.getLogger(ParallelizeConfigureAction.class);
+
+ private final Scufl2Tools scufl2Tools = new Scufl2Tools();
+ private final SelectionManager selectionManager;
+
+ public ParallelizeConfigureAction(Frame owner,
+ ParallelizeContextualView parallelizeContextualView,
+ Processor processor, EditManager editManager, SelectionManager selectionManager) {
+ super("Configure");
+ this.owner = owner;
+ this.parallelizeContextualView = parallelizeContextualView;
+ this.processor = processor;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String processorName = processor.getName();
+ String title = "Parallel jobs for service " + processorName;
+ final JDialog dialog = new HelpEnabledDialog(owner, title, true);
+ Configuration configuration;
+ try {
+ configuration = scufl2Tools.configurationFor(processor, selectionManager.getSelectedProfile());
+ } catch (IndexOutOfBoundsException ex) {
+ configuration = new Configuration();
+ }
+ ParallelizeConfigurationPanel parallelizeConfigurationPanel = new ParallelizeConfigurationPanel(configuration, processorName);
+ dialog.add(parallelizeConfigurationPanel, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout());
+
+ JButton okButton = new JButton(new OKAction(dialog,
+ parallelizeConfigurationPanel));
+ buttonPanel.add(okButton);
+
+ JButton resetButton = new JButton(new ResetAction(
+ parallelizeConfigurationPanel));
+ buttonPanel.add(resetButton);
+
+ JButton cancelButton = new JButton(new CancelAction(dialog));
+ buttonPanel.add(cancelButton);
+
+ dialog.add(buttonPanel, BorderLayout.SOUTH);
+ dialog.pack();
+ dialog.setLocationRelativeTo(null);
+ dialog.setVisible(true);
+ }
+
+ public class ResetAction extends AbstractAction {
+
+ private final ParallelizeConfigurationPanel parallelizeConfigurationPanel;
+
+ public ResetAction(ParallelizeConfigurationPanel parallelizeConfigurationPanel) {
+ super("Reset");
+ this.parallelizeConfigurationPanel = parallelizeConfigurationPanel;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ parallelizeConfigurationPanel.populate();
+ }
+
+ }
+
+ public class OKAction extends AbstractAction {
+
+ private final ParallelizeConfigurationPanel parallelizeConfigurationPanel;
+ private final JDialog dialog;
+
+ public OKAction(JDialog dialog, ParallelizeConfigurationPanel parallelizeConfigurationPanel) {
+ super("OK");
+ this.dialog = dialog;
+ this.parallelizeConfigurationPanel = parallelizeConfigurationPanel;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (parallelizeConfigurationPanel.validateConfig()) {
+ try {
+ try {
+ Configuration configuration = scufl2Tools.configurationFor(processor, selectionManager.getSelectedProfile());
+ ObjectNode json = configuration.getJsonAsObjectNode().deepCopy();
+ ObjectNode parallelizeNode = null;
+ if (json.has("parallelize")) {
+ parallelizeNode = (ObjectNode) json.get("parallelize");
+ } else {
+ parallelizeNode = json.objectNode();
+ json.put("parallelize", parallelizeNode);
+ }
+ JsonNode newParallelizeNode = parallelizeConfigurationPanel.getJson();
+ Iterator<Entry<String, JsonNode>> fields = newParallelizeNode.fields();
+ while (fields.hasNext()) {
+ Entry<String, JsonNode> entry = fields.next();
+ parallelizeNode.set(entry.getKey(), entry.getValue());
+ }
+ Edit<Configuration> edit = new ChangeJsonEdit(configuration, json);
+ editManager.doDataflowEdit(selectionManager.getSelectedWorkflowBundle(), edit);
+ } catch (IndexOutOfBoundsException ex) {
+ Configuration configuration = new Configuration();
+ configuration.setConfigures(processor);
+ ObjectNode json = configuration.getJsonAsObjectNode();
+ json.put("parallelize", parallelizeConfigurationPanel.getJson());
+ Edit<Profile> edit = new AddChildEdit<Profile>(selectionManager.getSelectedProfile(), configuration);
+ editManager.doDataflowEdit(selectionManager.getSelectedWorkflowBundle(), edit);
+ }
+ dialog.setVisible(false);
+ if (parallelizeContextualView != null) {
+ parallelizeContextualView.refreshView();
+ }
+ } catch (EditException e1) {
+ logger.warn("Could not configure jobs", e1);
+ JOptionPane.showMessageDialog(owner, "Could not configure jobs",
+ "An error occured when configuring jobs: " + e1.getMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ }
+
+ public class CancelAction extends AbstractAction {
+
+ private final JDialog dialog;
+
+ public CancelAction(JDialog dialog) {
+ super("Cancel");
+ this.dialog = dialog;
+
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(false);
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigureMenuAction.java b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigureMenuAction.java
new file mode 100644
index 0000000..194d81e
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeConfigureMenuAction.java
@@ -0,0 +1,77 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.parallelize;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+public class ParallelizeConfigureMenuAction extends AbstractContextualMenuAction {
+
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+
+ private static final URI PARALLELIZE_CONFIGURE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/parallelizeConfigure");
+
+ public static URI TYPE = URI.create("http://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Parallelize");
+
+ private EditManager editManager;
+
+ private SelectionManager selectionManager;
+
+ public ParallelizeConfigureMenuAction() {
+ super(configureRunningSection, 10, PARALLELIZE_CONFIGURE_URI);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Parallel jobs...") {
+ public void actionPerformed(ActionEvent e) {
+ Processor processor = (Processor) getContextualSelection().getSelection();
+ ParallelizeConfigureAction parallelizeConfigureAction = new ParallelizeConfigureAction(
+ null, null, processor, editManager, selectionManager);
+ parallelizeConfigureAction.actionPerformed(e);
+ }
+ };
+ }
+
+ public boolean isEnabled() {
+ return super.isEnabled() && (getContextualSelection().getSelection() instanceof Processor);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeContextualView.java b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeContextualView.java
new file mode 100644
index 0000000..22d2da1
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeContextualView.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * 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.parallelize;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+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 com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * View of a processor, including it's iteration stack, activities, etc.
+ *
+ * @author Alan R Williams
+ *
+ */
+@SuppressWarnings("serial")
+public class ParallelizeContextualView extends ContextualView {
+
+ private final Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ private Processor processor;
+
+ private JPanel panel;
+
+ private final EditManager editManager;
+
+ private final SelectionManager selectionManager;
+
+ public ParallelizeContextualView(Processor processor, EditManager editManager, SelectionManager selectionManager) {
+ super();
+ this.processor = processor;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ initialise();
+ initView();
+ }
+
+ @Override
+ public void refreshView() {
+ initialise();
+ }
+
+ private void initialise() {
+ if (panel == null) {
+ panel = createPanel();
+ } else {
+ panel.removeAll();
+ }
+
+ JTextArea textArea = new ReadOnlyTextArea();
+ textArea.setEditable(false);
+ String maxJobs = "1";
+ for (Configuration configuration : scufl2Tools.configurationsFor(processor, selectionManager.getSelectedProfile())) {
+ JsonNode processorConfig = configuration.getJson();
+ if (processorConfig.has("parallelize")) {
+ JsonNode parallelizeConfig = processorConfig.get("parallelize");
+ if (parallelizeConfig.has("maximumJobs")) {
+ maxJobs = parallelizeConfig.get("maximumJobs").asText();
+ }
+ }
+ }
+ textArea.setText("The maximum number of jobs is " + maxJobs);
+ textArea.setBackground(panel.getBackground());
+ panel.add(textArea, BorderLayout.CENTER);
+ revalidate();
+ }
+
+
+ @Override
+ public JComponent getMainFrame() {
+ return panel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Parallel jobs";
+ }
+
+ protected JPanel createPanel() {
+ JPanel result = new JPanel();
+ result.setLayout(new BorderLayout());
+
+
+ return result;
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 400;
+ }
+
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new ParallelizeConfigureAction(owner, this, processor, editManager, selectionManager);
+ }
+
+
+}
diff --git a/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeContextualViewFactory.java b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeContextualViewFactory.java
new file mode 100644
index 0000000..f9ff7e7
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/java/net/sf/taverna/t2/workbench/parallelize/ParallelizeContextualViewFactory.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.parallelize;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+public class ParallelizeContextualViewFactory implements ContextualViewFactory<Processor> {
+
+ public static URI TYPE = URI.create("http://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Parallelize");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public boolean canHandle(Object selection) {
+ return selection instanceof Processor;
+ }
+
+ public List<ContextualView> getViews(Processor selection) {
+ return Arrays.asList(new ContextualView[] {new ParallelizeContextualView(selection, editManager, selectionManager)});
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-parallelize-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..edd7b2d
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.parallelize.ParallelizeConfigureMenuAction
diff --git a/taverna-workbench-parallelize-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..8b94f86
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.parallelize.ParallelizeContextualViewFactory
diff --git a/taverna-workbench-parallelize-ui/src/main/resources/META-INF/spring/parallelize-ui-context-osgi.xml b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/spring/parallelize-ui-context-osgi.xml
new file mode 100644
index 0000000..1859672
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/spring/parallelize-ui-context-osgi.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ParallelizeConfigureMenuAction" auto-export="interfaces" />
+
+ <service ref="ParallelizeContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-parallelize-ui/src/main/resources/META-INF/spring/parallelize-ui-context.xml b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/spring/parallelize-ui-context.xml
new file mode 100644
index 0000000..1d1fdd4
--- /dev/null
+++ b/taverna-workbench-parallelize-ui/src/main/resources/META-INF/spring/parallelize-ui-context.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ParallelizeConfigureMenuAction" class="net.sf.taverna.t2.workbench.parallelize.ParallelizeConfigureMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+ <bean id="ParallelizeContextualViewFactory" class="net.sf.taverna.t2.workbench.parallelize.ParallelizeContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-perspective-biocatalogue/LocalTestLauncher.bat b/taverna-workbench-perspective-biocatalogue/LocalTestLauncher.bat
new file mode 100644
index 0000000..1c7f299
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/LocalTestLauncher.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo.
+del biocatalogue-perspective-local-launch.jar
+echo Deleted old JAR file if it was there.
+cd target\classes
+jar cfM ..\..\biocatalogue-perspective-local-launch.jar *.*
+cd ..\..
+echo JAR assembly done, launching app...
+
+java -cp .;.\biocatalogue-perspective-local-launch.jar;c:/Users/Sergey/.m2/repository/net/sf/taverna/t2/workbench/ui-api/0.2/ui-api-0.2.jar;c:\Users\Sergey\.m2\repository\net\sf\taverna\t2\lang\ui\1.0\ui-1.0.jar;c:\Users\Sergey\.m2\repository\net\sf\taverna\t2\ui-components\workflow-view\1.0\workflow-view-1.0.jar;c:\Users\Sergey\.m2\repository\net\sf\taverna\t2\ui-activities\wsdl-activity-ui\0.7\wsdl-activity-ui-0.7.jar;C:\Users\Sergey\.m2\repository\log4j\log4j\1.2.13\log4j-1.2.13.jar;C:\Users\Sergey\.m2\repository\net\sf\taverna\t2\workbench\commons-icons\0.2\commons-icons-0.2.jar;c:\Users\Sergey\.m2\repository\BrowserLauncher2\BrowserLauncher2\1.3\BrowserLauncher2-1.3.jar;C:\Users\Sergey\.m2\repository\jdom\jdom\1.0\jdom-1.0.jar;"c:\Program Files\Java\xmlbeans-2.4.0\lib\xbean.jar";"c:\Program Files\Java\xmlbeans-2.4.0\lib\jsr173_1.0_api.jar";.\lib\lablib-checkboxtree-3.1.jar;.\lib\core-renderer.jar;.\lib\commons-lang-2.4.jar net.sf.taverna.t2.ui.perspectives.biocatalogue.TestJFrameForLocalLaunch
+
+del biocatalogue-perspective-local-launch.jar
+echo Cleanup done - deleted old the JAR file that was used for the current launch.
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/lib/core-renderer.jar b/taverna-workbench-perspective-biocatalogue/lib/core-renderer.jar
new file mode 100644
index 0000000..871fabf
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/lib/core-renderer.jar
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/log4j.properties b/taverna-workbench-perspective-biocatalogue/log4j.properties
new file mode 100644
index 0000000..fcd1d0c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/log4j.properties
@@ -0,0 +1,4 @@
+log4j.rootLogger=DEBUG, A1
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
diff --git a/taverna-workbench-perspective-biocatalogue/pom.xml b/taverna-workbench-perspective-biocatalogue/pom.xml
new file mode 100644
index 0000000..3f5457a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/pom.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>perspective-biocatalogue</artifactId>
+ <name>BioCatalogue Perspective</name>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository</url>
+ </repository>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>fuse</id>
+ <name>fuseRepository</name>
+ <url>http://repo.fusesource.com/maven2-all/</url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>
+ http://www.mygrid.org.uk/maven/snapshot-repository
+ </url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <!-- <dependency> <groupId>net.sf.taverna.t2.ui-api</groupId> <artifactId>perspective-core</artifactId>
+ <version>${t2.ui.api.version}</version> </dependency> -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-impl</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <!-- required for providing contextual views in the bottom-left area of
+ Design perspective -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <!-- required for inserting a SOAP processor into the current workflow -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>wsdl-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+ <!-- required for importing REST processors into the current workflow -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>rest-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+ <!-- required for inserting a processor into the current workflow -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <!-- required registering with and opening help window -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>helper</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ <version>${jdom.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.xmlbeans</groupId>
+ <artifactId>xmlbeans</artifactId>
+ <version>2.5.0</version>
+ </dependency>
+
+ <!-- FlyingSaucer XHTML Renderer -->
+ <!-- (it is critical to use version R8, not any earlier ones - e.g. R8pre2,
+ etc.) -->
+ <dependency>
+ <groupId>org.xhtmlrenderer</groupId>
+ <artifactId>core-renderer</artifactId>
+ <version>${org.xhtmlrenderer.core-renderer.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>bouncycastle</groupId>
+ <artifactId>bcprov-jdk14</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>bouncycastle</groupId>
+ <artifactId>bcmail-jdk14</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- At least StringEscapeUtils class is used from this library -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>com.springsource.org.apache.commons.lang</artifactId>
+ <version>${commons.lang.version}</version>
+ </dependency>
+
+ <!-- Gson: Java to Json conversion -->
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>${gson.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <!-- Adds "xmlbeans:xmlbeans" Maven2 goal to compile the API binding classes
+ from XSD schema. -->
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xmlbeans-maven-plugin</artifactId>
+ <version>2.3.3</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>xmlbeans</goal>
+ </goals>
+ </execution>
+ </executions>
+ <inherited>true</inherited>
+ <configuration>
+ <!-- "javaSource=1.5" is required to make use of generics and have getXXXList()
+ methods available, not just getXXXArrray() -->
+ <javaSource>1.5</javaSource>
+ <download>true</download>
+ <schemaDirectory>src/main/xsd</schemaDirectory>
+ <!-- Default is target/generated-sources/xmlbeans - which the Maven
+ plugin should be able to add to the Project classpath -->
+ <!-- <sourceGenerationDirectory>src/main/java</sourceGenerationDirectory> -->
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/taverna-workbench-perspective-biocatalogue/schema_compilation/move_scomp_results_into_project.bat b/taverna-workbench-perspective-biocatalogue/schema_compilation/move_scomp_results_into_project.bat
new file mode 100644
index 0000000..3786d7c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/schema_compilation/move_scomp_results_into_project.bat
@@ -0,0 +1,29 @@
+@echo off
+
+echo This will replace the JAR file with compiled API classes
+pause
+del ..\lib\biocatalogue_api_classes.jar
+move biocatalogue_api_classes.jar ..\lib\
+echo JAR file replaced
+echo.
+
+REM replace the sources of API classes
+echo This will delete *ALL* files in \src\main\java\org\biocatalogue
+echo \src\main\java\org\purl
+echo \src\main\java\org\w3
+pause
+
+rd /S /Q ..\src\main\java\org\biocatalogue
+rd /S /Q ..\src\main\java\org\purl
+rd /S /Q ..\src\main\java\org\w3
+
+move /Y org\biocatalogue ..\src\main\java\org
+move /Y org\purl ..\src\main\java\org
+move /Y org\w3 ..\src\main\java\org
+rd org
+
+echo Sources of API classes replaced
+echo.
+
+echo Done!
+pause
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/schema_compilation/scomp_compile_from_web.bat b/taverna-workbench-perspective-biocatalogue/schema_compilation/scomp_compile_from_web.bat
new file mode 100644
index 0000000..ee10a50
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/schema_compilation/scomp_compile_from_web.bat
@@ -0,0 +1,9 @@
+@echo off
+
+REM -src . -- put source files here
+REM -srconly -- only sources, no compiling of java classes, no jar bundling
+REM -compiler -- where to find javac
+REM -javasource -- which JAVA version to aim for (1.5 uses generics)
+REM -dl -- allows download of referenced schemas
+
+scomp -src . -srconly -compiler "%JAVA_HOME%\bin\javac.exe" -javasource 1.5 -dl http://www.biocatalogue.org/2009/xml/rest/schema-v1.xsd
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/schema_compilation/scomp_compile_from_web_to_jar.bat b/taverna-workbench-perspective-biocatalogue/schema_compilation/scomp_compile_from_web_to_jar.bat
new file mode 100644
index 0000000..dd66206
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/schema_compilation/scomp_compile_from_web_to_jar.bat
@@ -0,0 +1,9 @@
+@echo off
+
+REM -src . -- put source files here
+REM -compiler -- where to find javac
+REM -javasource -- which JAVA version to aim for (1.5 uses generics)
+REM -out -- specifies the name of the target JAR file
+REM -dl -- allows download of referenced schemas
+
+scomp -src . -compiler "%JAVA_HOME%\bin\javac.exe" -javasource 1.5 -out biocatalogue_api_classes.jar -dl http://www.biocatalogue.org/2009/xml/rest/schema-v1.xsd
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/doc/BioCatalogue Plugin Documentation.odt b/taverna-workbench-perspective-biocatalogue/src/main/doc/BioCatalogue Plugin Documentation.odt
new file mode 100644
index 0000000..85056d7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/doc/BioCatalogue Plugin Documentation.odt
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/help/Index-TOC-Map-Additions.txt b/taverna-workbench-perspective-biocatalogue/src/main/help/Index-TOC-Map-Additions.txt
new file mode 100644
index 0000000..8d6c953
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/help/Index-TOC-Map-Additions.txt
@@ -0,0 +1,20 @@
+/* http://www.mygrid.org.uk/taverna2_1/helpset/toc.xml */
+
+<tocitem text="BioCatalogue Plugin" target="biocatalogue-plugin" expand="false">
+ <tocitem text="Feature Overview" target="biocatalogue-plugin-features"/>
+ <tocitem text="Feedback" target="biocatalogue-plugin-feedback"/>
+</tocitem>
+
+
+
+/* http://www.mygrid.org.uk/taverna2_1/helpset/index.xml */
+
+<indexitem text="BioCatalogue Plugin" target="biocatalogue-plugin"/>
+
+
+
+/* http://www.mygrid.org.uk/taverna2_1/helpset/map.xml */
+
+<mapID target="biocatalogue-plugin" url="html/biocatalogue-plugin.html"/>
+<mapID target="biocatalogue-plugin-features" url="html/biocatalogue-plugin-features.html"/>
+<mapID target="biocatalogue-plugin-feedback" url="html/biocatalogue-plugin-feedback.html"/>
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin-features.html b/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin-features.html
new file mode 100644
index 0000000..ece2949
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin-features.html
@@ -0,0 +1,113 @@
+<html>
+ <head>
+ <meta content="text/html; charset=MacRoman" http-equiv="Content-Type">
+ <link rel="stylesheet" href="./basic.css" type="text/css">
+ <meta name="generator" content="Helen">
+ <title>BioCatalogue Plugin - Features</title>
+ </head>
+ <body>
+ <h2>
+ BioCatalogue Plugin - Features
+ </h2>
+
+ <h3>
+ BioCatalogue Perspective
+ </h3>
+ <p>
+ This perspective is designed for searching and browsing the BioCatalogue
+ data. It aids with discovery of Processors that may be used in a workflow.
+ </p>
+
+ <p>
+ In the BioCatalogue perspective you may:
+ <ul>
+ <li>Search for Web Services by free text queries of by tags;</li>
+ <li>Filter search results by the same set of criteria as available on the BioCatalogue website;</li>
+ <li>Save favourite filters and search queries for re-use at a later point;</li>
+ <li>View search history;</li>
+ <li>Immediately see the types and monitoring statuses of all found Web
+ Services directly in the results listings;</li>
+ <li>Preview Web Services.</li>
+ </ul>
+ </p>
+
+ <p>
+ Web Service previews display a similar set of information to
+ that shown on the BioCatalogue website: service description,
+ type, location, provider and other details are shown. Listing
+ of all operations and their descriptions within that Web Service
+ is shown. Any operation may be added directly into the current
+ workflow or into the Service Panel in the Design Perspective
+ for later use.
+ </p>
+
+ <h3>
+ Integration into Design Perspective
+ </h3>
+
+ <p>
+ <ul>
+ <li>Any Web Service operations added to the Service Panel from the BioCatalogue
+ perspective can be dragged into the Workflow Diagram like any other Processors.
+ They are saved by the plugin, so that when Taverna is restarted, those services
+ can still be found in the Service Panel.
+ </li>
+ <li>Right mouse click on a Processor in the Workflow Explorer or Workflow Diagram
+ will display options provided by the plugin - for all WSDL Processors it is
+ possible to check their monitoring status or launch the Processor Preview.
+ </li>
+ <li>Right mouse click on an empty space in the Workflow Diagram will let to launch
+ the workflow "health check" - currently this feature will identify a list of all
+ WSDL activities in a workflow and will fetch the latest monitoring data about
+ each of the from BioCatalogue.
+ </li>
+ <li>"Details" tab in the contextual view area (bottom-left corner of the Design
+ Perspective) will display information about the WSDL Processors and their
+ input or output ports (if they are registered in BioCatalogue). These contextual
+ views are only shown if BioCatalogue knows how to handle the selected type of
+ workflow element.
+ </li>
+ </ul>
+ </p>
+
+
+ <h3>
+ Choosing the BioCatalogue Instance to Work With
+ </h3>
+
+ <p>
+ The BioCatalogue is an open-source project and anyone can setup their
+ own instance of the BioCatalogue software. By default, the plugin is
+ configured to use the main BioCatalogue website
+ (at <font color="blue">http://www.biocatalogue.org</font>) as a source
+ of data.
+ </p>
+ <p>
+ Should this be necessary, the plugin can be configured to use another
+ instance of BioCatalogue. This can be done through the Preferences dialog
+ of Taverna by going to: <pre>File -> Preferences -> BioCatalogue</pre>
+ </p>
+
+
+ <h3>
+ Known Issues and Missing Functionality
+ </h3>
+
+ <p>
+ Below are the most important known issues. These will be fixed in the later releases.
+ </p>
+
+ <p>
+ <ul>
+ <li>Previews are only available for SOAP services, but not REST services
+ or users, registries, service providers.
+ </li>
+ <li>Search history, favourite search queries and filters are not persisted.
+ This means that this data will only be available for the current working
+ session and will be lost after Taverna is switched off.
+ </li>
+ <li>Only read access to the BioCatalogue data is currently provided.</li>
+ </ul>
+ </p>
+ </body>
+</html>
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin-feedback.html b/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin-feedback.html
new file mode 100644
index 0000000..b2c48b4
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin-feedback.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <meta content="text/html; charset=MacRoman" http-equiv="Content-Type">
+ <link rel="stylesheet" href="./basic.css" type="text/css">
+ <meta name="generator" content="Helen">
+ <title>BioCatalogue Plugin - Feedback</title>
+ </head>
+ <body>
+ <h2>
+ BioCatalogue Plugin - Feedback
+ </h2>
+ <p>
+ Please provide us with your feedback to help improve the BioCatalogue plugin.
+ In order to do so, please go to <em>About</em> tab in the <em>BioCatalogue perspective</em>
+ and click the "Leave feedback" button - you will be taken to a web page with a form
+ to fill in and submit your comments.
+ </p>
+ <p>
+ Developers are very interested to hear:
+ <ul>
+ <li>suggestions regarding the existing functionality;</li>
+ <li>new feature requests;</li>
+ <li>ideas for improving the user interfaces;</li>
+ <li>any other feedback you may have.</li>
+ </ul>
+ </p>
+ </body>
+</html>
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin.html b/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin.html
new file mode 100644
index 0000000..152b935
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/help/biocatalogue-plugin.html
@@ -0,0 +1,54 @@
+<html>
+ <head>
+ <meta content="text/html; charset=MacRoman" http-equiv="Content-Type">
+ <link rel="stylesheet" href="./basic.css" type="text/css">
+ <meta name="generator" content="Helen">
+ <title>BioCatalogue Plugin</title>
+ </head>
+ <body>
+ <h2>
+ BioCatalogue Plugin
+ </h2>
+ <h3><small>Version: 0.1.1 (alpha)</small></h3>
+ <p>
+ The <em>BioCatalogue plugin</em> is intended to provide access to the data held
+ in the <em>BioCatalogue Web Services Registry</em> directly from <em>Taverna Workbench</em>.
+ </p>
+ <p>
+ In its current state the plugin is designed to:
+ <ul>
+ <li>display the integration capabilities with both BioCatalogue and Taverna;</li>
+ <li>provide a general idea of the kinds of data that can be fetched
+ from the Biocatalogue through its REST API;
+ </li>
+ <li>attempt to make the workflow composition process easier and provide useful
+ contextual data to help with understanding of existing workflows.
+ </li>
+ </ul>
+ </p>
+ <p>
+ This release has made the plugin compatible with the latest version of
+ Taverna - 2.2. Several important bugs were also fixed, however more new
+ functionality will be added soon.
+ </p>
+ <p>
+ To learn more about the available functionality, please see the <a href=
+ "./biocatalogue-plugin-features.html">feature list</a>.
+ </p>
+ <p><b>
+ Please note that this is an incomplete version of the BioCatalogue plugin.
+ You may see notifications that certain pieces of functionality have not been
+ implemented yet; some features are not yet fully stable, which means that
+ occasionally you may see unexpected error messages.
+ </b></p>
+ <p>
+ Any <a href="./biocatalogue-plugin-feedback.html">feedback</a> will be greatly apppreciated - it
+ will help to understand the true needs of the user community and develop
+ a complete version of this plugin later in the year.
+ </p>
+ <p>
+ This version of the plugin was developed by Sergejs Aleksejevs as part of his
+ final year project on the undergraduate Computer Science course at the University of Manchester.
+ </p>
+ </body>
+</html>
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/BioCataloguePluginConstants.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/BioCataloguePluginConstants.java
new file mode 100644
index 0000000..791b544
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/BioCataloguePluginConstants.java
@@ -0,0 +1,77 @@
+package net.sf.taverna.biocatalogue.model;
+
+import java.io.File;
+
+
+/**
+ * This class contains the collection of important constants,
+ * which are used throughout the BioCatalogue plugin.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCataloguePluginConstants
+{
+ public static final String APP_VISIBLE_NAME = "Service Catalogue Plugin";
+ public static final String APP_PREFIX = "T2ServiceCataloguePlugin:";
+
+
+ public static final boolean PERFORM_API_RESPONSE_TIME_LOGGING = true;
+ public static final boolean PERFORM_API_XML_DATA_BINDING_TIME_LOGGING = true;
+ public static final String API_OPERATION_LOG_FILENAME = "service_catalogue_api.log";
+
+
+ public static final int DEFAULT_SCROLL = 15; // default vertical scroll increment to be used in all JScrollPane instances within the plugin
+ public static final int DEFAULT_TOOLTIP_DURATION = 10000; // default duration of visibility of tooltips (in "ms")
+ public static final int DEFAULT_THREAD_STARTUP_TIME = 10; // this is the time (in "ms") that we think the system takes at most to start a new thread
+
+ public static final int API_DEFAULT_REQUESTED_TAG_COUNT_PER_PAGE = 50;
+ public static final int API_DEFAULT_REQUESTED_WEB_SERVICE_COUNT_PER_PAGE = 20;
+ public static final int API_DEFAULT_REQUESTED_SOAP_OPERATION_COUNT_PER_PAGE = 20;
+ public static final int API_DEFAULT_REQUESTED_REST_METHOD_COUNT_PER_PAGE = 20;
+ public static final int API_DEFAULT_REQUESTED_USER_COUNT_PER_PAGE = 20;
+ public static final int API_DEFAULT_REQUESTED_SERVICE_PROVIDER_COUNT_PER_PAGE = 20;
+
+
+ public static final int SEARCH_HISTORY_LENGTH = 50; // maximum number of search history items to store (if exceeded, oldest will be removed)
+ public static final int FAVOURITE_SEARCHES_LENGTH = 30; // maximum number of favourite search settings to store (if exceeded, oldest will be removed)
+ public static final int FAVOURITE_FILTERS_LENGTH = 30; // maximum number of favourite service filters to store (if exceeded, oldest will be removed)
+ public static final int RESOURCE_PREVIEW_HISTORY_LENGTH = 50;
+
+ public static final int RESOURCE_PREVIEW_BROWSER_PREFERRED_WIDTH = 750;
+ public static final int RESOURCE_PREVIEW_BROWSER_PREFERRED_HEIGHT = 600;
+
+ public static final String ACTION_FILTER_FOUND_SERVICES = APP_PREFIX + "filterFoundServices:";
+ public static final String ACTION_FILTER_BY_CATEGORY = APP_PREFIX + "filterByCategory:";
+ public static final String ACTION_SHOW_IN_WEB_BROWSER = APP_PREFIX + "showInWebBrowser:";
+ public static final String ACTION_SHOW_TAG_SELECTION_DIALOG = APP_PREFIX + "showTagSelectionDialgog";
+ public static final String ACTION_PREVIEW_CURRENT_FILTER = APP_PREFIX + "previewCurrentFilter";
+ public static final String ACTION_PREVIEW_RESOURCE = APP_PREFIX + "preview:";
+ public static final String ACTION_PREVIEW_SOAP_OPERATION_AFTER_LOOKUP = APP_PREFIX + "previewSoapOperationAfterLookup:";
+ public static final String ACTION_PREVIEWED_SERVICE_HEALTH_CHECK = APP_PREFIX + "previewedServiceHealthCheck";
+ public static final String ACTION_TAG_SEARCH_PREFIX = APP_PREFIX + "tag:";
+
+
+
+ public static final String CONFIG_FILE_FOLDER_WHEN_RUNNING_STANDALONE = ".Taverna2-ServiceCatalogue Plugin";
+
+
+
+ // ---------------------------- CONTEXTUAL VIEWS --------------------------------
+
+ // this value currently makes contextual views generated by this
+ // plugin the to be the last in the list
+ public static final int CONTEXTUAL_VIEW_PREFERRED_POSITION = 600;
+
+
+
+ // ------------------------------------------------------------------------------
+
+ /*
+ * Some of the settings are determined during the runtime - hence are non-final.
+ *
+ * These are set in MainComponent.initialiseEnvironment()
+ */
+
+ public static File CONFIG_FILE_FOLDER = new File(ApplicationRuntime.getInstance().getApplicationHomeDir(), "conf");
+ public static File LOG_FILE_FOLDER = new File(ApplicationRuntime.getInstance().getApplicationHomeDir(), "logs");
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/HTTPMethodInterpreter.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/HTTPMethodInterpreter.java
new file mode 100644
index 0000000..ecd4cf1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/HTTPMethodInterpreter.java
@@ -0,0 +1,46 @@
+package net.sf.taverna.biocatalogue.model;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.HttpVerb;
+
+import net.sf.taverna.t2.activities.rest.RESTActivity.HTTP_METHOD;
+
+/**
+ * Very simple class for translating HTTP method values returned
+ * by the BioCatalogue API into the set of values that are used
+ * by the REST activity.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class HTTPMethodInterpreter
+{
+ // deny instantiation of this class
+ private HTTPMethodInterpreter() { }
+
+ public static HTTP_METHOD getHTTPMethodForRESTActivity(HttpVerb.Enum httpVerb)
+ {
+ switch (httpVerb.intValue()) {
+ case HttpVerb.INT_GET: return HTTP_METHOD.GET;
+ case HttpVerb.INT_POST: return HTTP_METHOD.POST;
+ case HttpVerb.INT_PUT: return HTTP_METHOD.PUT;
+ case HttpVerb.INT_DELETE: return HTTP_METHOD.DELETE;
+ default:
+ String errorMsg = "Unable to translate " + httpVerb.toString() + " to correct representation for REST activity;\n" +
+ "this HTTP method wasn't supported at the time of implementation.";
+ Logger.getLogger(HTTPMethodInterpreter.class).error(errorMsg);
+ throw new UnsupportedHTTPMethodException(errorMsg);
+ }
+ }
+
+
+ public static class UnsupportedHTTPMethodException extends IllegalArgumentException
+ {
+ public UnsupportedHTTPMethodException() {
+ /* empty constructor */
+ }
+
+ public UnsupportedHTTPMethodException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/LoadingExpandedResource.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/LoadingExpandedResource.java
new file mode 100644
index 0000000..cbd3f2e
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/LoadingExpandedResource.java
@@ -0,0 +1,41 @@
+package net.sf.taverna.biocatalogue.model;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.impl.ResourceLinkImpl;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class LoadingExpandedResource extends ResourceLinkImpl
+{
+ private boolean nowLoading;
+ private ResourceLink associatedObj;
+
+ public LoadingExpandedResource(ResourceLink associatedObj)
+ {
+ super(ResourceLink.type);
+
+ this.associatedObj = associatedObj;
+ this.nowLoading = true;
+ }
+
+ public ResourceLink getAssociatedObj() {
+ return associatedObj;
+ }
+
+ public boolean isLoading() {
+ return (nowLoading);
+ }
+ public void setLoading(boolean isLoading) {
+ this.nowLoading = isLoading;
+ }
+
+ public String getHref() {
+ return (associatedObj.getHref());
+ }
+
+ public String getResourceName() {
+ return (associatedObj.getResourceName());
+ }
+}
+
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/LoadingResource.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/LoadingResource.java
new file mode 100644
index 0000000..a003075
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/LoadingResource.java
@@ -0,0 +1,39 @@
+package net.sf.taverna.biocatalogue.model;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.impl.ResourceLinkImpl;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class LoadingResource extends ResourceLinkImpl
+{
+ private boolean nowLoading;
+ private ResourceLink associatedObj;
+
+ public LoadingResource(String resourceURL, String resourceName) {
+ super(ResourceLink.type);
+
+ associatedObj = ResourceLink.Factory.newInstance();
+ associatedObj.setHref(resourceURL);
+ associatedObj.setResourceName(resourceName);
+
+ this.nowLoading = false;
+ }
+
+ public String getHref() {
+ return (associatedObj.getHref());
+ }
+
+ public String getResourceName() {
+ return (associatedObj.getResourceName());
+ }
+
+ public boolean isLoading() {
+ return (nowLoading);
+ }
+ public void setLoading(boolean isLoading) {
+ this.nowLoading = isLoading;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Pair.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Pair.java
new file mode 100644
index 0000000..b694db8
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Pair.java
@@ -0,0 +1,30 @@
+package net.sf.taverna.biocatalogue.model;
+
+/**
+ * Trivial class to represent a generic pair of objects.
+ * Any types of objects can be used.
+ *
+ * @author Sergejs Aleksejevs
+ *
+ * @param <T1> Type of the first object.
+ * @param <T2> Type of the second object.
+ */
+public class Pair<T1,T2>
+{
+ private final T1 firstObject;
+ private final T2 secondObject;
+
+ public Pair(T1 firstObject, T2 secondObject) {
+ this.firstObject = firstObject;
+ this.secondObject = secondObject;
+ }
+
+ public T1 getFirstObject() {
+ return firstObject;
+ }
+
+ public T2 getSecondObject() {
+ return secondObject;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Resource.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Resource.java
new file mode 100644
index 0000000..3095863
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Resource.java
@@ -0,0 +1,506 @@
+package net.sf.taverna.biocatalogue.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+import javax.swing.ListCellRenderer;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.ui.search_results.RESTMethodListCellRenderer;
+import net.sf.taverna.biocatalogue.ui.search_results.SOAPOperationListCellRenderer;
+import net.sf.taverna.biocatalogue.ui.search_results.ServiceListCellRenderer;
+import net.sf.taverna.t2.workbench.MainWindow;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.Registry;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestMethods;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceProvider;
+import org.biocatalogue.x2009.xml.rest.Services;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapOperations;
+import org.biocatalogue.x2009.xml.rest.User;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Resource
+{
+ /**
+ * A single point of definition of the types of resources that the BioCatalogue plugin
+ * "knows" about. This enum provides various details about resource types -
+ * display names for single items of that type, names of collections of items of that
+ * type, icons to represent the items of a particular type, etc.
+ *
+ * @author Sergejs Aleksejevs
+ */
+ public static enum TYPE
+ {
+ // the order is important - all these types will appear in the user interface
+ // in the same order as listed here
+ @SuppressWarnings("serial")
+ SOAPOperation (SoapOperation.class, SoapOperations.class, BeansForJSONLiteAPI.SOAPOperationsIndex.class, "WSDL service", "WSDL services",
+ "WSDL services can be directly imported into the current workflow or Service Panel",
+ ResourceManager.getIconFromTaverna(ResourceManager.SOAP_OPERATION_ICON), true, true, true, false, true, true, true, true,
+ SOAPOperationListCellRenderer.class, BioCatalogueClient.API_SOAP_OPERATIONS_URL,
+ new HashMap<String,String>() {{
+ }},
+ new HashMap<String,String>(BioCatalogueClient.API_INCLUDE_ANCESTORS) {{
+ put(BioCatalogueClient.API_PER_PAGE_PARAMETER, ""+BioCataloguePluginConstants.API_DEFAULT_REQUESTED_SOAP_OPERATION_COUNT_PER_PAGE);
+ }},
+ BioCataloguePluginConstants.API_DEFAULT_REQUESTED_SOAP_OPERATION_COUNT_PER_PAGE,
+ BioCatalogueClient.API_SOAP_OPERATION_FILTERS_URL),
+
+ @SuppressWarnings("serial")
+ RESTMethod (RestMethod.class, RestMethods.class, BeansForJSONLiteAPI.RESTMethodsIndex.class, "REST service", "REST services",
+ "REST services can be directly imported into the current workflow or Service Panel",
+ ResourceManager.getIconFromTaverna(ResourceManager.REST_METHOD_ICON), true, true, true, false, true, false, true, true,
+ RESTMethodListCellRenderer.class, BioCatalogueClient.API_REST_METHODS_URL,
+ new HashMap<String,String>() {{
+ }},
+ new HashMap<String,String>(BioCatalogueClient.API_INCLUDE_ANCESTORS) {{
+ put(BioCatalogueClient.API_PER_PAGE_PARAMETER, ""+BioCataloguePluginConstants.API_DEFAULT_REQUESTED_REST_METHOD_COUNT_PER_PAGE);
+ }},
+ BioCataloguePluginConstants.API_DEFAULT_REQUESTED_REST_METHOD_COUNT_PER_PAGE,
+ BioCatalogueClient.API_REST_METHOD_FILTERS_URL); //,
+
+ // TODO - the following resource types have been disabled, as no actions for them can be done yet
+ // -- they are still to be implemented; if the following types are uncommented, they will be
+ // automatically searchable and visible in BioCatalogue Exploration tab; ListCellRenderers, however,
+ // would need to be added first.
+// @SuppressWarnings("serial")
+// Service (Service.class, Services.class, BeansForJSONLiteAPI.ServicesIndex.class, "Web service", "Web services",
+// "<html>Web services represent collections of WSDL services or REST services.<br>" +
+// "They cannot be directly imported into the current workflow or Service Panel,<br>" +
+// "but they may contain much more information about individual WSDL or REST<br>" +
+// "services and also provide some context for their usage.</html>",
+// ResourceManager.getImageIcon(ResourceManager.SERVICE_ICON), true, true, true, false, false, false, true,
+// ServiceListCellRenderer.class, BioCatalogueClient.API_SERVICES_URL,
+// new HashMap<String,String>(BioCatalogueClient.API_INCLUDE_SUMMARY) {{
+// }},
+// new HashMap<String,String>() {{
+// put(BioCatalogueClient.API_PER_PAGE_PARAMETER, ""+BioCataloguePluginConstants.API_DEFAULT_REQUESTED_WEB_SERVICE_COUNT_PER_PAGE);
+// }},
+// BioCataloguePluginConstants.API_DEFAULT_REQUESTED_WEB_SERVICE_COUNT_PER_PAGE,
+// BioCatalogueClient.API_SERVICE_FILTERS_URL),
+//
+// ServiceProvider (ServiceProvider.class, ServiceProviders.class, BeansForJSONLiteAPI.ServiceProvidersIndex.class, "Service Provider", "Service Providers", "",
+// ResourceManager.getImageIcon(ResourceManager.SERVICE_PROVIDER_ICON), false, false, false, false, false, false, false,
+// ServiceProviderListCellRenderer.class, BioCatalogueClient.API_SERVICE_PROVIDERS_URL,
+// new HashMap<String,String>() {{
+// }},
+// new HashMap<String,String>() {{
+// put(BioCatalogueClient.API_PER_PAGE_PARAMETER, ""+BioCataloguePluginConstants.API_DEFAULT_REQUESTED_SERVICE_PROVIDER_COUNT_PER_PAGE);
+// }},
+// BioCataloguePluginConstants.API_DEFAULT_REQUESTED_SERVICE_PROVIDER_COUNT_PER_PAGE,
+// null),
+//
+// User (User.class, Users.class, BeansForJSONLiteAPI.UsersIndex.class, "User", "Users", "",
+// ResourceManager.getImageIcon(ResourceManager.USER_ICON), false, false, true, false, false, false, false,
+// UserListCellRenderer.class, BioCatalogueClient.API_USERS_URL,
+// new HashMap<String,String>() {{
+// }},
+// new HashMap<String,String>() {{
+// put(BioCatalogueClient.API_PER_PAGE_PARAMETER, ""+BioCataloguePluginConstants.API_DEFAULT_REQUESTED_USER_COUNT_PER_PAGE);
+// }},
+// BioCataloguePluginConstants.API_DEFAULT_REQUESTED_USER_COUNT_PER_PAGE,
+// BioCatalogueClient.API_USER_FILTERS_URL);
+
+
+ @SuppressWarnings("unchecked")
+ private Class xmlbeansGeneratedClass;
+ @SuppressWarnings("unchecked")
+ private Class xmlbeansGeneratedCollectionClass;
+ private Class<?> jsonLiteAPIBindingBeanClass;
+ private String resourceTypeName;
+ private String resourceCollectionName;
+ private String resourceTabTooltip;
+ private Icon icon;
+ private boolean defaultType;
+ private boolean suitableForTagSearch;
+ private boolean suitableForFiltering;
+ private boolean suitableForOpeningInPreviewBrowser;
+ private boolean suitableForAddingToServicePanel;
+ private boolean suitableForAddingToWorkflowDiagram;
+ private boolean suitableForHealthCheck;
+ private Class<? extends ListCellRenderer> resultListingCellRendererClass;
+ private String apiResourceCollectionIndex;
+ private Map<String,String> apiResourceCollectionIndexSingleExpandedResourceAdditionalParameters;
+ private Map<String,String> apiResourceCollectionIndexAdditionalParameters;
+ private int apiResourceCountPerIndexPage;
+ private String apiResourceCollectionFilters;
+ private final boolean suitableForAddingAllToServicePanel;
+
+ @SuppressWarnings("unchecked")
+ TYPE(Class xmlbeansGeneratedClass, Class xmlbeansGeneratedCollectionClass, Class<?> jsonLiteAPIBindingBeanClass,
+ String resourceTypeName, String resourceCollectionName, String resourceTabTooltip, Icon icon,
+ boolean defaultType, boolean suitableForTagSearch, boolean suitableForFiltering, boolean suitableForOpeningInPreviewBrowser,
+ boolean suitableForAddingToServicePanel, boolean suitableForAddingAllToServicePanel, boolean suitableForAddingToWorkflowDiagram,
+ boolean suitableForHealthCheck, Class<? extends ListCellRenderer> resultListingCellRendererClass,
+ String apiResourceCollectionIndex, Map<String,String> apiResourceCollectionIndexSingleExpandedResourceAdditionalParameters,
+ Map<String,String> apiResourceCollectionIndexAdditionalParameters, int apiResourceCountPerIndexListingPage,
+ String apiResourceCollectionFilters)
+ {
+ this.xmlbeansGeneratedClass = xmlbeansGeneratedClass;
+ this.xmlbeansGeneratedCollectionClass = xmlbeansGeneratedCollectionClass;
+ this.jsonLiteAPIBindingBeanClass = jsonLiteAPIBindingBeanClass;
+ this.resourceTypeName = resourceTypeName;
+ this.resourceCollectionName = resourceCollectionName;
+ this.resourceTabTooltip = resourceTabTooltip;
+ this.icon = icon;
+ this.defaultType = defaultType;
+ this.suitableForTagSearch = suitableForTagSearch;
+ this.suitableForFiltering = suitableForFiltering;
+ this.suitableForOpeningInPreviewBrowser = suitableForOpeningInPreviewBrowser;
+ this.suitableForAddingToServicePanel = suitableForAddingToServicePanel;
+ this.suitableForAddingAllToServicePanel = suitableForAddingAllToServicePanel;
+ this.suitableForAddingToWorkflowDiagram = suitableForAddingToWorkflowDiagram;
+ this.suitableForHealthCheck = suitableForHealthCheck;
+ this.resultListingCellRendererClass = resultListingCellRendererClass;
+ this.apiResourceCollectionIndex = apiResourceCollectionIndex;
+ this.apiResourceCollectionIndexSingleExpandedResourceAdditionalParameters = apiResourceCollectionIndexSingleExpandedResourceAdditionalParameters;
+ this.apiResourceCollectionIndexAdditionalParameters = apiResourceCollectionIndexAdditionalParameters;
+ this.apiResourceCountPerIndexPage = apiResourceCountPerIndexListingPage;
+ this.apiResourceCollectionFilters = apiResourceCollectionFilters;
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ public Class getXmlBeansGeneratedClass() {
+ return this.xmlbeansGeneratedClass;
+ }
+
+ /**
+ * @return Class that represents collection of resources of this type,
+ * as represented by XmlBeans.
+ */
+ @SuppressWarnings("unchecked")
+ public Class getXmlBeansGeneratedCollectionClass() {
+ return this.xmlbeansGeneratedCollectionClass;
+ }
+
+
+ /**
+ * @return Class of the bean to be used when de-serialising JSON
+ * data received from the 'Lite' BioCatalogue JSON API's index
+ * of resources of this type.
+ */
+ public Class<?> getJsonLiteAPIBindingBeanClass() {
+ return this.jsonLiteAPIBindingBeanClass;
+ }
+
+
+ /**
+ * @return Display name of a type of a single item belonging to that type.
+ * (E.g. 'User' or 'Service')
+ */
+ public String getTypeName() {
+ return this.resourceTypeName;
+ }
+
+ /**
+ * @return Display name of a collection of items of this type.
+ * (E.g. 'Users' or 'Services').
+ */
+ public String getCollectionName() {
+ return this.resourceCollectionName;
+ }
+
+ /**
+ * @return HTML-formatted string that can be used as a tooltip
+ * for tabs in BioCatalogue Exploration tab of BioCatalogue
+ * perspective.
+ */
+ public String getCollectionTabTooltip() {
+ return this.resourceTabTooltip;
+ }
+
+ /**
+ * @return Small icon that represents this resource type.
+ */
+ public Icon getIcon() {
+ return this.icon;
+ }
+
+ /**
+ * @return <code>true</code> - if used for search by default;<br/>
+ * <code>false</code> - otherwise.
+ */
+ public boolean isDefaultSearchType() {
+ return this.defaultType;
+ }
+
+ /**
+ * Resources not of all resource types can be searched for by tags (although every resource type
+ * can be searched for by a free-text query).
+ *
+ * @return <code>true</code> if resources of this type can be searched for by tags,<br/>
+ * <code>false</code> otherwise.
+ */
+ public boolean isSuitableForTagSearch() {
+ return this.suitableForTagSearch;
+ }
+
+ /**
+ * Not all resource types are suitable for filtering - for example, there are no
+ * filters available for service providers in BioCatalogue.
+ *
+ * @return <code>true</code> indicates that tab dedicated to displaying search
+ * results of this resource type can have a filter tree.
+ */
+ public boolean isSuitableForFiltering() {
+ return this.suitableForFiltering;
+ }
+
+ /**
+ * @return <code>true</code> indicates that "Preview" option can be made
+ * available for items of this type, as preview factory would be implemented
+ * for such resources.
+ */
+ public boolean isSuitableForOpeningInPreviewBrowser() {
+ return this.suitableForOpeningInPreviewBrowser;
+ }
+
+ public boolean isSuitableForAddingToServicePanel() {
+ return this.suitableForAddingToServicePanel;
+ }
+
+ public boolean isSuitableForAddingToWorkflowDiagram() {
+ return this.suitableForAddingToWorkflowDiagram;
+ }
+
+ /**
+ * @return <code>true</code> indicates that monitoring data can be obtained
+ * from BioCatalougue for this type of resource.
+ */
+ public boolean isSuitableForHealthCheck() {
+ return this.suitableForHealthCheck;
+ }
+
+
+ /**
+ * This method helps to defer instantiation of ListCellRenderers
+ * until they are first accessed - it is because construction of
+ * the renderers requires knowledge of all available resource types,
+ * therefore they cannot be instantiated until after Resource class
+ * has been fully loaded.
+ *
+ * @return {@link ListCellRenderer} for this type of resources or
+ * <code>null</code> if an error has occurred during
+ * instantiation of required renderer.
+ */
+ public ListCellRenderer getResultListingCellRenderer() {
+ try {
+ return this.resultListingCellRendererClass.newInstance();
+ }
+ catch (Exception e) {
+ Logger.getLogger(Resource.class).error("Unable to instantiate search results ListCellRenderer for " +
+ this.getCollectionName(), e);
+ JOptionPane.showMessageDialog(MainWindow.getMainWindow(),
+ "Taverna was unable to instantiate ListCellRenderer for " + this.getCollectionName() + ".\n\n" +
+ "This may make Taverna unstable.", "Service Catalogue Plugin", JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+ }
+
+ /**
+ * @return URL in the BioCatalogue API that provides an index of the collection of
+ * all resources of this type.
+ */
+ public String getAPIResourceCollectionIndex() {
+ return apiResourceCollectionIndex;
+ }
+
+ /**
+ * @return Keys and values for any additional URL parameters that need to be included into the
+ * BioCatalogue API requests that are made in order to fetch all necessary additional
+ * details for a *single* expanded entry in the search results listing.
+ */
+ public Map<String,String> getResourceCollectionIndexSingleExpandedResourceAdditionalParameters() {
+ return apiResourceCollectionIndexSingleExpandedResourceAdditionalParameters;
+ }
+
+ /**
+ * @return Keys and values for any additional URL parameters that need to be included into the
+ * requests sent to filtered indexes of collections of this type in the BioCatalogue API.
+ */
+ public Map<String,String> getAPIResourceCollectionIndexAdditionalParameters() {
+ return apiResourceCollectionIndexAdditionalParameters;
+ }
+
+ /**
+ * @return Number of resources of this type that one page of search results from
+ * the API will contain.
+ */
+ public int getApiResourceCountPerIndexPage() {
+ return apiResourceCountPerIndexPage;
+ }
+
+ /**
+ * @return BioCatalogue API URL that provides a collection of filters for the
+ * resource of this type.
+ */
+ public String getAPIResourceCollectionFiltersURL() {
+ return apiResourceCollectionFilters;
+ }
+
+
+ /**
+ * This method is useful for adding / removing tabs into the results view - provides
+ * and index for the tabbed view to place a tab, relevant to a particular resource type.
+ * This helps to preserve the order of tabs after adding / removing them.
+ *
+ * @return Zero-based index of this resource type in the <code>RESOURCE_TYPE</code> enum or
+ * <code>-1</code> if not found (which is impossible under normal conditions).
+ */
+ public int index()
+ {
+ TYPE[] values = TYPE.values();
+ for (int i = 0; i < values.length; i++) {
+ if (this == values[i]) {
+ return (i);
+ }
+ }
+ return (-1);
+ }
+
+
+
+ /**
+ * @return the suitableForAddingAllToServicePanel
+ */
+ public boolean isSuitableForAddingAllToServicePanel() {
+ return suitableForAddingAllToServicePanel;
+ }
+
+ };
+
+
+
+ // ----------------------------- RESOURCE CLASS -------------------------------
+
+
+ // current resource data
+ private final TYPE resourceType;
+ private final String resourceURL;
+ private final String resourceTitle;
+
+
+ public Resource(String resourceURL, String resourceTitle)
+ {
+ this.resourceURL = extractPureResourceURLFromPreviewActionCommand(resourceURL);
+ this.resourceTitle = resourceTitle;
+ this.resourceType = getResourceTypeFromResourceURL(resourceURL);
+ }
+
+ public TYPE getType() {
+ return resourceType;
+ }
+
+ public String getURL() {
+ return resourceURL;
+ }
+
+ public String getTitle() {
+ return resourceTitle;
+ }
+
+
+
+ public boolean equals(Object other)
+ {
+ if (other instanceof Resource)
+ {
+ // compare by all components
+ Resource otherRes = (Resource)other;
+ return (this.resourceType == otherRes.resourceType &&
+ this.resourceTitle.equals(otherRes.resourceTitle) &&
+ this.resourceURL.equals(otherRes.resourceURL));
+ }
+ else {
+ // other object is of different type
+ return (false);
+ }
+ }
+
+
+ /**
+ * @param url Either URL of the resource in BioCatalogue or preview action command
+ * ({@link BioCataloguePluginConstants#ACTION_PREVIEW_RESOURCE}).
+ * @return Type of this resource according to the BioCatalogue URL that points to this
+ * resource or <code>null</code> if the type of the resource couldn't be determined.
+ */
+ public static TYPE getResourceTypeFromResourceURL(String url)
+ {
+ String pureURL = extractPureResourceURLFromPreviewActionCommand(url);
+
+// if (pureURL.startsWith(BioCatalogueClient.API_SERVICES_URL)) return(TYPE.Service);
+// else
+ if (pureURL.startsWith(BioCatalogueClient.API_SOAP_OPERATIONS_URL)) {
+ return(TYPE.SOAPOperation);
+ }
+ if (pureURL.startsWith(BioCatalogueClient.API_REST_METHODS_URL)) {
+ return(TYPE.RESTMethod);
+ }
+// else if (pureURL.startsWith(BioCatalogueClient.API_SERVICE_PROVIDERS_URL)) return(TYPE.ServiceProvider); // TODO - re-enable these lines as soon as ServiceProvider and User type are started to be used
+// else if (pureURL.startsWith(BioCatalogueClient.API_USERS_URL)) return(TYPE.User);
+ return (null);
+ }
+
+
+ /**
+ * @param previewActionCommand Either resource preview action command or a 'pure' resource URL already.
+ * @return A "pure" resource URL in BioCatalogue with the action prefix
+ * ({@link BioCataloguePluginConstants#ACTION_PREVIEW_RESOURCE}) removed.
+ */
+ public static String extractPureResourceURLFromPreviewActionCommand(String previewActionCommand)
+ {
+ return (previewActionCommand.startsWith(BioCataloguePluginConstants.ACTION_PREVIEW_RESOURCE) ?
+ previewActionCommand.substring(BioCataloguePluginConstants.ACTION_PREVIEW_RESOURCE.length()) :
+ previewActionCommand);
+ }
+
+
+ /**
+ * @param resource
+ * @return Display name for listings of items.
+ */
+ public static String getDisplayNameForResource(ResourceLink resource)
+ {
+ if (resource instanceof SoapOperation) {
+ return ((SoapOperation)resource).getName();
+ }
+ else if (resource instanceof RestMethod)
+ {
+ RestMethod restMethod = (RestMethod)resource;
+ return (restMethod.getName() == null || restMethod.getName().length() == 0 ?
+ restMethod.getEndpointLabel() :
+ restMethod.getName());
+ }
+ else if (resource instanceof Service) {
+ return ((Service)resource).getName();
+ }
+ else if (resource instanceof ServiceProvider) {
+ return ((ServiceProvider)resource).getName();
+ }
+ else if (resource instanceof User) {
+ return ((User)resource).getName();
+ }
+ else if (resource instanceof Registry) {
+ return ((Registry)resource).getName();
+ }
+ else if (resource instanceof LoadingResource) {
+ return (resource.getResourceName());
+ }
+ else {
+ return ("ERROR: ITEM NOT RECOGNISED - Item is of known generic type from the Service Catalogue Plugin, but not specifically recognised" + resource.toString());
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/ResourceManager.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/ResourceManager.java
new file mode 100644
index 0000000..666cd6c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/ResourceManager.java
@@ -0,0 +1,326 @@
+package net.sf.taverna.biocatalogue.model;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.image.BufferedImage;
+import java.net.URL;
+import java.util.HashMap;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.activities.rest.ui.servicedescription.RESTActivityIcon;
+import net.sf.taverna.t2.activities.wsdl.servicedescriptions.WSDLActivityIcon;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.BioCataloguePerspective;
+
+/**
+ * This class will be a single point of lookup of all resource files.
+ * (Icons, images, CSS files, etc).
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class ResourceManager
+{
+ // subfolders, where some icons / other resources are kept
+ public static final String FAMFAMFAM_PATH = "famfamfam_silk/"; // free collection of icons
+ public static final String SERVICE_ICONS_PATH = "service_icons/"; // icons related to web services (e.g. service types)
+ public static final String FOLDS_PATH = "folds/"; // icons for 'folding' menus (like 'Search for...')
+ public static final String TRISTATE_TREE_ICONS_PATH = "tristate_checkbox/"; // icons for the tri-state filtering tree
+
+ // all known resources to follow
+ public static final int FAVICON = 1;
+
+ public static final int INFORMATION_ICON_LARGE = 10;
+
+ public static final int SPINNER_STILL = 20;
+ public static final int SPINNER = 21;
+ public static final int BAR_LOADER_GREY = 25;
+ public static final int BAR_LOADER_GREY_STILL = 26;
+ public static final int BAR_LOADER_ORANGE = 30;
+ public static final int BAR_LOADER_ORANGE_STILL = 31;
+
+ public static final int FOLD_ICON = 40;
+ public static final int UNFOLD_ICON = 41;
+ public static final int FOLD_ICON_16x16 = 42;
+ public static final int UNFOLD_ICON_16x16 = 43;
+
+ public static final int SERVICE_TYPE_SOAP_ICON = 50;
+ public static final int SERVICE_TYPE_REST_ICON = 51;
+ public static final int SERVICE_TYPE_MULTITYPE_ICON = 65;
+ public static final int SERVICE_TYPE_UNKNOWN_ICON = 70;
+
+ public static final int TRISTATE_CHECKBOX_CHECKED_ICON = 80;
+ public static final int TRISTATE_CHECKBOX_PARTIAL_ICON = 82;
+ public static final int TRISTATE_CHECKBOX_UNCHECKED_ICON = 85;
+ public static final int UNCHECKED_ICON = 86;
+
+ public static final int SERVICE_STATUS_PASSED_ICON = 100;
+ public static final int SERVICE_STATUS_PASSED_ICON_LARGE = 101;
+ public static final int SERVICE_STATUS_WARNING_ICON = 110;
+ public static final int SERVICE_STATUS_WARNING_ICON_LARGE = 111;
+ public static final int SERVICE_STATUS_FAILED_ICON = 120;
+ public static final int SERVICE_STATUS_FAILED_ICON_LARGE = 121;
+ public static final int SERVICE_STATUS_UNCHECKED_ICON = 130;
+ public static final int SERVICE_STATUS_UNCHECKED_ICON_LARGE = 131;
+ public static final int SERVICE_STATUS_UNKNOWN_ICON = 140;
+
+ public static final int UNKNOWN_RESOURCE_TYPE_ICON = 200;
+ public static final int USER_ICON = 205;
+ public static final int REGISTRY_ICON = 210;
+ public static final int SERVICE_PROVIDER_ICON = 215;
+ public static final int SERVICE_ICON = 220;
+ public static final int SOAP_OPERATION_ICON = 225;
+ public static final int REST_METHOD_ICON = 227;
+ public static final int SERVICE_CATEGORY_ICON = 230;
+ public static final int WSDL_DOCUMENT_ICON = 235;
+ public static final int TAG_ICON = 240;
+
+ public static final int OPEN_IN_BIOCATALOGUE_ICON = 310;
+ public static final int SEARCH_ICON = 315;
+ public static final int HISTORY_ICON = 320;
+ public static final int REFRESH_ICON = 330;
+ public static final int FAVOURITE_ICON = 335;
+ public static final int TICK_ICON = 340;
+ public static final int CROSS_ICON = 341;
+ public static final int WARNING_ICON = 342;
+ public static final int ERROR_ICON = 343;
+ public static final int SAVE_ICON = 345;
+ public static final int DELETE_ITEM_ICON = 350;
+ public static final int CLEAR_ICON = 355;
+ public static final int LOCKED_ICON = 360;
+ public static final int UNLOCKED_ICON = 365;
+
+ public static final int BACK_ICON = 370;
+ public static final int FORWARD_ICON = 375;
+ public static final int FILTER_ICON = 380;
+ public static final int PREVIEW_ICON = 385;
+ public static final int SUGGESTION_TO_USER_ICON = 390;
+ public static final int ADD_PROCESSOR_TO_WORKFLOW_ICON = 395;
+ public static final int ADD_PROCESSOR_AS_FAVOURITE_ICON = 396;
+ public static final int EXECUTE_HEALTH_CHECK_ICON = 397;
+ public static final int ADD_ALL_SERVICES_AS_FAVOURITE_ICON = 398;
+
+ public static final int SELECT_ALL_ICON = 400;
+ public static final int DESELECT_ALL_ICON = 405;
+ public static final int EXPAND_ALL_ICON = 410;
+ public static final int COLLAPSE_ALL_ICON = 420;
+
+ public static final int SORT_BY_NAME_ICON = 450;
+ public static final int SORT_BY_COUNTS_ICON = 455;
+
+ public static final int STYLES_CSS = 1000;
+
+
+ /**
+ * Simple method to retrieve relative path of a required resource.
+ */
+ public static String getResourceRelPath(int resourceId)
+ {
+ String resPath = "";
+
+ switch (resourceId) {
+ case FAVICON: resPath += "favicon.png";
+ break;
+ case INFORMATION_ICON_LARGE: resPath += "info-sphere-35.png";
+ break;
+ case SPINNER_STILL: resPath += "ajax-loader-still.gif";
+ break;
+ case SPINNER: resPath += "ajax-loader.gif";
+ break;
+ case BAR_LOADER_GREY: resPath += "ajax-loader-grey-bert2.gif";
+ break;
+ case BAR_LOADER_GREY_STILL: resPath += "ajax-loader-grey-bert2-still.png";
+ break;
+ case BAR_LOADER_ORANGE: resPath += "ajax-loader-orange-bert2.gif";
+ break;
+ case BAR_LOADER_ORANGE_STILL: resPath += "ajax-loader-orange-bert2-still.png";
+ break;
+ case FOLD_ICON: resPath += FOLDS_PATH + "fold.png";
+ break;
+ case UNFOLD_ICON: resPath += FOLDS_PATH + "unfold.png";
+ break;
+ case FOLD_ICON_16x16: resPath += FOLDS_PATH + "fold_16x16.png";
+ break;
+ case UNFOLD_ICON_16x16: resPath += FOLDS_PATH + "unfold_16x16.png";
+ break;
+ case SERVICE_TYPE_SOAP_ICON: resPath += SERVICE_ICONS_PATH + "service_type_soap.png";
+ break;
+ case SERVICE_TYPE_REST_ICON: resPath += SERVICE_ICONS_PATH + "service_type_rest.png";
+ break;
+ case SERVICE_TYPE_MULTITYPE_ICON: resPath += SERVICE_ICONS_PATH + "service_type_multitype.png";
+ break;
+ case SERVICE_TYPE_UNKNOWN_ICON: resPath += SERVICE_ICONS_PATH + "service_type_unknown.png";
+ break;
+ case SERVICE_STATUS_PASSED_ICON: resPath += FAMFAMFAM_PATH + "accept.png";
+ break;
+ case SERVICE_STATUS_PASSED_ICON_LARGE: resPath += "tick-sphere-35.png";
+ break;
+ case SERVICE_STATUS_WARNING_ICON: resPath += FAMFAMFAM_PATH + "error.png";
+ break;
+ case SERVICE_STATUS_WARNING_ICON_LARGE: resPath += "pling-sphere-35.png";
+ break;
+ case SERVICE_STATUS_FAILED_ICON: resPath += FAMFAMFAM_PATH + "exclamation.png";
+ break;
+ case SERVICE_STATUS_FAILED_ICON_LARGE: resPath += "cross-sphere-35.png";
+ break;
+ case SERVICE_STATUS_UNCHECKED_ICON: resPath += FAMFAMFAM_PATH + "help.png";
+ break;
+ case SERVICE_STATUS_UNCHECKED_ICON_LARGE: resPath += "query-sphere-35.png";
+ break;
+ case SERVICE_STATUS_UNKNOWN_ICON: resPath += FAMFAMFAM_PATH + "grey_circle.png";
+ break;
+ case TRISTATE_CHECKBOX_CHECKED_ICON: resPath += TRISTATE_TREE_ICONS_PATH + "tristate_checkbox_checked.png";
+ break;
+ case TRISTATE_CHECKBOX_PARTIAL_ICON: resPath += TRISTATE_TREE_ICONS_PATH + "tristate_checkbox_partial.png";
+ break;
+ case TRISTATE_CHECKBOX_UNCHECKED_ICON: resPath += TRISTATE_TREE_ICONS_PATH + "tristate_checkbox_unchecked.png";
+ break;
+ case UNCHECKED_ICON: resPath += "unchecked.png";
+ break;
+ case UNKNOWN_RESOURCE_TYPE_ICON: resPath += FAMFAMFAM_PATH + "grey_circle.png";
+ break;
+ case USER_ICON: resPath += FAMFAMFAM_PATH + "user.png";
+ break;
+ case REGISTRY_ICON: resPath += FAMFAMFAM_PATH + "remote_resource.png";
+ break;
+ case SERVICE_PROVIDER_ICON: resPath += FAMFAMFAM_PATH + "chart_organisation.png";
+ break;
+ case SERVICE_ICON: resPath += "favicon.png";
+ break;
+ case SOAP_OPERATION_ICON: resPath += FAMFAMFAM_PATH + "plugin.png";
+ break;
+ case REST_METHOD_ICON: resPath += FAMFAMFAM_PATH + "plugin.png";
+ break;
+ case SERVICE_CATEGORY_ICON: resPath += FAMFAMFAM_PATH + "text_list_numbers.png";
+ break;
+ case TAG_ICON: resPath += FAMFAMFAM_PATH + "tag_blue.png";
+ break;
+ case WSDL_DOCUMENT_ICON: resPath += FAMFAMFAM_PATH + "page_white_code.png";
+ break;
+ case OPEN_IN_BIOCATALOGUE_ICON: resPath += FAMFAMFAM_PATH + "magnifier.png";
+ break;
+ case SEARCH_ICON: resPath += FAMFAMFAM_PATH + "magnifier.png";
+ break;
+ case HISTORY_ICON: resPath += FAMFAMFAM_PATH + "folder_explore.png";
+ break;
+ case REFRESH_ICON: resPath += FAMFAMFAM_PATH + "arrow_refresh.png";
+ break;
+ case FAVOURITE_ICON: resPath += FAMFAMFAM_PATH + "star.png";
+ break;
+ case TICK_ICON: resPath += FAMFAMFAM_PATH + "tick.png";
+ break;
+ case CROSS_ICON: resPath += FAMFAMFAM_PATH + "cross.png";
+ break;
+ case WARNING_ICON: resPath += FAMFAMFAM_PATH + "error.png";
+ break;
+ case ERROR_ICON: resPath += FAMFAMFAM_PATH + "exclamation.png";
+ break;
+ case SAVE_ICON: resPath += FAMFAMFAM_PATH + "disk.png";
+ break;
+ case DELETE_ITEM_ICON: resPath += FAMFAMFAM_PATH + "cross.png";
+ break;
+ case CLEAR_ICON: resPath += "trash.png";
+ break;
+ case LOCKED_ICON: resPath += FAMFAMFAM_PATH + "lock.png";
+ break;
+ case UNLOCKED_ICON: resPath += FAMFAMFAM_PATH + "lock_open.png";
+ break;
+ case BACK_ICON: resPath += FAMFAMFAM_PATH + "arrow_left.png";
+ break;
+ case FORWARD_ICON: resPath += FAMFAMFAM_PATH + "arrow_right.png";
+ break;
+ case FILTER_ICON: resPath += FAMFAMFAM_PATH + "arrow_join (flipped vertically).png";
+ break;
+ case PREVIEW_ICON: resPath += FAMFAMFAM_PATH + "magnifier.png";
+ break;
+ case SUGGESTION_TO_USER_ICON: resPath += FAMFAMFAM_PATH + "lightbulb.png";
+ break;
+ case ADD_PROCESSOR_TO_WORKFLOW_ICON: resPath += "open_in_BioCatalogue.png";
+ break;
+ case ADD_PROCESSOR_AS_FAVOURITE_ICON: resPath += FAMFAMFAM_PATH + "star.png";
+ break;
+ case ADD_ALL_SERVICES_AS_FAVOURITE_ICON:resPath += FAMFAMFAM_PATH + "multiple_star.png";
+ break;
+ case EXECUTE_HEALTH_CHECK_ICON: resPath += FAMFAMFAM_PATH + "information.png";
+ break;
+ case SELECT_ALL_ICON: resPath += FAMFAMFAM_PATH + "tick.png";
+ break;
+ case DESELECT_ALL_ICON: resPath += FAMFAMFAM_PATH + "cross.png";
+ break;
+ case EXPAND_ALL_ICON: resPath += FAMFAMFAM_PATH + "text_linespacing.png";
+ break;
+ case COLLAPSE_ALL_ICON: resPath += FAMFAMFAM_PATH + "text_linespacing (collapse).png";
+ break;
+ case SORT_BY_NAME_ICON: resPath += FAMFAMFAM_PATH + "style.png";
+ break;
+ case SORT_BY_COUNTS_ICON: resPath += FAMFAMFAM_PATH + "sum.png";
+ break;
+ case STYLES_CSS: resPath += "styles.css";
+ break;
+ default: return (null);
+ }
+
+ return (resPath);
+ }
+
+
+ private static URL getResourceLocalURL(int resourceId) {
+ return (BioCataloguePerspective.class.getResource(getResourceRelPath(resourceId)));
+ }
+
+ private static HashMap<Integer, ImageIcon> iconMap = new HashMap<Integer, ImageIcon>();
+
+ public static ImageIcon getImageIcon(int iconId)
+ {
+ ImageIcon result = iconMap.get(iconId);
+ if (result == null) {
+ result = new ImageIcon(getResourceLocalURL(iconId));
+ iconMap.put(iconId, result);
+ }
+ return result;
+ }
+
+ public static ImageIcon getImageIcon(URL resourceLocalURL) {
+ return (new ImageIcon(resourceLocalURL));
+ }
+
+
+ public static Icon getIconFromTaverna(int iconId) {
+ switch (iconId) {
+ case SOAP_OPERATION_ICON: return (WSDLActivityIcon.getWSDLIcon());
+ case REST_METHOD_ICON: return (RESTActivityIcon.getRESTActivityIcon());
+ default: return (drawMissingIcon());
+ }
+ }
+
+
+ /**
+ * This method would be called by other methods in this class
+ * when they were unable to load requested icon.
+ *
+ * @return A 16x16 pixel icon that represents a missing icon -
+ * a red cross.
+ */
+ private static ImageIcon drawMissingIcon()
+ {
+ int w = 16;
+ int h = 16;
+ GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice gd = ge.getDefaultScreenDevice();
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+
+ BufferedImage image = gc.createCompatibleImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = image.createGraphics();
+ g.setColor(Color.RED);
+ g.setStroke(new BasicStroke(3, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
+ g.drawLine(4, 4, 12, 12);
+ g.drawLine(12, 4, 4, 12);
+ g.dispose();
+
+ return new ImageIcon(image);
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/ResourcePreviewContent.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/ResourcePreviewContent.java
new file mode 100644
index 0000000..7dc1fce
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/ResourcePreviewContent.java
@@ -0,0 +1,38 @@
+package net.sf.taverna.biocatalogue.model;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+
+/**
+ * Helper class to hold all data about the generated preview.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class ResourcePreviewContent
+{
+ private Resource resource;
+ private JComponent jcContent;
+
+ public ResourcePreviewContent(Resource resource, JComponent content)
+ {
+ this.resource = resource;
+ this.jcContent = content;
+ }
+
+ public Resource getResource() {
+ return(this.resource);
+ }
+
+ public JComponent getContent() {
+ return(this.jcContent);
+ }
+
+
+ public static ResourcePreviewContent createDummyInstance()
+ {
+ Resource r = new Resource(BioCatalogueClient.API_USERS_URL + "/1", "Dummy user");
+ return (new ResourcePreviewContent(r, new JLabel("dummy content - JLabel")));
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapOperationIdentity.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapOperationIdentity.java
new file mode 100644
index 0000000..df16566
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapOperationIdentity.java
@@ -0,0 +1,77 @@
+package net.sf.taverna.biocatalogue.model;
+
+/**
+ * Identifies a SOAP operation (or "processor" in Taverna terms)
+ * in the most straightforward way - by WSDL location and operation name.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SoapOperationIdentity extends SoapServiceIdentity
+{
+ public static final String ACTION_STRING_SEPARATOR = "===";
+
+ private final String operationName;
+ private final String description;
+
+ public SoapOperationIdentity(String wsdlLocation, String operationName, String description) {
+ super(wsdlLocation);
+ this.operationName = operationName;
+ this.description = description;
+ }
+
+ public SoapOperationIdentity(Object errorDetails) {
+ super(errorDetails);
+ this.operationName = null;
+ this.description = null;
+ }
+
+ public String getOperationName() {
+ return operationName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+
+ /**
+ * @return String that can be placed into an action command (i.e. into JClickableLabel)
+ * to identify a SOAP operation - WSDL location and operation name are concatenated
+ * with <code>SoapOperationIdentity.ACTION_STRING_SEPARATOR</code>.
+ */
+ public String toActionString() {
+ return (getWsdlLocation() + ACTION_STRING_SEPARATOR + this.operationName);
+ }
+
+
+ /**
+ * @param actionString String that includes WSDL location appended by
+ * <code>SoapOperationIdentity.ACTION_STRING_SEPARATOR</code>
+ * and by the operation name of a SOAP operations.
+ * <br/>
+ * The action string may either contain only WSDL location and operation
+ * name (which are joined by a specified separator) OR the action string
+ * may start from <code>BioCataloguePluginConstants.ACTION_PREVIEW_SOAP_OPERATION_AFTER_LOOKUP</code>.
+ * @return Instance of this class initialised with the values from the <code>actionString</code>
+ * or <code>null</code> if an error occurred.
+ */
+ public static SoapOperationIdentity fromActionString(String actionString)
+ {
+ if (actionString == null) return (null);
+
+ // remove the prefix if it is present
+ if (actionString.startsWith(BioCataloguePluginConstants.ACTION_PREVIEW_SOAP_OPERATION_AFTER_LOOKUP)) {
+ actionString = actionString.substring(BioCataloguePluginConstants.ACTION_PREVIEW_SOAP_OPERATION_AFTER_LOOKUP.length());
+ }
+
+ String[] parts = actionString.split(ACTION_STRING_SEPARATOR);
+ if (parts == null || parts.length != 2 ||
+ parts[0].length() == 0 || parts[1].length() == 0)
+ {
+ return (null);
+ }
+
+ return (new SoapOperationIdentity(parts[0], parts[1], null));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapOperationPortIdentity.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapOperationPortIdentity.java
new file mode 100644
index 0000000..6910e23
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapOperationPortIdentity.java
@@ -0,0 +1,26 @@
+package net.sf.taverna.biocatalogue.model;
+
+public class SoapOperationPortIdentity extends SoapOperationIdentity
+{
+ private String portName;
+ private boolean isInput;
+
+ public SoapOperationPortIdentity(String wsdlLocation, String operationName, String portName, boolean isInput) {
+ super(wsdlLocation, operationName, null);
+ this.portName = portName;
+ this.isInput = isInput;
+ }
+
+ public SoapOperationPortIdentity(Object errorDetails) {
+ super(errorDetails);
+ }
+
+ public String getPortName() {
+ return portName;
+ }
+
+ public boolean isInput() {
+ return isInput;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapProcessorIdentity.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapProcessorIdentity.java
new file mode 100644
index 0000000..814a701
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapProcessorIdentity.java
@@ -0,0 +1,27 @@
+package net.sf.taverna.biocatalogue.model;
+
+/**
+ * Identifies a SOAP Processor in Taverna terms. Adds a local name
+ * attribute to the details available in <code>SoapOperationIdentity</code>.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SoapProcessorIdentity extends SoapOperationIdentity
+{
+ private final String localName;
+
+ public SoapProcessorIdentity(String wsdlLocation, String operationName, String localName) {
+ super(wsdlLocation, operationName, null);
+ this.localName = localName;
+ }
+
+ public SoapProcessorIdentity(Object errorDetails) {
+ super(errorDetails);
+ this.localName = null;
+ }
+
+ public String getLocalName() {
+ return localName;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapServiceIdentity.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapServiceIdentity.java
new file mode 100644
index 0000000..a015ad2
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/SoapServiceIdentity.java
@@ -0,0 +1,45 @@
+package net.sf.taverna.biocatalogue.model;
+
+/**
+ * Identifies a SOAP service in the most straightforward
+ * way - by WSDL location.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SoapServiceIdentity
+{
+ private final String wsdlLocation;
+
+ // this variable holds an object that will be displayable
+ private final Object errorDetails;
+
+ public SoapServiceIdentity(String wsdlLocation) {
+ this.wsdlLocation = wsdlLocation;
+ this.errorDetails = null;
+ }
+
+ public SoapServiceIdentity(Object errorDetails) {
+ this.errorDetails = errorDetails;
+ this.wsdlLocation = null;
+ }
+
+ public String getWsdlLocation() {
+ return (wsdlLocation);
+ }
+
+ public boolean hasError() {
+ return (errorDetails != null);
+ }
+
+ /**
+ * @return Returned object contains an object that may be displayed
+ * in a JOptionPane or printed (in other words defining a
+ * sensible way of displaying itself), which has details of
+ * an error that has occurred which prevented from populating
+ * this instance with the actual details of their SOAP service.
+ */
+ public Object getErrorDetails() {
+ return (errorDetails);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Tag.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Tag.java
new file mode 100644
index 0000000..e1ca6b9
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Tag.java
@@ -0,0 +1,218 @@
+package net.sf.taverna.biocatalogue.model;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+import org.apache.commons.lang.StringEscapeUtils;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Tag implements Serializable
+{
+ private static final long serialVersionUID = 784872111173271581L;
+
+ private String tagURI; // URI to use on BioCatalogue to fetch all tagged items
+ private String tagNamespace; // namespace where this tag is defined
+ private String tagDisplayName; // only the actual tag (for display within the tag cloud)
+ private String fullTagName; // full tag name - including namespace
+ private int itemCount; // count of tagged items
+
+
+ /**
+ * Constructs a Tag instance from primitive components.
+ * All values are set directly, no internal inference made.
+ *
+ * @param tagURI
+ * @param tagNamespace
+ * @param tagDisplayName
+ * @param fullTagName
+ * @param itemCount
+ */
+ public Tag(String tagURI, String tagNamespace, String tagDisplayName, String fullTagName, int itemCount)
+ {
+ this.tagURI = tagURI;
+ this.tagNamespace = tagNamespace;
+ this.tagDisplayName = tagDisplayName;
+ this.setFullTagName(fullTagName);
+ this.itemCount = itemCount;
+ }
+
+
+ /**
+ * Constructs Tag instance from an XML representation of the Tag from BioCatalogue API.
+ *
+ * @param xmlTag
+ */
+ public Tag(org.biocatalogue.x2009.xml.rest.Tag xmlTag)
+ {
+ // these values come directly from the XML data obtained via the API
+ this.tagURI = xmlTag.getHref();
+ this.fullTagName = xmlTag.getName();
+ this.itemCount = xmlTag.getTotalItemsCount().intValue();
+
+ // NB! Namespace and the display name need to be inferred 'manually'.
+ // First - set the namespace; it's value is taken from the 'namespace'
+ // attribute of the tag URI.
+ this.tagNamespace = Util.extractURLParameter(this.tagURI, "namespace");
+
+ // Now set the display name; if full tag name is not a part of any ontology,
+ // display name will be identical to the full name.
+ if (this.fullTagName.startsWith("<") && this.fullTagName.endsWith(">")) {
+ int iStart = this.fullTagName.lastIndexOf('#') + 1;
+ this.tagDisplayName = this.fullTagName.substring(iStart, this.fullTagName.length() - 1);
+ }
+ else {
+ this.tagDisplayName = this.fullTagName;
+ }
+ }
+
+
+ // *** Various getters and setters ***
+
+ public void setTagURI(String tagURI) {
+ this.tagURI = tagURI;
+ }
+
+ public String getTagURI() {
+ return tagURI;
+ }
+
+
+ public void setTagNamespace(String tagNamespace) {
+ this.tagNamespace = tagNamespace;
+ }
+
+ public String getTagNamespace() {
+ return tagNamespace;
+ }
+
+
+ public void setTagDisplayName(String tagDisplayName) {
+ this.tagDisplayName = tagDisplayName;
+ }
+
+ public String getTagDisplayName() {
+ return tagDisplayName;
+ }
+
+
+ public void setFullTagName(String fullTagName) {
+ this.fullTagName = fullTagName;
+ }
+
+ /**
+ * @return Unique and unambiguous name of this tag on BioCatalogue:<br/>
+ * <ul>
+ * <li>for tags with no namespaces, they it is just plain text names;</li>
+ * <li>for those with namespaces, it will have the following form:<br/>
+ * "<code>< http://www.mygrid.org.uk/ontology#retrieving ></code>" (without spaces, though), where
+ * the first part before the '#' symbol is the namespace and the second part
+ * is the actual tag within that namespace.</li></ul>
+ */
+ public String getFullTagName() {
+ return fullTagName;
+ }
+
+
+ public int getItemCount() {
+ return itemCount;
+ }
+
+ public void setItemCount(int itemCount) {
+ this.itemCount = itemCount;
+ }
+
+
+ // *** Tag Comparators ***
+
+ public static class ReversePopularityComparator implements Comparator<Tag>
+ {
+ public ReversePopularityComparator() {
+ super();
+ }
+
+ public int compare(Tag t1, Tag t2)
+ {
+ if (t1.getItemCount() == t2.getItemCount()) {
+ // in case of the same popularity, compare by full tag names (which are unique)
+ return (t1.getFullTagName().compareTo(t2.getFullTagName()));
+ }
+ else {
+ // popularity isn't the same; arrange by popularity (more popular first)
+ return (t2.getItemCount() - t1.getItemCount());
+ }
+ }
+ }
+
+
+ public static class AlphanumericComparator implements Comparator<Tag>
+ {
+ public AlphanumericComparator() {
+ super();
+ }
+
+ public int compare(Tag t1, Tag t2) {
+ // full tag names are unique on BioCatalogue
+ return (t1.getFullTagName().compareTo(t2.getFullTagName()));
+ }
+ }
+
+ public static class AlphabeticalIgnoreCaseComparator implements Comparator<Tag>
+ {
+ public AlphabeticalIgnoreCaseComparator() {
+ super();
+ }
+
+ public int compare(Tag t1, Tag t2) {
+ // full tag names are unique on BioCatalogue
+ return (t1.getTagDisplayName().compareToIgnoreCase(t2.getTagDisplayName()));
+ }
+ }
+
+ /**
+ * This makes sure that things like instanceOf() and remove() in List interface
+ * work properly - this way resources are treated to be the same if they store
+ * identical data, rather than they simply hold the same reference.
+ */
+ public boolean equals(Object other) {
+ // could only be equal to another Tag object, not anything else
+ if (! (other instanceof Tag)) return (false);
+
+ // 'other' object is a Tag; equality is based on the data stored
+ // in the current and 'other' Tag instances
+ Tag otherTag = (Tag)other;
+ return (this.itemCount == otherTag.itemCount && this.fullTagName.equals(otherTag.fullTagName));
+ }
+
+
+ public String toString()
+ {
+ return ("Tag (" + this.fullTagName + ", " + this.itemCount + ")");
+ }
+
+
+ /**
+ * This method is used to generate the tooltip to be shown over the tag
+ * in the tagcloud. Shown text will contain the full tag name, namespace
+ * and frequency.
+ *
+ * @return HTML encoded string ready to be put into the tooltip.
+ */
+ public String getTagCloudTooltip()
+ {
+ StringBuilder tooltip = new StringBuilder("<html>");
+
+ tooltip.append(" <b>" + (this.fullTagName.length() > this.tagDisplayName.length() ? "Full tag" : "Tag") + ": </b>" + StringEscapeUtils.escapeHtml(this.fullTagName));
+ if (this.tagNamespace != null && this.tagNamespace.length() > 0) {
+ tooltip.append("<br> <b>Namespace: </b>" + StringEscapeUtils.escapeHtml(this.tagNamespace));
+ }
+ tooltip.append("<br> <b>Frequency: </b>" + this.itemCount);
+ tooltip.append("</html>");
+
+ return tooltip.toString();
+ }
+
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Util.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Util.java
new file mode 100644
index 0000000..06b30e3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/Util.java
@@ -0,0 +1,793 @@
+package net.sf.taverna.biocatalogue.model;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+
+import net.sf.taverna.raven.appconfig.ApplicationRuntime;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.BioCataloguePerspective;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * Class containing various reusable helper methods.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class Util
+
+
+{
+
+ private static Logger logger = Logger.getLogger(Util.class);
+
+
+ /**
+ * Makes sure that one component (for example, a window) is centered horizontally and vertically
+ * relatively to the other component. This is achieved by aligning centers of the two components.
+ *
+ * This method can be used even if the 'dependentComponent' is larger than the 'mainComponent'. In
+ * this case it is probably only useful for centering windows against each other rather than
+ * components inside a container.
+ *
+ * Method also makes sure that the dependent component will not be placed above the screen's upper
+ * edge and to the left of the left edge.
+ */
+ public static void centerComponentWithinAnother(Component mainComponent, Component dependentComponent)
+ {
+ int iMainComponentCenterX = (int)Math.round(mainComponent.getLocationOnScreen().getX() + (mainComponent.getWidth() / 2));
+ int iPosX = iMainComponentCenterX - (dependentComponent.getWidth() / 2);
+ if (iPosX < 0) iPosX = 0;
+
+ int iMainComponentCenterY = (int)Math.round(mainComponent.getLocationOnScreen().getY() + (mainComponent.getHeight() / 2));
+ int iPosY = iMainComponentCenterY - (dependentComponent.getHeight() / 2);
+ if (iPosY < 0) iPosY = 0;
+
+ dependentComponent.setLocation(iPosX, iPosY);
+ }
+
+
+ /**
+ * The parameter is the class name to be processed; class name is likely to be in
+ * the form <class_name>$<integer_value>, where the trailing part starting with
+ * the $ sign indicates the anonymous inner class within the base class. This will
+ * strip out that part of the class name to get the base class name.
+ */
+ public static String getBaseClassName(String strClassName)
+ {
+ // strip out the class name part after the $ sign; return
+ // the original value if the dollar sign wasn't found
+ String strResult = strClassName;
+
+ int iDollarIdx = strResult.indexOf("$");
+ if (iDollarIdx != -1) strResult = strResult.substring(0, iDollarIdx);
+
+ return (strResult);
+ }
+
+
+ /**
+ * Makes sure that the supplied string is no longer than provided length.
+ */
+ public static String ensureStringLength(String str, int iLength) {
+ if (str.length() > iLength) str = str.substring(0, iLength) + " (...)";
+ return (str);
+ }
+
+ /**
+ * Makes sure that the supplied string doesn't have any lines (separated by HTML line break tag) longer
+ * than specified; assumes that there are no line breaks in the source line.
+ *
+ * @param str The string to work with.
+ * @param iLineLength Desired length of each line.
+ * @param bIgnoreBrokenWords True if line breaks are to be inserted exactly each <code>iLineLength</code>
+ * symbols (which will most likely cause broken words); false to insert line breaks
+ * at the first space after <code>iLineLength</code> symbols since last line break.
+ * @return New string with inserted HTML line breaks.
+ */
+ public static String ensureLineLengthWithinString(String str, int iLineLength, boolean bIgnoreBrokenWords)
+ {
+ StringBuilder out = new StringBuilder(str);
+
+ // keep inserting line breaks from end of the line till the beginning until all done
+ int iLineBreakPosition = 0;
+ while (iLineBreakPosition >= 0 && iLineBreakPosition < out.length())
+ {
+ // insert line break either exactly at calculated position or
+ iLineBreakPosition += iLineLength;
+ iLineBreakPosition = (bIgnoreBrokenWords ?
+ iLineBreakPosition :
+ out.indexOf(" ", iLineBreakPosition));
+
+ if (iLineBreakPosition > 0 && iLineBreakPosition < out.length()) {
+ out.insert(iLineBreakPosition, "<br>");
+ iLineBreakPosition += 4; // -- four is the length of "<br>"
+ }
+ }
+
+ return (out.toString());
+ }
+
+
+ /**
+ * This is a convenience method for calling
+ * {@link Util#pluraliseNoun(String, long, boolean)}
+ * with <code>false</code> as a value for third parameter.
+ */
+ public static String pluraliseNoun(String noun, long count) {
+ return (pluraliseNoun(noun, count, false));
+ }
+
+ /**
+ * Performs naive pluralisation of the supplied noun.
+ *
+ * @param noun Noun in a singular form.
+ * @param count Number of occurrences of the item, for which the noun is provided.
+ * @param forceAppendingSByDefault <code>true</code> to make sure that "y" -> "ies"
+ * substitution is <b>not made</b>, but instead "s" is appended
+ * to unmodified root of the noun.
+ * @return Pluralised <code>noun</code>: with appended -s or -y replaced with -ies.
+ */
+ public static String pluraliseNoun(String noun, long count, boolean forceAppendingSByDefault)
+ {
+ if (count % 10 != 1 || count == 11) {
+ if (!forceAppendingSByDefault && noun.endsWith("y")) {
+ return (noun.substring(0, noun.length() - 1) + "ies"); // e.g. ENTRY -> ENTRIES
+ }
+ else {
+ return (noun + "s"); // e.g. SHIP -> SHIPS
+ }
+ }
+ else {
+ // no need to pluralise - count is of the type 21, 31, etc..
+ return noun;
+ }
+ }
+
+
+ /**
+ * Calculates time difference between two {@link Calendar} instances.
+ *
+ * @param earlier The "earlier" date.
+ * @param later The "later" date.
+ * @param maxDifferenceMillis The maximum allowed time difference between the two
+ * {@link Calendar} instances, in milliseconds. If the calculated
+ * difference will exceed <code>maxDifferenceMillis</code>,
+ * <code>null</code> will be returned. If this parameter has
+ * a value <code>less or equal to zero</code>, any time difference
+ * between the {@link Calendar} instances will be permitted.
+ * @return String in the form "XX seconds|minutes|hours|days ago". Proper pluralisation will
+ * be performed on the name of the time unit. <code>null</code> will be returned in
+ * cases where one of the {@link Calendar} instances is <code>null</code>, time
+ * difference between the provided instances is greater than <code>maxDifferenceMillis</code>
+ * or <code>earlier</code> date is not really earlier than <code>later</code> one.
+ */
+ public static String getAgoString(Calendar earlier, Calendar later, long maxDifferenceMillis)
+ {
+ // one of the dates is missing
+ if (earlier == null || later == null) {
+ return null;
+ }
+
+ if (earlier.before(later)) {
+ long differenceMillis = later.getTimeInMillis() - earlier.getTimeInMillis();
+
+ if (maxDifferenceMillis <= 0 || (maxDifferenceMillis > 0 && differenceMillis <= maxDifferenceMillis))
+ {
+ long result = 0;
+ String unitName = "";
+
+ if (differenceMillis < 60 * 1000) {
+ result = differenceMillis / 1000;
+ unitName = "second";
+ }
+ else if (differenceMillis < 60 * 60 * 1000) {
+ result = differenceMillis / (60 * 1000);
+ unitName = "minute";
+ }
+ else if (differenceMillis < 24 * 60 * 60 * 1000) {
+ result = differenceMillis / (60 * 60 * 1000);
+ unitName = "hour";
+ }
+ else {
+ result = differenceMillis / (24 * 60 * 60 * 1000);
+ unitName = "day";
+ }
+
+ return (result + " " + Util.pluraliseNoun(unitName, result, true) + " ago");
+ }
+ else {
+ // the difference is too large - larger than the supplied threshold
+ return null;
+ }
+ }
+ else {
+ // the "later" date is not really later than the "earlier" one
+ return null;
+ }
+ }
+
+
+ /**
+ * Joins the set of tokens in the provided list into a single string.
+ * This method is a shorthand for {@link Util#join(List, String, String, String)}.
+ *
+ * @param tokens List of strings to join.
+ * @param separator Separator to insert between individual strings.
+ * @return String of the form <code>[token1][separator][token2][separator]...[tokenN]</code>
+ */
+ public static String join(List<String> tokens, String separator) {
+ return (join(tokens, null, null, separator));
+ }
+
+ /**
+ * Joins the set of tokens in the provided list into a single string.
+ *
+ * Any empty strings or <code>null</code> entries in the <code>tokens</code> list
+ * will be removed to achieve a better resulting joined string.
+ *
+ * @param tokens List of strings to join.
+ * @param prefix String to prepend to each token.
+ * @param suffix String to append to each token.
+ * @param separator Separator to insert between individual strings.
+ * @return String of the form <code>[prefix][token1][suffix][separator][prefix][token2][suffix][separator]...[prefix][tokenN][suffix]</code>
+ * <br/><br/>
+ * Example: call <code>join(["cat","sat","on","the","mat"], "[", "]", ", ")</code> results in the following output:
+ * <code>"[cat], [sat], [on], [the], [mat]"</code>
+ */
+ public static String join(List<String> tokens, String prefix, String suffix, String separator)
+ {
+ if (tokens == null) {
+ // nothing to join
+ return (null);
+ }
+
+ // list of strings is not empty, but some pre-processing is necessary
+ // to remove any empty strings that may be there
+ for (int i = tokens.size() - 1; i >= 0; i--) {
+ if (tokens.get(i) == null || tokens.get(i).length() == 0) {
+ tokens.remove(i);
+ }
+ }
+
+ // now start the actual processing, but it may be the case that all strings
+ // were empty and we now have an empty list
+ if (tokens.isEmpty()) {
+ // nothing to join - just return an empty string
+ return ("");
+ }
+ else {
+ // there are some tokens -- perform the joining
+ String effectivePrefix = (prefix == null ? "" : prefix);
+ String effectiveSuffix = (suffix == null ? "" : suffix);
+ String effectiveSeparator = (separator == null ? "" : separator);
+
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < tokens.size(); i++) {
+ result.append(effectivePrefix + tokens.get(i) + effectiveSuffix);
+ result.append(i == tokens.size() - 1 ? "" : effectiveSeparator);
+ }
+
+ return (result.toString());
+ }
+ }
+
+ /**
+ * Determines whether the plugin is running as a standalone JFrame or inside Taverna Workbench.
+ * This is a naive test, based only on the fact that Taverna uses Raven ApplicationRuntime.
+ */
+ public static boolean isRunningInTaverna()
+ {
+ try {
+ // ApplicationRuntime class is defined within Taverna API. If this is available,
+ // it should mean that the plugin runs within Taverna.
+ ApplicationRuntime.getInstance();
+ return true;
+ }
+ catch (NoClassDefFoundError e) {
+ return false;
+ }
+ }
+
+
+ // === STRIPPING OUT HTML FROM STRINGS ===
+
+ public static String prepareStringForComponent(String source) {
+ return "<html>" + StringEscapeUtils.escapeHtml(source) + "</html>";
+ }
+
+
+ /*
+ * === The following section is providing URL handling methods. ===
+ */
+
+ /**
+ * See: {@link Util#appendStringBeforeParametersOfURL(String, String, boolean)}
+ *
+ * Assumes the last parameter as false, so that the URL encoding will be done.
+ */
+ public static String appendStringBeforeParametersOfURL(String url, String strToAppend) {
+ return (appendStringBeforeParametersOfURL(url, strToAppend, false));
+ }
+
+ /**
+ * Tiny helper to strip out all HTML tags. This will not leave any HTML tags
+ * at all (so that the content can be displayed in DialogTextArea - and the
+ * like - components. This helps to present HTML content inside JAVA easier.
+ */
+ public static String stripAllHTML(String source) {
+ // don't do anything if not string is provided
+ if (source == null)
+ return ("");
+
+ // need to preserve at least all line breaks
+ // (ending and starting paragraph also make a line break)
+ source = source.replaceAll("</p>[\r\n]*<p>", "<br/>");
+ source = source.replaceAll("[\\s]+", " ");
+ source = source.replaceAll("\\<br/?\\>", "\n");
+ source = source.replaceAll("\n ", "\n");
+
+ // strip all HTML
+ source = source.replaceAll("\\<.*?\\>", ""); // any HTML tags
+ source = source.replaceAll("&\\w{1,4};", ""); // this is for things like " ", ">", etc
+
+ return (source);
+ }
+
+
+ /**
+ * Appends given string at the end of the provided URL just before the list of parameters in the url.
+ *
+ * For example, appending ".xml" to URL "http://www.sandbox.biocatalogue.org/services?tag=blast" will
+ * yield "http://www.sandbox.biocatalogue.org/services.xml?tag=blast".
+ *
+ * No duplication checking is made - if the URL is already ending (before parameters) with the value of
+ * the string to append, that string will still be appended.
+ *
+ * @param url URL to append the string to.
+ * @param strToAppend The string to append. The value will be url-encode before appending.
+ * @return New string containing modified <code>url</code> with the <code>strToAppend</code> appended
+ * before the "query string" of the URL.
+ */
+ public static String appendStringBeforeParametersOfURL(String url, String strToAppend, boolean ignoreURLEncoding)
+ {
+ StringBuilder modifiedURL = new StringBuilder(url);
+
+ int iPositionToInsertProvidedString = modifiedURL.indexOf("?");
+ if (iPositionToInsertProvidedString == -1) iPositionToInsertProvidedString = modifiedURL.length();
+
+ String stringToInsert = (ignoreURLEncoding ? strToAppend : Util.urlEncodeQuery(strToAppend));
+ modifiedURL.insert(iPositionToInsertProvidedString, stringToInsert);
+
+ return (modifiedURL.toString());
+ }
+
+
+ /**
+ * This method takes a collection of name-value pairs in the form of a map.
+ * It then adds all parameters from this map to the provided URL.
+ *
+ * If any parameter has the same name as was already present in the URL, the new value
+ * will replace the existing one.
+ *
+ * The implementation of this method is not particularly efficient - it makes a
+ * lot of overhead, but it's fine for non-intensive usage.
+ *
+ * @param url The URL to add a new parameter to.
+ * @param Map of parameters to add to the URL. Keys and values of the map are the names and values for URL parameters.
+ * @return New string which is the original <code>url</code> with all provided
+ * parameters (in the form <code>name</code>=<code>value</code> pair) added to it.
+ */
+ public static String appendAllURLParameters(String url, Map<String,String> parameterMap)
+ {
+ if (parameterMap == null || parameterMap.size() == 0) {
+ // nothing to add, return the same URL
+ return (url);
+ }
+ else {
+ // just call an overloaded method which has the main logic
+ // to do this action for each name-value pair in the map
+ String out = url;
+ for (Map.Entry<String,String> anotherParameter : parameterMap.entrySet()) {
+ out = appendURLParameter(out, anotherParameter);
+ }
+ return (out);
+ }
+ }
+
+
+
+ /**
+ * This method takes a string representation of a URL and a name-value pair
+ * of strings - in the form of Map.Entry instance to add to the URL as a new parameter.
+ *
+ * If parameter with the same name was already present in the URL, the new value
+ * will replace the existing one.
+ *
+ * @param url The URL to add a new parameter to.
+ * @param Map.Entry instance containing the name & the value for the parameter to add.
+ * @return New string which is the original <code>url</code> with the desired
+ * parameter (<code>name</code>=<code>value</code> pair) added to it.
+ */
+ public static String appendURLParameter(String url, Map.Entry<String,String> parameter)
+ {
+ if (parameter == null) {
+ // nothing to add, return the same URL
+ return (url);
+ }
+ else {
+ // just call an overloaded method which has the main logic to do this action
+ return (appendURLParameter(url, parameter.getKey(), parameter.getValue()));
+ }
+ }
+
+
+ /**
+ * This method takes a string representation of a URL and a name-value pair
+ * of strings to add to the URL as a new parameter.
+ *
+ * If parameter with the same name was already present in the URL, the new value
+ * will replace the existing one.
+ *
+ * @param url The URL to add a new parameter to.
+ * @param parameter String array with 2 elements - first element is the name
+ * of the parameter to add, second - the value of the parameter.
+ * @return New string which is the original <code>url</code> with the desired
+ * parameter (<code>name</code>=<code>value</code> pair) added to it.
+ */
+ public static String appendURLParameter(String url, String[] parameter)
+ {
+ if (parameter == null || parameter.length != 2) {
+ // nothing to add, return the same URL
+ return (url);
+ }
+ else {
+ // just call an overloaded method which has the main logic to do this action
+ return (appendURLParameter(url, parameter[0], parameter[1]));
+ }
+ }
+
+
+ /**
+ * This method takes a string representation of a URL and a name-value pair
+ * of strings to add to the URL as a new parameter.
+ *
+ * If parameter with the same name was already present in the URL, the new value
+ * will replace the existing one.
+ *
+ * @param url The URL to add a new parameter to.
+ * @param name Name of the parameter to add.
+ * @param value Value of the parameter to add.
+ * @return New string which is the original <code>url</code> with the desired
+ * parameter (<code>name</code>=<code>value</code> pair) added to it.
+ */
+ public static String appendURLParameter(String url, String name, String value)
+ {
+ // if name of the parameter is not given, ignore this request
+ // (makes sense to return the same URL as the input in this case -
+ // as appending "nothing" wouldn't make it any different)
+ if (name == null || name.length() == 0) {
+ return (url);
+ }
+
+ // do everything in the protected block
+ try
+ {
+ // parse the parameters of the given URL
+ Map<String,String> urlParameters = extractURLParameters(url);
+ if (urlParameters == null) {
+ // there were no parameters in the original URL, create new map
+ urlParameters = new HashMap<String,String>();
+ }
+
+ // add the new parameter (this will replace a parameter with identical
+ // name if it was already present in the map)
+ urlParameters.put(name, value);
+
+ // parse the URL string into the URL object to extract original query string
+ URL theURL = new URL(url);
+ String originalQueryString = theURL.getQuery();
+
+ // prepare the basis for the new URL to return
+ String newUrl = null;
+ if (originalQueryString != null) {
+ // replace the original query string with empty space to
+ // give way for appending the new query string
+ newUrl = url.replace(originalQueryString, "");
+ }
+ else {
+ // there were no parameters in the original URL
+ newUrl = url + "?";
+ }
+
+ // append the new query string
+ newUrl += constructURLQueryString(urlParameters);
+
+ return (newUrl);
+ }
+ catch (Exception e)
+ {
+ logger.error("\nCouldn't append parameter ('" + name + "', '" + value + "') to the URL: " + url, e);
+ return (null);
+ }
+ }
+
+
+ /**
+ * Extracts a value of a specific parameter from the supplied URL.
+ *
+ * @param url The URL to extract the parameter from.
+ * @param parameterName Name of the URL parameter to extract the value for.
+ * @return Value of the parameter with <code>parameterName</code> in the given <code>url</code>.
+ * If the parameter with specified name is not found in the given <code>url</code>,
+ * <code>null</code> is returned instead.
+ */
+ public static String extractURLParameter(String url, String parameterName)
+ {
+ // both URL and the name of the required parameter must be supplied
+ if (url == null || url.length() == 0 || parameterName == null || parameterName.length() == 0) return null;
+
+ Map<String,String> urlParameters = extractURLParameters(url);
+ if (urlParameters != null) {
+ // the URL has some parameters; check what's the value of the desired parameter
+ return (urlParameters.get(parameterName));
+ }
+ else {
+ // the URL doesn't contain any parameters
+ return (null);
+ }
+ }
+
+
+ /**
+ * Extracts the query string from the provided URL. Parses this query string into
+ * a map of parameters.
+ *
+ * All parameters (both names and values) will have special characters unescaped
+ * (i.e. decoded from the standard url-encoding) and can be used directly.
+ *
+ * @param url The string representation of the URL to parse.
+ */
+ public static Map<String,String> extractURLParameters(String url)
+ {
+ try {
+ // extract the query part of the supplied URL
+ URL theURL = new URL(url);
+ String queryString = theURL.getQuery();
+
+ // prepare storage for output
+ Map<String,String> parameterMap = null;
+
+ // extract each name-value pair from query string (if any are specified in the URL)
+ if (queryString != null && queryString.length() > 0)
+ {
+ // only initialise if there are some parameters
+ parameterMap = new HashMap<String,String>();
+
+ for (String parameter : queryString.split("&")) {
+ String[] nameValueArr = parameter.split("=");
+
+ String name = nameValueArr[0]; // parameter name must always be present
+ String value = (nameValueArr.length == 2 ? nameValueArr[1] : null); // could be that parameter value is not set (e.g. "q=") - insert null then
+
+ // decode possible special characters
+ name = urlDecodeQuery(name);
+ if (value != null) value = urlDecodeQuery(value);
+
+ parameterMap.put(name, value);
+ }
+ }
+
+ return (parameterMap);
+ }
+ catch (MalformedURLException e)
+ {
+ // some problem occurred - report it; can't return any data in this case
+ logger.error("Couldn't parse parameters of a URL: " + url + "; details below:", e);
+ return null;
+ }
+ }
+
+
+ /**
+ * This method is the opposite for <code>extractURLParameters(String url)</code>.
+ * It takes a map of parameters, performs URL-encoding of each and assembles them
+ * into a query string.
+ *
+ * The query string then can be added to the <code>URL</code> object by using standard
+ * Java API.
+ *
+ * @param urlParameters Map of parameters to use in query string construction.
+ * @return URL-encoded query string.
+ */
+ public static String constructURLQueryString(Map<String,String> urlParameters)
+ {
+ if (urlParameters != null) {
+ StringBuilder queryString = new StringBuilder();
+
+ // iterate through all parameters and reconstruct the query string
+ for (Map.Entry<String,String> parameter : urlParameters.entrySet())
+ {
+ if (queryString.length() > 0) queryString.append("&"); // parameter separator
+ queryString.append(urlEncodeQuery(parameter.getKey()) + "=" + urlEncodeQuery(parameter.getValue()));
+ }
+
+ return (queryString.toString());
+ }
+ else {
+ return (null);
+ }
+ }
+
+
+ /**
+ * Prepares the string to serve as a part of url query to the server.
+ * @param query The string that needs URL encoding.
+ * @return URL encoded string that can be inserted into the request URL.
+ */
+ public static String urlEncodeQuery(String query)
+ {
+ // "fast exit" - if null supplied, just return an empty string;
+ // this is because in the URLs we have "q=", rather than "q=null" - this will cater for such cases
+ if (query == null) return ("");
+
+ // encode the query
+ String strRes = "";
+ try {
+ strRes = URLEncoder.encode(query, "UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {
+ // do nothing
+ }
+
+ return (strRes);
+ }
+
+
+ /**
+ * Decodes a string which came as a part of of URL (e.g. a URL parameter). This converts
+ * codes of escaped special characters back into those special characters.
+ *
+ * @param query The string that needs URL decoded.
+ * @return Decoded string that will contain all the special characters.
+ */
+ public static String urlDecodeQuery(String query)
+ {
+ String strRes = "";
+
+ try {
+ strRes = URLDecoder.decode(query, "UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {
+ // do nothing
+ }
+
+ return (strRes);
+ }
+
+
+ /**
+ * This method is "clones" an object supplied as an argument. It uses
+ * serialisation to achieve this (as opposed to manually implementing deep
+ * copying of all referenced objects in the graph of the provided object).
+ * This technique is used to make sure that the new object will be exact
+ * replica, but totally independent of the original one.
+ *
+ * Note that this code works ~100 times slower than it would do if deep copying
+ * was implemented. However, this will not be used in tight loops (and in loops
+ * at all), so for one-off tasks it is fine.
+ *
+ * @author Dave Miller<br/>
+ * Original version of the code in this method is taken from
+ * <a href="http://www.javaworld.com/javaworld/javatips/jw-javatip76.html?page=2">
+ * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html?page=2
+ * </a> [accessed on 25/Feb/2010].
+ * <br/><br/>
+ *
+ * @author Subhajit Dasgupta<br/>
+ * Example of using an alternative class loader during object de-serialisation
+ * was taken from
+ * <a href="http://blogs.sun.com/adventures/entry/desrializing_objects_custom_class_loaders">
+ * http://blogs.sun.com/adventures/entry/desrializing_objects_custom_class_loaders
+ * </a> [accessed on 29/Mar/2010].
+ *
+ * @return Deep copy of the provided object. If deep copying doesn't succeed,
+ * <code>null</code> is returned.
+ */
+ public static Object deepCopy(Object objectToCopy)
+ {
+ // a "safety net" - a class loader of BioCatalogue perspective may be used in
+ // de-serialisation process to make sure that all classes are recognised
+ // (system class loader may not be able to "see" all BioCatalogue plugin's files,
+ // but just those in Taverna's /lib folder)
+ final ClassLoader[] customClassLoaders = new ClassLoader[] { BioCataloguePerspective.class.getClassLoader() };
+
+ try
+ {
+ ObjectOutputStream oos = null;
+ ObjectInputStream ois = null;
+ try
+ {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ oos = new ObjectOutputStream(bos);
+
+ // serialise and pass the object
+ oos.writeObject(objectToCopy);
+ oos.flush();
+
+ // read and return the new object
+ ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
+ ois = new ObjectInputStream(bin) {
+ /**
+ * <code>resolveClass()</code> method is overridden to make use of
+ * custom ClassLoader in the de-serialisation process.
+ * <br/>
+ * This is needed to make sure that the ClassLoader of the BioCatalogue
+ * perspective is used as opposed to the system ClassLoader which will
+ * only be able to see classes from Taverna's /lib folder.
+ */
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException
+ {
+ String className = desc.getName();
+ try {
+ // attempt to use default class loader
+ return Class.forName(className);
+ }
+ catch (ClassNotFoundException exc)
+ {
+ // default class loader was unable to locate a required class -
+ // attempt to use one of the provided class loaders
+ for (ClassLoader cl : customClassLoaders) {
+ try {
+ return cl.loadClass(className);
+ }
+ catch (ClassNotFoundException e) {
+ /* do nothing here - there may be other class loaders to try */
+ }
+ }
+ // none of the class loaders was able to recognise the currently
+ // de-serialised class, so it's indeed an exception
+ throw new ClassNotFoundException(className +
+ " -- neither system, nor alternative class loaders were able to load this class");
+ }
+ }
+ };
+ return ois.readObject();
+ }
+ catch(Exception e)
+ {
+ logger.error("Could not perform deep copy of " + objectToCopy.getClass() + " instance", e);
+ }
+ finally
+ {
+ oos.close();
+ ois.close();
+ }
+ }
+ catch (Exception e) {
+ logger.error("Could not close object streams during deep copy of " + objectToCopy.getClass() + " instance");
+ }
+
+ // Error occurred - couldn't produce the deep copy...
+ return null;
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BeanForPOSTToFilteredIndex.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BeanForPOSTToFilteredIndex.java
new file mode 100644
index 0000000..d879377
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BeanForPOSTToFilteredIndex.java
@@ -0,0 +1,12 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import java.util.Map;
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BeanForPOSTToFilteredIndex
+{
+ public Map<String, String[]> filters;
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BeansForJSONLiteAPI.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BeansForJSONLiteAPI.java
new file mode 100644
index 0000000..ea3b391
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BeansForJSONLiteAPI.java
@@ -0,0 +1,84 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+
+/**
+ * Binding beans for GSON library to instantiate objects
+ * from JSON data obtained from the 'Lite' version of the
+ * BioCatalogue JSON API.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BeansForJSONLiteAPI
+{
+
+ public static abstract class ResourceIndex
+ {
+ public ResourceIndex() { }
+ public abstract ResourceLinkWithName[] getResources();
+ }
+
+
+ public static class SOAPOperationsIndex extends ResourceIndex {
+ public SOAPOperationsIndex() { }
+ public ResourceLinkWithName[] soap_operations;
+
+ public ResourceLinkWithName[] getResources() {
+ return soap_operations;
+ }
+ }
+
+ public static class RESTMethodsIndex extends ResourceIndex {
+ public RESTMethodsIndex() { }
+ public ResourceLinkWithName[] rest_methods;
+
+ public ResourceLinkWithName[] getResources() {
+ return rest_methods;
+ }
+ }
+
+ public static class ServicesIndex extends ResourceIndex {
+ public ServicesIndex() { }
+ public ResourceLinkWithName[] services;
+
+ public ResourceLinkWithName[] getResources() {
+ return services;
+ }
+ }
+
+ public static class ServiceProvidersIndex extends ResourceIndex {
+ public ServiceProvidersIndex() { }
+ public ResourceLinkWithName[] service_providers;
+
+ public ResourceLinkWithName[] getResources() {
+ return service_providers;
+ }
+ }
+
+ public static class UsersIndex extends ResourceIndex {
+ public UsersIndex() { }
+ public ResourceLinkWithName[] users;
+
+ public ResourceLinkWithName[] getResources() {
+ return users;
+ }
+ }
+
+
+
+ public static class ResourceLinkWithName
+ {
+ private ResourceLinkWithName() { }
+
+ private String resource;
+ private String name;
+
+ public String getURL() {
+ return (this.resource);
+ }
+
+ public String getName() {
+ return (this.name);
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueAPIRequest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueAPIRequest.java
new file mode 100644
index 0000000..ea55a77
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueAPIRequest.java
@@ -0,0 +1,47 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+/**
+ * A class to wrap BioCatalogue API requests - will include
+ * the type (GET, POST, etc), URL and the data to send
+ * if that's a POST / PUT request.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCatalogueAPIRequest
+{
+ public static enum TYPE {
+ GET,
+ POST,
+ PUT,
+ DELETE
+ }
+
+
+ private TYPE requestType;
+ private String url;
+ private String data;
+
+
+ public BioCatalogueAPIRequest(TYPE requestType, String url, String data) {
+ this.requestType = requestType;
+ this.url = url;
+ this.data = data;
+ }
+
+
+ public TYPE getRequestType() {
+ return requestType;
+ }
+
+ public String getURL(){
+ return url;
+ }
+ public void setURL(String url) {
+ this.url = url;
+ }
+
+ public String getData(){
+ return data;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java
new file mode 100644
index 0000000..0e033cc
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java
@@ -0,0 +1,785 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import java.io.*;
+import java.net.*;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.Pair;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.SoapOperationPortIdentity;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceIndex;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config.BioCataloguePluginConfiguration;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.Annotations;
+import org.biocatalogue.x2009.xml.rest.AnnotationsDocument;
+import org.biocatalogue.x2009.xml.rest.CollectionCoreStatistics;
+import org.biocatalogue.x2009.xml.rest.Filters;
+import org.biocatalogue.x2009.xml.rest.FiltersDocument;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestMethodDocument;
+import org.biocatalogue.x2009.xml.rest.RestMethods;
+import org.biocatalogue.x2009.xml.rest.RestMethodsDocument;
+import org.biocatalogue.x2009.xml.rest.Search;
+import org.biocatalogue.x2009.xml.rest.SearchDocument;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceDocument;
+import org.biocatalogue.x2009.xml.rest.ServiceProvider;
+import org.biocatalogue.x2009.xml.rest.ServiceProviderDocument;
+import org.biocatalogue.x2009.xml.rest.ServiceProviders;
+import org.biocatalogue.x2009.xml.rest.ServiceProvidersDocument;
+import org.biocatalogue.x2009.xml.rest.Services;
+import org.biocatalogue.x2009.xml.rest.ServicesDocument;
+import org.biocatalogue.x2009.xml.rest.SoapInput;
+import org.biocatalogue.x2009.xml.rest.SoapInputDocument;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapOperationDocument;
+import org.biocatalogue.x2009.xml.rest.SoapOperations;
+import org.biocatalogue.x2009.xml.rest.SoapOperationsDocument;
+import org.biocatalogue.x2009.xml.rest.SoapOutput;
+import org.biocatalogue.x2009.xml.rest.SoapOutputDocument;
+import org.biocatalogue.x2009.xml.rest.SoapService;
+import org.biocatalogue.x2009.xml.rest.SoapServiceDocument;
+import org.biocatalogue.x2009.xml.rest.Tag;
+import org.biocatalogue.x2009.xml.rest.TagDocument;
+import org.biocatalogue.x2009.xml.rest.Tags;
+import org.biocatalogue.x2009.xml.rest.TagsDocument;
+import org.biocatalogue.x2009.xml.rest.User;
+import org.biocatalogue.x2009.xml.rest.UserDocument;
+import org.biocatalogue.x2009.xml.rest.Users;
+import org.biocatalogue.x2009.xml.rest.UsersDocument;
+
+import com.google.gson.Gson;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class BioCatalogueClient
+{
+ // ******* CONSTANTS *******
+ // plugin details
+ public static final String PLUGIN_VERSION = "0.1.1";
+ public static final String PLUGIN_USER_AGENT = "Taverna2-ServiceCatalogue-plugin/" +
+ PLUGIN_VERSION +
+ " Java/" + System.getProperty("java.version");
+
+ public static final String XML_MIME_TYPE = "application/xml";
+ public static final String JSON_MIME_TYPE = "application/json";
+ public static final String LITE_JSON_MIME_TYPE = "application/biocat-lite+json";
+
+ public static final String XML_DATA_FORMAT = ".xml";
+ public static final String JSON_DATA_FORMAT = ".json";
+ public static final String LITE_JSON_DATA_FORMAT = ".bljson";
+
+
+
+ // API URLs
+ public static final String DEFAULT_API_SANDBOX_BASE_URL = "http://sandbox.biocatalogue.org";
+ public static final String DEFAULT_API_TEST_SERVER_BASE_URL = "http://test.biocatalogue.org";
+ public static final String DEFAULT_API_LIVE_SERVER_BASE_URL = "http://www.biocatalogue.org";
+
+ private static String BASE_URL; // BioCatalogue base URL to use (can be updated at runtime)
+
+ public static String API_REGISTRIES_URL;
+ public static String API_SERVICE_PROVIDERS_URL;
+ public static String API_USERS_URL;
+ public static String API_USER_FILTERS_URL;
+ public static String API_SERVICES_URL;
+ public static String API_SERVICE_FILTERS_URL;
+ public static String API_SOAP_OPERATIONS_URL;
+ public static String API_SOAP_OPERATION_FILTERS_URL;
+ public static String API_REST_METHODS_URL;
+ public static String API_REST_METHOD_FILTERS_URL;
+ public static String API_TAG_CLOUD_URL;
+ public static String API_SEARCH_URL;
+ public static String API_LOOKUP_URL;
+
+ // URL modifiers
+ public static final Map<String,String> API_INCLUDE_SUMMARY = Collections.singletonMap("include","summary"); // for fetching Service
+ public static final Map<String,String> API_INCLUDE_ANCESTORS = Collections.singletonMap("include", "ancestors,inputs,outputs"); // for fetching SOAP Operations and REST Methods
+ public static final String[] API_SORT_BY_NAME = {"sort","name"}; // for tag cloud
+ public static final String[] API_SORT_BY_COUNTS = {"sort","counts"}; // for tag cloud
+ public static final String[] API_ALSO_INPUTS_OUTPUTS = {"also","inputs,outputs"}; // for annotations on SOAP operation
+
+ public static final String API_PER_PAGE_PARAMETER = "per_page";
+ public static final String API_PAGE_PARAMETER = "page";
+ public static final String API_LIMIT_PARAMETER = "limit";
+ public static final String API_SERVICE_MONITORING_URL_SUFFIX = "/monitoring";
+ public static final String API_FILTERED_INDEX_SUFFIX = "/filtered_index";
+
+ // API Request scope
+ public static final String API_SCOPE_PARAMETER = "scope";
+ public static final String API_SCOPE_SOAP_OPERATIONS = "soap_operations";
+ public static final String API_SCOPE_REST_METHODS = "rest_methods";
+ public static final String API_SCOPE_SERVICES = "services";
+ public static final String API_SCOPE_SERVICE_PROVIDERS = "service_providers";
+ public static final String API_SCOPE_REGISTRIES = "registries";
+ public static final String API_SCOPE_USERS = "users";
+
+ public static final String API_TAG_PARAMETER = "tag";
+
+ public static final String API_LOOKUP_WSDL_LOCATION_PARAMETER = "wsdl_location";
+ public static final String API_LOOKUP_OPERATION_NAME_PARAMETER = "operation_name";
+ public static final String API_LOOKUP_SOAP_INPUT_NAME_PARAMETER = "input_name";
+ public static final String API_LOOKUP_SOAP_OUTPUT_NAME_PARAMETER = "output_name";
+
+
+ // *************************
+
+ // universal date formatters
+ private static final DateFormat DATE_FORMATTER = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy");
+ private static final DateFormat SHORT_DATE_FORMATTER = new SimpleDateFormat("HH:mm 'on' dd/MM/yyyy");
+ private static final DateFormat API_LOGGING_TIMESTAMP_FORMATTER = DateFormat.getDateTimeInstance();
+
+
+ // SETTINGS
+ private Properties iniSettings; // settings that are read/stored from/to INI file
+
+ private File fAPIOperationLog;
+ private PrintWriter pwAPILogWriter;
+
+ // the logger
+ private Logger logger = Logger.getLogger(BioCatalogueClient.class);
+
+ private static BioCatalogueClient INSTANCE;
+
+ // default constructor
+ private BioCatalogueClient()
+ {
+ // TODO: load any config settings (if necessary)
+
+ // load the BioCatalogue API base URL from the plugin's configuration settings
+ this.setBaseURL(BioCataloguePluginConfiguration.getInstance().
+ getProperty(BioCataloguePluginConfiguration.SERVICE_CATALOGUE_BASE_URL));
+
+ // open API operation log file, if necessary
+ if (BioCataloguePluginConstants.PERFORM_API_RESPONSE_TIME_LOGGING ||
+ BioCataloguePluginConstants.PERFORM_API_XML_DATA_BINDING_TIME_LOGGING )
+ {
+ try {
+ BioCataloguePluginConstants.LOG_FILE_FOLDER.mkdirs(); // just in case this log file was never written - create the folder as well
+ fAPIOperationLog = new File(BioCataloguePluginConstants.LOG_FILE_FOLDER,
+ BioCataloguePluginConstants.API_OPERATION_LOG_FILENAME);
+ pwAPILogWriter = new PrintWriter(new FileOutputStream(fAPIOperationLog, true), true); // auto-flush makes sure that even if app crashes, log will not be lost
+ }
+ catch (NullPointerException e) {
+ pwAPILogWriter = new PrintWriter(System.out, true);
+ logger.error("ERROR: Folder to log API operation details is unknown (using System.out instead)... Details:", e);
+ }
+ catch (FileNotFoundException e) {
+ logger.error("ERROR: Couldn't open API operation log file... Details:", e);
+ }
+ }
+ }
+
+ public static synchronized BioCatalogueClient getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new BioCatalogueClient();
+ }
+ return INSTANCE;
+ }
+
+
+ public String getBaseURL() {
+ return this.BASE_URL;
+ }
+
+ /**
+ * Updates the base API URL and also
+ * updates derived URLs of sub-URLs
+ * (e.g. BASE_URL + /services, etc)
+ *
+ * @param baseURL The new value for the BioCatalogue API base URL.
+ */
+ public void setBaseURL(String baseURL)
+ {
+ // make sure the base URL doesn't have a slash at the end
+ // (otherwise double slashes may occur during URL manipulation)
+ while (baseURL.endsWith("/")) { baseURL = baseURL.substring(0, baseURL.length() - 1); }
+
+ this.BASE_URL = baseURL;
+
+ API_REGISTRIES_URL = BASE_URL + "/registries";
+ API_SERVICE_PROVIDERS_URL = BASE_URL + "/service_providers";
+ API_USERS_URL = BASE_URL + "/users";
+ API_USER_FILTERS_URL = API_USERS_URL + "/filters";
+ API_SERVICES_URL = BASE_URL + "/services";
+ API_SERVICE_FILTERS_URL = API_SERVICES_URL + "/filters";
+ API_SOAP_OPERATIONS_URL = BASE_URL + "/soap_operations";
+ API_SOAP_OPERATION_FILTERS_URL = API_SOAP_OPERATIONS_URL + "/filters";
+ API_REST_METHODS_URL = BASE_URL + "/rest_methods";
+ API_REST_METHOD_FILTERS_URL = API_REST_METHODS_URL + "/filters";
+ API_TAG_CLOUD_URL = BASE_URL + "/tags";
+ API_SEARCH_URL = BASE_URL + "/search";
+ API_LOOKUP_URL = BASE_URL + "/lookup";
+ }
+
+ public File getAPIOperationLog() {
+ return fAPIOperationLog;
+ }
+
+ public PrintWriter getAPILogWriter() {
+ return pwAPILogWriter;
+ }
+
+
+ // ************ METHODS FOR RETRIEVAL OF SPECIALISED OBJECT FROM THE API VIA XML ************
+
+ public Annotations getBioCatalogueAnnotations(String strAnnotationsURL) throws Exception {
+ return (parseAPIResponseStream(Annotations.class, doBioCatalogueGET(strAnnotationsURL)));
+ }
+
+ public Filters getBioCatalogueFilters(String strURL) throws Exception {
+ return (parseAPIResponseStream(Filters.class, doBioCatalogueGET(strURL)));
+ }
+
+ public Services getBioCatalogueServices(String strURL) throws Exception {
+ return (parseAPIResponseStream(Services.class, doBioCatalogueGET(strURL)));
+ }
+
+ public Service getBioCatalogueService(String serviceURL) throws Exception {
+ return (parseAPIResponseStream(Service.class, doBioCatalogueGET(serviceURL)));
+ }
+
+ public Service getBioCatalogueServiceSummary(String serviceURL) throws Exception {
+ return (parseAPIResponseStream(Service.class, doBioCatalogueGET(Util.appendAllURLParameters(serviceURL, API_INCLUDE_SUMMARY))));
+ }
+
+ public Service getBioCatalogueServiceMonitoringData(String serviceURL) throws Exception
+ {
+ return (parseAPIResponseStream(Service.class,
+ doBioCatalogueGET(serviceURL + API_SERVICE_MONITORING_URL_SUFFIX))
+ );
+ }
+
+ public SoapService getBioCatalogueSoapService(String soapServiceURL) throws Exception {
+ return (parseAPIResponseStream(SoapService.class, doBioCatalogueGET(soapServiceURL)));
+ }
+
+ public SoapOperation getBioCatalogueSoapOperation(String soapOperationURL) throws Exception {
+ return (parseAPIResponseStream(SoapOperation.class, doBioCatalogueGET(soapOperationURL)));
+ }
+
+ public RestMethod getBioCatalogueRestMethod(String restMethodURL) throws Exception {
+ return (parseAPIResponseStream(RestMethod.class, doBioCatalogueGET(restMethodURL)));
+ }
+
+ public Search getBioCatalogueSearchData(String searchURL) throws Exception {
+ return (parseAPIResponseStream(Search.class, doBioCatalogueGET(searchURL)));
+ }
+
+ public Tag getBioCatalogueTag(String searchByTagURL) throws Exception {
+ return (parseAPIResponseStream(Tag.class, doBioCatalogueGET(searchByTagURL)));
+ }
+
+ public Tags getBioCatalogueTags(String tagsURL) throws Exception {
+ return (parseAPIResponseStream(Tags.class, doBioCatalogueGET(tagsURL)));
+ }
+
+
+ public ResourceLink getBioCatalogueResource(Class<? extends ResourceLink> classOfResourceToFetch, String resourceURL) throws Exception {
+ return (parseAPIResponseStream(classOfResourceToFetch, doBioCatalogueGET(resourceURL)));
+ }
+
+
+ public <T extends ResourceLink> Pair<CollectionCoreStatistics, List<T>> getListOfItemsFromResourceCollectionIndex(
+ Class<T> classOfCollectionOfRequiredReturnedObjects, BioCatalogueAPIRequest filteringRequest) throws Exception
+ {
+ ResourceLink matchingItems = null;
+ if (filteringRequest.getRequestType() == BioCatalogueAPIRequest.TYPE.GET) {
+ matchingItems = parseAPIResponseStream(classOfCollectionOfRequiredReturnedObjects, doBioCatalogueGET(filteringRequest.getURL()));
+ }
+ else {
+ matchingItems = parseAPIResponseStream(classOfCollectionOfRequiredReturnedObjects,
+ doBioCataloguePOST_SendJSON_AcceptXML(filteringRequest.getURL(), filteringRequest.getData()));
+ }
+
+ CollectionCoreStatistics statistics = null;
+
+ List<T> matchingItemList = new ArrayList<T>();
+
+ // SOAP Operations
+ if (classOfCollectionOfRequiredReturnedObjects.equals(SoapOperations.class)) {
+ SoapOperations soapOperations = (SoapOperations)matchingItems;
+ matchingItemList.addAll((Collection<? extends T>)(soapOperations.getResults().getSoapOperationList()));
+ statistics = soapOperations.getStatistics();
+ }
+
+ // REST Methods
+ else if (classOfCollectionOfRequiredReturnedObjects.equals(RestMethods.class)) {
+ RestMethods restMethods = (RestMethods)matchingItems;
+ matchingItemList.addAll((Collection<? extends T>)(restMethods.getResults().getRestMethodList()));
+ statistics = restMethods.getStatistics();
+ }
+
+ // Services
+ else if (classOfCollectionOfRequiredReturnedObjects.equals(Services.class)) {
+ Services services = (Services)matchingItems;
+ matchingItemList.addAll((Collection<? extends T>)(services.getResults().getServiceList()));
+ statistics = services.getStatistics();
+ }
+
+ // Service Providers
+ else if (classOfCollectionOfRequiredReturnedObjects.equals(ServiceProviders.class)) {
+ ServiceProviders serviceProviders = (ServiceProviders)matchingItems;
+ matchingItemList.addAll((Collection<? extends T>)(serviceProviders.getResults().getServiceProviderList()));
+ statistics = serviceProviders.getStatistics();
+ }
+
+ // Users
+ else if (classOfCollectionOfRequiredReturnedObjects.equals(Users.class)) {
+ Users users = (Users)matchingItems;
+ matchingItemList.addAll((Collection<? extends T>)(users.getResults().getUserList()));
+ statistics = users.getStatistics();
+ }
+
+ // no such option - error
+ else {
+ return null;
+ }
+
+ return new Pair<CollectionCoreStatistics, List<T>>(statistics, matchingItemList);
+ }
+
+
+
+
+ /**
+ * @param wsdlLocation
+ * @param operationName
+ * @return SoapOperation instance or <code>null</code> if nothing was found (or error occurred).
+ * @throws Exception
+ */
+ public SoapOperation lookupSoapOperation(SoapOperationIdentity soapOperationDetails) throws Exception
+ {
+ // first of all check for any problems with input data
+ if (soapOperationDetails == null || soapOperationDetails.hasError() ||
+ soapOperationDetails.getWsdlLocation() == null || soapOperationDetails.getWsdlLocation().length() == 0 ||
+ soapOperationDetails.getOperationName() == null || soapOperationDetails.getOperationName().length() == 0)
+ {
+ // something's not right - return null
+ return (null);
+ }
+
+ String lookupURL = Util.appendURLParameter(API_LOOKUP_URL, API_LOOKUP_WSDL_LOCATION_PARAMETER, soapOperationDetails.getWsdlLocation());
+ lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_OPERATION_NAME_PARAMETER, soapOperationDetails.getOperationName());
+
+ ServerResponseStream lookupResponse = doBioCatalogueGET(lookupURL);
+ if (lookupResponse.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ return null;
+ }
+ return (parseAPIResponseStream(SoapOperation.class, lookupResponse));
+ }
+
+
+ public <T extends ResourceLink> T lookupSoapOperationPort(Class<T> requiredResultClass, SoapOperationPortIdentity portDetails) throws Exception
+ {
+ // first of all check for any problems with port details
+ if (portDetails == null || portDetails.hasError() ||
+ portDetails.getWsdlLocation() == null || portDetails.getWsdlLocation().length() == 0 ||
+ portDetails.getOperationName() == null || portDetails.getOperationName().length() == 0 ||
+ portDetails.getPortName() == null || portDetails.getPortName().length() == 0)
+ {
+ // something's not right - return null
+ return (null);
+ }
+
+ // now check that specified class matches the port type
+ if (portDetails.isInput() && !requiredResultClass.equals(SoapInput.class) ||
+ !portDetails.isInput() && !requiredResultClass.equals(SoapOutput.class))
+ {
+ return (null);
+ }
+
+ String lookupURL = Util.appendURLParameter(API_LOOKUP_URL, API_LOOKUP_WSDL_LOCATION_PARAMETER, portDetails.getWsdlLocation());
+ lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_OPERATION_NAME_PARAMETER, portDetails.getOperationName());
+ if (portDetails.isInput()) {
+ lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_SOAP_INPUT_NAME_PARAMETER, portDetails.getPortName());
+ }
+ else {
+ lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_SOAP_OUTPUT_NAME_PARAMETER, portDetails.getPortName());
+ }
+
+ ServerResponseStream lookupResponse = doBioCatalogueGET(lookupURL);
+ if (lookupResponse.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ return null;
+ }
+ return (parseAPIResponseStream(requiredResultClass, lookupResponse));
+ }
+
+
+ public Service lookupParentService(SoapOperationIdentity soapOperationDetails) throws Exception
+ {
+ SoapOperation soapOperation = this.lookupSoapOperation(soapOperationDetails);
+ if (soapOperation != null) {
+ return (getBioCatalogueService(soapOperation.getAncestors().getService().getHref()));
+ }
+ else {
+ // lookup didn't find the SOAP operation or there
+ // was some problem with the input data
+ return (null);
+ }
+ }
+
+
+ public Service lookupParentServiceMonitoringData(SoapOperationIdentity soapOperationDetails) throws Exception
+ {
+ SoapOperation soapOperation = this.lookupSoapOperation(soapOperationDetails);
+ if (soapOperation != null) {
+ return (getBioCatalogueServiceMonitoringData(soapOperation.getAncestors().getService().getHref()));
+ }
+ else {
+ // lookup didn't find the SOAP operation or there
+ // was some problem with the input data
+ return (null);
+ }
+ }
+
+
+ // ************ METHODS FOR RETRIEVAL OF SPECIALISED OBJECT FROM THE API VIA LITE JSON ************
+
+ public BeansForJSONLiteAPI.ResourceIndex getBioCatalogueResourceLiteIndex(TYPE resourceType, String resourceIndexURL) throws Exception
+ {
+ ServerResponseStream response = doBioCatalogueGET_LITE_JSON(resourceIndexURL);
+
+ Gson gson = new Gson();
+ return (ResourceIndex)(gson.fromJson(new InputStreamReader(response.getResponseStream()), resourceType.getJsonLiteAPIBindingBeanClass()));
+ }
+
+
+ public BeansForJSONLiteAPI.ResourceIndex postBioCatalogueResourceLiteIndex(TYPE resourceType, String resourceIndexURL, String postData) throws Exception
+ {
+ ServerResponseStream response = doBioCataloguePOST_SendJSON_AcceptLITEJSON(resourceIndexURL, postData);
+
+ Gson gson = new Gson();
+ return (ResourceIndex)(gson.fromJson(new InputStreamReader(response.getResponseStream()), resourceType.getJsonLiteAPIBindingBeanClass()));
+ }
+
+
+ // ************ GENERIC API CONNECTIVITY METHODS ************
+
+ /**
+ * Generic method to issue GET requests to BioCatalogue server.
+ *
+ * This is a convenience method to be used instead of {@link BioCatalogueClient#doBioCatalogueGET_XML(String)}.
+ *
+ * @param strURL The URL on BioCatalogue to issue GET request to.
+ * @return TODO
+ * @throws Exception
+ */
+ public ServerResponseStream doBioCatalogueGET(String strURL) throws Exception {
+ return (doBioCatalogueGET_XML(strURL));
+ }
+
+ public ServerResponseStream doBioCatalogueGET_XML(String strURL) throws Exception {
+ return (doBioCatalogueGET(strURL, XML_MIME_TYPE, XML_DATA_FORMAT));
+ }
+
+ public ServerResponseStream doBioCatalogueGET_JSON(String strURL) throws Exception {
+ return (doBioCatalogueGET(strURL, JSON_MIME_TYPE, JSON_DATA_FORMAT));
+ }
+
+ public ServerResponseStream doBioCatalogueGET_LITE_JSON(String strURL) throws Exception {
+ return (doBioCatalogueGET(strURL, LITE_JSON_MIME_TYPE, LITE_JSON_DATA_FORMAT));
+ }
+
+
+ public ServerResponseStream doBioCatalogueGET(String strURL, String ACCEPT_HEADER, String REQUESTED_DATA_FORMAT) throws Exception
+ {
+ // TODO - HACK to speed up processing append .xml / .json / .bljson to all URLs to avoid LinkedData content negotiation
+ strURL = Util.appendStringBeforeParametersOfURL(strURL, REQUESTED_DATA_FORMAT);
+
+ // open server connection using provided URL (with no further modifications to it)
+ URL url = new URL(strURL);
+
+ Calendar requestStartedAt = Calendar.getInstance();
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+ conn.setRequestProperty("Accept", ACCEPT_HEADER);
+
+// if(LOGGED_IN) {
+// // if the user has "logged in", also add authentication details
+// conn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+// }
+
+ // fetch server's response
+ ServerResponseStream serverResponse = doBioCatalogueReceiveServerResponse(conn, strURL, true);
+
+ if (BioCataloguePluginConstants.PERFORM_API_RESPONSE_TIME_LOGGING) {
+ logAPIOperation(requestStartedAt, "GET", serverResponse);
+ }
+ return (serverResponse);
+ }
+
+
+
+ public ServerResponseStream doBioCataloguePOST_SendJSON_AcceptXML(String strURL, String strDataBody) throws Exception {
+ return (doBioCataloguePOST(strURL, strDataBody, JSON_MIME_TYPE, XML_MIME_TYPE, XML_DATA_FORMAT));
+ }
+
+ public ServerResponseStream doBioCataloguePOST_SendJSON_AcceptLITEJSON(String strURL, String strDataBody) throws Exception {
+ return (doBioCataloguePOST(strURL, strDataBody, JSON_MIME_TYPE, LITE_JSON_MIME_TYPE, LITE_JSON_DATA_FORMAT));
+ }
+
+
+ /**
+ * Generic method to execute POST requests to BioCatalogue server.
+ *
+ * @param strURL The URL on BioCatalogue to POST to.
+ * @param strDataBody Body of the message to be POSTed to <code>strURL</code>.
+ * @return An object containing server's response body as an InputStream and
+ * a response code.
+ * @param CONTENT_TYPE_HEADER MIME type of the sent data.
+ * @param ACCEPT_HEADER MIME type of the data to be received.
+ * @param REQUESTED_DATA_FORMAT
+ * @throws Exception
+ */
+ public ServerResponseStream doBioCataloguePOST(String strURL, String strDataBody, String CONTENT_TYPE_HEADER,
+ String ACCEPT_HEADER, String REQUESTED_DATA_FORMAT) throws Exception
+ {
+ // TODO - HACK to speed up processing append .xml / .json / .bljson to all URLs to avoid LinkedData content negotiation
+ strURL = Util.appendStringBeforeParametersOfURL(strURL, REQUESTED_DATA_FORMAT);
+
+ // open server connection using provided URL (with no further modifications to it)
+ URL url = new URL (strURL);
+
+ Calendar requestStartedAt = Calendar.getInstance();
+ HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+ urlConn.setRequestMethod("POST");
+ urlConn.setDoOutput(true);
+ urlConn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+ urlConn.setRequestProperty("Content-Type", CONTENT_TYPE_HEADER);
+ urlConn.setRequestProperty("Accept", ACCEPT_HEADER);
+
+ // prepare and POST XML data
+ OutputStreamWriter out = new OutputStreamWriter(urlConn.getOutputStream());
+ out.write(strDataBody);
+ out.close();
+
+
+ // fetch server's response
+ ServerResponseStream serverResponse = doBioCatalogueReceiveServerResponse(urlConn, strURL, false);
+
+ if (BioCataloguePluginConstants.PERFORM_API_RESPONSE_TIME_LOGGING) {
+ logAPIOperation(requestStartedAt, "POST", serverResponse);
+ }
+ return (serverResponse);
+ }
+
+
+ /**
+ * Generic method to execute DELETE requests to myExperiment server.
+ * This is only to be called when a user is logged in.
+ *
+ * @param strURL The URL on myExperiment to direct DELETE request to.
+ * @return An object containing XML Document with server's response body and
+ * a response code. Response body XML document might be null if there
+ * was an error or the user wasn't authorised to perform a certain action.
+ * Response code will always be set.
+ * @throws Exception
+ */
+ /*public ServerResponse doMyExperimentDELETE(String strURL) throws Exception
+ {
+ // open server connection using provided URL (with no modifications to it)
+ URL url = new URL(strURL);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+
+ // "tune" the connection
+ conn.setRequestMethod("DELETE");
+ conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+ conn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+
+ // check server's response
+ return (doMyExperimentReceiveServerResponse(conn, strURL, true));
+ }*/
+
+
+ /**
+ * A common method for retrieving BioCatalogue server's response for both
+ * GET and POST requests.
+ *
+ * @param conn Instance of the established URL connection to poll for server's response.
+ * @param strURL The URL on BioCatalogue with which the connection is established.
+ * @param bIsGetRequest Flag for identifying type of the request. True when the current
+ * connection executes GET request; false when it executes a POST / DELETE request.
+ * @return TODO
+ */
+ @SuppressWarnings("unchecked")
+ private ServerResponseStream doBioCatalogueReceiveServerResponse(HttpURLConnection conn, String strURL, boolean bIsGETRequest) throws Exception
+ {
+ int iResponseCode = conn.getResponseCode();
+
+ switch (iResponseCode)
+ {
+ case HttpURLConnection.HTTP_OK:
+ // regular operation path - simply return the reference to the data input stream
+ return (new ServerResponseStream(iResponseCode, conn.getInputStream(), strURL));
+
+ case HttpURLConnection.HTTP_BAD_REQUEST:
+ // this was a bad XML request - need full XML response to retrieve the error message from it;
+ // Java throws IOException if getInputStream() is used when non HTTP_OK response code was received -
+ // hence can use getErrorStream() straight away to fetch the error document
+ return (new ServerResponseStream(iResponseCode, conn.getErrorStream(), strURL));
+
+ case HttpURLConnection.HTTP_UNAUTHORIZED:
+ // this content is not authorised for current user
+ return (new ServerResponseStream(iResponseCode, null, strURL));
+
+ case HttpURLConnection.HTTP_NOT_FOUND:
+ // nothing was found at the provided URL
+ return (new ServerResponseStream(iResponseCode, conn.getErrorStream(), strURL));
+
+ default:
+ // unexpected response code - raise an exception
+ throw new IOException("Received unexpected HTTP response code (" + iResponseCode + ") while " +
+ (bIsGETRequest ? "fetching data at " : "posting data to ") + strURL);
+ }
+ }
+
+
+ /**
+ * This method is here to make sure that *all* parsing of received input stream data
+ * from the API is parsed ("bound") into Java objects in a central place - so it's
+ * possible to measure performance of XmlBeans for various inputs.
+ *
+ * NB! There is a serious limitation in Java's generics. Generic methods cannot
+ * access any of the static context of the classes of type parameters, because
+ * it wasn't designed for this. The only purpose of type parameters is compile-time
+ * type-checking.
+ * This means that even though all classes that could potentially be supplied as a
+ * type-parameter would have certain static functionality, it's not possible to access
+ * that through using the type-parameter like it's done in normal polymorhic situations.
+ * Therefore, some switching based on the class of the type-parameter for this method is
+ * done...
+ *
+ * @param <T>
+ * @param classOfRequiredReturnedObject Class of the object that the caller expects to receive
+ * after parsing provided server's response. For example,
+ * a call to /tags.xml return the <pre>[tags]...[/tags]</pre>
+ * document. <code>TagsDocument</code> should be used to access
+ * its static factory and parse the input stream - the return
+ * value will have type <code>Tags</code> -- <code>Tags.class</code>
+ * is the required input value for this parameter in this situation then.
+ * @param serverResponse This object should contain the input stream obtained from the API in return
+ * to the call on some URL.
+ * @return InputStream data parsed into the Java object of the supplied type [T].
+ * @throws Exception
+ */
+ @SuppressWarnings("unchecked")
+ private <T extends ResourceLink> T parseAPIResponseStream(Class<T> classOfRequiredReturnedObject, ServerResponseStream serverResponse) throws Exception
+ {
+ T parsedObject = null;
+ InputStream xmlInputStream = serverResponse.getResponseStream();
+
+ // choose a factory to parse the response and perform parsing
+ Calendar parsingStartedAt = Calendar.getInstance();
+ if (classOfRequiredReturnedObject.equals(Annotations.class)) {
+ parsedObject = (T)AnnotationsDocument.Factory.parse(xmlInputStream).getAnnotations();
+ }
+ else if (classOfRequiredReturnedObject.equals(Filters.class)) {
+ parsedObject = (T)FiltersDocument.Factory.parse(xmlInputStream).getFilters();
+ }
+ else if (classOfRequiredReturnedObject.equals(RestMethods.class)) {
+ parsedObject = (T)RestMethodsDocument.Factory.parse(xmlInputStream).getRestMethods();
+ }
+ else if (classOfRequiredReturnedObject.equals(RestMethod.class)) {
+ parsedObject = (T)RestMethodDocument.Factory.parse(xmlInputStream).getRestMethod();
+ }
+ else if (classOfRequiredReturnedObject.equals(Search.class)) {
+ parsedObject = (T)SearchDocument.Factory.parse(xmlInputStream).getSearch();
+ }
+ else if (classOfRequiredReturnedObject.equals(Services.class)) {
+ parsedObject = (T)ServicesDocument.Factory.parse(xmlInputStream).getServices();
+ }
+ else if (classOfRequiredReturnedObject.equals(Service.class)) {
+ parsedObject = (T)ServiceDocument.Factory.parse(xmlInputStream).getService();
+ }
+ else if (classOfRequiredReturnedObject.equals(ServiceProviders.class)) {
+ parsedObject = (T)ServiceProvidersDocument.Factory.parse(xmlInputStream).getServiceProviders();
+ }
+ else if (classOfRequiredReturnedObject.equals(ServiceProvider.class)) {
+ parsedObject = (T)ServiceProviderDocument.Factory.parse(xmlInputStream).getServiceProvider();
+ }
+ else if (classOfRequiredReturnedObject.equals(SoapOperations.class)) {
+ parsedObject = (T)SoapOperationsDocument.Factory.parse(xmlInputStream).getSoapOperations();
+ }
+ else if (classOfRequiredReturnedObject.equals(SoapOperation.class)) {
+ parsedObject = (T)SoapOperationDocument.Factory.parse(xmlInputStream).getSoapOperation();
+ }
+ else if (classOfRequiredReturnedObject.equals(SoapService.class)) {
+ parsedObject = (T)SoapServiceDocument.Factory.parse(xmlInputStream).getSoapService();
+ }
+ else if (classOfRequiredReturnedObject.equals(SoapInput.class)) {
+ parsedObject = (T)SoapInputDocument.Factory.parse(xmlInputStream).getSoapInput();
+ }
+ else if (classOfRequiredReturnedObject.equals(SoapOutput.class)) {
+ parsedObject = (T)SoapOutputDocument.Factory.parse(xmlInputStream).getSoapOutput();
+ }
+ else if (classOfRequiredReturnedObject.equals(Tags.class)) {
+ parsedObject = (T)TagsDocument.Factory.parse(xmlInputStream).getTags();
+ }
+ else if (classOfRequiredReturnedObject.equals(Tag.class)) {
+ parsedObject = (T)TagDocument.Factory.parse(xmlInputStream).getTag();
+ }
+ else if (classOfRequiredReturnedObject.equals(Users.class)) {
+ parsedObject = (T)UsersDocument.Factory.parse(xmlInputStream).getUsers();
+ }
+ else if (classOfRequiredReturnedObject.equals(User.class)) {
+ parsedObject = (T)UserDocument.Factory.parse(xmlInputStream).getUser();
+ }
+
+
+ // log the operation if necessary
+ if (BioCataloguePluginConstants.PERFORM_API_XML_DATA_BINDING_TIME_LOGGING) {
+ logAPIOperation(parsingStartedAt, null, serverResponse);
+ }
+
+ return (parsedObject);
+ }
+
+
+ // ************ HELPERS ************
+
+ public static DateFormat getDateFormatter() {
+ return(BioCatalogueClient.DATE_FORMATTER);
+ }
+
+ public static DateFormat getShortDateFormatter() {
+ return(BioCatalogueClient.SHORT_DATE_FORMATTER);
+ }
+
+
+ /**
+ * This is a helper to facilitate performance monitoring of the API usage.
+ *
+ * @param opearationStartedAt Instance of Calendar initialised with the date/time value of
+ * when the logged operation was started.
+ * @param requestType "GET" or "POST" to indicate that this was the actual URL connection with the BioCatalogue server
+ * to fetch some data; <code>null</code> to indicate an xml-binding operation using XmlBeans.
+ * @param serverResponse Will be used to extract the request URL.
+ */
+ private void logAPIOperation(Calendar opearationStartedAt, String requestType, ServerResponseStream serverResponse)
+ {
+ // just in case check that the writer was initialised
+ if (pwAPILogWriter != null) {
+ pwAPILogWriter.println(API_LOGGING_TIMESTAMP_FORMATTER.format(opearationStartedAt.getTime()) + ", " +
+ (System.currentTimeMillis() - opearationStartedAt.getTimeInMillis()) + ", " +
+ (requestType == null ? "xml_parsing" : requestType) + ", " +
+ serverResponse.getRequestURL());
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java
new file mode 100644
index 0000000..d79235d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java
@@ -0,0 +1,40 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import net.sf.taverna.biocatalogue.model.Util;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public abstract class ServerResponse
+{
+ // this code is to be used when a local failure is encountered and the
+ // server response is a blank / invalid one
+ public static int LOCAL_FAILURE_CODE = -1;
+
+ // server response code - in theory should correspond to HTTP response codes
+ private int iResponseCode;
+
+ // URL that was used to make the request
+ private String requestURL;
+
+
+ public ServerResponse() {
+ // do nothing - empty constructor
+ }
+
+ public ServerResponse(int responseCode, String requestURL) {
+ super();
+ this.iResponseCode = responseCode;
+ this.requestURL = Util.urlDecodeQuery(requestURL);
+ }
+
+
+ public int getResponseCode() {
+ return (this.iResponseCode);
+ }
+
+ public String getRequestURL() {
+ return requestURL;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java
new file mode 100644
index 0000000..d4ebb56
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java
@@ -0,0 +1,30 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import java.io.InputStream;
+
+/**
+ * This class is a custom version of ServerResponse which contains the
+ * InputStream with the the actual server response data.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class ServerResponseStream extends ServerResponse
+{
+ private InputStream responseStream;
+
+ public ServerResponseStream(int responseCode, InputStream serverResponseStream, String requestURL)
+ {
+ super(responseCode, requestURL);
+ this.setResponseStream(serverResponseStream);
+ }
+
+ public void setResponseStream(InputStream responseStream)
+ {
+ this.responseStream = responseStream;
+ }
+
+ public InputStream getResponseStream()
+ {
+ return responseStream;
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java
new file mode 100644
index 0000000..0c4018a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java
@@ -0,0 +1,221 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+//import javax.annotation.Resource;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.CollectionCoreStatistics;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+import com.google.gson.Gson;
+
+import net.sf.taverna.biocatalogue.model.Pair;
+import net.sf.taverna.biocatalogue.model.Tag;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BeanForPOSTToFilteredIndex;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueAPIRequest;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceIndex;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance.TYPE;
+import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsRenderer;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchEngine
+{
+ private static Logger logger = Logger.getLogger(SearchEngine.class);
+
+ protected SearchInstance searchInstance;
+ protected final BioCatalogueClient client;
+ protected final SearchInstanceTracker activeSearchInstanceTracker;
+ protected final CountDownLatch doneSignal;
+ protected final SearchResultsRenderer renderer;
+
+
+ public SearchEngine(SearchInstance searchInstance,
+ SearchInstanceTracker activeSearchInstanceTracker,
+ CountDownLatch doneSignal,
+ SearchResultsRenderer renderer)
+ {
+
+ this.searchInstance = searchInstance;
+ this.client = BioCatalogueClient.getInstance();
+ this.activeSearchInstanceTracker = activeSearchInstanceTracker;
+ this.doneSignal = doneSignal;
+ this.renderer = renderer;
+ }
+
+
+
+ /**
+ * @return <code>true</code> if the thread launched by this search engine is still
+ * the one treated as 'active' in the context of the user actions in the plugin;<br/>
+ * <code>false</code> - otherwise.
+ */
+ protected boolean isCurrentSearch() {
+ return (activeSearchInstanceTracker.isCurrentSearchInstance(
+ this.searchInstance.getResourceTypeToSearchFor(), searchInstance));
+ }
+
+
+
+ /**
+ * Primary API request is the one that is *generated* when the search is first executed --
+ * for further requests (like fetching more data) it won't be fully generated, but rather
+ * will be derived from this primary one.
+ */
+ protected BioCatalogueAPIRequest generateSearchRequest() {
+ return (generateSearchRequest(searchInstance.getSearchType()));
+ }
+
+ protected BioCatalogueAPIRequest generateSearchRequest(TYPE searchType)
+ {
+ // construct search request to execute on BioCatalogue server
+ BioCatalogueAPIRequest.TYPE requestType = BioCatalogueAPIRequest.TYPE.GET;
+ String requestURL = null;
+ String requestData = null;
+
+ switch (searchType) {
+ case QuerySearch:
+ requestURL = Util.appendURLParameter(searchInstance.getResourceTypeToSearchFor().getAPIResourceCollectionIndex(), "q", searchInstance.getSearchString());
+ break;
+
+ case TagSearch:
+ List<String> tags = new ArrayList<String>();
+ for (Tag t : searchInstance.getSearchTags()) {
+ tags.add(t.getFullTagName());
+ }
+ String tagParamValue = Util.join(tags, "[", "]", ",");
+ requestURL = Util.appendURLParameter(searchInstance.getResourceTypeToSearchFor().getAPIResourceCollectionIndex(), "tag", tagParamValue);
+ break;
+
+ case Filtering:
+ requestType = BioCatalogueAPIRequest.TYPE.POST;
+
+ // get search URL for the 'base' search upon which the filtering is based
+ requestURL = generateSearchRequest(searchInstance.getServiceFilteringBasedOn()).getURL();
+ requestURL = Util.appendStringBeforeParametersOfURL(requestURL, BioCatalogueClient.API_FILTERED_INDEX_SUFFIX, true);
+
+ // the base URL was prepared, now prepare filtering parameters as POST data
+ BeanForPOSTToFilteredIndex dataBean = new BeanForPOSTToFilteredIndex();
+ dataBean.filters = searchInstance.getFilteringSettings().getFilteringURLParameters();
+ Gson gson = new Gson();
+ requestData = gson.toJson(dataBean);
+ break;
+ }
+
+ // make sure that the URL was generated
+ if (requestURL == null) {
+ logger.error("Primary search URL couldn't be generated; Search engine must have encountered " +
+ "an unexpected search instance type: " + searchInstance.getSearchType());
+ return (null);
+ }
+
+ // Sort by name (for REST and SOAP only at the moment. Parent Web services do not have the sort by name facility yet.)
+// if (!searchInstance.getResourceTypeToSearchFor().equals(net.sf.taverna.biocatalogue.model.Resource.TYPE.Service)){
+ requestURL = Util.appendURLParameter(requestURL, "sort_by", "name");
+ requestURL = Util.appendURLParameter(requestURL, "sort_order", "asc");
+ requestURL = Util.appendURLParameter(requestURL, "include", "ancestors");
+// }
+ logger.info("Service Catalogue Plugin: Request URL for search: " + requestURL);
+
+ // append some search-type-independent parameters and return the URL
+ requestURL = Util.appendAllURLParameters(requestURL, searchInstance.getResourceTypeToSearchFor().getAPIResourceCollectionIndexAdditionalParameters());
+ return (new BioCatalogueAPIRequest(requestType, requestURL, requestData));
+ }
+
+
+
+ public void startNewSearch()
+ {
+ // construct API request for this search
+ BioCatalogueAPIRequest searchRequest = generateSearchRequest();
+
+ // perform the actual search operation
+ try
+ {
+ ResourceIndex resourceIndex = null;
+ if (searchRequest.getRequestType() == BioCatalogueAPIRequest.TYPE.GET) {
+ resourceIndex = client.getBioCatalogueResourceLiteIndex(searchInstance.getResourceTypeToSearchFor(), searchRequest.getURL());
+ }
+ else {
+ // can only be POST then!
+ resourceIndex = client.postBioCatalogueResourceLiteIndex(searchInstance.getResourceTypeToSearchFor(), searchRequest.getURL(), searchRequest.getData());
+ }
+ SearchResults searchResults = new SearchResults(searchInstance.getResourceTypeToSearchFor(), resourceIndex);
+
+ // only update search results of the associated search instance if the caller thread of
+ // this operation is still active - synchronisation helps to make sure that the results
+ // will definitely only be rendered if the current search instance is definitely active:
+ // this way searches finishing in quick succession will 'flash' the results for a short
+ // while before being updated, but that will happen in the correct order
+ synchronized (activeSearchInstanceTracker) {
+ if (isCurrentSearch()) {
+ searchInstance.setSearchResults(searchResults);
+ renderer.renderInitialResults(searchInstance);
+ }
+ }
+ }
+ catch (Exception e) {
+ logger.error("ERROR: Couldn't execute initial phase of a search by query, details below:", e);
+ }
+
+ // no matter if search was completed or interrupted by a new search, notify the caller // FIXME - is this needed?
+ searchCompleteNotifyCaller();
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public void fetchMoreResults(int resultPageNumber)
+ {
+ if (resultPageNumber < 1 || resultPageNumber > searchInstance.getSearchResults().getTotalResultPageNumber()) {
+ logger.error("Prevented attempt to fetch an invalid result page: " + resultPageNumber + ". Returning...");
+ return;
+ }
+
+ // construct search URL to hit on BioCatalogue server --
+ // it is exactly as the one for the initial search, but with a page number
+ // parameter being added
+ BioCatalogueAPIRequest searchRequest = generateSearchRequest();
+ searchRequest.setURL(Util.appendURLParameter(searchRequest.getURL(), BioCatalogueClient.API_PAGE_PARAMETER, ""+resultPageNumber));
+
+ // fetch required result page
+ try
+ {
+ Pair<CollectionCoreStatistics,List<ResourceLink>> newResultBatch = client.getListOfItemsFromResourceCollectionIndex(
+ searchInstance.getResourceTypeToSearchFor().getXmlBeansGeneratedCollectionClass(), searchRequest);
+
+ int firstNewEntryIndex = searchInstance.getSearchResults().getFirstItemIndexOn(resultPageNumber);
+ searchInstance.getSearchResults().addSearchResults(newResultBatch.getSecondObject(), firstNewEntryIndex);
+
+ // only update search results of the associated search instance if the caller thread of
+ // this operation is still active
+ if (isCurrentSearch()) {
+ renderer.renderFurtherResults(searchInstance, firstNewEntryIndex, searchInstance.getResourceTypeToSearchFor().getApiResourceCountPerIndexPage());
+ }
+ }
+ catch (Exception e) {
+ // FIXME
+ }
+
+
+ // no matter if search was completed or interrupted by a new search, notify the caller // FIXME - is this needed?
+ searchCompleteNotifyCaller();
+ }
+
+
+ /**
+ * This method is used for notifying the object that has started the
+ * search of this particular search operation being complete.
+ */
+ protected void searchCompleteNotifyCaller() {
+ this.doneSignal.countDown();
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java
new file mode 100644
index 0000000..fa43632
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java
@@ -0,0 +1,490 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Tag;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsRenderer;
+
+
+/**
+ * Class to hold settings for search instance. Objects of this type will
+ * be used to re-run a search instance at a later time -- or to apply
+ * filtering onto a previously executed search.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SearchInstance implements Comparable<SearchInstance>, Serializable
+{
+ private static final long serialVersionUID = -5236966374301885370L;
+
+ // CONSTANTS
+ public static enum TYPE
+ {
+ QuerySearch(ResourceManager.getImageIcon(ResourceManager.SEARCH_ICON)),
+ TagSearch(ResourceManager.getImageIcon(ResourceManager.TAG_ICON)),
+ Filtering(ResourceManager.getImageIcon(ResourceManager.FILTER_ICON));
+
+ private Icon icon;
+
+ /**
+ * @param icon Icon to represent search instances in different listings
+ * - for example in search history.
+ */
+ TYPE(Icon icon) {
+ this.icon = icon;
+ }
+
+ /**
+ * @return An icon that is most suitable to display search instance of this type in a UI component.
+ */
+ public Icon getIcon() {
+ return this.icon;
+ }
+ }
+
+
+
+ // SEARCH SETTINGS - for either search by query or search by tag
+ private TYPE searchType;
+ private final TYPE serviceFilteringBasedOn; // service filtering may be based on {@link TYPE.QuerySearch} or {@link TYPE.TagSearch}
+ private final Resource.TYPE resourceTypeToSearchFor;
+
+ private final String searchString;
+ private final List<Tag> searchTags;
+
+
+ // SERVICE FILTERING settings
+ private ServiceFilteringSettings filteringSettings;
+
+ // SEARCH RESULTS
+ private transient SearchResults searchResults; // don't want to store search results when serialising...
+
+
+
+ /**
+ * Constructs a query search instance for finding instance of a specific resource type.
+ *
+ * @param searchString
+ * @param resourceTypeToSearchFor
+ */
+ public SearchInstance(String searchString, Resource.TYPE resourceTypeToSearchFor)
+ {
+ this.searchType = TYPE.QuerySearch;
+ this.serviceFilteringBasedOn = null;
+
+ this.resourceTypeToSearchFor = resourceTypeToSearchFor;
+
+ this.searchString = searchString;
+ this.searchTags = null;
+ }
+
+
+
+ /**
+ * Constructing a search instance for finding instance of a specific resource type by a single tag.
+ *
+ * @param searchTag
+ * @param resourceTypeToSearchFor
+ */
+ public SearchInstance(Tag searchTag, Resource.TYPE resourceTypeToSearchFor)
+ {
+ this.searchType = TYPE.TagSearch;
+ this.serviceFilteringBasedOn = null;
+
+ this.resourceTypeToSearchFor = resourceTypeToSearchFor;
+
+ this.searchTags = Collections.singletonList(searchTag);
+ this.searchString = null;
+ }
+
+
+ /**
+ * Constructing a search instance for finding instance of a specific resource type by a list of tags.
+ *
+ * @param searchTags
+ * @param resourceTypeToSearchFor
+ */
+ public SearchInstance(List<Tag> searchTags, Resource.TYPE resourceTypeToSearchFor)
+ {
+ this.searchType = TYPE.TagSearch;
+ this.serviceFilteringBasedOn = null;
+
+ this.resourceTypeToSearchFor = resourceTypeToSearchFor;
+
+ this.searchTags = searchTags;
+ this.searchString = null;
+ }
+
+
+
+ /**
+ * Constructing service filtering search instance.
+ *
+ * @param si SearchInstance to base the current on.
+ * Can be either {@link TYPE#TagSearch} or {@link TYPE#QuerySearch} type of SearchInstance.
+ * @param filteringSettings Filtering settings associated with this search instance.
+ */
+ public SearchInstance(SearchInstance si, ServiceFilteringSettings filteringSettings) throws IllegalArgumentException
+ {
+ if (!si.isTagSearch() && !si.isQuerySearch()) {
+ throw new IllegalArgumentException("Cannot create Service Filtering search instance - " +
+ "supplied base search instance must be either QuerySearch or TagSearch");
+ }
+
+ this.searchType = TYPE.Filtering;
+ this.serviceFilteringBasedOn = si.searchType;
+
+ this.resourceTypeToSearchFor = si.resourceTypeToSearchFor;
+
+ // this search instance inherits search term (i.e. search query or the tag) from the supplied search instance
+ this.searchString = si.isQuerySearch() ? si.searchString : null;
+ this.searchTags = si.isTagSearch() ? si.searchTags : null;
+
+ // also, store the filtering settings that are to be applied to the newly
+ // created search instance
+ this.filteringSettings = filteringSettings;
+ }
+
+
+ /**
+ * Determines whether the two search instances are identical.
+ */
+ // TODO - fix the equals() method
+ public boolean equals(Object other)
+ {
+ if (other instanceof SearchInstance)
+ {
+ SearchInstance s = (SearchInstance)other;
+
+ boolean bSearchTypesMatch = (this.searchType == s.getSearchType());
+ if (bSearchTypesMatch) {
+ switch (this.searchType) {
+ case QuerySearch: bSearchTypesMatch = this.searchString.equals(s.getSearchString()); break;
+
+ case TagSearch: bSearchTypesMatch = this.searchTags.equals(s.getSearchTags()); break;
+
+ case Filtering: bSearchTypesMatch = this.serviceFilteringBasedOn == s.getServiceFilteringBasedOn();
+ if (bSearchTypesMatch) {
+ if (this.serviceFilteringBasedOn == TYPE.QuerySearch) {
+ bSearchTypesMatch = this.searchString.equals(s.getSearchString());
+ }
+ else {
+ bSearchTypesMatch = this.searchTags.equals(s.getSearchTags());
+ }
+ }
+ if (bSearchTypesMatch) {
+ if (this.filteringSettings != null) {
+ bSearchTypesMatch = this.filteringSettings.equals(s.getFilteringSettings());
+ }
+ else if (s.filteringSettings != null) {
+ // other isn't null, this one is - so 'false'
+ bSearchTypesMatch = false;
+ }
+ else {
+ // both could be null
+ bSearchTypesMatch = (this.filteringSettings == s.getFilteringSettings());
+ }
+ }
+ break;
+ default: bSearchTypesMatch = false;
+ }
+ }
+
+ return (bSearchTypesMatch &&
+ /* TODO re-enable this when limits are implemented -- this.iResultCountLimit == s.getResultCountLimit() && */
+ this.resourceTypeToSearchFor == s.getResourceTypeToSearchFor());
+ }
+ else
+ return (false);
+ }
+
+
+ public int compareTo(SearchInstance other)
+ {
+ if (this.equals(other)) return(0);
+ else
+ {
+ // this will return results in the descending order - which is
+ // fine, because the way this collection will be rendered will
+ // eventually traverse it from the rear end first; so results
+ // will be shown alphabetically
+ return (-1 * this.toString().compareTo(other.toString()));
+ }
+ }
+
+
+ /**
+ * See {@link SearchInstance#getDescriptionStringForSearchStatus(SearchInstance)}
+ */
+ public String getDescriptionStringForSearchStatus() {
+ return (getDescriptionStringForSearchStatus(this));
+ }
+
+
+ /**
+ * @param si {@link SearchInstance} for which the method is executed.
+ * @return String that can be used as a description of the provided {@link SearchInstance}
+ * in the search status label. Returned strings may look like: <br/>
+ * - <code>empty search string</code><br/>
+ * - <code>query "[search_query]"</code><br/>
+ * - <code>tag "[search_tag]"</code><br/>
+ * - <code>tags "[tag1]", "[tag2]", "[tag3]"</code><br/>
+ * - <code>query "[search_query]" and X filter(s)</code><br/>
+ * - <code>tag "[search_tag]" and X filter(s)</code><br/>
+ * - <code>tags "[tag1]", "[tag2]", "[tag3]" and X filter(s)</code><br/>
+ */
+ public static String getDescriptionStringForSearchStatus(SearchInstance si)
+ {
+ switch (si.searchType)
+ {
+ case QuerySearch: String searchQuery = si.getSearchTerm();
+ return (searchQuery.length() == 0 ?
+ "empty search string" :
+ "query " + si.getSearchTerm());
+
+ case TagSearch: return (Util.pluraliseNoun("tag", si.getSearchTags().size()) + " " + si.getSearchTerm());
+
+ case Filtering: int filterNumber = si.getFilteringSettings().getNumberOfFilteringCriteria();
+
+ SearchInstance tempBaseSI = si.deepCopy();
+ tempBaseSI.searchType = si.getServiceFilteringBasedOn();
+ return getDescriptionStringForSearchStatus(tempBaseSI) + " and " + filterNumber + " " + Util.pluraliseNoun("filter", filterNumber);
+
+ default: return ("unexpected type of search");
+ }
+ }
+
+
+ public String toString()
+ {
+ String out = "<html>";
+
+ if (this.isQuerySearch() || this.isTagSearch()) {
+ out += (this.isTagSearch() ? "Tag s" : "S") + "earch: '" + getSearchTerm() + "' [" + this.detailsAsString() + "]";
+ }
+ else if (this.isServiceFilteringSearch()) {
+ out += "Filter:<br>" +
+ (getSearchTerm().length() > 0 ? ("- based on " + (this.isQuerySearch() ? "term" : "tag") + " '" + getSearchTerm() + "'<br>") : "") +
+ "- scope: " + detailsAsString() + "<br>" +
+ "- " + this.filteringSettings.detailsAsString();
+ }
+
+ out += "</html>";
+
+ return (out);
+ }
+
+
+ /**
+ * @return A string representation of search settings held in this object;
+ * actual search value (string/tag) are ignored - this only affects
+ * types to search and the number of objects to fetch.
+ */
+ public String detailsAsString()
+ {
+ // include the name of the resource type collection that is to be / was searched for
+ String str = this.getResourceTypeToSearchFor().getCollectionName();
+
+ // add the rest to the string representation of the search instance
+ str = str /* TODO re-enable when limits are implemented -- "; limit: " + this.iResultCountLimit +*/;
+
+ return (str);
+ }
+
+
+
+ // ***** Getters for all fields *****
+
+ /**
+ * @return Type of this search instance.
+ */
+ public TYPE getSearchType() {
+ return (this.searchType);
+ }
+
+
+ /**
+ * @return True if this search settings instance describes a search by tag.
+ */
+ public boolean isTagSearch() {
+ return (this.searchType == TYPE.TagSearch);
+ }
+
+
+ /**
+ * @return True if this search settings instance describes a search by query.
+ */
+ public boolean isQuerySearch() {
+ return (this.searchType == TYPE.QuerySearch);
+ }
+
+
+ /**
+ * @return True if this search settings instance describes service filtering operation.
+ */
+ public boolean isServiceFilteringSearch() {
+ return (this.searchType == TYPE.Filtering);
+ }
+
+
+ /**
+ * Allows to test which type of search this filtering operation is based on -- any filtering
+ * operation can be:
+ * <li>derived from an initial search by tag(s) or by free-text query</li>
+ * <li>or can be just a standalone filtering operation, where filtering criteria are
+ * applied to all resources of the specified type, without prior search.</li>
+ *
+ * @return Value {@link TYPE#QuerySearch} or {@link TYPE#TagSearch} if this filtering operation has a known "parent",<br/>
+ * <code>null</code> if this is a proper search (not a filtering!) operation, or
+ * if this filtering operation was not based on any search.
+ */
+ public TYPE getServiceFilteringBasedOn() {
+ return serviceFilteringBasedOn;
+ }
+
+
+ public Resource.TYPE getResourceTypeToSearchFor() {
+ return this.resourceTypeToSearchFor;
+ }
+
+
+ /**
+ * @return Search string; only valid when SearchSettings object holds data about a search by query, not a tag search.
+ */
+ public String getSearchString() {
+ return searchString;
+ }
+
+ public List<Tag> getSearchTags() {
+ return searchTags;
+ }
+
+ /**
+ * This method is to be used when the type of search is not checked - in
+ * case of query search the method returns the search string, otherwise
+ * the tag(s) that is to be searched.
+ *
+ * @return The value will be returned in double quotes.
+ */
+ public String getSearchTerm()
+ {
+ if (this.searchType == TYPE.QuerySearch || this.serviceFilteringBasedOn == TYPE.QuerySearch) {
+ return (this.searchString.length() == 0 ?
+ "" :
+ "\"" + this.searchString + "\"");
+ }
+ else {
+ List<String> tagDisplayNames = new ArrayList<String>();
+ for (Tag t : this.searchTags) {
+ tagDisplayNames.add(t.getTagDisplayName());
+ }
+ return (Util.join(tagDisplayNames, "\"", "\"", ", "));
+ }
+ }
+
+
+ public ServiceFilteringSettings getFilteringSettings() {
+ return filteringSettings;
+ }
+ public void setFilteringSettings(ServiceFilteringSettings filteringSettings) {
+ this.filteringSettings = filteringSettings;
+ }
+
+
+ public SearchResults getSearchResults() {
+ return searchResults;
+ }
+ public void setSearchResults(SearchResults searchResults) {
+ this.searchResults = searchResults;
+ }
+
+ /**
+ * @return True if search results are available;
+ * False if no search results are available - probably search hasn't been carried out yet.
+ */
+ public boolean hasSearchResults() {
+ return (searchResults != null);
+ }
+
+ /**
+ * @return True if this is a new search; false otherwise.
+ * (Search is currently treated as new if there are no search results available yet.)
+ */
+ public boolean isNewSearch() {
+ return (!hasSearchResults());
+ }
+
+ /**
+ * Removes any previous search results; after execution of
+ * this method this search instance is treated as "new search".
+ */
+ public void clearSearchResults() {
+ this.searchResults = null;
+ }
+
+
+
+
+ // *** Methods that call SearchEngine in order to start new / resume result fetching for a previous search ***
+ //
+ // They are used to relay external calls to these methods to the underlying instance
+ // of SearchEngine which will perform the actual search operations for this search instance.
+
+ /**
+ * @param activeSearchInstanceTracker Tracker of current search instances for different resource types -
+ * aids in early termination of older searches.
+ * @param doneSignal Means of notifying the parentSeachThread of completing the requested search operation.
+ * The parent thread will block until doneSignal is activated.
+ * @param renderer {@link SearchResultsRenderer} that will render results of this search.
+ */
+ public void startNewSearch(SearchInstanceTracker activeSearchInstanceTracker,
+ CountDownLatch doneSignal, SearchResultsRenderer renderer)
+ {
+ new SearchEngine(this, activeSearchInstanceTracker, doneSignal, renderer).startNewSearch();
+ }
+
+
+ /**
+ * @param activeSearchInstanceTracker Tracker of current search instances for different resource types -
+ * aids in early termination of older searches.
+ * @param doneSignal Means of notifying the parentSeachThread of completing the requested search operation.
+ * The parent thread will block until doneSignal is activated.
+ * @param renderer {@link SearchResultsRenderer} that will render results of this search.
+ * @param resultPageNumber
+ */
+ public void fetchMoreResults(SearchInstanceTracker activeSearchInstanceTracker,
+ CountDownLatch doneSignal, SearchResultsRenderer renderer, int resultPageNumber)
+ {
+ new SearchEngine(this, activeSearchInstanceTracker, doneSignal, renderer).fetchMoreResults(resultPageNumber);
+ }
+
+
+
+
+ /**
+ * Used in the plugin, for example, to transfer search results from Search tab to
+ * Filtering tab. This way both tabs will remain completely independent.
+ *
+ * @return Deep copy of this SearchInstance object. If deep copying doesn't succeed,
+ * <code>null</code> is returned.
+ */
+ public SearchInstance deepCopy() {
+ return (SearchInstance)Util.deepCopy(this);
+ }
+
+ public boolean isEmptySearch() {
+ return ((searchString == null) || searchString.isEmpty()) &&
+ ((searchTags == null) || searchTags.isEmpty()) &&
+ ((filteringSettings == null) || (filteringSettings.getNumberOfFilteringCriteria() == 0));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java
new file mode 100644
index 0000000..906ded1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java
@@ -0,0 +1,57 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+
+/**
+ * Implementations of this interface will keep track of
+ * current search instances for different resource types
+ * (under assumption that {@link SearchInstance} classes
+ * can only deal with one resource type per instance).
+ *
+ * In the BioCatalogue plugin it is one of the UI components
+ * that needs to keep track of active search instances. This
+ * interface helps to decouple the search engine model from the
+ * UI.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public interface SearchInstanceTracker
+{
+ /**
+ * Clears all records of previous search instances.
+ */
+ public void clearPreviousSearchInstances();
+
+ /**
+ * Registers an instance of {@link SearchInstance} class
+ * as a current one for a specific resource type.
+ *
+ * Repeated calls to this method with the same parameter
+ * should overwrite old values.
+ *
+ * @param searchType Resource type to associate the registered
+ * {@link SearchInstance} with.
+ */
+ public void registerSearchInstance(Resource.TYPE searchType, SearchInstance searchInstance);
+
+
+ /**
+ * Tests if provided {@link SearchInstance} is registered as the
+ * current one.
+ *
+ * @param searchType Resource type to perform the test for.
+ * @param searchInstance {@link SearchInstance} object that is expected to be
+ * the current search instance for the specified resource type.
+ * @return <code>true</code> - if the provided <code>searchInstance</code> is indeed
+ * currently registered as the active one for the given resouce type;<br/>
+ * <code>false</code> - otherwise.
+ */
+ public boolean isCurrentSearchInstance(Resource.TYPE searchType, SearchInstance searchInstance);
+
+
+ /**
+ * @param searchType
+ * @return Currently active {@link SearchInstance} object for the specified resource type.
+ */
+ public SearchInstance getCurrentSearchInstance(Resource.TYPE searchType);
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java
new file mode 100644
index 0000000..cf5e7db
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java
@@ -0,0 +1,70 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.taverna.biocatalogue.model.Tag;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.ui.SearchOptionsPanel;
+
+/**
+ * Instances of this class can store the state of the
+ * {@link SearchOptionsPanel} / {@link TagSelectionDialog} in
+ * order to help instantiate {@link SearchInstance} objects.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SearchOptions
+{
+ private SearchInstance preconfiguredSearchInstance;
+ private SearchInstance.TYPE searchType;
+ private String searchString;
+ private List<Tag> searchTags;
+ private List<TYPE> resourceTypesToSearchFor;
+
+ public SearchOptions(String searchString, List<TYPE> searchTypes) {
+ this.preconfiguredSearchInstance = null;
+ this.searchType = SearchInstance.TYPE.QuerySearch;
+ this.searchString = searchString;
+ this.searchTags = null;
+ this.resourceTypesToSearchFor = searchTypes;
+ }
+
+ public SearchOptions(List<Tag> searchTags, List<TYPE> searchTypes) {
+ this.preconfiguredSearchInstance = null;
+ this.searchType = SearchInstance.TYPE.TagSearch;
+ this.searchString = null;
+ this.searchTags = searchTags;
+ this.resourceTypesToSearchFor = searchTypes;
+ }
+
+ public SearchOptions(SearchInstance preconfiguredSearchInstance) {
+ this.preconfiguredSearchInstance = preconfiguredSearchInstance;
+ this.searchType = preconfiguredSearchInstance.getSearchType();
+ this.searchString = preconfiguredSearchInstance.getSearchString();
+ this.searchTags = preconfiguredSearchInstance.getSearchTags();
+ this.resourceTypesToSearchFor = Collections.singletonList(preconfiguredSearchInstance.getResourceTypeToSearchFor());
+ }
+
+
+ public SearchInstance getPreconfiguredSearchInstance() {
+ return preconfiguredSearchInstance;
+ }
+
+ public SearchInstance.TYPE getSearchType() {
+ return searchType;
+ }
+
+ public String getSearchString() {
+ return searchString;
+ }
+
+ public List<Tag> getSearchTags() {
+ return searchTags;
+ }
+
+ public List<TYPE> getResourceTypesToSearchFor() {
+ return resourceTypesToSearchFor;
+ }
+
+}
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java
new file mode 100644
index 0000000..d18076c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java
@@ -0,0 +1,214 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceIndex;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceLinkWithName;
+
+import org.apache.log4j.Logger;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+
+/**
+ * Generic class for any kinds of search results.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResults implements Serializable
+{
+ private static final long serialVersionUID = 6994685875323246165L;
+
+ private transient Logger logger; // don't want to serialise the logger...
+
+ private final TYPE typeOfResourcesInTheResultSet;
+ private final int totalResultCount;
+
+ // Data store for found items
+ protected ArrayList<ResourceLink> foundItems;
+ private int fullyFetchedItemCount;
+
+
+
+ public SearchResults(TYPE typeOfResourcesInTheResultSet, BeansForJSONLiteAPI.ResourceIndex resourceIndex)
+ {
+ this.typeOfResourcesInTheResultSet = typeOfResourcesInTheResultSet;
+ this.totalResultCount = resourceIndex.getResources().length;
+ this.fullyFetchedItemCount = 0;
+
+ this.logger = Logger.getLogger(this.getClass());
+
+ initialiseSearchResultCollection(resourceIndex);
+ }
+
+
+ /**
+ * The collection of results is initialised to cater for the expected number of
+ * values - placeholder with just a name and URL for each of the expected result entries is stored.
+ *
+ * @param resourceIndex
+ */
+ protected void initialiseSearchResultCollection(ResourceIndex resourceIndex)
+ {
+ foundItems = new ArrayList<ResourceLink>();
+ foundItems.ensureCapacity(getTotalMatchingItemCount());
+
+ ResourceLinkWithName resourceLink = null;
+ for (int i = 0; i < getTotalMatchingItemCount(); i++) {
+ resourceLink = resourceIndex.getResources()[i];
+ this.foundItems.add(new LoadingResource(resourceLink.getURL(), resourceLink.getName()));
+ }
+ }
+
+
+ public synchronized void addSearchResults(List<ResourceLink> searchResultsData, int positionToStartAddingResults)
+ {
+ // only update a specific portion of results
+ for (int i = 0; i < searchResultsData.size(); i++) {
+ this.foundItems.set(i + positionToStartAddingResults, searchResultsData.get(i));
+ }
+
+ fullyFetchedItemCount += searchResultsData.size();
+ }
+
+
+ public TYPE getTypeOfResourcesInTheResultSet() {
+ return typeOfResourcesInTheResultSet;
+ }
+
+
+ /**
+ * @return List of resources that have matched the search query
+ * and/or specified filtering criteria.
+ */
+ public List<ResourceLink> getFoundItems() {
+ return (this.foundItems);
+ }
+
+
+ /**
+ * @return Number of resources that have matched the search query
+ * (and/or specified filtering criteria) that have already been
+ * fetched.
+ */
+ public int getFetchedItemCount() {
+ return (this.fullyFetchedItemCount);
+ }
+
+
+ /**
+ * @return Total number of resources that have matched the search query
+ * (and/or specified filtering criteria) - most of these will
+ * likely not be fetched yet.
+ */
+ public int getTotalMatchingItemCount() {
+ return (this.totalResultCount);
+ }
+
+
+ /**
+ * @return Total number of pages in the current result set.
+ */
+ public int getTotalResultPageNumber() {
+ int numberOfResourcesPerPageForThisResourceType = this.getTypeOfResourcesInTheResultSet().getApiResourceCountPerIndexPage();
+ return (int)(Math.ceil((double)getTotalMatchingItemCount() / numberOfResourcesPerPageForThisResourceType));
+ }
+
+
+ /**
+ * List of matching items will be partial and populated sequentially
+ * based on user actions. Therefore, this method helps to check
+ * which list entries are still not populated.
+ *
+ * @param startIndex Beginning of the range to check.
+ * @param endIndex End of the range to check.
+ * @return Zero-based index of the first entry in the list of
+ * matching resources that hasn't been fetched yet.
+ * Will return <code>-1</code> if the provided range
+ * parameters are incorrect or if all items in the
+ * specified range are already available.
+ */
+ public int getFirstMatchingItemIndexNotYetFetched(int startIndex, int endIndex)
+ {
+ // check the specified range is correct
+ if (startIndex < 0 || endIndex > getTotalMatchingItemCount() - 1) {
+ return (-1);
+ }
+
+ // go through the search results in the specified range
+ // in an attempt to find an item that hasn't been fetched
+ // just yet
+ for (int i = startIndex; i <= endIndex; i++) {
+ ResourceLink item = this.foundItems.get(i);
+ if (item != null && item instanceof LoadingResource && !((LoadingResource)item).isLoading()) {
+ return (i);
+ }
+ }
+
+ // apparently, all items in the provided range are fetched
+ return (-1);
+ }
+
+
+
+ /**
+ * @param matchingItemIndex Index of the matching item from search results.
+ * @return Index (starting from "1") of page in the search results, where
+ * the matching item with a specified index is located. If the
+ * <code>matchingItemIndex</code> is wrong, <code>-1</code> is returned.
+ */
+ public int getMatchingItemPageNumberFor(int matchingItemIndex)
+ {
+ // check the specified index is correct
+ if (matchingItemIndex < 0 || matchingItemIndex > getTotalMatchingItemCount() - 1) {
+ return (-1);
+ }
+
+ int resultsPerPageForThisType = this.getTypeOfResourcesInTheResultSet().getApiResourceCountPerIndexPage();
+ return (matchingItemIndex / resultsPerPageForThisType + 1);
+ }
+
+
+ /**
+ * @param resultPageNumber Number of the page, for which the calculations are to be done.
+ * @return Index of the first result entry on the specified result page. If <code>resultPageNumber</code>
+ * is less than <code>1</code> or greater than the total number of pages in the result set,
+ * a value of <code>-1</code> will be returned.
+ */
+ public int getFirstItemIndexOn(int resultPageNumber)
+ {
+ // page number must be in a valid range - starting with 1..onwards
+ if (resultPageNumber < 1 || resultPageNumber > getTotalResultPageNumber()) {
+ return (-1);
+ }
+
+ int numberOfResourcesPerPageForThisResourceType = this.getTypeOfResourcesInTheResultSet().getApiResourceCountPerIndexPage();
+ return ((resultPageNumber - 1) * numberOfResourcesPerPageForThisResourceType);
+ }
+
+
+
+ /**
+ * Mainly for testing - outputs number of search results per item type.
+ */
+ public String toString()
+ {
+ // FIXME
+
+// StringBuilder out = new StringBuilder("Breakdown of item counts by type:\n");
+// for (Map.Entry<Integer,String> itemTypeNamePair : Resource.ALL_SUPPORTED_RESOURCE_COLLECTION_NAMES.entrySet()) {
+// out.append(itemTypeNamePair.getValue() + ": " +getFetchedItemCount(itemTypeNamePair.getKey()) +
+// "/" + getTotalItemCount(itemTypeNamePair.getKey()) + "\n");
+// }
+//
+// return (out.toString());
+
+ return ("search results... not implemented!!!");
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/ServiceFilteringSettings.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/ServiceFilteringSettings.java
new file mode 100644
index 0000000..76d4eca
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/ServiceFilteringSettings.java
@@ -0,0 +1,184 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.ui.filtertree.FilterTreeNode;
+import net.sf.taverna.biocatalogue.ui.tristatetree.JTriStateTree;
+
+/**
+ * This class provides functionality to deal with service filtering settings.
+ * Particularly used to save the current state of the filtering tree as a
+ * favourite filter.
+ *
+ * Instances of this class hold all necessary information to restore the
+ * filtering state at a later point.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class ServiceFilteringSettings implements Comparable<ServiceFilteringSettings>, Serializable
+{
+ private static final long serialVersionUID = -5706169924295062628L;
+
+ private String filterName;
+ private int filteringCriteriaNumber;
+ private List<TreePath> filterTreeRootsOfCheckedPaths;
+
+
+
+ /**
+ * Stores current filtering selection in the provided JTriStateTree
+ * instance into the instance of this class.
+ *
+ * @param filterTree The JTriStateTree instance to get the current selection from.
+ */
+ public ServiceFilteringSettings(JTriStateTree filterTree)
+ {
+ this(null, filterTree);
+ }
+
+
+ /**
+ * Stores current filtering selection in the provided JTriStateTree
+ * instance into the instance of this class.
+ *
+ * @param filterName The name to associate with this filter.
+ * @param filterTree The JTriStateTree instance to get the current selection from.
+ */
+ @SuppressWarnings("unchecked")
+ public ServiceFilteringSettings(String filterName, JTriStateTree filterTree)
+ {
+ this.filterName = filterName;
+
+ this.filteringCriteriaNumber = filterTree.getLeavesOfCheckedPaths().size();
+
+ // a deep copy of the data from the filter tree is created, so that the data stored in this instance
+ // is fully independent of the filter tree itself; therefore local copy of this data may be modified
+ // as needed and will not affect the main filter (and vice versa)
+ this.filterTreeRootsOfCheckedPaths = (List<TreePath>)Util.deepCopy(filterTree.getRootsOfCheckedPaths());
+ }
+
+
+ /**
+ * Analyses the filter tree and produces part of the request URL containing settings regarding filters.
+ */
+ @SuppressWarnings("unchecked")
+ public Map<String,String[]> getFilteringURLParameters()
+ {
+ // analyse filter tree to get checked elements
+ Map<String,HashSet<String>> selections = new HashMap<String,HashSet<String>>();
+
+ // cycle through the deepest selected nodes;
+ // NB! the CheckboxTree acts in a way that if A contains B,C --
+ // 1) if only B is checked, tp.getLastPathComponent() will be B;
+ // 2) if both B,C are checked, tp.getLastPathComponent() will be A;
+ for (TreePath selectedRootNodePath : getFilterTreeRootsOfCheckedPaths()) {
+ FilterTreeNode selectedNode = (FilterTreeNode)selectedRootNodePath.getLastPathComponent();
+
+ // identify affected nodes
+ HashSet<FilterTreeNode> affectedNodes = new HashSet<FilterTreeNode>();
+ if (selectedNode.isFilterCategory()) {
+ // case as in example 2) -- need to "extract" nodes that are one level deeper
+ for (Enumeration children = selectedNode.children(); children.hasMoreElements(); ) {
+ affectedNodes.add((FilterTreeNode)children.nextElement());
+ }
+ }
+ else {
+ // case as in example 1)
+ affectedNodes.add(selectedNode);
+ }
+
+ // walk through the identified collection of nodes and build the data structure with URL values
+ for (FilterTreeNode node : affectedNodes) {
+ if (selections.containsKey(node.getType())) {
+ selections.get(node.getType()).add(node.getUrlValue());
+ }
+ else {
+ HashSet<String> newSet = new HashSet<String>();
+ newSet.add(node.getUrlValue());
+
+ selections.put(node.getType(), newSet);
+ }
+ }
+ }
+
+
+ // now use the constructed set of data to build the map of filtering URL parameters
+ Map<String,String[]> filterUrlParameters = new HashMap<String,String[]>();
+ for(String key : selections.keySet())
+ {
+ List<String> categoryValues = new ArrayList<String>();
+ for (String value : selections.get(key)) {
+ categoryValues.add(value);
+ }
+
+ filterUrlParameters.put(key, categoryValues.toArray(new String[0]));
+ }
+
+ return (filterUrlParameters);
+ }
+
+
+ // *** Getters ***
+
+ public String getFilterName() {
+ return (this.filterName == null || filterName.length() == 0 ? "untitled filter" : this.filterName);
+ }
+
+ public List<TreePath> getFilterTreeRootsOfCheckedPaths() {
+ return filterTreeRootsOfCheckedPaths;
+ }
+
+ /**
+ * @return Number of filtering criteria within the current filter.
+ */
+ public int getNumberOfFilteringCriteria() {
+ return filteringCriteriaNumber;
+ }
+
+ // *** End of getters ***
+
+
+ public boolean equals(Object other)
+ {
+ if (other instanceof ServiceFilteringSettings)
+ {
+ ServiceFilteringSettings o = (ServiceFilteringSettings)other;
+ return (this.filterName.equals(o.filterName) &&
+ this.filterTreeRootsOfCheckedPaths.equals(o.filterTreeRootsOfCheckedPaths));
+ }
+ else {
+ return false;
+ }
+ }
+
+
+ public int compareTo(ServiceFilteringSettings other)
+ {
+ int iOrdering = this.filterName.compareTo(other.filterName);
+ if (iOrdering == 0) {
+ iOrdering = this.getNumberOfFilteringCriteria() - other.getNumberOfFilteringCriteria();
+ }
+
+ // inverse order, as the traversal of lists in the favourite filters panel is
+ // done this way round
+ return (-1 * iOrdering);
+ }
+
+
+ public String toString() {
+ return ("Filter: '" + getFilterName() + "' [" + detailsAsString() + "]");
+ }
+
+ public String detailsAsString() {
+ return (getNumberOfFilteringCriteria() + " filtering criteria");
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/AnnotationBean.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/AnnotationBean.java
new file mode 100644
index 0000000..81ce849
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/AnnotationBean.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.biocatalogue.test;
+
+
+public class AnnotationBean
+{
+ public AnnotationBean() { }
+
+ public String self;
+ private int version;
+ private String created;
+ public Annotatable annotatable;
+ private Source source;
+ private Attribute attribute;
+ private Value value;
+
+
+ public static class Annotatable
+ {
+ private Annotatable() { }
+
+ private String name;
+ public String resource;
+ private String type;
+ }
+
+ public static class Source
+ {
+ private Source() { }
+
+ private String name;
+ private String resource;
+ private String type;
+ }
+
+ public static class Attribute
+ {
+ private Attribute() { }
+
+ private String name;
+ private String resource;
+ private String identifier;
+ }
+
+ public static class Value
+ {
+ private Value() { }
+
+ private String resource;
+ private String type;
+ private String content;
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/DrawDefaultIconTest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/DrawDefaultIconTest.java
new file mode 100644
index 0000000..1bec9e6
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/DrawDefaultIconTest.java
@@ -0,0 +1,38 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.image.BufferedImage;
+
+import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
+
+public class DrawDefaultIconTest {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args)
+ {
+ int w = 16;
+ int h = 16;
+ GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice gd = ge.getDefaultScreenDevice();
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+
+ BufferedImage image = gc.createCompatibleImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = image.createGraphics();
+ g.setColor(Color.RED);
+ g.setStroke(new BasicStroke(3, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
+ g.drawLine(4, 4, 12, 12);
+ g.drawLine(12, 4, 4, 12);
+ g.dispose();
+
+ JOptionPane.showMessageDialog(null, new ImageIcon(image));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest.java
new file mode 100644
index 0000000..eba01d6
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.biocatalogue.test;
+
+import com.google.gson.Gson;
+
+public class GSONTest
+{
+
+ public static void main(String[] args) throws Exception
+ {
+ String json = "[{\"annotatable\":{\"name\":\"IndexerService\",\"resource\":\"http://sandbox.biocatalogue.org/services/2158\",\"type\":\"Service\"},\"self\":\"http://sandbox.biocatalogue.org/annotations/47473\",\"value\":{\"resource\":\"http://sandbox.biocatalogue.org/tags/indexing\",\"type\":\"Tag\",\"content\":\"indexing\"},\"version\":1,\"created\":\"2010-01-13T09:24:04Z\",\"source\":{\"name\":\"Marco Roos\",\"resource\":\"http://sandbox.biocatalogue.org/users/48\",\"type\":\"User\"},\"attribute\":{\"name\":\"Tag\",\"resource\":\"http://sandbox.biocatalogue.org/annotation_attributes/2\",\"identifier\":\"http://www.biocatalogue.org/attribute#Category\"}}]";
+
+ Gson gson = new Gson();
+ AnnotationBean[] a = gson.fromJson(json, AnnotationBean[].class);
+
+ System.out.println("Self URL: " + a[0].self);
+ System.out.println("Annotatable resource: " + a[0].annotatable.resource);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest_exportingJSON.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest_exportingJSON.java
new file mode 100644
index 0000000..6e63ebb
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest_exportingJSON.java
@@ -0,0 +1,30 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BeanForPOSTToFilteredIndex;
+
+import com.google.gson.Gson;
+
+public class GSONTest_exportingJSON
+{
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args)
+ {
+ Map<String, String[]> m = new HashMap<String, String[]>();
+ m.put("a", new String[] {"b","c"});
+ m.put("d", new String[] {"e","f"});
+
+ BeanForPOSTToFilteredIndex b = new BeanForPOSTToFilteredIndex();
+ b.filters = m;
+
+ Gson gson = new Gson();
+ System.out.println(gson.toJson(b));
+
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest_forSoapOperationsIndex.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest_forSoapOperationsIndex.java
new file mode 100644
index 0000000..30035f7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/GSONTest_forSoapOperationsIndex.java
@@ -0,0 +1,27 @@
+package net.sf.taverna.biocatalogue.test;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+
+public class GSONTest_forSoapOperationsIndex
+{
+
+ /**
+ * @param args
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception
+ {
+ BioCatalogueClient client = BioCatalogueClient.getInstance();
+
+ String url = BioCatalogueClient.API_SOAP_OPERATIONS_URL;
+// url = Util.appendURLParameter(url, "q", "blast");
+ BeansForJSONLiteAPI.ResourceIndex soapOpIndex = client.getBioCatalogueResourceLiteIndex(Resource.TYPE.SOAPOperation, url);
+
+ System.out.println("result count: " + soapOpIndex.getResources().length + "\n\n");
+// System.out.println(soapOpIndex.soap_operations[1].getName() + "\n" + soapOpIndex.soap_operations[1].getURL() + "\n\n");
+
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/JWaitDialogTest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/JWaitDialogTest.java
new file mode 100644
index 0000000..2aed586
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/JWaitDialogTest.java
@@ -0,0 +1,36 @@
+package net.sf.taverna.biocatalogue.test;
+
+import net.sf.taverna.biocatalogue.ui.JWaitDialog;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent;
+
+public class JWaitDialogTest
+{
+
+ public static void main(String[] args)
+ {
+ System.out.println("start test");
+
+ final JWaitDialog jwd = new JWaitDialog(MainComponent.dummyOwnerJFrame, "Old title", "Please wait... Please wait... Please wait... Please wait...");
+
+ // NB! Background process must be started before the modal dialog box
+ // is made visible - otherwise processing freezes.
+ new Thread("testing delayed update of JWaitDialog")
+ {
+ public void run()
+ {
+ // wait for some time
+ try { Thread.sleep(3000); }
+ catch (InterruptedException e) { /* do nothing */ }
+
+ // update the dialog
+ jwd.setTitle("New title");
+ jwd.waitFinished("Great, all done!");
+
+ System.out.println("end test");
+ }
+ }.start();
+
+ jwd.setVisible(true);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/LinkedListEqualsTest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/LinkedListEqualsTest.java
new file mode 100644
index 0000000..6f252bd
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/LinkedListEqualsTest.java
@@ -0,0 +1,25 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.util.LinkedList;
+
+public class LinkedListEqualsTest
+{
+ public static void main(String[] args)
+ {
+ LinkedList l = new LinkedList();
+
+ String a = new String("test1");
+ String b = new String("test2");
+
+ System.out.println(a == b);
+ System.out.println(a.equals(b));
+
+ l.add(a);
+ l.add(b);
+
+ System.out.println(l);
+ System.out.println(l.indexOf(a));
+ System.out.println(l.indexOf(b));
+
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestAPICaller.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestAPICaller.java
new file mode 100644
index 0000000..a48a996
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestAPICaller.java
@@ -0,0 +1,241 @@
+package net.sf.taverna.biocatalogue.test;
+
+import javax.swing.JFrame;
+import java.awt.Dimension;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import javax.swing.JTextField;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JTextPane;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class TestAPICaller extends JFrame implements ActionListener {
+
+ private JPanel jContentPane = null;
+ private JTextField tfURL = null;
+ private JButton bSubmitRequest = null;
+ private JButton bClear = null;
+ private JScrollPane spOutputPane = null;
+ private JTextPane tpOutputPane = null;
+
+ /**
+ * This method initializes
+ *
+ */
+ public TestAPICaller() {
+ super();
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ */
+ private void initialize() {
+ this.setSize(new Dimension(515, 321));
+ this.setTitle("Test Service Catalogue API Caller");
+ this.setContentPane(getJContentPane());
+
+ this.bSubmitRequest.setDefaultCapable(true);
+ this.getRootPane().setDefaultButton(bSubmitRequest);
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ if (jContentPane == null) {
+ GridBagConstraints gridBagConstraints3 = new GridBagConstraints();
+ gridBagConstraints3.fill = GridBagConstraints.BOTH;
+ gridBagConstraints3.gridwidth = 2;
+ gridBagConstraints3.gridx = 0;
+ gridBagConstraints3.gridy = 2;
+ gridBagConstraints3.ipadx = 459;
+ gridBagConstraints3.ipady = 182;
+ gridBagConstraints3.weightx = 1.0;
+ gridBagConstraints3.weighty = 1.0;
+ gridBagConstraints3.insets = new Insets(4, 12, 9, 12);
+ GridBagConstraints gridBagConstraints2 = new GridBagConstraints();
+ gridBagConstraints2.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints2.gridy = 1;
+ gridBagConstraints2.ipadx = 0;
+ gridBagConstraints2.ipady = 0;
+ gridBagConstraints2.insets = new Insets(0, 5, 7, 12);
+ gridBagConstraints2.weightx = 0.5;
+ gridBagConstraints2.gridx = 1;
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints1.gridy = 1;
+ gridBagConstraints1.ipadx = 0;
+ gridBagConstraints1.ipady = 0;
+ gridBagConstraints1.insets = new Insets(0, 12, 7, 5);
+ gridBagConstraints1.weightx = 0.5;
+ gridBagConstraints1.gridx = 0;
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.ipadx = 466;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.ipady = 3;
+ gridBagConstraints.insets = new Insets(13, 12, 8, 12);
+ jContentPane = new JPanel();
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(getTfURL(), gridBagConstraints);
+ jContentPane.add(getBSubmitRequest(), gridBagConstraints1);
+ jContentPane.add(getBClear(), gridBagConstraints2);
+ jContentPane.add(getSpOutputPane(), gridBagConstraints3);
+ }
+ return jContentPane;
+ }
+
+ /**
+ * This method initializes tfURL
+ *
+ * @return javax.swing.JTextField
+ */
+ private JTextField getTfURL() {
+ if (tfURL == null) {
+ tfURL = new JTextField();
+ tfURL.setText(BioCatalogueClient.DEFAULT_API_SANDBOX_BASE_URL);
+ }
+ return tfURL;
+ }
+
+ /**
+ * This method initializes tfSubmitRequest
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getBSubmitRequest() {
+ if (bSubmitRequest == null) {
+ bSubmitRequest = new JButton();
+ bSubmitRequest.setText("Submit Request");
+ bSubmitRequest.addActionListener(this);
+ }
+ return bSubmitRequest;
+ }
+
+
+ /**
+ * This method initializes bClear
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getBClear() {
+ if (bClear == null) {
+ bClear = new JButton();
+ bClear.setText("Clear Output");
+ bClear.addActionListener(this);
+ }
+ return bClear;
+ }
+
+
+ /**
+ * This method initializes tpOutputPane
+ *
+ * @return javax.swing.JTextPane
+ */
+ private JTextPane getTpOutputPane() {
+ if (tpOutputPane == null) {
+ tpOutputPane = new JTextPane();
+ tpOutputPane.setContentType("text/plain");
+ }
+ return tpOutputPane;
+ }
+
+
+ /**
+ * This method initializes spOutputPane
+ *
+ * @return javax.swing.JScrollPane
+ */
+ private JScrollPane getSpOutputPane() {
+ if (spOutputPane == null) {
+ spOutputPane = new JScrollPane();
+ spOutputPane.setViewportView(getTpOutputPane());
+ }
+ return spOutputPane;
+ }
+
+
+ // ACTION LISTENER
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(bSubmitRequest)) {
+ tfURL.selectAll();
+
+ // call the actual test method
+ runBioCatalogueAPITest(tfURL.getText());
+ }
+ else if (e.getSource().equals(bClear)) {
+ this.tpOutputPane.setText("");
+ }
+
+ }
+
+
+ // ACTUAL TEST CLASS
+
+ private void runBioCatalogueAPITest(String url) {
+ final String urlFinal = url;
+ new Thread("making request") {
+ public void run() {
+ tpOutputPane.setText("Initialising Service Catalogue client...");
+ BioCatalogueClient client = null;
+ try {
+ client = BioCatalogueClient.getInstance();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ final StringBuilder text = new StringBuilder();
+ try {
+ tpOutputPane.setText("Sending request...");
+ BufferedReader br = new BufferedReader(new InputStreamReader(client.doBioCatalogueGET(urlFinal).getResponseStream()));
+ String str = "";
+
+ while ((str = br.readLine()) != null) {
+ text.append(str + "\n");
+ }
+
+ br.close();
+ }
+ catch (Exception e) {
+ text.append(e);
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ tpOutputPane.setText(text.toString());
+ }
+ });
+ }
+ }.start();
+
+ }
+
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestDoubleUsageOfSameSwingElement.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestDoubleUsageOfSameSwingElement.java
new file mode 100644
index 0000000..e5ad39c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestDoubleUsageOfSameSwingElement.java
@@ -0,0 +1,32 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+
+public class TestDoubleUsageOfSameSwingElement extends JFrame
+{
+ private String text = "abc";
+ boolean bLowerCase = true;
+
+ public TestDoubleUsageOfSameSwingElement()
+ {
+ final JButton bBtn = new JButton(text);
+ bBtn.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ bLowerCase = !bLowerCase;
+ bBtn.setText(bLowerCase ? text.toLowerCase() : text.toUpperCase());
+ }
+ });
+
+ this.setLayout(new BorderLayout());
+ this.add(bBtn, BorderLayout.WEST);
+ this.add(bBtn, BorderLayout.EAST);
+
+ this.pack();
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestUtilURLHandling.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestUtilURLHandling.java
new file mode 100644
index 0000000..71a6ca7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestUtilURLHandling.java
@@ -0,0 +1,87 @@
+package net.sf.taverna.biocatalogue.test;
+
+import net.sf.taverna.biocatalogue.model.Util;
+
+public class TestUtilURLHandling
+{
+ public static void main(String[] args)
+ {
+ String url1 = "http://sandbox.biocatalogue.org/search";
+ String url2 = "http://sandbox.biocatalogue.org/search?";
+ String url3 = "http://sandbox.biocatalogue.org/search?q=";
+ String url4 = "http://sandbox.biocatalogue.org/search?q=franck";
+ String url5 = "http://sandbox.biocatalogue.org/services?tag=%5Bbiology%5D";
+ String url6 = "http://sandbox.biocatalogue.org/services?tag=%5B%3Chttp%3A%2F%2Fwww.mygrid.org.uk%2Fontology%23DDBJ%3E%5D";
+
+
+ System.out.println("----------------------------");
+ System.out.println("Extracting URL parameters:\n");
+
+
+ System.out.println(url1 + "\nParameter map:\n" + Util.extractURLParameters(url1));
+ System.out.println("Reconstructed query string from parameter map: " + Util.constructURLQueryString(Util.extractURLParameters(url1)) + "\n");
+
+
+ System.out.println(url2 + "\nParameter map:\n" + Util.extractURLParameters(url2));
+ System.out.println("Reconstructed query string from parameter map: " + Util.constructURLQueryString(Util.extractURLParameters(url2)) + "\n");
+
+
+ System.out.println(url3 + "\nParameter map:\n" + Util.extractURLParameters(url3));
+ System.out.println("Reconstructed query string from parameter map: " + Util.constructURLQueryString(Util.extractURLParameters(url3)) + "\n");
+
+
+ System.out.println(url4 + "\nParameter map:\n" + Util.extractURLParameters(url4));
+ System.out.println("Reconstructed query string from parameter map: " + Util.constructURLQueryString(Util.extractURLParameters(url4)) + "\n");
+
+
+ System.out.println(url5 + "\nParameter map:\n" + Util.extractURLParameters(url5));
+ System.out.println("Reconstructed query string from parameter map: " + Util.constructURLQueryString(Util.extractURLParameters(url5)) + "\n");
+
+
+ System.out.println("\n\n----------------------------");
+ System.out.println("Adding parameters:\n");
+
+ String newUrl = Util.appendURLParameter(url1, "testParam", "testValue");
+ System.out.println(url1 + "\n" + newUrl + "\n");
+
+ newUrl = Util.appendURLParameter(url2, "testParam", "testValue");
+ System.out.println(url2 + "\n" + newUrl + "\n");
+
+ newUrl = Util.appendURLParameter(url3, "testParam", "testValue");
+ System.out.println(url3 + "\n" + newUrl + "\n");
+
+ newUrl = Util.appendURLParameter(url4, "testParam", "testValue");
+ System.out.println(url4 + "\n" + newUrl + "\n");
+
+ newUrl = Util.appendURLParameter(url5, "testParam", "testValue");
+ System.out.println(url5 + "\n" + newUrl + "\n");
+
+
+ System.out.println("\n\n----------------------------");
+ System.out.println("Getting parameter values:\n");
+
+ System.out.println("Value of '" + "testParam" + "' in the URL: " + url1 + " -- " + Util.extractURLParameter(url1, "testParam"));
+ System.out.println("Value of '" + "testParam" + "' in the URL: " + url2 + " -- " + Util.extractURLParameter(url2, "testParam"));
+ System.out.println("Value of '" + "q" + "' in the URL: " + url3 + " -- " + Util.extractURLParameter(url3, "q"));
+ System.out.println("Value of '" + "q" + "' in the URL: " + url4 + " -- " + Util.extractURLParameter(url4, "q"));
+ System.out.println("Value of '" + "tag" + "' in the URL: " + url5 + " -- " + Util.extractURLParameter(url5, "tag"));
+
+
+ System.out.println("\n\n----------------------------");
+ System.out.println("URL decoding:\n");
+
+ System.out.println("Original URL: " + url6 + "\nDecoded URL: " + Util.urlDecodeQuery(url6));
+
+
+ System.out.println("\n\n----------------------------");
+ System.out.println("Appending a string before URL parameters:\n");
+
+ String strToAppend = ".xml";
+ System.out.println("Appending '" + strToAppend + "' in the URL: " + url1 + " -- " + Util.appendStringBeforeParametersOfURL(url1, strToAppend));
+ System.out.println("Appending '" + strToAppend + "' in the URL: " + url2 + " -- " + Util.appendStringBeforeParametersOfURL(url2, strToAppend));
+ System.out.println("Appending '" + strToAppend + "' in the URL: " + url3 + " -- " + Util.appendStringBeforeParametersOfURL(url3, strToAppend));
+ System.out.println("Appending '" + strToAppend + "' in the URL: " + url4 + " -- " + Util.appendStringBeforeParametersOfURL(url4, strToAppend));
+ System.out.println("Appending '" + strToAppend + "' in the URL: " + url5 + " -- " + Util.appendStringBeforeParametersOfURL(url5, strToAppend));
+ System.out.println("Appending '" + strToAppend + "' in the URL: " + url6 + " -- " + Util.appendStringBeforeParametersOfURL(url6, strToAppend));
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestXHTMLRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestXHTMLRenderer.java
new file mode 100644
index 0000000..a50254e
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/TestXHTMLRenderer.java
@@ -0,0 +1,42 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.awt.Container;
+import java.io.File;
+
+import javax.swing.JFrame;
+
+import org.xhtmlrenderer.simple.FSScrollPane;
+import org.xhtmlrenderer.simple.XHTMLPanel;
+
+public class TestXHTMLRenderer extends JFrame {
+ public TestXHTMLRenderer() {
+ try {
+ init();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void init() throws Exception {
+ Container contentPane = this.getContentPane();
+
+ XHTMLPanel panel = new XHTMLPanel();
+ panel.getSharedContext().getTextRenderer().setSmoothingThreshold(0); // Anti-aliasing for all font sizes
+ panel.setDocument(new File("c:\\Temp\\MyExperiment\\T2 BioCatalogue Plugin\\BioCatalogue Plugin\\resources\\test.html"));
+
+ FSScrollPane scroll = new FSScrollPane(panel);
+ contentPane.add(scroll);
+
+ this.setTitle("XHTML rendered test");
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.pack();
+ this.setSize(1024, 768);
+ }
+
+
+ public static void main(String[] args) {
+ JFrame f = new TestXHTMLRenderer();
+ f.setVisible(true);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/WrappableJLabelTest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/WrappableJLabelTest.java
new file mode 100644
index 0000000..22afbd4
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/WrappableJLabelTest.java
@@ -0,0 +1,35 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class WrappableJLabelTest extends JFrame
+{
+ public WrappableJLabelTest() {
+
+ // depending on the LayoutManager of the container, JLabel may
+ // be resized or simply "cut off" on the edges - e.g. FlowLayout
+ // cuts it off, BorderLayout does the resizing
+ JPanel jpTestPanel = new JPanel(new BorderLayout());
+ jpTestPanel.add(new JLabel("<html><span color=\"red\">a very long</span> text that <b>is just</b> " +
+ "showing how the whole thing looks - will it wrap text or not; this " +
+ "is the question</html>"), BorderLayout.CENTER);
+
+ this.getContentPane().add(jpTestPanel);
+
+ this.pack();
+ }
+
+ public static void main(String[] args)
+ {
+ WrappableJLabelTest f = new WrappableJLabelTest();
+ f.setLocationRelativeTo(null);
+ f.setPreferredSize(new Dimension(400, 300));
+ f.setVisible(true);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/XStreamTest.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/XStreamTest.java
new file mode 100644
index 0000000..edc1d5a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/test/XStreamTest.java
@@ -0,0 +1,32 @@
+package net.sf.taverna.biocatalogue.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+
+
+public class XStreamTest
+{
+
+ public static void main(String[] args)
+ {
+ List<SoapOperationIdentity> processors = new ArrayList<SoapOperationIdentity>();
+ processors.add(new SoapOperationIdentity("http://www.test.com/test.wsdl", "aa", null));
+ processors.add(new SoapOperationIdentity("http://www.example.com/example.wsdl", "bb", null));
+
+ XStream xstream = new XStream(new DomDriver());
+ String xml = xstream.toXML(processors);
+
+ System.out.println(xml);
+
+ List<SoapOperationIdentity> processorsFromXML = (List<SoapOperationIdentity>)xstream.fromXML(xml);
+ System.out.println("\n\n");
+ System.out.println(processorsFromXML.get(0).getWsdlLocation() + " - " + processorsFromXML.get(0).getOperationName());
+ System.out.println(processorsFromXML.get(1).getWsdlLocation() + " - " + processorsFromXML.get(1).getOperationName());
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/BioCatalogueExplorationTab.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/BioCatalogueExplorationTab.java
new file mode 100644
index 0000000..d3f452d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/BioCatalogueExplorationTab.java
@@ -0,0 +1,131 @@
+package net.sf.taverna.biocatalogue.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.LayoutFocusTraversalPolicy;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsMainPanel;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class BioCatalogueExplorationTab extends JPanel implements HasDefaultFocusCapability
+{
+ private final MainComponent pluginPerspectiveMainComponent;
+ private final BioCatalogueClient client;
+ private final Logger logger;
+
+
+ // COMPONENTS
+ private BioCatalogueExplorationTab thisPanel;
+
+ private SearchOptionsPanel searchOptionsPanel;
+ private SearchResultsMainPanel tabbedSearchResultsPanel;
+
+
+ public BioCatalogueExplorationTab()
+ {
+ this.thisPanel = this;
+
+ this.pluginPerspectiveMainComponent = MainComponentFactory.getSharedInstance();
+ this.client = BioCatalogueClient.getInstance();
+ this.logger = Logger.getLogger(this.getClass());
+
+ initialiseUI();
+
+ // this is to make sure that search will get focused when this tab is opened
+ // -- is a workaround to a bug in JVM
+ setFocusCycleRoot(true);
+ setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
+ public Component getDefaultComponent(Container cont) {
+ return (thisPanel.getDefaultComponent());
+ }
+ });
+ }
+
+
+ private void initialiseUI()
+ {
+ this.tabbedSearchResultsPanel = new SearchResultsMainPanel();
+ this.searchOptionsPanel = new SearchOptionsPanel(tabbedSearchResultsPanel);
+
+
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.0;
+ c.anchor = GridBagConstraints.WEST;
+ c.insets = new Insets(3,10,3,10);
+ String baseString= "<html><b>Using service catalogue at </b>" + client.getBaseURL() + "</html>";
+ this.add(new JLabel(baseString), c);
+
+
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 0.1;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.EAST;
+ c.insets = new Insets(3,30,3,10);
+
+ this.add(searchOptionsPanel, c);
+
+ c.insets = new Insets(0,0,0,0);
+ c.gridy++;
+ c.gridx = 0;
+ c.gridwidth = 2;
+ c.weightx = c.weighty = 1.0;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.CENTER;
+ this.add(tabbedSearchResultsPanel, c);
+
+ this.setBorder(BorderFactory.createEmptyBorder(20, 10, 10, 10));
+ }
+
+
+ public SearchResultsMainPanel getTabbedSearchResultsPanel() {
+ return tabbedSearchResultsPanel;
+ }
+
+
+
+ // *** Callbacks for HasDefaultFocusCapability interface ***
+
+ public void focusDefaultComponent() {
+ this.searchOptionsPanel.focusDefaultComponent();
+ }
+
+ public Component getDefaultComponent() {
+ return (this.searchOptionsPanel.getDefaultComponent());
+ }
+
+ // *********************************************************
+
+
+ public static void main(String[] args) {
+ JFrame f = new JFrame();
+ f.getContentPane().add(new BioCatalogueExplorationTab());
+ f.setSize(1000, 800);
+ f.setLocationRelativeTo(null);
+
+ f.setVisible(true);
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/HasDefaultFocusCapability.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/HasDefaultFocusCapability.java
new file mode 100644
index 0000000..d8915d5
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/HasDefaultFocusCapability.java
@@ -0,0 +1,15 @@
+package net.sf.taverna.biocatalogue.ui;
+
+import java.awt.Component;
+
+/**
+ * Indicates that the class which implements this interface will focus default
+ * component (as if the component represented by that class was activated).
+ *
+ * @author Sergejs Aleksejevs
+ */
+public interface HasDefaultFocusCapability
+{
+ public void focusDefaultComponent();
+ public Component getDefaultComponent();
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/JClickableLabel.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/JClickableLabel.java
new file mode 100644
index 0000000..0cc9246
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/JClickableLabel.java
@@ -0,0 +1,172 @@
+package net.sf.taverna.biocatalogue.ui;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.EventListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class JClickableLabel extends JLabel implements MouseListener
+{
+ /**
+ * Default height of the JClickableLabel - calculated based on 16-pixel
+ * standard square icon and 3-pixel thick padding on top / bottom of element.
+ */
+ public static final int DEFAULT_HEIGHT = 22;
+
+ public static final Color DEFAULT_REGULAR_FOREGROUND_COLOR = Color.BLUE;
+ public static final Color DEFAULT_HOVER_FOREGROUND_COLOR = new Color(133, 53, 53);
+
+
+ // This will hold the data which is relevant to processing the 'click' event on this label
+ private final String strData;
+
+ // This will hold a reference to ResourcePreviewBrowser instance that is supposed to process the clicks
+ // on JClickableLabels
+ private ActionListener clickHandler;
+
+
+ private Color REGULAR_FOREGROUND_COLOR = DEFAULT_REGULAR_FOREGROUND_COLOR;
+ private Color HOVER_FOREGROUND_COLOR = DEFAULT_HOVER_FOREGROUND_COLOR;
+
+
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler)
+ {
+ this(strLabel, strDataForAction, eventHandler, null);
+ }
+
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler, Icon icon)
+ {
+ this(strLabel, strDataForAction, eventHandler, icon, SwingUtilities.LEFT);
+ }
+
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler, Icon icon, int horizontalAlignment)
+ {
+ this(strLabel, strDataForAction, eventHandler, icon, horizontalAlignment, null);
+ }
+
+ /**
+ *
+ * @param strLabel Textual label that will be visible in the UI.
+ * @param strDataForAction Data that will be passed to eventHandler when click on the label is made.
+ * @param eventHandler ActionListener that will process clicks on this label.
+ * @param icon Icon to display in the label.
+ * @param horizontalAlignment This is one of SwingConstants: LEFT, CENTER, RIGHT, LEADING or TRAILING
+ * @param strTooltip Tooltip to show over the label - if none is provided (e.g. null value), the strLabel will be used as a tooltip.
+ */
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler, Icon icon, int horizontalAlignment, String strTooltip)
+ {
+ super(strLabel, icon, horizontalAlignment);
+
+ this.strData = strDataForAction;
+ this.clickHandler = (ActionListener)eventHandler;
+
+ // empty border at the top and bottom will simulate "line-spacing"
+ // (this is only needed when an icon is displayed)
+ if (icon != null) {
+ this.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0));
+ }
+
+ // the tooltip for now only shows the full label text
+ this.setToolTipText(strTooltip == null ? strLabel : strTooltip);
+ this.setForeground(REGULAR_FOREGROUND_COLOR);
+ this.addMouseListener(this);
+ }
+
+
+ public void setRegularForegroundColor(Color regularForegroundColor)
+ {
+ REGULAR_FOREGROUND_COLOR = regularForegroundColor;
+
+ // apply the new foreground color immediately
+ this.setForeground(REGULAR_FOREGROUND_COLOR);
+ }
+
+ public Color getRegularForegroundColor() {
+ return REGULAR_FOREGROUND_COLOR;
+ }
+
+
+ public void setHoverForegroundColor(Color hoverForegroundColor)
+ {
+ // will be applied the next time mouse hovers over this label
+ HOVER_FOREGROUND_COLOR = hoverForegroundColor;
+ }
+
+ public Color getHoverForegroundColor() {
+ return HOVER_FOREGROUND_COLOR;
+ }
+
+
+ /**
+ * @return The "hidden" string value that is normally sent as an <code>ActionCommand</code>
+ * within <code>ActionEvent</code> when JClickableLabel is clicked.
+ */
+ public String getData() {
+ return (this.strData);
+ }
+
+
+ /**
+ * @return String value of the label that this JClickableLabel would have in the UI.
+ */
+ public String toString() {
+ return (this.getText());
+ }
+
+
+ /* This class extends JLabel, so it can't extend MouseAdapter;
+ * therefore, empty methods will be added for not useful callbacks
+ * from the MouseListener interface.
+ */
+ public void mouseClicked(MouseEvent e)
+ {
+ // call 'actionPerformed' method on the clickHandler instance that was supplied
+ // on creation of the JClickableLabel instance
+ this.clickHandler.actionPerformed(new ActionEvent(this, e.getID(), this.strData));
+ }
+
+ public void mouseEntered(MouseEvent e)
+ {
+ this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) ;
+ this.setForeground(HOVER_FOREGROUND_COLOR);
+ }
+
+ public void mouseExited(MouseEvent e)
+ {
+ this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)) ;
+ this.setForeground(REGULAR_FOREGROUND_COLOR);
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ // do nothing
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ // do nothing
+ }
+
+
+ /**
+ * @return A dummy instance of JClickable label - only intended to
+ * represent an object of this class; doesn't have a click handler,
+ * so a click on it will result in a <code>NullPointerException</code>.
+ */
+ public static JClickableLabel getDummyInstance() {
+ return (new JClickableLabel("dummy", "", null));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/JWaitDialog.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/JWaitDialog.java
new file mode 100644
index 0000000..526066b
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/JWaitDialog.java
@@ -0,0 +1,234 @@
+package net.sf.taverna.biocatalogue.ui;
+
+import java.awt.BorderLayout;
+//import java.awt.Dimension;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.workbench.MainWindow;
+
+/**
+ * Creates a modal non-resizable dialog window.
+ *
+ * Intended to be used for operations that consume some
+ * time, but the user must wait for them to complete before
+ * proceeding.
+ *
+ * Initially the dialog shows a specified string message or
+ * component and a "loader" bar - dynamic GIF image that
+ * displays "activity" going on. At this stage the window
+ * cannot be closed.
+ *
+ * When the operation completes, the caller notifies the dialog
+ * that it has finished, provides a new message / component to
+ * display and allows the dialog to be closed.
+ *
+ * If the operation doesn't complete within the specified time,
+ * a timeout occurs and the dialog windows lets to close itself.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class JWaitDialog extends JDialog
+{
+ private static final int DEFAULT_TIMEOUT = 10000;
+ private static final ImageIcon LOADER_ICON = ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE);
+
+ private JPanel jpInformationPanel;
+ private JLabel jlLoaderIcon;
+
+ private JButton bOK;
+ private JPanel jpOKButton;
+
+ private Timer timeoutTimer;
+ private boolean hasTimedOut;
+
+
+ /**
+ * Creates a new Wait Dialog with no parent and default timeout on
+ * operation - <code>JWaitDialog.DEFAULT_TIMEOUT</code>.
+ *
+ * @param dialogTitle Title to set for the dialog window.
+ * @param waitMessage Text to be displayed in the body of this dialog while
+ * the user waits.
+ */
+ public JWaitDialog(String dialogTitle, String waitMessage) {
+ this(null, dialogTitle, new JLabel(waitMessage, JLabel.CENTER), DEFAULT_TIMEOUT);
+ }
+
+
+ /**
+ * Creates a new Wait Dialog with specified parent and default timeout on
+ * operation - <code>JWaitDialog.DEFAULT_TIMEOUT</code>.
+ *
+ * @param owner Specified JFrame is set as an owner for this Wait Dialog.
+ * @param dialogTitle Title to set for the dialog window.
+ * @param waitMessage Text to be displayed in the body of this dialog while
+ * the user waits.
+ */
+ public JWaitDialog(JFrame owner, String dialogTitle, String waitMessage) {
+ this(owner, dialogTitle, new JLabel(waitMessage, JLabel.CENTER), DEFAULT_TIMEOUT);
+ }
+
+
+ /**
+ * Creates a new Wait Dialog with specified parent and timeout on
+ * operation.
+ *
+ * @param owner Specified JFrame is set as an owner for this Wait Dialog.
+ * @param dialogTitle Title to set for the dialog window.
+ * @param waitMessage Text to be displayed in the body of this dialog while
+ * the user waits.
+ * @param timeoutMillis Duration of the timeout on the operation - after this
+ * time has passed the window will notify of the timeout
+ * and allow to close itself. Value of 0 indicates that the timeout will never occur.
+ */
+ public JWaitDialog(JFrame owner, String dialogTitle, String waitMessage, int timeoutMillis) {
+ this(owner, dialogTitle, new JLabel(waitMessage, JLabel.CENTER), timeoutMillis);
+ }
+
+
+ /**
+ * Creates a new Wait Dialog with parent JFrame.
+ *
+ * @param owner Specified JFrame is set as an owner for this Wait Dialog.
+ * @param dialogTitle Title to set for the dialog window.
+ * @param waitInformationComponent Component to be shown in the body of this
+ * dialog windows while the user waits for an operation to complete.
+ * @param timeoutMillis Duration of the timeout on the operation - after this
+ * time has passed the window will notify of the timeout
+ * and allow to close itself. Value of 0 indicates that the timeout will never occur.
+ */
+ public JWaitDialog(JFrame owner, String dialogTitle, JComponent waitInformationComponent, int timeoutMillis)
+ {
+ super(owner);
+ this.setModal(true);
+ this.setTitle(dialogTitle);
+
+ // this will show the wait message to the user
+ jpInformationPanel = new JPanel(new GridLayout());
+ jpInformationPanel.add(waitInformationComponent);
+ jpInformationPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 10, 20));
+
+ // some graphical indication that the loading activity is going on
+ jlLoaderIcon = new JLabel(LOADER_ICON);
+ jlLoaderIcon.setBorder(BorderFactory.createEmptyBorder(0, 20, 20, 20));
+
+ // put components into the dialog box
+ this.getContentPane().setLayout(new BorderLayout());
+ this.getContentPane().add(jpInformationPanel, BorderLayout.CENTER);
+ this.getContentPane().add(jlLoaderIcon, BorderLayout.SOUTH);
+
+ this.pack();
+ // Set the height of the dialog not to be more than 500; the message is in the scroll pane so that should be OK
+ this.setSize(new Dimension(this.getPreferredSize().width, this.getPreferredSize().height > 500 ? 500 : this.getPreferredSize().height));
+ // this.setResizable(false);
+ this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+
+ // center this window within the main Taverna Workbench window
+ this.setLocationRelativeTo(MainWindow.getMainWindow());
+
+
+ // start the timer - on timeout it will produce the
+ // timeout message and allow to close the window
+ hasTimedOut = false;
+ if (timeoutMillis > 0)
+ {
+ timeoutTimer = new Timer();
+ timeoutTimer.schedule(
+ new TimerTask() {
+ public void run() {
+ waitFinished(new JLabel("<html><center>The operation did not complete within the " +
+ "allocated time.</center></html>",
+ UIManager.getIcon("OptionPane.warningIcon"), JLabel.CENTER));
+ hasTimedOut = true;
+ }
+ },
+ timeoutMillis);
+ }
+ }
+
+
+ public void waitFinished(String resultMessage) {
+ waitFinished(new JLabel(resultMessage, JLabel.CENTER));
+ }
+
+ public void waitFinished(JComponent resultInformationComponent)
+ {
+ // this prevents the real response to be set after the
+ // timeout message was already displayed
+ if (!hasTimedOut)
+ {
+ // first of all stop the timeout timer: if this
+ // method was called by the application explicitly, not on
+ // timeout, we don't want the timeout message to appear after that
+ if (timeoutTimer != null) { timeoutTimer.cancel(); }
+
+ // change the information component
+ jpInformationPanel.removeAll();
+ jpInformationPanel.add(resultInformationComponent);
+
+ // the OK button will allow closing the window
+ bOK = new JButton("OK");
+ //bOK.setPreferredSize(new Dimension(LOADER_ICON.getIconWidth(), (int) (1.5 * LOADER_ICON.getIconHeight())));
+ bOK.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // just remove the window
+ dispose();
+ }
+ });
+ bOK.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ // a fallback mechanism - default button doesn't work for some reason
+ // when the button is added into the dialog not in the constructor
+ bOK.doClick();
+ }
+ }
+ });
+ bOK.setDefaultCapable(true);
+ this.getRootPane().setDefaultButton(bOK);
+
+ // wrap OK button into a panel to add empty borders
+ jpOKButton = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
+ jpOKButton.add(bOK);
+ jpOKButton.setBorder(BorderFactory.createEmptyBorder(0, 20, 20 - (bOK.getPreferredSize().height - LOADER_ICON.getIconHeight()), 20));
+
+
+ // add OK button instead of the loader icon
+ this.getContentPane().remove(jlLoaderIcon);
+ this.getContentPane().add(jpOKButton, BorderLayout.SOUTH);
+ this.bOK.requestFocusInWindow();
+
+ // re-enable (X) button in the title bar
+ this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+
+ // update the size of this window - as the inner sizes of components have
+ // been likely to change; then center the dialog box within its parent
+ this.pack();
+ // Set the height of the dialog not to be more than 500; the message is in the scroll pane so that should be OK
+ this.setSize(new Dimension(this.getPreferredSize().width, this.getPreferredSize().height > 500 ? 500 : this.getPreferredSize().height));
+ this.setLocationRelativeTo(MainWindow.getMainWindow());
+ }
+ }
+
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/SearchOptionsPanel.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/SearchOptionsPanel.java
new file mode 100644
index 0000000..b0d9d26
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/SearchOptionsPanel.java
@@ -0,0 +1,167 @@
+package net.sf.taverna.biocatalogue.ui;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Arrays;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.search.SearchOptions;
+import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsMainPanel;
+import net.sf.taverna.t2.lang.ui.DeselectingButton;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SearchOptionsPanel extends JPanel implements HasDefaultFocusCapability
+{
+ // COMPONENTS
+ private SearchOptionsPanel thisPanel;
+
+private JTextField tfSearchQuery;
+ private JButton bSearch;
+
+ private final SearchResultsMainPanel tabbedSearchResultsPanel;
+
+
+ public SearchOptionsPanel(SearchResultsMainPanel tabbedSearchResultsPanel)
+ {
+ super();
+ this.thisPanel = this;
+ this.tabbedSearchResultsPanel = tabbedSearchResultsPanel;
+
+ this.initialiseUI();
+ }
+
+
+ private void initialiseUI()
+ {
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0.0;
+ c.fill = GridBagConstraints.NONE;
+
+
+ this.tfSearchQuery = new JTextField(30);
+ this.tfSearchQuery.setToolTipText(
+ "<html> Tips for creating search queries:<br>" +
+ " 1) Use wildcards to make more flexible queries. Asterisk (<b>*</b>) matches any zero or more<br>" +
+ " characters (e.g. <b><i>Seq*</i></b> would match <b><i>Sequence</i></b>), question mark (<b>?</b>) matches any single<br>" +
+ " character (e.g. <b><i>Bla?t</i></b> would match <b><i>Blast</i></b>).<br>" +
+ " 2) Enclose the <b><i>\"search query\"</i></b> in double quotes to make exact phrase matching, otherwise<br>" +
+ " items that contain any (or all) words in the <b><i>search query</i></b> will be found.</html>");
+
+ this.tfSearchQuery.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ tfSearchQuery.selectAll();
+ }
+ public void focusLost(FocusEvent e) { /* do nothing */ }
+ });
+ this.tfSearchQuery.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ // ENTER pressed - start search by simulating "search" button click
+ // (only do this if the "search" button was active at that moment)
+ if (e.getKeyCode() == KeyEvent.VK_ENTER && bSearch.isEnabled()) {
+ bSearch.doClick();
+ }
+ }
+ });
+ JButton jbClearSearch = new DeselectingButton(new AbstractAction("Clear") {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ tfSearchQuery.setText("");
+ clearSearch();
+ }}, "");
+ jbClearSearch.setIcon(ResourceManager.getImageIcon(ResourceManager.CLEAR_ICON));
+
+ this.add(jbClearSearch, c);
+
+ c.gridx++;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 0.1;
+ this.add(tfSearchQuery, c);
+
+
+ // --- Search button ---
+
+ c.gridx++;
+ c.weightx = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.EAST;
+ this.bSearch = new DeselectingButton("Search",
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (getSearchQuery().length() == 0) {
+ clearSearch();
+ }
+ else {
+ // search query available - collect data about the current search and execute it
+ tabbedSearchResultsPanel.startNewSearch(thisPanel.getState());
+ }
+ }
+ },
+ tfSearchQuery.getToolTipText());
+ this.bSearch.setIcon(ResourceManager.getImageIcon(ResourceManager.SEARCH_ICON));
+ this.add(bSearch, c);
+
+}
+
+ private void clearSearch() {
+ tabbedSearchResultsPanel.clearSearch();
+ thisPanel.focusDefaultComponent();
+ }
+
+ /**
+ * Saves the current state of the search options into a single {@link SearchOptions} object.
+ */
+ public SearchOptions getState() {
+ return (new SearchOptions(getSearchQuery(), Arrays.asList(TYPE.values())));
+ }
+
+
+ // *** GETTERS AND SETTERS ***
+
+ public String getSearchQuery() {
+ return (this.tfSearchQuery.getText().trim());
+ }
+ public void setSearchQuery(String strSearchQuery) {
+ this.tfSearchQuery.setText(strSearchQuery);
+ }
+
+
+ // *** Callbacks for HasDefaultFocusCapability interface ***
+
+ public void focusDefaultComponent() {
+ this.tfSearchQuery.selectAll();
+ this.tfSearchQuery.requestFocusInWindow();
+ }
+
+ public Component getDefaultComponent() {
+ return(this.tfSearchQuery);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/FilterTreeNode.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/FilterTreeNode.java
new file mode 100644
index 0000000..fedd553
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/FilterTreeNode.java
@@ -0,0 +1,91 @@
+package net.sf.taverna.biocatalogue.ui.filtertree;
+
+import net.sf.taverna.biocatalogue.ui.tristatetree.TriStateTreeNode;
+
+/**
+ * This class allows storing two pieces of data relevant to content filtering
+ * within the node of a tree. These values are kept hidden from the user and
+ * are only used when the filtering is about to happen.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class FilterTreeNode extends TriStateTreeNode
+{
+ private String type;
+ private String urlValue;
+ final private boolean isFilterCategory;
+
+
+ /**
+ * This constructor is useful for root nodes, which need not have filter type / value.
+ */
+ public FilterTreeNode(Object userObject) {
+ super(userObject);
+
+ this.isFilterCategory = true;
+ }
+
+
+ /**
+ * @param userObject As in the superclass (DefaultMutableTreeNode) - the object which represents the node in the UI
+ * @param filterType Type of the filter - e.g. 'Service Categories' --> "cat"; 'Service Types' --> "t"
+ * @param filterUrlValue Value that should be added to the URL to perform the filtering operation
+ */
+ public FilterTreeNode(Object userObject, String filterType, String filterUrlValue) {
+ super(userObject);
+
+ this.setType(filterType);
+ this.setUrlValue(filterUrlValue);
+ this.isFilterCategory = false;
+ }
+
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setUrlValue(String urlValue) {
+ this.urlValue = urlValue;
+ }
+
+
+ public String getUrlValue() {
+ return urlValue;
+ }
+
+ /**
+ * @return True if and only if this node is one of the "root" filter categories (not to be mixed with root of the filter tree).
+ */
+ public boolean isFilterCategory() {
+ return isFilterCategory;
+ }
+
+
+ /**
+ * @return <code>true</code> if the current {@link FilterTreeNode} represents a tag with a namespace
+ * (i.e. an ontological term), whose full tag name looks like:
+ * <code>< http://example.namespace.com#tag_display_name ></code>
+ */
+ public boolean isTagWithNamespaceNode() {
+ return (this.getType() != null && this.getType().contains("tag") && this.getUrlValue().contains("#") &&
+ this.getUrlValue().startsWith("<") && this.getUrlValue().endsWith(">"));
+ }
+
+
+ /**
+ * Static wrapper for {@link FilterTreeNode#isTagWithNamespaceNode()}
+ *
+ * @param filterType
+ * @param filterUrlValue
+ * @return
+ */
+ public static boolean isTagWithNamespaceNode(String filterType, String filterUrlValue) {
+ return (new FilterTreeNode("test_user_object", filterType, filterUrlValue).isTagWithNamespaceNode());
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/FilterTreePane.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/FilterTreePane.java
new file mode 100644
index 0000000..75a80ea
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/FilterTreePane.java
@@ -0,0 +1,348 @@
+package net.sf.taverna.biocatalogue.ui.filtertree;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.model.search.ServiceFilteringSettings;
+import net.sf.taverna.biocatalogue.ui.tristatetree.JTriStateTree;
+import net.sf.taverna.biocatalogue.ui.tristatetree.TriStateTreeCheckingListener;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.log4j.Logger;
+
+import org.biocatalogue.x2009.xml.rest.Filter;
+import org.biocatalogue.x2009.xml.rest.FilterGroup;
+import org.biocatalogue.x2009.xml.rest.FilterType;
+import org.biocatalogue.x2009.xml.rest.Filters;
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class FilterTreePane extends JPanel implements TriStateTreeCheckingListener
+{
+ private TYPE resourceType;
+ private String filtersURL;
+ private BioCatalogueClient client;
+ private Logger logger;
+
+ private FilterTreePane thisPanel;
+
+ private JToolBar tbFilterTreeToolbar;
+
+ private JPanel jpFilters = null;
+ private JFilterTree filterTree; // tree component to display filter selections
+ private Filters filtersRoot; // last filters element which was received from the API
+
+
+
+ public FilterTreePane(TYPE resourceType)
+ {
+ this.thisPanel = this;
+
+ this.resourceType = resourceType;
+ this.filtersURL = resourceType.getAPIResourceCollectionFiltersURL();
+ this.client = BioCatalogueClient.getInstance();
+ this.logger = Logger.getLogger(this.getClass());
+
+ initialiseUI();
+ loadFiltersAndBuildTheTree();
+ }
+
+
+ private void initialiseUI()
+ {
+ jpFilters = new JPanel();
+ jpFilters.setBackground(Color.WHITE);
+
+ JScrollPane spFilters = new JScrollPane(jpFilters);
+ spFilters.setMinimumSize(new Dimension(235,0));
+ spFilters.setPreferredSize(new Dimension(300,0));
+ spFilters.getVerticalScrollBar().setUnitIncrement(BioCataloguePluginConstants.DEFAULT_SCROLL);
+
+
+ tbFilterTreeToolbar = createTreeActionToolbar();
+ resetTreeActionToolbar();
+
+ this.setLayout(new BorderLayout());
+ this.add(tbFilterTreeToolbar, BorderLayout.NORTH);
+ this.add(spFilters, BorderLayout.CENTER);
+ }
+
+
+ /**
+ * @return A toolbar that replicates all actions available in the contextual menu of
+ * the filtering tree - mainly: saving current filter, reloading filter tree,
+ * expanding/collapsing and selecting/deselecting everything in the tree.
+ */
+private JToolBar createTreeActionToolbar()
+ {
+
+
+ // the actual toolbar - no actions are added to it yet: done in a separate method
+ JToolBar tbTreeActions = new JToolBar(JToolBar.HORIZONTAL);
+ tbTreeActions.setAlignmentX(RIGHT_ALIGNMENT);
+ tbTreeActions.setBorderPainted(true);
+ tbTreeActions.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ tbTreeActions.setFloatable(false);
+ return (tbTreeActions);
+ }
+
+
+ /**
+ * Resets the action toolbar to the original state.
+ */
+ public void resetTreeActionToolbar()
+ {
+
+ tbFilterTreeToolbar.removeAll();
+ tbFilterTreeToolbar.repaint();
+ }
+
+
+ /**
+ * This method loads filter data from API and populates the view.
+ */
+ private void loadFiltersAndBuildTheTree()
+ {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run()
+ {
+ resetTreeActionToolbar();
+
+ jpFilters.removeAll();
+ jpFilters.setLayout(new BorderLayout());
+ jpFilters.add(new JLabel(" Loading filters..."), BorderLayout.NORTH);
+ jpFilters.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE)), BorderLayout.CENTER);
+ thisPanel.validate();
+ thisPanel.repaint(); // validate and repaint this component to make sure that
+ // scroll bar around the filter tree placeholder panel disappears
+ }
+ });
+
+ new Thread("Load filters") {
+ public void run() {
+ try {
+ // load filter data
+ filtersRoot = client.getBioCatalogueFilters(filtersURL);
+
+ // Create root of the filter tree component
+ FilterTreeNode root = new FilterTreeNode("root");
+
+ // populate the tree via its root element
+ for (FilterGroup fgroup : filtersRoot.getGroupList())
+ {
+ // attach filter group directly to the root node
+ FilterTreeNode fgroupNode = new FilterTreeNode("<html><span style=\"color: black; font-weight: bold;\">" + StringEscapeUtils.escapeHtml(fgroup.getName().toString()) + "</span></html>");
+ root.add(fgroupNode);
+
+
+ // go through all filter types in this group and add them to the tree
+ for (FilterType ftype : fgroup.getTypeList())
+ {
+ // if there's more than one filter type in the group, add the type node as another level of nesting
+ // (otherwise, attach filters inside the single type directly to the group node)
+ FilterTreeNode filterTypeNode = fgroupNode;
+ if (fgroup.getTypeList().size() > 1) {
+ filterTypeNode = new FilterTreeNode("<html><span style=\"color: black; font-weight: bold;\">" + StringEscapeUtils.escapeHtml(ftype.getName().toString()) + "</span></html>");
+ fgroupNode.add(filterTypeNode);
+ }
+
+ // For some reason sorting the list of filters before inserting into tree
+ // messes up the tree nodes
+// Collections.sort(ftype.getFilterList(), new Comparator<Filter>(){
+// @Override
+// public int compare(Filter f1, Filter f2) {
+// return (f1.getName().compareToIgnoreCase(f2.getName()));
+// }
+// });
+ addFilterChildren(filterTypeNode, ftype.getUrlKey().toString(), ftype.getFilterList());
+ }
+ }
+
+ // Create the tree view with the populated root
+ filterTree = new JFilterTree(root);
+ filterTree.setRootVisible(false); // don't want the root to be visible; not a standard thing, so not implemented within JTriStateTree
+ filterTree.setLargeModel(true); // potentially can have many filters!
+ filterTree.addCheckingListener(thisPanel);
+
+
+ // insert the created tree view into the filters panel
+ jpFilters.removeAll();
+ jpFilters.setLayout(new GridLayout(0,1));
+ jpFilters.add(filterTree);
+ jpFilters.validate();
+
+
+ // add actions from the contextual menu of the filter tree into the toolbar
+ // that replicates those plus adds additional ones in this panel
+ tbFilterTreeToolbar.removeAll();
+ for (Action a : filterTree.getContextualMenuActions()) {
+ tbFilterTreeToolbar.add(a);
+ }
+
+
+ // enable all actions
+ filterTree.enableAllContextualMenuAction(true);
+ }
+ catch (Exception e) {
+ logger.error("Failed to load filter tree from the following URL: " + filtersURL, e);
+ }
+ }
+
+
+ /**
+ * Recursive method to populate a node of the filter tree with all
+ * sub-filters.
+ *
+ * Ontological terms will be underlined.
+ *
+ * @param root Tree node to add children to.
+ * @param filterList A list of Filters to add to "root" as children.
+ */
+ private void addFilterChildren(FilterTreeNode root, String filterCategory, List<Filter> filterList) {
+ for (Filter f : filterList) {
+
+ // Is this an ontological term?
+ String ontology = null;
+ if (FilterTreeNode.isTagWithNamespaceNode(filterCategory, f
+ .getUrlValue())) {
+ String nameAndNamespace = f.getUrlValue().substring(1,
+ f.getUrlValue().length() - 1);
+ String[] namePlusNamespace = nameAndNamespace
+ .split("#");
+ ontology = JFilterTree
+ .getOntologyFromNamespace(namePlusNamespace[0]);
+ }
+
+ FilterTreeNode fNode = new FilterTreeNode("<html><span color=\"black\"" /*(FilterTreeNode.isTagWithNamespaceNode(filterCategory, f.getUrlValue()) ? " style=\"text-decoration: underline;\"" : "") */ + ">" +
+ StringEscapeUtils.escapeHtml(f.getName()) + " (" + f.getCount() + ")" + "</span>" +
+ /*(FilterTreeNode.isTagWithNamespaceNode(filterCategory, f.getUrlValue()) ? "<span color=\"gray\"> ("+f.getCount().intValue()+")</span></html>" : "</html>"),*/
+ (ontology != null ? "<span color=\"#3090C7\"> <"+ ontology +"></span></html>" : "</html>"),
+ filterCategory, f.getUrlValue());
+ addFilterChildren(fNode, filterCategory, f.getFilterList());
+
+ // Insert the node into the (alphabetically) sorted children nodes
+ List<FilterTreeNode> children = Collections.list(root.children());
+ // Search for the index the new node should be inserted at
+ int index = Collections.binarySearch(children, fNode,
+ new Comparator<FilterTreeNode>() {
+ @Override
+ public int compare(FilterTreeNode o1,
+ FilterTreeNode o2) {
+ String str1 = ((String) o1.getUserObject())
+ .toString();
+ String str2 = ((String) o2.getUserObject())
+ .toString();
+ return (str1.compareToIgnoreCase(str2));
+ }
+ });
+
+ if (index < 0){ // not found - index will be equal to -insertion-point -1
+ index = -index - 1;
+ }// else node with the same name found in the array - insert it at that position
+ root.insert(fNode, index);
+
+ //root.add(fNode);
+ }
+ }
+ }.start();
+ }
+
+
+ /**
+ * @param si Uses this SearchInstance to restore the checking
+ * state of filtering criteria in the filter tree.
+ */
+ public void restoreFilteringSettings(SearchInstance si) {
+ this.filterTree.restoreFilterCheckingSettings(si.getFilteringSettings().getFilterTreeRootsOfCheckedPaths());
+ }
+
+
+ /**
+ * Clears any selections made in the filter tree -
+ * i.e. both clears checked nodes and removes all tree path selections.
+ */
+ public void clearSelection() {
+ // filter tree may not have been initialised yet, so perform a check
+ if (this.filterTree != null)
+ {
+ // remove, then restore self as a listener - this is to avoid
+ // receiving checking state change event
+ this.filterTree.removeCheckingListener(thisPanel);
+ this.filterTree.selectAllNodes(false);
+ this.filterTree.clearSelection();
+ this.filterTree.addCheckingListener(thisPanel);
+ }
+ }
+
+
+ /**
+ * Collapses all expanded nodes in the filter tree.
+ */
+ public void collapseAll() {
+ // filter tree may not have been initialised yet, so perform a check
+ if (this.filterTree != null) {
+ this.filterTree.collapseAll();
+ }
+ }
+
+ public void applyQueryString(final String queryString) {
+ this.filtersURL = resourceType.getAPIResourceCollectionFiltersURL() + "?q=" + queryString;
+ loadFiltersAndBuildTheTree();
+ }
+
+ /**
+ * Used for making preferred height of the search status label
+ * the same as the height of this toolbar.
+ *
+ * @return
+ */
+ public Dimension getTreeToolbarPreferredSize() {
+ return this.tbFilterTreeToolbar.getPreferredSize();
+ }
+
+
+ // *** Callback for TriStateTreeCheckingListener ***
+
+ /**
+ * We start a new search as soon as checking state of the filter tree changes.
+ */
+ public void triStateTreeCheckingChanged(JTriStateTree source)
+ {
+ MainComponentFactory.getSharedInstance().getBioCatalogueExplorationTab().getTabbedSearchResultsPanel().
+ startNewFiltering(resourceType, new ServiceFilteringSettings(filterTree));
+ }
+
+
+public void reset() {
+ this.filtersURL = resourceType.getAPIResourceCollectionFiltersURL();
+ loadFiltersAndBuildTheTree();
+}
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/JFilterTree.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/JFilterTree.java
new file mode 100644
index 0000000..a6cc111
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/filtertree/JFilterTree.java
@@ -0,0 +1,69 @@
+package net.sf.taverna.biocatalogue.ui.filtertree;
+
+import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.biocatalogue.ui.tristatetree.JTriStateTree;
+import net.sf.taverna.biocatalogue.ui.tristatetree.TriStateTreeNode;
+
+/**
+ * This subclass of {@link JTriStateTree} provides custom behaviour
+ * for tooltips: ontological terms will now always get a tooltip that
+ * displays the namespace for the tag, but plain text tags will still
+ * behave as before - the way it is defined in the superclass (so that
+ * the tooltip will only be shown if the tag does not fully fit into
+ * the visible part of the {@link FilterTreePane}.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class JFilterTree extends JTriStateTree
+{
+
+ private static Map<String, String> nameSpaceToOntologyMap = new HashMap<String, String>(){
+ {
+ put("http://www.mygrid.org.uk/ontology", "mygrid-domain-ontology");
+ put("http://www.mygrid.org.uk/mygrid-moby-service", "mygrid-service-ontology");
+ }
+ };
+
+
+ public JFilterTree(TriStateTreeNode root) {
+ super(root);
+ }
+
+
+ public String getToolTipText(MouseEvent e)
+ {
+ Object correspondingObject = super.getTreeNodeObject(e);
+ if (correspondingObject != null && correspondingObject instanceof FilterTreeNode) {
+ FilterTreeNode filterNode = (FilterTreeNode) correspondingObject;
+
+ if (filterNode.isTagWithNamespaceNode())
+ {
+ String nameAndNamespace = filterNode.getUrlValue().substring(1, filterNode.getUrlValue().length() - 1);
+ String[] namePlusNamespace = nameAndNamespace.split("#");
+
+ return ("<html>" + namePlusNamespace[1] + " (<b>Namespace: </b>" + namePlusNamespace[0] + ")</html>");
+ }
+ }
+
+ return super.getToolTipText(e);
+ }
+
+ public static String getOntologyFromNamespace(String namespace){
+ if (namespace == null){
+ return null;
+ }
+ else{
+ if (nameSpaceToOntologyMap.containsKey(namespace)){
+ return nameSpaceToOntologyMap.get(namespace);
+ }
+ else{
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
new file mode 100644
index 0000000..a223fc8
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
@@ -0,0 +1,220 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public abstract class ExpandableOnDemandLoadedListCellRenderer extends JPanel implements ListCellRenderer
+{
+ protected static final int DESCRIPTION_MAX_LENGTH_COLLAPSED = 90;
+ protected static final int DESCRIPTION_MAX_LENGTH_EXPANDED = 500;
+
+ protected static final int LINE_LENGTH = 90;
+
+
+ protected static final int TOOLTIP_DESCRIPTION_LENGTH = 150;
+ protected static final int TOOLTIP_LINE_LENGTH = 60;
+
+ // list cells are not repainted by Swing by default - hence to use animated GIFs inside cells,
+ // need to have a special class that takes care of changing the frames as necessary
+ protected JLabel loaderBarAnimationOrange = new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE), JLabel.CENTER);
+ protected JLabel loaderBarAnimationGrey = new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_GREY), JLabel.CENTER);
+ protected JLabel loaderBarAnimationGreyStill = new JLabel (ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_GREY_STILL), JLabel.CENTER);
+
+
+ protected JPanel thisPanel;
+ private List<Class<? extends ResourceLink>> resourceClasses;
+
+
+ protected JLabel jlExpand;
+ protected static Rectangle expandRect;
+
+ public ExpandableOnDemandLoadedListCellRenderer()
+ {
+ this.thisPanel = this;
+
+ resourceClasses = new ArrayList<Class<? extends ResourceLink>>();
+ try {
+ for (Resource.TYPE resourceType : Resource.TYPE.values()) {
+ resourceClasses.add(resourceType.getXmlBeansGeneratedClass());
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
+ public static Rectangle getExpandRect() {
+ return (expandRect == null ? new Rectangle() : expandRect);
+ }
+
+
+ public Component getListCellRendererComponent(JList list, Object itemToRender, int itemIndex, boolean isSelected, boolean cellHasFocus)
+ {
+ // the same instance of the cell renderer is used for all cells, so
+ // need to remove everything from the current panel to ensure clean
+ // painting of the current cell
+ this.removeAll();
+
+ // GET THE DATA
+
+ // LoadingResource is a placeholder for the detailed data on the resource --
+ // it is being quickly fetched from the API and contanins just the name and the URL
+ // of the actual resource;
+ //
+ // these entries will be placed into the list when the initial part of the search
+ // is complete, further details will be loaded asynchronously and inserted into
+ // the same area
+ if (itemToRender instanceof LoadingResource) {
+ prepareInitiallyLoadingEntry(itemToRender);
+ }
+
+ // real data about some resource: details, but in the collapsed form
+ else if (isInstanceOfResourceType(itemToRender)) {
+ prepareLoadedEntry(itemToRender, isSelected);
+ }
+
+ // error case - unknown resource...
+ else {
+ prepareUnknownResourceTypeEntry();
+ }
+
+
+ // MAKE SURE CELL SELECTION WORKS AS DESIRED
+ if (shouldBeHidden(itemToRender)) {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+ BorderFactory.createLineBorder(Color.DARK_GRAY)));
+ setBackground(list.getBackground());
+ setForeground(list.getBackground());
+ }
+ else if (isSelected) {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+ BorderFactory.createLineBorder(Color.DARK_GRAY)));
+ setBackground(Color.decode("#BAE8FF")); // very light blue colour
+ setForeground(list.getSelectionForeground());
+ } else {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+ BorderFactory.createLineBorder(Color.DARK_GRAY)));
+ setBackground(Color.WHITE);
+ setForeground(list.getForeground());
+ }
+
+ this.revalidate();
+
+ if (expandRect == null && jlExpand != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ expandRect = jlExpand.getBounds();
+ expandRect.x -= Math.abs(thisPanel.getBounds().x);
+ }
+ });
+ }
+
+ return (this);
+ }
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected abstract GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender);
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param isSelected
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected abstract GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean isSelected);
+
+
+ private void prepareUnknownResourceTypeEntry()
+ {
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(8, 6, 6, 3);
+ this.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.UNKNOWN_RESOURCE_TYPE_ICON)), c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(8, 3, 6, 3);
+ this.add(new JLabel("<html><font color=\"#FF0000\">ERROR: This item shoulnd't have been here...</font></html>"), c);
+
+ c.gridx = 1;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 1.0;
+ c.weighty = 0;
+ c.insets = new Insets(3, 3, 3, 3);
+ this.add(new JLabel(" "), c);
+
+ c.gridy++;
+ c.insets = new Insets(3, 3, 8, 3);
+ this.add(new JLabel(" "), c);
+ }
+
+
+ private boolean isInstanceOfResourceType(Object itemToRender)
+ {
+ for (Class<? extends ResourceLink> resourceClass : resourceClasses) {
+ if (resourceClass.isInstance(itemToRender)) {
+ return (true);
+ }
+ }
+
+ return (false);
+ }
+
+ protected TYPE determineResourceType(Object itemToRender) {
+ if (itemToRender instanceof ResourceLink) {
+ return (Resource.getResourceTypeFromResourceURL(((ResourceLink)itemToRender).getHref()));
+ }
+ else {
+ return (null);
+ }
+ }
+
+ abstract boolean shouldBeHidden(Object itemToRender);
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
new file mode 100644
index 0000000..64bdfbf
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
@@ -0,0 +1,248 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JLabel;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestParameter;
+import org.biocatalogue.x2009.xml.rest.RestRepresentation;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.RestMethod.Ancestors;
+
+
+/**
+ *
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class RESTMethodListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+ private JLabel jlTypeIcon = new JLabel();
+ private JLabel jlItemStatus = new JLabel();
+ private JLabel jlItemTitle = new JLabel("X");
+ private JLabel jlPartOf = new JLabel("X");
+ private ReadOnlyTextArea jtDescription = new ReadOnlyTextArea(5, 80);
+ private JLabel jlMethodType = new JLabel("X");
+ private JLabel jlUrlTemplate = new JLabel("X");
+ private JLabel jlMethodParameters = new JLabel("X");
+ private JLabel jlInputRepresentations = new JLabel("X");
+ private JLabel jlOutputRepresentations = new JLabel("X");
+
+ private GridBagConstraints c;
+
+ private static Resource.TYPE resourceType = Resource.TYPE.RESTMethod;
+
+
+
+ public RESTMethodListCellRenderer() {
+ jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+ jtDescription.setOpaque(false);
+ jtDescription.setLineWrap(true);
+ jtDescription.setWrapStyleWord(true);
+ }
+
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender)
+ {
+ LoadingResource resource = (LoadingResource)itemToRender;
+
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ jlItemStatus.setIcon(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+
+ jlItemTitle.setText("<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(resource)) + "<font color=\"gray\"><i>- fetching more information</i></font></html>");
+
+ jlPartOf.setText("");
+ jtDescription.setText(" ");
+ jlMethodType.setText(" ");
+ jlUrlTemplate.setText(" ");
+ jlMethodParameters.setText(" ");
+ jlInputRepresentations.setText(" ");
+ jlOutputRepresentations.setText(" ");
+
+ return (arrangeLayout());
+ }
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean selected)
+ {
+ RestMethod restMethod = (RestMethod)itemToRender;;
+
+ Ancestors ancestors = restMethod.getAncestors();
+ Service service = ancestors.getService();
+ String title = "<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(restMethod));
+
+ if (restMethod.isSetArchived() || service.isSetArchived()) {
+ jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+ title = title + "<i> - this operation is archived and probably cannot be used</i></html>";
+ }
+ else {
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ title = title + "</html>";
+ }
+
+ // service status
+ jlItemStatus.setIcon(ServiceMonitoringStatusInterpreter.getStatusIcon(service, false));
+ jlItemTitle.setText(title);
+
+ jlPartOf.setText("<html><b>Part of: </b>" + restMethod.getAncestors().getRestService().getResourceName() + "</html>");
+
+ String strDescription = (restMethod.getDescription() == null || restMethod.getDescription().length() == 0 ?
+ "No description" :
+ Util.stripAllHTML(restMethod.getDescription()));
+ jtDescription.setText(strDescription);
+
+ jlMethodType.setText("<html><b>HTTP Method: </b>" + StringEscapeUtils.escapeHtml(restMethod.getHttpMethodType().toString()) + "</html>");
+ jlUrlTemplate.setText("<html><b>URL Template: </b>" + StringEscapeUtils.escapeHtml(restMethod.getUrlTemplate()) + "</html>");
+
+ List<String> names = new ArrayList<String>();
+ for (RestParameter restParameter : restMethod.getInputs().getParameters().getRestParameterList()) {
+ names.add(restParameter.getName() + (restParameter.getIsOptional() ? " (optional)" : ""));
+ }
+
+ String methodParameters = "<b>" + names.size() + " " + Util.pluraliseNoun("Parameter", names.size()) + "</b>";
+ if(names.size() > 0) {
+ methodParameters += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ methodParameters = "<html>" + methodParameters + "</html>";
+ jlMethodParameters.setText(methodParameters);
+
+ names.clear();
+ for (RestRepresentation restRepresentation : restMethod.getInputs().getRepresentations().getRestRepresentationList()) {
+ names.add(restRepresentation.getContentType());
+ }
+
+ String inputRepresentations = "<b>" + names.size() + " " + Util.pluraliseNoun("Input representation", names.size()) + "</b>";
+ if(names.size() > 0) {
+ inputRepresentations += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ inputRepresentations = "<html>" + inputRepresentations + "</html>";
+
+ jlInputRepresentations.setText(inputRepresentations);
+
+ // output representations
+ names.clear();
+ for (RestRepresentation restRepresentation : restMethod.getOutputs().getRepresentations().getRestRepresentationList()) {
+ names.add(restRepresentation.getContentType());
+ }
+
+ String outputRepresentations = "<b>" + names.size() + " " + Util.pluraliseNoun("Output representation", names.size()) + "</b>";
+ if(names.size() > 0) {
+ outputRepresentations += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ outputRepresentations = "<html>" + outputRepresentations + "</html>";
+
+ jlOutputRepresentations.setText(outputRepresentations);
+
+ return (arrangeLayout());
+ }
+
+
+ /**
+ * @return Final state of the {@link GridBagConstraints} instance
+ * that was used to lay out components in the panel.
+ */
+ private GridBagConstraints arrangeLayout()
+ {
+ // POPULATE PANEL WITH PREPARED COMPONENTS
+ this.setLayout(new GridBagLayout());
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(8, 6, 6, 3);
+ this.add(jlTypeIcon, c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(8, 3, 6, 3);
+ this.add(jlItemTitle, c);
+
+ c.gridx++;
+ c.gridheight = 8;
+ c.weightx = 0;
+ c.weighty = 1.0;
+ this.add(jlItemStatus, c);
+
+ c.gridx = 1;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 1.0;
+ c.weighty = 0;
+ this.add(jlPartOf, c);
+
+ c.fill = GridBagConstraints.NONE;
+ c.gridy++;
+ this.add(jtDescription, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy++;
+ this.add(jlMethodType, c);
+
+ c.gridy++;
+ this.add(jlUrlTemplate, c);
+
+ c.gridy++;
+ this.add(jlMethodParameters, c);
+
+ c.gridy++;
+ this.add(jlInputRepresentations, c);
+
+ c.gridy++;
+ this.add(jlOutputRepresentations, c);
+ return (c);
+ }
+
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+ if (!(itemToRender instanceof RestMethod)) {
+ return false;
+ }
+ RestMethod restMethod = (RestMethod)itemToRender;;
+
+ Ancestors ancestors = restMethod.getAncestors();
+ Service service = ancestors.getService();
+ String title = Resource.getDisplayNameForResource(restMethod);
+
+ if (restMethod.isSetArchived() || service.isSetArchived()) {
+ return true;
+ }
+ else {
+ return false;
+ }
+
+}
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
new file mode 100644
index 0000000..77939c7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
@@ -0,0 +1,257 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+import net.sf.taverna.biocatalogue.model.LoadingExpandedResource;
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+import org.biocatalogue.x2009.xml.rest.SoapInput;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapOutput;
+import org.biocatalogue.x2009.xml.rest.SoapService;
+import org.biocatalogue.x2009.xml.rest.Service.ServiceTechnologyTypes;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType.Enum;
+import org.biocatalogue.x2009.xml.rest.SoapOperation.Ancestors;
+
+
+/**
+ *
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SOAPOperationListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+
+ private JLabel jlTypeIcon = new JLabel();
+ private JLabel jlItemStatus = new JLabel();
+ private JLabel jlItemTitle = new JLabel("X");
+ private JLabel jlPartOf = new JLabel("X");
+ private JLabel jlWsdlLocation = new JLabel("X");
+ private ReadOnlyTextArea jtDescription = new ReadOnlyTextArea(5,80);
+ private JLabel jlSoapInputs = new JLabel("X");
+ private JLabel jlSoapOutputs = new JLabel("X");
+
+ private GridBagConstraints c;
+
+ private static Resource.TYPE resourceType = Resource.TYPE.SOAPOperation;
+
+
+ public SOAPOperationListCellRenderer() {
+ jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+ jtDescription.setOpaque(false);
+ jtDescription.setLineWrap(true);
+ jtDescription.setWrapStyleWord(true);
+ }
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender)
+ {
+ LoadingResource resource = (LoadingResource)itemToRender;
+
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ jlItemStatus.setIcon(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+
+ jlItemTitle.setText("<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(resource)) + "<font color=\"gray\"><i>- fetching more information</i></font></html>");
+
+ jlPartOf.setText(" ");
+ jlWsdlLocation.setText(" ");
+ jtDescription.setText("");
+ jlSoapInputs.setText(" ");
+ jlSoapOutputs.setText(" ");
+
+ return (arrangeLayout());
+ }
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param selected
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean selected)
+ {
+ SoapOperation soapOp = (SoapOperation)itemToRender;
+
+ Ancestors ancestors = soapOp.getAncestors();
+ SoapService soapService = ancestors.getSoapService();
+ Service service = ancestors.getService();
+ String title = StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(soapOp));
+
+ if (soapOp.isSetArchived() || service.isSetArchived()) {
+ jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+ title = "<html>" + title + "<i> - this operation is archived and probably cannot be used</i></html>";
+ } else if (isSoapLab(service)) {
+ jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+ title = "<html>" + title + "<i> - this operation can only be used as part of a SoapLab service</i></html>";
+ }
+ else {
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ title = "<html>" + title + "</html>";
+ }
+
+ // service status
+ jlItemStatus.setIcon(ServiceMonitoringStatusInterpreter.getStatusIcon(service, false));
+ jlItemTitle.setText(title);
+
+ jlPartOf.setText("<html><b>Part of: </b>" + StringEscapeUtils.escapeHtml(soapOp.getAncestors().getSoapService().getResourceName()) + "</html>");
+
+ jlWsdlLocation.setText("<html><b>WSDL location: </b>" + soapService.getWsdlLocation() + "</html>");
+
+ String strDescription = (soapOp.getDescription() == null || soapOp.getDescription().length() == 0 ?
+ "No description" :
+ Util.stripAllHTML(soapOp.getDescription()));
+
+ jtDescription.setText(strDescription);
+
+ // add SOAP inputs
+ List<String> names = new ArrayList<String>();
+ for (SoapInput soapInput : soapOp.getInputs().getSoapInputList()) {
+ names.add(soapInput.getName());
+ }
+
+ String soapInputs = "<b>" + names.size() + " " + Util.pluraliseNoun("Input", names.size()) + "</b>";
+ if(names.size() > 0) {
+ soapInputs += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ soapInputs = "<html>" + soapInputs + "</html>";
+ jlSoapInputs.setText(soapInputs);
+
+ c.gridy++;
+ this.add(jlSoapInputs, c);
+
+
+ // add SOAP outputs
+ names.clear();
+ for (SoapOutput soapOutput : soapOp.getOutputs().getSoapOutputList()) {
+ names.add(soapOutput.getName());
+ }
+
+ String soapOutputs = "<b>" + names.size() + " " + Util.pluraliseNoun("Output", names.size()) + "</b>";
+ if(names.size() > 0) {
+ soapOutputs += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ soapOutputs = "<html>" + soapOutputs + "</html>";
+ jlSoapOutputs.setText(soapOutputs);
+
+ return (arrangeLayout());
+ }
+
+
+private boolean isSoapLab(Service service) {
+ boolean result = false;
+ ServiceTechnologyTypes serviceTechnologyTypes = service.getServiceTechnologyTypes();
+ if (serviceTechnologyTypes == null) {
+ return result;
+ }
+ List<Enum> typeList = serviceTechnologyTypes.getTypeList();
+ if (typeList == null) {
+ return result;
+ }
+ result = typeList.contains(ServiceTechnologyType.SOAPLAB);
+ return result;
+}
+
+
+ /**
+ * @return Final state of the {@link GridBagConstraints} instance
+ * that was used to lay out components in the panel.
+ */
+ private GridBagConstraints arrangeLayout()
+ {
+ // POPULATE PANEL WITH PREPARED COMPONENTS
+ this.setLayout(new GridBagLayout());
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(8, 6, 6, 3);
+ this.add(jlTypeIcon, c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(8, 3, 6, 3);
+ this.add(jlItemTitle, c);
+
+ c.gridx++;
+ c.gridheight = 7;
+ c.weightx = 0;
+ c.weighty = 1.0;
+ this.add(jlItemStatus, c);
+
+ c.gridx = 1;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ this.add(jlPartOf, c);
+
+ c.gridy++;
+ this.add(jlWsdlLocation, c);
+
+ c.fill = GridBagConstraints.NONE;
+ c.gridy++;
+ this.add(jtDescription, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy++;
+ this.add(jlSoapInputs, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy++;
+ this.add(jlSoapOutputs, c);
+
+ return (c);
+ }
+
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+ if (!(itemToRender instanceof SoapOperation)) {
+ return false;
+ }
+ SoapOperation soapOp = (SoapOperation) itemToRender;
+ Ancestors ancestors = soapOp.getAncestors();
+ Service service = ancestors.getService();
+ if (soapOp.isSetArchived() || service.isSetArchived()) {
+ return true;
+ } else if (isSoapLab(service)) {
+ return true;
+ }
+ else {
+ return false;
+ }
+
+}
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
new file mode 100644
index 0000000..c88de40
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
@@ -0,0 +1,870 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.LoadingExpandedResource;
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.ui.JWaitDialog;
+import net.sf.taverna.t2.lang.ui.ModelMap;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceHealthChecker;
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.ModelMapConstants;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+
+/**
+ * This class is responsible for producing search results listing panel. It only
+ * shows a single listing for a specified type. Multiple types are handled by
+ * having different tabs in {@link SearchResultsMainPanel} with instances of
+ * this class in each.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SearchResultsListingPanel extends JPanel implements MouseListener,
+ SearchResultsRenderer, MouseMotionListener {
+ public static final int SEARCH_STATUS_TOOLTIP_LINE_LENGTH = 65;
+
+ private static final Logger logger = Logger.getLogger(SearchResultsListingPanel.class);
+ private final SearchResultsMainPanel parentMainSearchResultsPanel;
+
+ // currently displayed search results
+ SearchInstance searchInstance;
+
+ // main UI components
+ private SearchResultsListingPanel thisPanel;
+ private DefaultListModel resultsListingModel;
+ private JList jlResultsListing;
+ private JScrollPane spResultsListing;
+
+ // contextual menu
+ private JPopupMenu contextualMenu;
+ private Action addToServicePanelAction;
+ private Action addToWorkflowDiagramAction;
+ private Action openInBioCatalogueAction;
+ private Action doHealthCheckAction;
+ private Action addAllOperationsToServicePanelAction;
+
+ // search status and actions on selected items in the list
+ private JToolBar tbSelectedItemActions;
+ protected JPanel jpSearchStatus;
+ private JLabel jlSearchStatus;
+
+ // this is used for previewing items from the result listing through
+ // contextual menu -
+ // value will be updated by mouse event accordingly
+ private ResourceLink potentialObjectToPreview;
+ private final TYPE typeToPreview;
+
+ // Design perspective - some actions require switching to it
+ PerspectiveSPI designPerspective;
+
+ private ListCellRenderer listingCellRenderer;
+
+ /**
+ * @param typeToPreview
+ * Resource type that will be previewed in this panel.
+ * @param parentMainSearchResultsPanel
+ * Reference to a "parent" of this panel - this is needed to
+ * notify the main results panel with the
+ */
+ public SearchResultsListingPanel(TYPE typeToPreview,
+ SearchResultsMainPanel parentMainSearchResultsPanel) {
+ this.thisPanel = this;
+
+ this.typeToPreview = typeToPreview;
+ listingCellRenderer = this.typeToPreview
+ .getResultListingCellRenderer();
+ this.parentMainSearchResultsPanel = parentMainSearchResultsPanel;
+ MainComponentFactory
+ .getSharedInstance();
+
+ initialiseUI();
+
+ this.setPreferredSize(new Dimension(800, 400));
+ }
+
+ private void initialiseUI() {
+
+ this.addToServicePanelAction = new AbstractAction(
+ "Add to Service Panel",
+ ResourceManager
+ .getImageIcon(ResourceManager.ADD_PROCESSOR_AS_FAVOURITE_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "Add selected "
+ + typeToPreview.getTypeName()
+ + " to the Service Panel");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final JWaitDialog jwd = new JWaitDialog(
+ MainComponent.dummyOwnerJFrame,
+ "Service Catalogue Plugin - Adding "
+ + typeToPreview.getTypeName(),
+ "<html><center>Please wait for selected "
+ + typeToPreview.getTypeName()
+ + " details to be fetched from the Service Catalogue<br>"
+ + "and to be added into the Service Panel.</center></html>");
+
+ new Thread("Adding " + typeToPreview.getTypeName()
+ + " into Service Panel") {
+ public void run() {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink processorResourceToAdd = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ JComponent insertionOutcome = Integration
+ .insertProcesorIntoServicePanel(processorResourceToAdd);
+ jwd.waitFinished(insertionOutcome);
+
+ // Switch to Design Perspective
+ switchToDesignPerspective();
+ }
+ }.start();
+
+ // NB! The modal dialog window needs to be made visible after
+ // the background
+ // process (i.e. adding a processor) has already been started!
+ jwd.setVisible(true);
+ }
+ };
+
+ // For a parent Web service, action to add all operations to the Service Panel.
+ // Works for SOAP services at the moment.
+ this.addAllOperationsToServicePanelAction = new AbstractAction(
+ "Add all operations to Service Panel",
+ ResourceManager
+ .getImageIcon(ResourceManager.ADD_ALL_SERVICES_AS_FAVOURITE_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "Add all associated services to the Service Panel");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final JWaitDialog jwd = new JWaitDialog(
+ MainComponent.dummyOwnerJFrame,
+ "Service Catalogue Plugin - Adding "
+ + typeToPreview.getTypeName(),
+ "<html><center>Please wait for selected "
+ + typeToPreview.getTypeName()
+ + " details to be fetched from the Service Catalogue<br>"
+ + "and to be added into the Service Panel.</center></html>");
+
+ new Thread("Adding all operations of " + typeToPreview.getTypeName()
+ + " to the Service Panel") {
+ public void run() {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink resourceLink = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ JComponent insertionOutcome = Integration
+ .insertAllOperationsIntoServicePanel(resourceLink);
+ jwd.waitFinished(insertionOutcome);
+
+ // Switch to Design Perspective
+ switchToDesignPerspective();
+ }
+ }.start();
+
+ // NB! The modal dialog window needs to be made visible after
+ // the background
+ // process (i.e. adding a processor) has already been started!
+ jwd.setVisible(true);
+ }
+ };
+
+ this.addToWorkflowDiagramAction = new AbstractAction(
+ "Add to workflow",
+ ResourceManager
+ .getImageIcon(ResourceManager.ADD_PROCESSOR_TO_WORKFLOW_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "<html>Insert selected "
+ + typeToPreview.getTypeName()
+ + " into the current workflow</html>");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final JWaitDialog jwd = new JWaitDialog(
+ MainComponent.dummyOwnerJFrame,
+ "Service Catalogue Plugin - Adding "
+ + typeToPreview.getTypeName(),
+ "<html><center>Please wait for selected "
+ + typeToPreview.getTypeName()
+ + " details to be fetched from the Service Catalogue<br>"
+ + "and to be added into the current workflow.</center></html>");
+
+ new Thread("Adding " + typeToPreview.getTypeName()
+ + " into workflow") {
+ public void run() {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink processorResourceToAdd = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ JComponent insertionOutcome = Integration
+ .insertProcessorIntoCurrentWorkflow(processorResourceToAdd);
+ jwd.waitFinished(insertionOutcome);
+
+ // Switch to Design Perspective
+ switchToDesignPerspective();
+ }
+ }.start();
+
+ // NB! The modal dialog window needs to be made visible after
+ // the background
+ // process (i.e. adding a processor) has already been started!
+ jwd.setVisible(true);
+ }
+ };
+
+ this.openInBioCatalogueAction = new AbstractAction(
+ "Open in the Service Catalogue",
+ ResourceManager
+ .getImageIcon(ResourceManager.OPEN_IN_BIOCATALOGUE_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "<html>View selected "
+ + typeToPreview.getTypeName()
+ + " on the Service Catalogue Web site.<br>"
+ + "This will open your standard Web browser.</html>");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String hrefString = potentialObjectToPreview.getHref();
+ try {
+ Desktop.getDesktop().browse(new URI(hrefString));
+ }
+ catch (Exception ex) {
+ logger.error("Failed while trying to open the URL in a standard browser; URL was: " +
+ hrefString + "\nException was: " + ex + "\n" + ex.getStackTrace());
+ };
+ }
+ };
+
+ this.doHealthCheckAction = new AbstractAction(
+ "Check monitoring status",
+ ResourceManager
+ .getImageIcon(ResourceManager.EXECUTE_HEALTH_CHECK_ICON)) {
+ // Tooltip
+ {
+ this
+ .putValue(
+ SHORT_DESCRIPTION,
+ "<html>Fetch the latest monitoring data for selected "
+ + typeToPreview.getTypeName()
+ + ".<br>"
+ + "Data will be obtained from the Service Catalogue and displayed in a popup window.</html>");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink resourceForHealthCheck = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ ServiceHealthChecker.checkResource(resourceForHealthCheck);
+ }
+ };
+
+ tbSelectedItemActions = new JToolBar(JToolBar.HORIZONTAL);
+ tbSelectedItemActions.setBorderPainted(true);
+ tbSelectedItemActions.setBorder(BorderFactory.createEmptyBorder(5, 5,
+ 5, 3));
+ tbSelectedItemActions.setFloatable(false);
+ if (typeToPreview.isSuitableForAddingToServicePanel()) {
+ tbSelectedItemActions.add(addToServicePanelAction);
+ }
+ if (typeToPreview.isSuitableForAddingAllToServicePanel()) {
+ tbSelectedItemActions.add(addAllOperationsToServicePanelAction);
+ }
+ if (typeToPreview.isSuitableForAddingToWorkflowDiagram()) {
+ tbSelectedItemActions.add(addToWorkflowDiagramAction);
+ }
+ if (typeToPreview.isSuitableForHealthCheck()) {
+ tbSelectedItemActions.add(doHealthCheckAction);
+ }
+ tbSelectedItemActions.add(openInBioCatalogueAction);
+
+ // *** Prepare search results status panel ***
+
+ GridBagConstraints c = new GridBagConstraints();
+ jpSearchStatus = new JPanel(new GridBagLayout());
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 0;
+ jpSearchStatus.add(tbSelectedItemActions, c);
+
+ jlSearchStatus = new JLabel();
+ jlSearchStatus.setIconTextGap(20);
+ c.weightx = 1.0;
+ c.insets = new Insets(0, 20, 0, 0);
+ jpSearchStatus.add(jlSearchStatus, c);
+
+ if (parentMainSearchResultsPanel.getFilterTreePaneFor(typeToPreview) != null) {
+ Dimension preferredSize = new Dimension(200,
+ parentMainSearchResultsPanel.getFilterTreePaneFor(
+ typeToPreview).getTreeToolbarPreferredSize().height);
+
+ // HACK: due to concurrency issues, sometimes this doesn't work
+ // correctly -
+ // to rectify the problem using the hard-coded value that was
+ // correct at
+ // the time of coding...
+ if (preferredSize.height < 30) {
+ preferredSize.height = 33;
+ }
+
+ jpSearchStatus.setPreferredSize(preferredSize);
+ }
+
+ // *** Create list to hold search results and wrap it into a scroll pane
+ // ***
+ resultsListingModel = new DefaultListModel();
+ jlResultsListing = new JList(resultsListingModel);
+ jlResultsListing.setDoubleBuffered(true);
+ jlResultsListing.setCellRenderer(listingCellRenderer);
+ jlResultsListing.addMouseListener(this);
+ jlResultsListing.addMouseMotionListener(this);
+ jlResultsListing.setBackground(thisPanel.getBackground());
+
+ jlResultsListing.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ jlResultsListing.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ // update value to be used in contextual menu click handler
+ // to act on the just-selected entry
+ potentialObjectToPreview = getResourceSelectedInJList();
+
+ if (potentialObjectToPreview != null) {
+
+ // only enable actions in the menu if the list entry
+ // that is being
+ // clicked on is beyond the initial 'loading' state
+ boolean shown = !isListEntryOnlyWithInitialDetails(potentialObjectToPreview);
+ boolean shownAndNotArchived = shown && !isArchived(potentialObjectToPreview);
+ addToServicePanelAction
+ .setEnabled(shownAndNotArchived);
+ addAllOperationsToServicePanelAction
+ .setEnabled(shownAndNotArchived && !(potentialObjectToPreview instanceof RestMethod));
+ addToWorkflowDiagramAction
+ .setEnabled(shownAndNotArchived);
+ openInBioCatalogueAction
+ .setEnabled(shown);
+ doHealthCheckAction
+ .setEnabled(shown);
+
+ return;
+ }
+ }
+
+ // disable actions if nothing is selected in the list or if
+ // selection is still "adjusting"
+ addToServicePanelAction.setEnabled(false);
+ addAllOperationsToServicePanelAction.setEnabled(false);
+ addToWorkflowDiagramAction.setEnabled(false);
+ openInBioCatalogueAction.setEnabled(false);
+ doHealthCheckAction.setEnabled(false);
+ }
+ });
+
+ spResultsListing = new JScrollPane(jlResultsListing);
+ spResultsListing.getVerticalScrollBar().addAdjustmentListener(
+ new AdjustmentListener() {
+ public void adjustmentValueChanged(AdjustmentEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ // load missing details on adjusting the scroll bar
+ //
+ // only start loading more results in case if the
+ // value is "not adjusting" -
+ // this means that the mouse has been released and
+ // is not dragging the scroll bar
+ // any more, so effectively the user has stopped
+ // scrolling
+ checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+ }
+ }
+ });
+
+ // tie components to the class panel itself
+ this.resetSearchResultsListing(true);
+
+ // *** Create CONTEXTUAL MENU ***
+
+ contextualMenu = new JPopupMenu();
+ if (typeToPreview.isSuitableForAddingToServicePanel()) {
+ contextualMenu.add(addToServicePanelAction);
+ contextualMenu.add(addAllOperationsToServicePanelAction);
+ }
+ if (typeToPreview.isSuitableForAddingToWorkflowDiagram()) {
+ contextualMenu.add(addToWorkflowDiagramAction);
+ }
+ if (typeToPreview.isSuitableForHealthCheck()) {
+ contextualMenu.add(doHealthCheckAction);
+ }
+ contextualMenu.add(openInBioCatalogueAction);
+ }
+
+ /**
+ * Allows to set the search status by supplying the message to display.
+ */
+ protected void setSearchStatusText(final String statusString,
+ final boolean spinnerActive) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jlSearchStatus
+ .setIcon(spinnerActive ? ResourceManager
+ .getImageIcon(ResourceManager.BAR_LOADER_ORANGE)
+ : null);
+
+ jlSearchStatus.setText(statusString);
+ jlSearchStatus.setToolTipText("<html>"
+ + Util.ensureLineLengthWithinString(statusString,
+ SEARCH_STATUS_TOOLTIP_LINE_LENGTH, false)
+ + "</html>");
+ }
+ });
+ }
+
+ /**
+ * This helper method is used to initialise this panel. Also invoked when
+ * search results need to be cleared.
+ *
+ * @param showSuggestion
+ * <code>true</code> should be used on first load of the panel -
+ * in that case a suggestion would be displayed to perform a
+ * search, tag search or start directly with filtering;<br/>
+ * <code>false</code> to be used when resetting the panel after
+ * perfoming the search, but not finding any results.
+ */
+ public void resetSearchResultsListing(boolean showSuggestion) {
+ setSearchStatusText("No searches were made yet", false);
+
+ String labelText = "<html><center>"
+ + (showSuggestion ? "You can find "
+ + this.typeToPreview.getCollectionName()
+ + " by typing a search query."
+ + (this.typeToPreview.isSuitableForFiltering() ? "<br><br>Alternatively, you can select some filters from the tree on the left."
+ : "")
+ : "There are no "
+ + this.typeToPreview.getCollectionName()
+ + " that match your search criteria<br><br>"
+ + "Please try making the search query shorter or selecting fewer filters")
+ + "</center></html>";
+
+ JLabel jlMainLabel = new JLabel(labelText, JLabel.CENTER);
+ jlMainLabel.setFont(jlMainLabel.getFont().deriveFont(Font.PLAIN, 16));
+ jlMainLabel.setBorder(BorderFactory.createEtchedBorder());
+
+ this.removeAll();
+ this.setLayout(new BorderLayout(0, 0));
+ this.add(jpSearchStatus, BorderLayout.NORTH);
+ this.add(jlMainLabel, BorderLayout.CENTER);
+ this.validate();
+
+ // disable the toolbar actions
+ this.addToServicePanelAction.setEnabled(false);
+ this.addToWorkflowDiagramAction.setEnabled(false);
+ this.openInBioCatalogueAction.setEnabled(false);
+ this.doHealthCheckAction.setEnabled(false);
+ this.addAllOperationsToServicePanelAction.setEnabled(false);
+ }
+
+ /**
+ * Statistics will be rendered along with the collection of found items.
+ *
+ * @param searchInstance
+ * SearchInstance containing search results to render.
+ */
+ public void renderResults(SearchInstance searchInstance) {
+ // make the current search instance available globally within this class
+ this.searchInstance = searchInstance;
+
+ // stop spinner icon on the tab that is populated and add number of
+ // results
+ parentMainSearchResultsPanel.setDefaultIconForTab(typeToPreview);
+ parentMainSearchResultsPanel.setDefaultTitleForTabWithSuffix(
+ typeToPreview, " ("
+ + searchInstance.getSearchResults()
+ .getTotalMatchingItemCount() + ")");
+
+ // if nothing was found - display notification and finish result
+ // processing
+ if (searchInstance.getSearchResults().getTotalMatchingItemCount() == 0) {
+ resetSearchResultsListing(false);
+
+ // must happen after resetting the listing, as it replaces the
+ // default status text
+ setSearchStatusText("No results found for "
+ + searchInstance.getDescriptionStringForSearchStatus(),
+ false);
+ return;
+ }
+
+ // populate results
+ if (searchInstance.getSearchResults().getTotalMatchingItemCount() > 0) {
+ // populate the list box with users
+
+ List<? extends ResourceLink> foundItems = searchInstance
+ .getSearchResults().getFoundItems();
+ for (ResourceLink item : foundItems) {
+ resultsListingModel.addElement(item);
+ }
+ }
+
+ // update the UI once contents are ready
+ thisPanel.removeAll();
+ thisPanel.setLayout(new BorderLayout(0, 0));
+ thisPanel.add(jpSearchStatus, BorderLayout.NORTH);
+ thisPanel.add(spResultsListing, BorderLayout.CENTER);
+ thisPanel.repaint();
+
+ // automatically start loading details for the first section of result
+ // listing
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+ }
+ });
+
+ // *** Also update status text ***
+
+ setSearchStatusText("Search results for "
+ + searchInstance.getDescriptionStringForSearchStatus(), false);
+ }
+
+ /**
+ * Check if details are fetched for all result entries that are currently
+ * visible in the JList.
+ *
+ * If some are not yet loaded, identifies the page in the index of
+ * corresponding resources to fetch details.
+ *
+ * When done, recursively calls itself again to verify that no more entries
+ * need further details loaded.
+ */
+ private void checkAllEntriesInTheVisiblePartOfJListAreLoaded() {
+ int firstVisibleIndex = jlResultsListing.getFirstVisibleIndex();
+
+ if (firstVisibleIndex >= 0) {
+ int lastVisibleIndex = jlResultsListing.getLastVisibleIndex();
+
+ final int firstNotFetchedMatchingItemIndex = searchInstance
+ .getSearchResults().getFirstMatchingItemIndexNotYetFetched(
+ firstVisibleIndex, lastVisibleIndex);
+ final int pageToFetchNumber = searchInstance.getSearchResults()
+ .getMatchingItemPageNumberFor(
+ firstNotFetchedMatchingItemIndex);
+
+ // check if found a valid page to load
+ if (pageToFetchNumber != -1) {
+ int numberOfResourcesPerPageForThisResourceType = searchInstance
+ .getSearchResults().getTypeOfResourcesInTheResultSet()
+ .getApiResourceCountPerIndexPage();
+
+ int firstListIndexToLoad = searchInstance.getSearchResults()
+ .getFirstItemIndexOn(pageToFetchNumber); // first
+ // element
+ // on the
+ // page that
+ // is about
+ // to be
+ // loaded
+ int countToLoad = Math.min(
+ numberOfResourcesPerPageForThisResourceType, // if the
+ // last
+ // page
+ // isn't
+ // full,
+ // need
+ // to
+ // mark
+ // less
+ // items
+ // than
+ // the
+ // full
+ // page
+ resultsListingModel.getSize() - firstListIndexToLoad);
+
+ // mark the next "page" of items in the JList as "loading" -
+ // but also mark them in the SearchResults backing list, so
+ // that next calls to this listener are aware of the previous
+ // items that were marked as "loading"
+ for (int i = firstListIndexToLoad; i < firstListIndexToLoad
+ + countToLoad; i++) {
+ ((LoadingResource) searchInstance.getSearchResults()
+ .getFoundItems().get(i)).setLoading(true);
+ }
+
+ // update the UI to show 'loading' state on relevant entries
+ renderFurtherResults(searchInstance, firstListIndexToLoad,
+ countToLoad);
+
+ // now start loading data for the 'loading' entries
+ final CountDownLatch latch = new CountDownLatch(1);
+ new Thread("Search via the API") {
+ public void run() {
+ try {
+ searchInstance.fetchMoreResults(
+ parentMainSearchResultsPanel, latch,
+ thisPanel, pageToFetchNumber);
+ } catch (Exception e) {
+ logger.error("Error while searching via the Service Catalogue API", e);
+
+ }
+ }
+ }.start();
+
+ // wait for the previous portion of results to load, then fetch
+ // the next portion
+ new Thread(
+ "Fetch more another page of details for search results") {
+ public void run() {
+ try {
+ latch.await();
+ checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+ } catch (InterruptedException e) {
+ logger
+ .error(
+ "Failed to wait for the previous page of results to load to check if "
+ + "another one needs loading as well. Details in the attache exception.",
+ e);
+ }
+ }
+ }.start();
+
+ }
+ }
+ }
+
+ /**
+ * Tests whether {@link ResourceLink} object corresponding to an entry in
+ * the search results list is in the state where only the first (initial)
+ * fragment of data was loaded (through BioCatalogue LITE JSON API) that
+ * contains just the title + URL of the resource.
+ *
+ * @param resource
+ * @return
+ */
+ private boolean isListEntryOnlyWithInitialDetails(ResourceLink resource) {
+ return (resource instanceof LoadingResource);
+ }
+
+ private boolean isArchived(ResourceLink resource) {
+ if (listingCellRenderer instanceof ExpandableOnDemandLoadedListCellRenderer) {
+ ExpandableOnDemandLoadedListCellRenderer r = (ExpandableOnDemandLoadedListCellRenderer) listingCellRenderer;
+ return r.shouldBeHidden(resource);
+ }
+ return false;
+ }
+
+
+ // ***** Callbacks for MouseListener *****
+
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseEntered(MouseEvent e) { /* NOT IN USE */
+ }
+
+ public void mouseExited(MouseEvent e) { /* NOT IN USE */
+ }
+
+ public void mousePressed(MouseEvent e) {
+ // checked in both mousePressed() & mouseReleased() for cross-platform
+ // operation
+ maybeShowPopupMenu(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ // checked in both mousePressed() & mouseReleased() for cross-platform
+ // operation
+ maybeShowPopupMenu(e);
+ }
+
+ // ***** Callbacks for MouseMotionListener *****
+
+ public void mouseMoved(MouseEvent e) {
+ }
+
+ public void mouseDragged(MouseEvent e) { /* do nothing */
+ }
+
+ /**
+ * Gets the selected object from the specified list. Used for previewing
+ * items through double-clicks and contextual menu.
+ *
+ * @return <code>null</code> if no selection in the list,
+ * <code>ResourceLink</code> object that is currently selected
+ * otherwise.
+ */
+ private ResourceLink getResourceSelectedInJList() {
+ return (jlResultsListing.getSelectedIndex() == -1 ? null
+ : (ResourceLink) jlResultsListing.getSelectedValue());
+ }
+
+ private void maybeShowPopupMenu(MouseEvent e) {
+ if (e.getSource().equals(jlResultsListing) && e.isPopupTrigger()
+ && jlResultsListing.locationToIndex(e.getPoint()) != -1) {
+ // select the entry in the list that triggered the event to show
+ // this popup menu
+ jlResultsListing.setSelectedIndex(jlResultsListing
+ .locationToIndex(e.getPoint()));
+
+ // update value to be used in contextual menu click handler to act
+ // on the just-selected entry
+ potentialObjectToPreview = getResourceSelectedInJList();
+
+ // show the contextual menu
+ this.contextualMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ // *** Callbacks for SearchResultsRenderer ***
+
+ public void renderInitialResults(final SearchInstance si) {
+ // NB! critical to have UI update done within the invokeLater()
+ // method - this is to prevent UI from 'flashing' and to
+ // avoid concurrency-related errors
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // make sure to remove any old results from the list model!
+ resultsListingModel.clear();
+
+ // display the partial search results
+ logger.debug("Started rendering initial search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+ renderResults(si);
+ logger.debug("Finished rendering initial search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+ }
+ });
+ }
+
+ public void renderFurtherResults(SearchInstance si, int startIndex,
+ int count) {
+ renderFurtherResults(si, startIndex, count, false);
+ }
+
+ public void renderFurtherResults(final SearchInstance si,
+ final int startIndex, final int count,
+ final boolean disableListDataListeners) {
+ logger.debug("Started rendering further search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+
+ // NB! very important to remove all listeners here, so that the JList
+ // won't "freeze"
+ // on updating the components
+ ListDataListener[] listeners = null;
+ if (disableListDataListeners) {
+ listeners = resultsListingModel.getListDataListeners();
+ for (ListDataListener listener : listeners) {
+ resultsListingModel.removeListDataListener(listener);
+ }
+ }
+
+ for (int i = startIndex; i < startIndex + count
+ && i < resultsListingModel.getSize(); i++) {
+ resultsListingModel.set(i, searchInstance.getSearchResults()
+ .getFoundItems().get(i));
+ }
+
+ // reset all listeners in case they were removed
+ if (disableListDataListeners) {
+ for (ListDataListener listener : listeners) {
+ resultsListingModel.addListDataListener(listener);
+ }
+ }
+
+ // NB! critical to have UI update done within the invokeLater()
+ // method - this is to prevent UI from 'flashing' and to
+ // avoid some weird errors
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jlResultsListing.validate();
+ jlResultsListing.repaint();
+
+ logger.debug("Finished rendering further search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+ }
+ });
+ }
+
+ private void switchToDesignPerspective() {
+ if (designPerspective == null) {
+ for (PerspectiveSPI perspective : Workbench.getInstance()
+ .getPerspectives().getPerspectives()) {
+ if (perspective.getText().equalsIgnoreCase("design")) {
+ designPerspective = perspective;
+ break;
+ }
+ }
+ }
+
+ if (designPerspective != null) {
+ ModelMap.getInstance().setModel(
+ ModelMapConstants.CURRENT_PERSPECTIVE, designPerspective);
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
new file mode 100644
index 0000000..a3fca27
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
@@ -0,0 +1,498 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JToggleButton;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.model.search.SearchInstanceTracker;
+import net.sf.taverna.biocatalogue.model.search.SearchOptions;
+import net.sf.taverna.biocatalogue.model.search.ServiceFilteringSettings;
+import net.sf.taverna.biocatalogue.ui.filtertree.FilterTreePane;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+import org.apache.log4j.Logger;
+
+/**
+ * This class represents the main panel that deals with the status
+ * and results of the current search.
+ *
+ * It has a status label, spinner to depict search in progress,
+ * actual search results split into tabs by their type, a toolbar
+ * with search history, favourite searches settings, favourite filters,
+ * ability to restart last search, etc.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResultsMainPanel extends JPanel implements ActionListener, SearchInstanceTracker
+{
+ private final SearchResultsMainPanel instanceOfSelf;
+ private Logger logger;
+
+ private LinkedHashMap<TYPE, JComponent> searchResultTabs;
+ private Map<TYPE, SearchResultsListingPanel> searchResultListings;
+
+ // holds a reference to the instance of the search instances in the current context
+ // that should be active at the moment (will aid early termination of older searches
+ // when new ones are started)
+ private Map<TYPE, SearchInstance> currentSearchInstances;
+
+ // holds a map of references to the current instances of filter trees per resource type
+ private Map<TYPE, FilterTreePane> currentFilterPanes;
+
+
+ // COMPONENTS
+ private JTabbedPane tabbedSearchResultPanel;
+
+ protected JToggleButton bToggleSearchHistory;
+ protected JButton bRefreshLastSearch;
+ protected JButton bClearSearchResults;
+
+
+ public SearchResultsMainPanel()
+ {
+ this.instanceOfSelf = this;
+ MainComponentFactory.getSharedInstance();
+ this.logger = Logger.getLogger(SearchResultsMainPanel.class);
+
+ this.currentSearchInstances = new HashMap<TYPE,SearchInstance>();
+
+ this.searchResultListings = new HashMap<TYPE, SearchResultsListingPanel>();
+ this.currentFilterPanes = new HashMap<TYPE,FilterTreePane>();
+ this.searchResultTabs = new LinkedHashMap<TYPE, JComponent>(); // crucial to preserve the order -- so that these tabs always appear in the UI in the same order!
+ initialiseResultTabsMap();
+
+ initialiseUI();
+ }
+
+
+ private void initialiseUI()
+ {
+ // create a panel for tabbed listings of search results
+ this.tabbedSearchResultPanel = new JTabbedPane();
+ reloadResultTabsFromMap();
+
+ // pack all main components together
+ JPanel jpMainResultsPanel = new JPanel(new BorderLayout());
+ jpMainResultsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 3));
+
+ jpMainResultsPanel.add(tabbedSearchResultPanel, BorderLayout.CENTER);
+
+
+ // --- Put together all parts ---
+ // main components in the middle, toolbar on the right
+ this.setMinimumSize(new Dimension(450, 50));
+ this.setLayout(new BorderLayout());
+ this.add(jpMainResultsPanel, BorderLayout.CENTER);
+
+ // FIXME - add toolbar to the main window!
+// this.add(tbSearchActions, BorderLayout.EAST);
+ }
+
+
+
+ // ----- Hiding / Showing tabs for various search result types -----
+
+ /**
+ * Dynamically populates the map of resource types and components that represent these types
+ * in the tabbed pane -- this is only to be done once during the initialisation.
+ */
+ private void initialiseResultTabsMap()
+ {
+ for (TYPE t : TYPE.values()) {
+ toggleResultTabsInMap(t, t.isDefaultSearchType());
+ }
+ }
+
+
+ /**
+ * Adds or removes a tab for a specified type of resource.
+ *
+ * @param type Resource type for which the tab is to be added / removed.
+ * @param doShowTab Defines whether to add or remove tab for this resource type.
+ */
+ public void toggleResultTabsInMap(TYPE type, boolean doShowTab)
+ {
+ JPanel jpResultTabContent = null;
+
+ if (doShowTab)
+ {
+ jpResultTabContent = new JPanel(new GridLayout());
+
+ // decide if this resource type supports filtering
+ if (type.isSuitableForFiltering()) {
+ FilterTreePane filterTreePane = new FilterTreePane(type);
+ this.currentFilterPanes.put(type, filterTreePane);
+ }
+ else {
+ // not suitable for filtering - record this in a map
+ this.currentFilterPanes.put(type, null);
+ }
+
+
+ SearchResultsListingPanel resultsListingPanel = new SearchResultsListingPanel(type, this);
+ this.searchResultListings.put(type, resultsListingPanel);
+
+ if (this.currentFilterPanes.get(type) == null) {
+ jpResultTabContent.add(resultsListingPanel);
+ }
+ else {
+ JSplitPane spFiltersAndResultListing = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+ spFiltersAndResultListing.setLeftComponent(this.currentFilterPanes.get(type));
+ spFiltersAndResultListing.setRightComponent(resultsListingPanel);
+ jpResultTabContent.add(spFiltersAndResultListing);
+ }
+ }
+ else {
+ // tab for this type is being hidden - just remove the references
+ // to the search result listing and to filter pane
+ this.searchResultListings.put(type, null);
+ this.currentFilterPanes.put(type, null);
+ }
+
+ this.searchResultTabs.put(type, jpResultTabContent);
+ }
+
+
+ /**
+ * (Re-)loads the user interface from the internal map.
+ */
+ public void reloadResultTabsFromMap()
+ {
+ Component selectedTabsComponent = tabbedSearchResultPanel.getSelectedComponent();
+ tabbedSearchResultPanel.removeAll();
+ for (TYPE type : this.searchResultTabs.keySet()) {
+ JComponent c = this.searchResultTabs.get(type);
+ if (c != null) {
+ tabbedSearchResultPanel.addTab(type.getCollectionName(), type.getIcon(), c, type.getCollectionTabTooltip());
+ }
+ }
+
+ // attempt to re-select the same tab that was open before reloading
+ try {
+ tabbedSearchResultPanel.setSelectedComponent(selectedTabsComponent);
+ }
+ catch (IllegalArgumentException e) {
+ // failed - probably previously selected tab got removed - select the first one
+ tabbedSearchResultPanel.setSelectedIndex(0);
+ }
+ }
+
+
+ /**
+ * @param resourceType Resource type to look for.
+ * @return Current index of the tab in the results tabbed pane view
+ * that holds a component showing search results for this type.
+ * Returns <code>-1</code> if requested type is not currently displayed.
+ */
+ protected int getTabIndexForResourceType(TYPE resourceType) {
+ return (tabbedSearchResultPanel.indexOfComponent(searchResultTabs.get(resourceType)));
+ }
+
+
+ // ----- ------
+
+
+ /**
+ * This method is intended to be called when filter options in one of the tabs change.
+ * It starts the new filtering operation.
+ *
+ * Effectively it sets the filtering parameters for the SearchInstance
+ * and then starts a new search with that {@link SearchInstance} wrapped into {@link SearchOptions}.
+ *
+ * @param resourceType Resource type for which the new filtering operation is started
+ * @param filteringSettings Filtering settings for the current filtering operation
+ * obtained from the filter tree (or favourite filters).
+ */
+ public void startNewFiltering(TYPE resourceType, ServiceFilteringSettings filteringSettings)
+ {
+ SearchInstance siPreviousSearchForThisType = getCurrentSearchInstance(resourceType);
+
+ // pass on the filtering parameters to the relevant search instance (this will overwrite the old ones if any were present!)
+ if (siPreviousSearchForThisType == null) {
+ // no filterings have been done earlier for this resource type;
+ // we'll need a new (blank) query search SearchInstance and
+ // wrap it into a service filtering SearchInstance
+ siPreviousSearchForThisType = new SearchInstance(new SearchInstance("", resourceType), filteringSettings);
+ }
+ else {
+ if (!siPreviousSearchForThisType.isServiceFilteringSearch()) {
+ // just wrap existing search instance that was (probably) transferred from the Search tab
+ // into another SearchInstance that explicitly deals with service filtering
+ siPreviousSearchForThisType = new SearchInstance(siPreviousSearchForThisType, filteringSettings);
+ }
+ else {
+ // previous search instance dealt with filtering -
+ // simply update the filtering settings (but before that
+ // run a 'deep copy' of the original search instance, so
+ // that the new one gets a new reference; this will aid
+ // in early termination of older filterings)
+ siPreviousSearchForThisType = siPreviousSearchForThisType.deepCopy();
+ siPreviousSearchForThisType.setFilteringSettings(filteringSettings);
+ }
+ }
+
+ // proceed with "search" as usual - it will treat this search instance differently
+ // from "ordinary" search
+ startNewSearch(new SearchOptions(siPreviousSearchForThisType));
+ }
+
+
+ /**
+ * Worker method responsible for starting a new search via the API.
+ *
+ * This method is to be used when a *new* search is started. It will
+ * mainly make updates to the UI and store the new search in the history.
+ */
+ public void startNewSearch(final SearchOptions searchOptions)
+ {
+ try
+ {
+ for (final TYPE resourceType : searchOptions.getResourceTypesToSearchFor())
+ {
+ SearchInstance si = null;
+ switch (searchOptions.getSearchType()) {
+ case QuerySearch: si = new SearchInstance(searchOptions.getSearchString(), resourceType);
+ resetAllFilterPanes(searchOptions.getSearchString());
+ break;
+
+ case TagSearch: if (resourceType.isSuitableForTagSearch()) {
+ si = new SearchInstance(searchOptions.getSearchTags(), resourceType);
+ resetAllFilterPanes(searchOptions.getSearchString());
+ }
+ else {
+ // FIXME implement this... - show "no results" in the appropriate tab
+ JOptionPane.showMessageDialog(null, "'" + resourceType.getTypeName() + "' resource type is not suitable for tag search");
+ }
+ break;
+
+ case Filtering: if (resourceType.isSuitableForFiltering()) {
+ si = searchOptions.getPreconfiguredSearchInstance();
+ }
+ else {
+ // FIXME implement this... - show "no results" in the appropriate tab
+ JOptionPane.showMessageDialog(null, "'" + resourceType.getTypeName() + "' resource type is not suitable for filtering");
+ }
+ break;
+ }
+
+ if (si.isEmptySearch()) {
+ clearListingPanels();
+ return;
+ }
+
+ // Record 'this' search instance and set it as the new "primary" one for
+ // this resource type;
+ // (this way it if a new search thread starts afterwards, it is possible to
+ // detect this and stop the 'older' search, because it is no longer relevant)
+ registerSearchInstance(resourceType, si);
+
+ // start spinner icon for this tab to indicate search in progress - also show status message
+ setSpinnerIconForTab(resourceType);
+ setDefaultTitleForTab(resourceType);
+ searchResultListings.get(resourceType).setSearchStatusText("Searching for " + si.getDescriptionStringForSearchStatus() + "...", true);
+
+
+ // start the actual search
+ final SearchInstance siToStart = si;
+ new Thread(searchOptions.getSearchType() + " of " + resourceType.getCollectionName() + " via the API") {
+ public void run() {
+ siToStart.startNewSearch(instanceOfSelf, new CountDownLatch(1), searchResultListings.get(resourceType)); // FIXME - the new countdown latch is never used...
+ }
+ }.start();
+ }
+ }
+ catch (Exception e) {
+ logger.error("Error while searching via the Service Catalogue API. Error details attached.", e);
+ }
+
+}
+
+
+
+ /**
+ * Clears selection of filtering criteria and collapses any expanded nodes
+ * in all filter tree panes.<br/><br/>
+ *
+ * To be used for resetting all filter panes when the new query / tag
+ * search starts.
+ * @param queryString
+ */
+ private void resetAllFilterPanes(String queryString) {
+ for (FilterTreePane filterTreePane : this.currentFilterPanes.values()) {
+ if (filterTreePane != null) {
+ filterTreePane.clearSelection();
+ filterTreePane.collapseAll();
+ if ((queryString != null) && !queryString.isEmpty()) {
+ filterTreePane.applyQueryString(queryString);
+ }
+ }
+ }
+ }
+
+
+ protected void setSpinnerIconForTab(TYPE resourceType) {
+ tabbedSearchResultPanel.setIconAt(getTabIndexForResourceType(resourceType), ResourceManager.getImageIcon(ResourceManager.SPINNER));
+ }
+
+ protected void setDefaultIconForTab(TYPE resourceType) {
+ this.tabbedSearchResultPanel.setIconAt(getTabIndexForResourceType(resourceType), resourceType.getIcon());
+ }
+
+
+ /**
+ * Same as {@link SearchResultsMainPanel#setDefaultTitleForTab(TYPE)},
+ * but allows to append a specified string at the end of the default title.
+ *
+ * @param resourceType
+ * @param suffix
+ */
+ protected void setDefaultTitleForTabWithSuffix(TYPE resourceType, String suffix) {
+ tabbedSearchResultPanel.setTitleAt(getTabIndexForResourceType(resourceType),
+ resourceType.getCollectionName() + (suffix == null ? "" : suffix) );
+ }
+
+
+ /**
+ * Sets default title for a tab that contains panel representing
+ * search results of the specified resource type. Default title
+ * is just a name of the collections of resources in that tab.
+ *
+ * @param resourceType
+ */
+ protected void setDefaultTitleForTab(TYPE resourceType) {
+ setDefaultTitleForTabWithSuffix(resourceType, null);
+ }
+
+
+ /**
+ * @param resourceType Resource type for which the search result listing panel is requested.
+ * @return Reference to the requested panel or <code>null</code> if a tab for the specified
+ * <code>resourceType</code> does not exist.
+ */
+ protected SearchResultsListingPanel getResultsListingFor(TYPE resourceType) {
+ return (this.searchResultListings.get(resourceType));
+ }
+
+
+ /**
+ * @param resourceType Resource type for which filter tree pane is to be returned.
+ * @return Reference to the requested filter tree pane or <code>null</code> if
+ * there is no search result tab for the specified <code>resourceType</code>
+ * (or if that <code>resourceType</code> does not support filtering).
+ */
+ protected FilterTreePane getFilterTreePaneFor(TYPE resourceType) {
+ return (this.currentFilterPanes.get(resourceType));
+ }
+
+
+ // *** Callback for ActionListener interface ***
+
+ public void actionPerformed(ActionEvent e)
+ {
+ // FIXME -- remove this...
+// if (e.getSource().equals(bRefreshLastSearch))
+// {
+// // restore state of the search options panel
+// pluginPerspectiveMainComponent.getSearchTab().restoreSearchOptions(siPreviousSearch);
+//
+// // completely re-run the previous search
+// startNewSearch(siPreviousSearch);
+// }
+// else if (e.getSource().equals(bClearSearchResults))
+// {
+// // manual request to clear results of previous search
+//
+// // if any search thread was running, deactivate it as well
+// if (isSearchThreadRunning()) {
+// vCurrentSearchThreadID.set(0, null);
+// }
+//
+// // changing both - spinner image and the status text simultaneously
+// setSearchStatusText("No searches were made yet", false);
+//
+// // removed the previous search, hence makes no sense to allow to clear "previous" results again
+// bClearSearchResults.setEnabled(false);
+//
+// // only remove data about previous search and disable refresh button
+// // if no search thread is currently running - otherwise keep the button
+// // enabled in case there is a need to re-start the search if it's frozen
+// if (!isSearchThreadRunning()) {
+// siPreviousSearch = null;
+// bRefreshLastSearch.setEnabled(false);
+// }
+//
+// // also notify tabbed results panel, so that it removes the actual search results
+// searchResultsPanel.clearPreviousSearchResults();
+// }
+// else if (e.getSource().equals(this.jclPreviewCurrentFilteringCriteria))
+// {
+// // open a preview window showing current filtering settings
+// SwingUtilities.invokeLater(new Runnable()
+// {
+// public void run() {
+// ServiceFilteringSettingsPreview p = new ServiceFilteringSettingsPreview(siPreviousSearch.getFilteringSettings());
+// p.setVisible(true);
+// }
+// });
+//
+// }
+ }
+
+
+ // *** Callbacks for SearchInstanceTracker interface ***
+
+ public synchronized void clearPreviousSearchInstances() {
+ this.currentSearchInstances.clear();
+ }
+
+ public synchronized boolean isCurrentSearchInstance(TYPE searchType, SearchInstance searchInstance) {
+ // NB! it is crucial to perform test by reference here (hence the use of "==", not equals()!)
+ return (this.currentSearchInstances.get(searchType) == searchInstance);
+ }
+
+ public synchronized void registerSearchInstance(TYPE searchType, SearchInstance searchInstance) {
+ this.currentSearchInstances.put(searchType, searchInstance);
+ }
+
+ public synchronized SearchInstance getCurrentSearchInstance(TYPE searchType) {
+ return this.currentSearchInstances.get(searchType);
+ }
+
+public void clearListingPanels() {
+ for (SearchResultsListingPanel listingPanel : searchResultListings.values()) {
+ listingPanel.resetSearchResultsListing(true);
+ }
+ for (TYPE t : searchResultListings.keySet()) {
+ setDefaultTitleForTab(t);
+ }
+
+}
+
+public void clearSearch() {
+ clearPreviousSearchInstances();
+ clearListingPanels();
+ for (FilterTreePane treePanel : currentFilterPanes.values()) {
+ treePanel.reset();
+ }
+}
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
new file mode 100644
index 0000000..58ee5fd
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
@@ -0,0 +1,47 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+
+/**
+ * This interfaces avoids coupling of search engine classes
+ * directly with the UI classes.
+ *
+ * Search engines would send new chunks of search results
+ * to the <code>SearchResultsRenderer</code> as soon as
+ * they become available.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public interface SearchResultsRenderer
+{
+ /**
+ * Render initial fragment of search results. This includes
+ * creating a new <code>ListModel</code> in the results listing
+ * and populating it with the first chunk of results and placeholders
+ * for those results that haven't been fetched yet.
+ *
+// * @param searchThreadID This is the ID of the thread that initiated search // FIXME
+// * from within the UI component, rather than the ID of
+// * the real worker search engine's search thread.
+// * It is used to test whether that thread is still active -
+// * to determine whether the partial results need to be rendered.
+ * @param si The search instance containing partial search results to be rendered.
+ */
+ void renderInitialResults(SearchInstance si);
+
+
+ /**
+ * Update the results listing with a specific fragment of the collection
+ * of search results.
+ *
+ * @param si The search instance containing partial search results to be rendered.
+ * @param startIndex First index in the result collection to update.
+ * @param count Number of result listing entries to update.
+ * <br/>
+ * At most <code>count</code> results will be rendered - less can be rendered
+ * if end of result list is reached earlier. <code>count</code> is normally
+ * just a page size for a specific resource type.
+ */
+ void renderFurtherResults(SearchInstance si, int startIndex, int count);
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java
new file mode 100644
index 0000000..eafe095
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java
@@ -0,0 +1,291 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.biocatalogue.x2009.xml.rest.Location;
+import org.biocatalogue.x2009.xml.rest.ResourceLinkWithString;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceSummary.Provider;
+
+import net.sf.taverna.biocatalogue.model.LoadingExpandedResource;
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+
+/**
+ * This list cell renderer is will display (web) Service items in the search results
+ * list box.
+ *
+ * In the collapsed state, four data items will be shown per service: status (as per monitoring data),
+ * service type (SOAP / REST), the name of the service and its description.
+ *
+ * In the expanded state, other details are added to the ones above: service categories, locations,
+ * endpoints and providers.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class ServiceListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+ private JLabel jlTypeIcon;
+ private JLabel jlItemStatus;
+ private JLabel jlItemTitle;
+ private JLabel jlDescription;
+
+ private GridBagConstraints c;
+
+
+ public ServiceListCellRenderer() {
+ /* do nothing */
+ }
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender)
+ {
+ TYPE resourceType = determineResourceType(itemToRender);
+ LoadingResource resource = (LoadingResource)itemToRender;
+
+ jlTypeIcon = new JLabel(resourceType.getIcon());
+ jlItemStatus = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNKNOWN_ICON));
+
+ jlItemTitle = new JLabel(Resource.getDisplayNameForResource(resource), JLabel.LEFT);
+ jlItemTitle.setForeground(Color.decode("#AD0000")); // very dark red
+ jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+
+ jlDescription = resource.isLoading() ? loaderBarAnimationGrey : loaderBarAnimationGreyStill;
+
+ return (arrangeLayout(false));
+ }
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean selected)
+ {
+ TYPE resourceType = determineResourceType(itemToRender);
+ Service service = (Service)itemToRender;;
+
+ // service type
+ if (service.getServiceTechnologyTypes() != null && service.getServiceTechnologyTypes().getTypeList().size() > 0)
+ {
+ if (service.getServiceTechnologyTypes().getTypeList().size() > 1 &&
+ !(service.getServiceTechnologyTypes().getTypeList().get(0).toString().equalsIgnoreCase("SOAP")) &&
+ service.getServiceTechnologyTypes().getTypeList().get(1).toString().equalsIgnoreCase("SOAPLAB")
+ )
+ {
+ jlTypeIcon = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_TYPE_MULTITYPE_ICON));
+ }
+ else if (service.getServiceTechnologyTypes().getTypeArray(0).toString().equalsIgnoreCase("SOAP")) {
+ jlTypeIcon = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_TYPE_SOAP_ICON));
+ }
+ else if (service.getServiceTechnologyTypes().getTypeArray(0).toString().equalsIgnoreCase("REST")) {
+ jlTypeIcon = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_TYPE_REST_ICON));
+ }
+ }
+ else {
+ // can't tell the type - just show as a service of no particular type
+ jlTypeIcon = new JLabel(resourceType.getIcon());
+ }
+
+
+ // service status
+ jlItemStatus = new JLabel(ServiceMonitoringStatusInterpreter.getStatusIcon(service, true));
+
+ jlItemTitle = new JLabel(Resource.getDisplayNameForResource(service), JLabel.LEFT);
+ jlItemTitle.setForeground(Color.decode("#AD0000")); // very dark red
+ jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+
+ int descriptionMaxLength = DESCRIPTION_MAX_LENGTH_EXPANDED;
+ String strDescription = Util.stripAllHTML(service.getDescription());
+ strDescription = (strDescription == null || strDescription.length() == 0 ?
+ "<font color=\"gray\">no description</font>" :
+ StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(strDescription, LINE_LENGTH, false)));
+
+ if (strDescription.length() > descriptionMaxLength) {
+ strDescription = strDescription.substring(0, descriptionMaxLength) + "<font color=\"gray\">(...)</font>";
+ }
+ strDescription = "<html><b>Description: </b>" + strDescription + "</html>";
+ jlDescription = new JLabel(strDescription);
+
+ return (arrangeLayout(true));
+ }
+
+
+ /**
+ * @return Final state of the {@link GridBagConstraints} instance
+ * that was used to lay out components in the panel.
+ */
+ private GridBagConstraints arrangeLayout(boolean showActionButtons)
+ {
+ // POPULATE PANEL WITH PREPARED COMPONENTS
+ this.setLayout(new GridBagLayout());
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(10, 6, 6, 3);
+ this.add(jlTypeIcon, c);
+
+ c.gridx++;
+ c.insets = new Insets(10, 3, 6, 3);
+ this.add(jlItemStatus, c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(10, 3, 6, 3);
+ this.add(jlItemTitle, c);
+
+ if (showActionButtons) {
+ c.gridx++;
+ c.gridheight = 3;
+ c.weightx = 0;
+ c.weighty = 1.0;
+ jlExpand = new JLabel(ResourceManager.getImageIcon(ResourceManager.FOLD_ICON));
+ this.add(jlExpand, c);
+ }
+
+ c.gridx = 2;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 1.0;
+ c.weighty = 0;
+ c.insets = new Insets(3, 3, 3, 3);
+ this.add(jlDescription, c);
+
+ return (c);
+ }
+
+
+
+ protected void prepareLoadingExpandedEntry(Object itemToRender)
+ {
+ LoadingExpandedResource expandedResource = (LoadingExpandedResource) itemToRender;
+ GridBagConstraints c = prepareLoadedEntry(expandedResource.getAssociatedObj(), false);
+
+ if (expandedResource.isLoading())
+ {
+ c.gridx = 0;
+ c.gridy++;
+ c.gridwidth = 3;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1.0;
+ this.add(loaderBarAnimationOrange, c);
+ }
+ else
+ {
+ // *** additional data for this Web Service operations ***
+ Service service = (Service) expandedResource.getAssociatedObj();
+
+
+ // -- categories --
+ int categoryCount = service.getSummary().getCategoryList().size();
+ String categoryString = "";
+ if (categoryCount > 0) {
+ List<String> categoryNames = new ArrayList<String>();
+ for (ResourceLinkWithString category : service.getSummary().getCategoryList()) {
+ categoryNames.add(category.getStringValue());
+ }
+ categoryString = "<html><b>" + Util.pluraliseNoun("Category", categoryCount) + ": </b>" + StringEscapeUtils.escapeHtml(Util.join(categoryNames, ", ")) + "</html>";
+ }
+ else {
+ categoryString = "<html><b>Category: </b><font color=\"gray\">unknown</font></html>";
+ }
+
+ c.gridy++;
+ this.add(new JLabel(categoryString),c);
+
+
+ // -- endpoints --
+ int endpointCount = service.getSummary().getEndpointList().size();
+ String endpointString = "";
+ if (endpointCount > 0) {
+ endpointString = "<html><b>" + Util.pluraliseNoun("Endpoint", endpointCount) + ": </b>" +
+ StringEscapeUtils.escapeHtml(Util.join(service.getSummary().getEndpointList(), ", ")) + "</html>";
+ }
+ else {
+ endpointString = "<html><b>Endpoint: </b><font color=\"gray\">unknown</font></html>";
+ }
+
+ c.gridy++;
+ this.add(new JLabel(endpointString), c);
+
+
+ // -- providers --
+ int providerCount = service.getSummary().getProviderList().size();
+ String providerString = "";
+ if (providerCount > 0) {
+ List<String> providerNames = new ArrayList<String>();
+ for (Provider serviceProvider : service.getSummary().getProviderList()) {
+ providerNames.add(serviceProvider.getName());
+ }
+ providerString = "<html><b>" + Util.pluraliseNoun("Provider", providerCount) + ": </b>" + StringEscapeUtils.escapeHtml(Util.join(providerNames, ", ")) + "</html>";
+ }
+ else {
+ providerString = "<html><b>Provider: </b><font color=\"gray\">unknown</font></html>";
+ }
+
+ c.gridy++;
+ this.add(new JLabel(providerString),c);
+
+
+ // -- locations --
+ int locationCount = service.getSummary().getLocationList().size();
+ String locationString = "";
+ List<String> locations = new ArrayList<String>();
+ if (locationCount > 0) {
+ for (Location location : service.getSummary().getLocationList()) {
+ List<String> locationNameFragments = new ArrayList<String>();
+ locationNameFragments.add(location.getCity());
+ locationNameFragments.add(location.getCountry());
+ locations.add(Util.join(locationNameFragments, ", "));
+ }
+ }
+ locationString = "<html><b>" + Util.pluraliseNoun("Location", locations.size()) + ": </b>" +
+ (locations.size() > 0 ? StringEscapeUtils.escapeHtml(Util.join(locations, "; ")) : "<font color=\"gray\">unknown</font>") +
+ "</html>";
+
+ c.gridy++;
+ c.insets = new Insets(3, 3, 12, 3);
+ this.add(new JLabel(locationString),c);
+ }
+ }
+
+
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+ return false;
+}
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java
new file mode 100644
index 0000000..304e50a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java
@@ -0,0 +1,631 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class JTriStateTree extends JTree
+{
+ // This is used to manage location and padding of tooltips on long items
+ // that don't fit into the visible part of this tree.
+ private static final int JCHECKBOX_WIDTH = 16;
+
+ private JTriStateTree instanceOfSelf;
+ private JPopupMenu contextualMenu;
+
+ private TriStateTreeNode root;
+
+ // will enable/disable checkboxes - when disabled the selection
+ // will remain, but will appear as read-only
+ private boolean bCheckingEnabled;
+
+ private List<Action> contextualMenuActions;
+ private Action expandAllAction;
+ private Action collapseAllAction;
+ private Action selectAllAction;
+ private Action deselectAllAction;
+
+ private Set<TriStateTreeCheckingListener> checkingListeners;
+
+
+ @SuppressWarnings("serial")
+public JTriStateTree(TriStateTreeNode root)
+ {
+ super(root);
+ this.root = root;
+ this.instanceOfSelf = this;
+
+ // by default checking is allowed
+ this.bCheckingEnabled = true;
+
+ // initially, no checking listeners
+ checkingListeners = new HashSet<TriStateTreeCheckingListener>();
+
+ // initially set to show the [+]/[-] icons for expanding collapsing top-level nodes
+ this.setShowsRootHandles(true);
+
+ // use the cell rendered which understands the three states of the
+ // nodes of this tree
+ this.setCellRenderer(new TriStateCheckBoxTreeCellRenderer());
+
+
+ // create all necessary actions for the popup menu: selecting/deselecting and expanding/collapsing all nodes
+ this.selectAllAction = new AbstractAction("Select all", ResourceManager.getImageIcon(ResourceManager.SELECT_ALL_ICON))
+ {
+ // Tooltip
+ { this.putValue(SHORT_DESCRIPTION, "Select all nodes in the tree"); }
+
+ public void actionPerformed(ActionEvent e) {
+ selectAllNodes(true);
+ }
+ };
+
+ // Use the Taverna untick icon
+ this.deselectAllAction = new AbstractAction("Deselect all", ResourceManager.getImageIcon(ResourceManager.UNCHECKED_ICON))
+ {
+ // Tooltip
+ { this.putValue(SHORT_DESCRIPTION, "Deselect all nodes in the tree"); }
+
+ public void actionPerformed(ActionEvent e) {
+ selectAllNodes(false);
+ }
+ };
+
+
+// this.expandAllAction = new AbstractAction("Expand all", ResourceManager.getImageIcon(ResourceManager.EXPAND_ALL_ICON))
+ // Use the standard Taverna plus icon
+ this.expandAllAction = new AbstractAction("Expand all", WorkbenchIcons.plusIcon)
+ {
+ // Tooltip
+ { this.putValue(SHORT_DESCRIPTION, "Expand all nodes in the tree"); }
+
+ public void actionPerformed(ActionEvent e) {
+ expandAll();
+ }
+ };
+
+// this.collapseAllAction = new AbstractAction("Collapse all", ResourceManager.getImageIcon(ResourceManager.COLLAPSE_ALL_ICON))
+ // Use the standard Taverna minus icon
+ this.collapseAllAction = new AbstractAction("Collapse all", WorkbenchIcons.minusIcon)
+ {
+ // Tooltip
+ { this.putValue(SHORT_DESCRIPTION, "Collapse all expanded nodes in the tree"); }
+
+ public void actionPerformed(ActionEvent e) {
+ collapseAll();
+ }
+ };
+
+
+ // populate the popup menu with created menu items
+ contextualMenuActions = Arrays.asList(new Action[] {expandAllAction, collapseAllAction, deselectAllAction});
+
+ contextualMenu = new JPopupMenu();
+ contextualMenu.add(expandAllAction);
+ contextualMenu.add(collapseAllAction);
+ contextualMenu.add(new JPopupMenu.Separator());
+ //contextualMenu.add(selectAllAction);
+ contextualMenu.add(deselectAllAction);
+
+
+ this.addMouseListener(new MouseAdapter() {
+ // use mousePressed, not mouseClicked to make sure that
+ // quick successive clicks get processed correctly, otherwise
+ // some clicks are disregarded
+ public void mousePressed(MouseEvent e)
+ {
+ // only listen to checkbox checking requests if this is
+ // a correct type of mouse event for this
+ if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1)
+ {
+ int clickedRow = instanceOfSelf.getRowForLocation(e.getX(), e.getY());
+
+ // only make changes to node selections if checking is enabled in the tree and
+ // it was a node which was clicked, not [+]/[-] or blank space
+ if (bCheckingEnabled && clickedRow != -1)
+ {
+ Object clickedObject = instanceOfSelf.getPathForRow(clickedRow).getLastPathComponent();
+ if (clickedObject instanceof TriStateTreeNode) {
+ TriStateTreeNode node = ((TriStateTreeNode)clickedObject);
+
+ // toggle state of the clicked node + propagate the changes to
+ // the checking state of all nodes
+ node.toggleState(true);
+
+ // repaint the whole tree
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ instanceOfSelf.repaint();
+ }
+ });
+
+ // notify all listeners
+ notifyCheckingListeners();
+ }
+ }
+ }
+ else {
+ // not a checking action - instead, bring up a popup menu
+ contextualMenu.show(instanceOfSelf, e.getX(), e.getY());
+ }
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ if (e.isPopupTrigger()) {
+ // another way a popup menu may be called on different systems
+ contextualMenu.show(instanceOfSelf, e.getX(), e.getY());
+ }
+ }
+
+
+ /**
+ * This method enables tooltips on this instance of JTriStateTree
+ * when mouse enters its bounds. Custom tooltips will be used, but
+ * this notifies ToolTipManager that tooltips must be shown on this
+ * tree.
+ */
+ public void mouseEntered(MouseEvent e) {
+ instanceOfSelf.setToolTipText("Filter tree");
+ }
+
+ /**
+ * Removes tooltips from this JTriStateTree when mouse leaves its bounds.
+ */
+ public void mouseExited(MouseEvent e) {
+ instanceOfSelf.setToolTipText(null);
+ }
+
+ });
+
+ }
+
+
+ /**
+ * This method is used to determine tooltip location.
+ *
+ * Helps to ensure that the tooltip appears directly over the
+ * text in the row over which the mouse currently hovers.
+ */
+ public Point getToolTipLocation(MouseEvent e)
+ {
+ int iRowIndex = this.getRowForLocation(e.getX(), e.getY());
+ if (iRowIndex != -1) {
+ // mouse hovers over one of the rows - make top-left corner of
+ // the tooltip to appear over the top-left corner of that row
+ Rectangle bounds = this.getRowBounds(iRowIndex);
+ return (new Point(bounds.x + JCHECKBOX_WIDTH, bounds.y));
+ }
+ else {
+ // let ToolTipManager determine where to show the tooltip (if it will be shown)
+ return null;
+ }
+ }
+
+
+ /**
+ * Supports dynamic tooltips for the contents of this JTriStateTree -
+ * the tooltips will only be shown for those tree nodes that don't
+ * fully fit within the visible bounds of the tree.
+ *
+ * For other nodes no tooltip will be shown.
+ */
+ public String getToolTipText(MouseEvent e)
+ {
+ String strTooltip = null;
+
+ Object correspondingObject = getTreeNodeObject(e);
+ if (correspondingObject != null) {
+ // mouse is hovering over some row in the tree, not a blank space --
+ // obtain a component that is identical to the one which is currently displayed at the identified row in the tree
+ Component rendering = this.getCellRenderer().getTreeCellRendererComponent(this, correspondingObject, false, false,
+ true, this.getRowForLocation(e.getX(), e.getY()), false);
+
+ if (rendering.getPreferredSize().width + getToolTipLocation(e).x - JCHECKBOX_WIDTH > this.getVisibleRect().width) {
+ // if the component is not fully visible, the tooltip will be displayed -
+ // tooltip text matches the one for this row in the tree, will just be shown in full
+ strTooltip = correspondingObject.toString();
+ }
+ }
+
+ // return either tooltip text or 'null' if no tooltip is currently required
+ return (strTooltip);
+ }
+
+
+ /**
+ * Check whether a {@link MouseEvent} happened in such a location
+ * in the {@link JTriStateTree} that corresponds to some node or a
+ * blank space.
+ *
+ * @param e
+ * @return Object contained in the tree node that corresponds to the
+ * location of specified {@link MouseEvent} <code>e</code>;
+ * or <code>null</code> if the event happened over a blank space.
+ */
+ public Object getTreeNodeObject(MouseEvent e)
+ {
+ int iRowIndex = this.getRowForLocation(e.getX(), e.getY());
+ if (iRowIndex != -1) {
+ // mouse is hovering over some row in the tree, not a blank space
+ return (this.getPathForRow(iRowIndex).getLastPathComponent());
+ }
+
+ return (null);
+ }
+
+
+ /**
+ * @return List of the highest-level nodes of the tree that have full (not partial) selection and,
+ * therefore, act as roots of checked paths.
+ */
+ public List<TreePath> getRootsOfCheckedPaths()
+ {
+ return getRootsOfCheckedPaths(this.root);
+ }
+
+ /**
+ * A recursive version of the getCheckedRootsOfCheckedPaths().
+ * Performs all the work for a given node and returns result to
+ * the caller.
+ *
+ * @param startNode Node to start with.
+ * @return
+ */
+ private List<TreePath> getRootsOfCheckedPaths(TriStateTreeNode startNode)
+ {
+ ArrayList<TreePath> pathsToRootsOfCheckings = new ArrayList<TreePath>();
+
+ Object currentNode = null;
+ for (Enumeration e = startNode.children(); e.hasMoreElements(); )
+ {
+ currentNode = e.nextElement();
+ if (currentNode instanceof TriStateTreeNode) {
+ TriStateTreeNode curTriStateNode = (TriStateTreeNode)currentNode;
+
+ if (curTriStateNode.getState().equals(TriStateCheckBox.State.CHECKED)) {
+ pathsToRootsOfCheckings.add(new TreePath(curTriStateNode.getPath()));
+ }
+ else if (curTriStateNode.getState().equals(TriStateCheckBox.State.PARTIAL)) {
+ pathsToRootsOfCheckings.addAll(getRootsOfCheckedPaths(curTriStateNode));
+ }
+ }
+ }
+
+ return (pathsToRootsOfCheckings);
+ }
+
+
+ /**
+ * @return List of TreePath objects, where the last component in each
+ * path is the root of an unchecked path in this tree. In other
+ * words each of those last components is either an unchecked
+ * leaf node or a node, none of whose children (and itself as
+ * well) are checked.
+ */
+ public List<TreePath> getRootsOfUncheckedPaths()
+ {
+ return (getRootsOfUncheckedPaths(this.root));
+ }
+
+
+ /**
+ * Recursive worker method for <code>getRootsOfUncheckedPaths()</code>.
+ */
+ private List<TreePath> getRootsOfUncheckedPaths(TreeNode startNode)
+ {
+ List<TreePath> rootNodesOfUncheckedPaths = new ArrayList<TreePath>();
+
+ Object currentNode = null;
+ for (Enumeration e = startNode.children(); e.hasMoreElements(); )
+ {
+ currentNode = e.nextElement();
+ if (!(currentNode instanceof TriStateTreeNode) ||
+ ((TriStateTreeNode)currentNode).getState().equals(TriStateCheckBox.State.UNCHECKED))
+ {
+ rootNodesOfUncheckedPaths.add(new TreePath(((DefaultMutableTreeNode)currentNode).getPath()));
+ }
+ else {
+ rootNodesOfUncheckedPaths.addAll(getRootsOfUncheckedPaths((TreeNode)currentNode));
+ }
+ }
+
+ return (rootNodesOfUncheckedPaths);
+ }
+
+
+ /**
+ * @return List of TreePath objects, that point to all "leaf"
+ * nodes in the tree that are checked - in other words
+ * this method returns a collection of paths to all "deepest"
+ * nodes in this tree that are checked and do not have any
+ * (checked) children.
+ */
+ public List<TreePath> getLeavesOfCheckedPaths() {
+ return (getLeavesOfCheckedPaths(this.root));
+ }
+
+
+ /**
+ * Recursive worker method for {@link JTriStateTree#getLeavesOfCheckedPaths()}
+ */
+ private List<TreePath> getLeavesOfCheckedPaths(TriStateTreeNode startNode)
+ {
+ List<TreePath> leavesOfCheckedPaths = new ArrayList<TreePath>();
+
+ // this node is only relevant if it is checked itself - if not,
+ // it must be the first-level child of another node that is checked
+ // and is only considered here on the recursive pass (but will be discarded)
+ if (startNode.getState().equals(TriStateCheckBox.State.CHECKED) ||
+ startNode.getState().equals(TriStateCheckBox.State.PARTIAL))
+ {
+ // "ask" all children to do the same...
+ Object currentNode = null;
+ for (Enumeration e = startNode.children(); e.hasMoreElements(); ) {
+ currentNode = e.nextElement();
+ if (currentNode instanceof TriStateTreeNode) {
+ leavesOfCheckedPaths.addAll(getLeavesOfCheckedPaths((TriStateTreeNode)currentNode));
+ }
+ }
+
+ // ...if we have a list of leaf nodes, then this node can't be a leaf;
+ // -> but alternatively, if the list is empty, it means that this node is
+ // itself a leaf node and must be added to the result
+ if (leavesOfCheckedPaths.isEmpty()) {
+ leavesOfCheckedPaths.add(new TreePath(startNode.getPath()));
+ }
+ }
+
+ return (leavesOfCheckedPaths);
+ }
+
+
+ /**
+ * @return List of all contextual menu actions that are available for this tree.
+ */
+ public List<Action> getContextualMenuActions() {
+ return this.contextualMenuActions;
+ }
+
+
+ /**
+ * Enables or disables all actions in the contextual menu
+ * @param actionsAreEnabled
+ */
+ public void enableAllContextualMenuAction(boolean actionsAreEnabled) {
+ for (Action a : getContextualMenuActions()) {
+ a.setEnabled(actionsAreEnabled);
+ }
+ }
+
+
+ /**
+ * Selects or deselects all nodes.
+ * @param selectAll True - to select all; false - to reset all selections.
+ */
+ public void selectAllNodes(boolean selectAll) {
+ root.setState(selectAll ? TriStateCheckBox.State.CHECKED : TriStateCheckBox.State.UNCHECKED);
+ root.updateStateOfRelatedNodes();
+ this.repaint();
+
+ // even though this isn't a click in the tree, the selection has changed -
+ // notify all listeners
+ notifyCheckingListeners();
+ }
+
+
+ /**
+ * TODO - this method doesn't take into account a possibility that the
+ * filter tree might have changed
+ *
+ * @param rootsOfCheckedPaths A list of TreePath objects which represent a checking state of
+ * the nodes in this tree (as returned by <code>getRootsOfCheckedPaths()</code>).
+ *
+ * The last node of each path is the one that should have <code>TriStateCheckBox.State.CHECKED</code>
+ * state (so that last node is a root of checked path that start at that node). Related partial
+ * checkings for the UI can be computed from that by the tree checking model.
+ *
+ * Therefore, a single "real" checking per provided TreePath from <code>rootsOfCheckedPaths</code> is
+ * made.
+ */
+ public void restoreFilterCheckingSettings(List<TreePath> rootsOfCheckedPaths)
+ {
+ // start with removing all selections
+ this.selectAllNodes(false);
+
+ for (TreePath p : rootsOfCheckedPaths) {
+ restoreTreePathCheckingSettings(this.root, p);
+ }
+ }
+
+ /**
+ * A worker method for <code>restoreFilterCheckingSettings(List<TreePath> rootsOfCheckedPaths)</code>.
+ * See that method for further details.
+ *
+ * @param startNode A node of this tree.
+ * @param pathFromStartNode A TreePath object from the stored filter, where the first node must be
+ * equals to <code>startNode</code> (based on the <code>userObject</code>,
+ * but not the checking state), should the traversal of the tree result in
+ * checking the last node of this TreePath eventually - which is the goal
+ * of this method.
+ * @return True if traversal of <code>pathFromStartNode</code> succeeded and a node in this tree was checked;
+ * false if traversal couldn't find a matching node in this tree, and so no checking was made.
+ */
+ private boolean restoreTreePathCheckingSettings(TriStateTreeNode startNode, TreePath pathFromStartNode)
+ {
+ if (startNode == null || pathFromStartNode == null || pathFromStartNode.getPathCount() == 0) {
+ // no match - no data to work with
+ return (false);
+ }
+
+ // compare the "roots" - provided start node and the root of the provided path
+ // (based on the 'user object', but not the selection state)
+ if (startNode.equals(pathFromStartNode.getPathComponent(0)))
+ {
+ if (pathFromStartNode.getPathCount() == 1) {
+ // provided startNode is equals to the only node in the provided tree path -
+ // so it is the node to mark as checked; also - make sure that this selection
+ // propagates through tree
+ startNode.setState(TriStateCheckBox.State.CHECKED, true);
+
+ // we've found the required node in this path - no further search needed,
+ // so terminate this method
+ return (true);
+ }
+ else {
+ // provided startNode is equals to the first node of the provided tree path -
+ // meaning that at this stage we need to traverse all children of the startNode
+ // and look for the child that would match the next element in the provided tree path
+ //
+ // to do this, produce a new tree path from the provided one that doesn't contain
+ // the first node - then proceed recursively
+ Object[] currentPathComponents = pathFromStartNode.getPath();
+ Object[] reducedPathComponents = new Object[currentPathComponents.length - 1];
+ System.arraycopy(currentPathComponents, 1, reducedPathComponents, 0, currentPathComponents.length - 1);
+
+ Enumeration children = startNode.children();
+ while (children.hasMoreElements()) {
+ TriStateTreeNode currentChild = (TriStateTreeNode)children.nextElement();
+
+ // if recursive call succeeds, no need to iterate any further
+ if (restoreTreePathCheckingSettings(currentChild, new TreePath(reducedPathComponents))) return (true);
+ }
+ }
+ }
+
+ // the startNode doesn't match the the first element in the provided tree path
+ // or no match could be found during recursive search for the node to "check"
+ return (false);
+ }
+
+
+ /**
+ * Expands all paths in this tree.
+ */
+ public void expandAll()
+ {
+ // this simply expands all tree nodes
+ // TODO - this actually "freezes" the UI if there are many nodes in the tree
+ // some better solution to be found (e.g. expand the nodes in the model, then update UI, or similar)
+ for (int i = 0; i < getRowCount(); i++) {
+ instanceOfSelf.expandRow(i);
+ }
+ }
+
+
+ /**
+ * Collapses all paths in this tree.
+ */
+ public void collapseAll()
+ {
+ // this simply collapses all expanded nodes - this is very quick, execute just as it is
+ for (int i = getRowCount() - 1; i >= 0; i--) {
+ instanceOfSelf.collapseRow(i);
+ }
+ }
+
+
+ /**
+ * Removes all nodes in this tree that are unchecked.
+ *
+ * It doesn't iterate through *all* nodes - if some node is
+ * indeed unchecked, it removes that node and any children that
+ * it has (because unchecked node is the root of an unchecked path).
+ */
+ public void removeAllUncheckedNodes()
+ {
+ // get the tree model first - will be used to remove the nodes
+ DefaultTreeModel theTreeModel = (DefaultTreeModel)this.treeModel;
+
+ // remove unchecked nodes
+ List<TreePath> allNodesToRemove = this.getRootsOfUncheckedPaths();
+ for (TreePath p : allNodesToRemove) {
+ theTreeModel.removeNodeFromParent((MutableTreeNode)p.getLastPathComponent());
+ }
+ }
+
+
+ /**
+ * Provides access to the contextual menu of this JTriStateTree.
+ *
+ * @return Reference to the contextual menu.
+ */
+ public JPopupMenu getContextualMenu() {
+ return contextualMenu;
+ }
+
+
+ public void setCheckingEnabled(boolean bCheckingEnabled) {
+ this.bCheckingEnabled = bCheckingEnabled;
+ }
+
+ /**
+ * @return True if the current state of this JTriStateTree
+ * allows making changes to checking of checkboxes
+ * in its nodes.
+ */
+ public boolean isCheckingEnabled() {
+ return bCheckingEnabled;
+ }
+
+
+ /**
+ * @param listener New tree checking listener to register for updates
+ * to tree node selections.
+ */
+ public void addCheckingListener(TriStateTreeCheckingListener listener) {
+ if (listener != null) {
+ this.checkingListeners.add(listener);
+ }
+ }
+
+
+ /**
+ * @param listener Tree checking listener to remove.
+ */
+ public void removeCheckingListener(TriStateTreeCheckingListener listener) {
+ if (listener != null) {
+ this.checkingListeners.remove(listener);
+ }
+ }
+
+
+ /**
+ * Sends a signal to all listeners to check the state of the tree,
+ * as it has changed.
+ */
+ private void notifyCheckingListeners() {
+ for (TriStateTreeCheckingListener listener : this.checkingListeners) {
+ listener.triStateTreeCheckingChanged(instanceOfSelf);
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z
new file mode 100644
index 0000000..8c60d87
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java
new file mode 100644
index 0000000..6d58db3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java
@@ -0,0 +1,67 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Test extends JFrame
+{
+ public Test() {
+ this.setLayout(new BorderLayout());
+
+ this.add(new TriStateCheckBox("LOL", TriStateCheckBox.State.PARTIAL), BorderLayout.NORTH);
+
+ TriStateTreeNode root = new TriStateTreeNode("root");
+ TriStateTreeNode c1 = new TriStateTreeNode("child 1");
+ TriStateTreeNode c2 = new TriStateTreeNode("child 2");
+ TriStateTreeNode c3 = new TriStateTreeNode("child 3");
+
+ TriStateTreeNode c1_1 = new TriStateTreeNode("child 1_1");
+ TriStateTreeNode c1_2 = new TriStateTreeNode("child 1_2");
+ TriStateTreeNode c1_3 = new TriStateTreeNode("child 1_3");
+
+ TriStateTreeNode c2_1 = new TriStateTreeNode("child 2_1");
+ TriStateTreeNode c2_2 = new TriStateTreeNode("child 2_2");
+ TriStateTreeNode c2_3 = new TriStateTreeNode("child 2_3");
+
+ TriStateTreeNode c3_1 = new TriStateTreeNode("child 3_1");
+ TriStateTreeNode c3_2 = new TriStateTreeNode("child 3_2");
+ TriStateTreeNode c3_3 = new TriStateTreeNode("child 3_3");
+
+ TriStateTreeNode c1_1_1 = new TriStateTreeNode("child 1_1_1");
+ TriStateTreeNode c1_1_2 = new TriStateTreeNode("child 1_1_2");
+ TriStateTreeNode c1_1_3 = new TriStateTreeNode("child 1_1_3");
+
+ // adding second level children
+ root.add(c1); root.add(c2); root.add(c3);
+
+ // adding third-level children
+ c1.add(c1_1); c1.add(c1_2); c1.add(c1_3);
+ c2.add(c2_1); c2.add(c2_2); c2.add(c2_3);
+ c3.add(c3_1); c3.add(c3_2); c3.add(c3_3);
+
+ // adding fourth-level children
+ c1_1.add(c1_1_1); c1_1.add(c1_1_2); c1_1.add(c1_1_3);
+
+
+ // NB! important to create the tree when 'root' is already populated with children
+ JTriStateTree tree = new JTriStateTree(root);
+ tree.setRootVisible(false);
+ tree.setShowsRootHandles(true);
+ this.add(new JScrollPane(tree), BorderLayout.CENTER);
+
+ this.setDefaultCloseOperation(EXIT_ON_CLOSE);
+ this.pack();
+ }
+
+
+ public static void main(String[] args) {
+ JFrame a = new Test();
+ a.setVisible(true);
+ }
+
+}
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java
new file mode 100644
index 0000000..361dfc3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java
@@ -0,0 +1,172 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+/*
+ * Taken from: http://72.5.124.102/thread.jspa?threadID=721308&messageID=9955637
+ * Data webpage accessed: 07/February/2010
+ *
+ * Modified by Sergejs Aleksejevs
+ */
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JToggleButton;
+import javax.swing.UIManager;
+
+public class TriStateCheckBox extends JCheckBox
+{
+ private static final long serialVersionUID = 1L;
+
+ public static enum State {
+ CHECKED, UNCHECKED, PARTIAL
+ };
+
+
+ // these colors will be used for painting the 'partial' state of the checkbox -
+ // a gradient painting will be made from top-left to bottom-right
+ private Color partialStateTopLeftColor = Color.GREEN.darker().darker().darker();
+ private Color partialStateBottomRightColor = Color.GREEN.brighter().brighter().brighter();
+
+
+ /**
+ * Creates an initially unselected check box button with no text, no icon.
+ */
+ public TriStateCheckBox() {
+ this(null, State.UNCHECKED);
+ }
+
+ /**
+ * Creates a check box with text and icon, and specifies whether or not it is initially
+ * selected.
+ *
+ * @param text
+ * The text of the check box.
+ * @param initial
+ * The initial state
+ */
+ public TriStateCheckBox(String text, State initial) {
+ super.setText(text);
+ setModel(new TriStateModel(initial));
+ setIcon(new TriStateIcon(this));
+ // some UI settings
+ setRolloverEnabled(true);
+ }
+
+ /**
+ * Set the new state to either CHECKED, PARTIAL or UNCHECKED.
+ */
+ public void setState(State state) {
+ ((TriStateModel) model).setState(state);
+ }
+
+ /**
+ * Return the current state, which is determined by the selection status of the model.
+ */
+ public State getState() {
+ return ((TriStateModel) model).getState();
+ }
+
+ public void setSelected(boolean selected) {
+ ((TriStateModel) model).setSelected(selected);
+ }
+
+ /** The model for the button */
+ private static class TriStateModel extends JToggleButton.ToggleButtonModel {
+
+ private static final long serialVersionUID = 1L;
+ protected State state;
+
+ public TriStateModel(State state) {
+ this.state = state;
+ }
+
+ public boolean isSelected() {
+ return state == State.CHECKED;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ fireStateChanged();
+ }
+
+ public void setPressed(boolean pressed) {
+ if (pressed) {
+ switch (state) {
+ case UNCHECKED:
+ state = State.CHECKED;
+ break;
+ case PARTIAL:
+ state = State.UNCHECKED;
+ break;
+ case CHECKED:
+ state = State.PARTIAL;
+ break;
+ }
+ }
+
+ }
+
+ public void setSelected(boolean selected) {
+ if (selected) {
+ this.state = State.CHECKED;
+ } else {
+ this.state = State.UNCHECKED;
+ }
+ }
+ }
+
+ private class TriStateIcon implements Icon
+ {
+ private Icon checkBoxIcon;
+ private TriStateCheckBox triStateCheckBox;
+ public TriStateIcon(TriStateCheckBox triStateCheckBox) {
+ this.triStateCheckBox = triStateCheckBox;
+ this.checkBoxIcon = UIManager.getIcon("CheckBox.icon");
+
+ return;
+ }
+
+ public final int getIconHeight() {
+ return this.checkBoxIcon.getIconHeight();
+
+ }
+
+ public final int getIconWidth() {
+ return this.checkBoxIcon.getIconWidth();
+
+ }
+
+
+ public void paintIcon(Component c, Graphics g, int x, int y)
+ {
+ checkBoxIcon.paintIcon(triStateCheckBox, g, x, y);
+ if (triStateCheckBox.getState().equals(TriStateCheckBox.State.PARTIAL))
+ {
+ // this is changed to create the gradient paint dynamically every time;
+ // this makes sure that the gradient is relative to the actual position of the checkbox,
+ // rather than in the absolute coordinates of the parent component
+ GradientPaint gradient = new GradientPaint(x, y, partialStateTopLeftColor, x + 8, y + 8, partialStateBottomRightColor, false);
+
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.setPaint(gradient);
+ final int deltaX = 2;
+ final int deltaY = 2;
+ final int xNew = x + deltaX;
+ final int yNew = y + deltaY;
+ final int width = getIconWidth() - 2*deltaX;
+ final int height = getIconHeight() - 2*deltaY;
+ g2d.fillRect(xNew, yNew, width, height);
+ }
+ }
+
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java
new file mode 100644
index 0000000..dd78bf7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.border.Border;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+
+
+/**
+ * Provides a mechanism for rendering tri-state tree nodes.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class TriStateCheckBoxTreeCellRenderer extends DefaultTreeCellRenderer
+{
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected, boolean expanded, boolean leaf, int row,
+ boolean hasFocus)
+ {
+ Border treeNodePanelBorder = null; // will be obtained from default rendering and applied to the new one
+ Color backgroundColor = null; // likewise: will be applied to all constituents of the new rendering
+
+ // obtain the default rendering, we'll then customize it
+ Component defaultRendering = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+
+ // it is most likely that the default rendering will be a JLabel, check just to be safe
+ if (defaultRendering instanceof JLabel)
+ {
+ JLabel defaultRenderedLabel = ((JLabel)defaultRendering);
+
+ // if this is not the case, it kind of undermines the whole purpose
+ // of using this tree cell renderer, but check just to be sure
+ if (value instanceof TriStateTreeNode) {
+ // a state value from within the TriStateTreeNode will be used to
+ // set the correct state in its rendering
+ switch (((TriStateTreeNode)value).getState()) {
+ case CHECKED:
+ if (((TriStateTreeNode)value).getPath().length > 2) {
+ // only allow CHECKED state icon for nodes that are deeper than second
+ // level in the tree - that is for any nodes that do not represent categories
+ // in the tree (root is not shown, so nodes that represent categories are
+ // effectively multiple category "roots" that have actual contents inside them)
+ defaultRenderedLabel.setIcon(ResourceManager.getImageIcon(ResourceManager.TRISTATE_CHECKBOX_CHECKED_ICON));
+ break;
+ }
+ // else -- 'fall through' to PARTIAL icon: this was a CHECKED state for the category node
+ case PARTIAL: defaultRenderedLabel.setIcon(ResourceManager.getImageIcon(ResourceManager.TRISTATE_CHECKBOX_PARTIAL_ICON)); break;
+ case UNCHECKED: defaultRenderedLabel.setIcon(ResourceManager.getImageIcon(ResourceManager.TRISTATE_CHECKBOX_UNCHECKED_ICON)); break;
+ default: defaultRenderedLabel.setIcon(null); break;
+ }
+ }
+ }
+
+ return (defaultRendering);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java
new file mode 100644
index 0000000..e612e54
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java
@@ -0,0 +1,12 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+/**
+ * A simple interface to enable tracking tree checking
+ * changes.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public interface TriStateTreeCheckingListener
+{
+ public void triStateTreeCheckingChanged(JTriStateTree source);
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java
new file mode 100644
index 0000000..248cdf8
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java
@@ -0,0 +1,246 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.util.Enumeration;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+/**
+ * This class models tri-state nodes in the tree. Effectively
+ * it associates a tri-state checkbox with each tree node.
+ *
+ * Useful for partial selections of hierarchical data -
+ * partial selection of a node indicates that some of the
+ * children of that node are selected.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class TriStateTreeNode extends DefaultMutableTreeNode
+{
+ private TriStateCheckBox.State state;
+
+ /**
+ * Creates a regular tree node; associated tri-state checkbox state is set to UNCHECKED.
+ *
+ * @param userObject The object this tree node will represent.
+ */
+ public TriStateTreeNode(Object userObject) {
+ this(userObject, TriStateCheckBox.State.UNCHECKED);
+ }
+
+ /**
+ * Creates a regular tree node; associated tri-state checkbox state is set to the provided <code>initialState</code> value.
+ *
+ * @param userObject The object this tree node will represent.
+ * @param initialState One of the enum values of <code>TriStateCheckBox.State</code>.
+ */
+ public TriStateTreeNode(Object userObject, TriStateCheckBox.State initialState) {
+ super(userObject);
+ this.state = initialState;
+ }
+
+
+ /**
+ * Compares based on the user object, not the state of this node.
+ */
+ public boolean equals(Object other) {
+ if (other instanceof TriStateTreeNode) {
+ return (this.userObject.equals(((TriStateTreeNode)other).userObject));
+ }
+ else {
+ return (false);
+ }
+ }
+
+
+ /**
+ * Sets the state of the current node and (optionally) propagates
+ * those changes through the tree.
+ *
+ * @param state The new state to set - value from <code>TriStateCheckBox.State</code> enum.
+ * @param propagateChangesToRelatedNodes True - to use the tree checking model to
+ * propagate changes of the state of the current tree node to the
+ * other related tree nodes (e.g. all descendants and ancestors) -
+ * up and down the tree hierarchy. False - to only update the current
+ * node and make no changes to the rest of the tree.
+ */
+ public void setState(TriStateCheckBox.State state, boolean propagateChangesToRelatedNodes)
+ {
+ this.state = state;
+
+ // check if the tree checking model should be activated
+ if (propagateChangesToRelatedNodes) {
+ updateStateOfRelatedNodes();
+ }
+ }
+
+
+ /**
+ * Sets the state of the current node.
+ *
+ * @param state The new state to set - value from <code>TriStateCheckBox.State</code> enum.
+ */
+ public void setState(TriStateCheckBox.State state) {
+ setState(state, false);
+ }
+
+
+ public TriStateCheckBox.State getState() {
+ return state;
+ }
+
+
+ /**
+ * Toggles the state of the associated tri-state checkbox.
+ * State transitions are as follows:</br>
+ * <code>
+ * TriStateCheckBox.State.CHECKED -> TriStateCheckBox.State.UNCHECKED
+ * TriStateCheckBox.State.PARTIAL -> TriStateCheckBox.State.UNCHECKED
+ * TriStateCheckBox.State.UNCHECKED -> TriStateCheckBox.State.CHECKED
+ * </code>
+ *
+ * @param propagateChangesToRelatedNodes True - to use the tree checking model to
+ * propagate changes of the state of the current tree node to the
+ * other related tree nodes (e.g. all descendants and ancestors) -
+ * up and down the tree hierarchy. False - to only update the current
+ * node and make no changes to the rest of the tree.
+ * @return The value of the new state.
+ */
+ public TriStateCheckBox.State toggleState(boolean propagateChangesToRelatedNodes)
+ {
+ if (state.equals(TriStateCheckBox.State.CHECKED) || state.equals(TriStateCheckBox.State.PARTIAL)) {
+ state = TriStateCheckBox.State.UNCHECKED;
+ }
+ else if (state.equals(TriStateCheckBox.State.UNCHECKED)) {
+ state = TriStateCheckBox.State.CHECKED;
+ }
+
+ // check if the tree checking model should be activated
+ if (propagateChangesToRelatedNodes) {
+ updateStateOfRelatedNodes();
+ }
+
+ return (state);
+ }
+
+
+ /*
+ * === The tree CHECKING MODEL ===
+ *
+ * Effectively, this defines the way the tree reacts to it's nodes
+ * being checked / unchecked. Only one model is implemented at the
+ * moment, therefore it's not extracted into a separate class, but
+ * remains to be a part of the TriStateTreeNode.
+ *
+ * Could possibly be better placed within the JTriStateTree, rather
+ * than TriStateTreeNode.
+ */
+
+ /**
+ * The entry point - must be invoked to traverse the tree and make
+ * changes to checking states of related tree nodes.
+ */
+ public void updateStateOfRelatedNodes()
+ {
+ updateStateOfAncestors(this.getParent());
+ updateStateOfDescendants(this);
+ }
+
+
+ /**
+ * Recursively visits all ancestors of the <code>parentNode</code>
+ * and decides on their checking states based on the states of their
+ * children nodes.
+ *
+ * @param parentNode Initially - parent node of the current node (i.e. the one,
+ * for which a state update has been made); then updated for
+ * recursive calls.
+ */
+ private void updateStateOfAncestors(TreeNode parentNode)
+ {
+ // reached root of the tree, do nothing - return
+ if (parentNode == null) {
+ return;
+ }
+
+ if (parentNode instanceof TriStateTreeNode) {
+ TriStateTreeNode parentTriStateNode = (TriStateTreeNode)parentNode;
+
+ // explicitly fetch children into a new enumeration - this is
+ // to make sure that we work with the same enumeration, rather
+ // than obtaining a fresh one with every reference to 'parentTriStateNode.children()'
+ Enumeration childNodes = parentTriStateNode.children();
+
+ // go through all the children and count the number of selected ones
+ int iChildrenCount = 0;
+ int iPartiallySelectedChildren = 0;
+ int iSelectedChildren = 0;
+
+ while(childNodes.hasMoreElements()) {
+ Object node = childNodes.nextElement();
+ if (node instanceof TriStateTreeNode) {
+ TriStateTreeNode currentNode = (TriStateTreeNode)node;
+ iChildrenCount++;
+ if (currentNode.getState().equals(TriStateCheckBox.State.CHECKED)) {
+ iSelectedChildren++;
+ }
+ else if (currentNode.getState().equals(TriStateCheckBox.State.PARTIAL)) {
+ iPartiallySelectedChildren++;
+ }
+ }
+ }
+
+
+ // decide on the state of the 'parentNode' based on the checking state of its children
+ if (iSelectedChildren == 0 && iPartiallySelectedChildren == 0) {
+ // no children are selected
+ parentTriStateNode.setState(TriStateCheckBox.State.UNCHECKED);
+ }
+ else if ((iSelectedChildren + iPartiallySelectedChildren) > 0 && iSelectedChildren < iChildrenCount) {
+ // some children are selected (either partially or fully)
+ parentTriStateNode.setState(TriStateCheckBox.State.PARTIAL);
+ }
+ else if (iSelectedChildren > 0 && iSelectedChildren == iChildrenCount) {
+ // all children are selected
+ parentTriStateNode.setState(TriStateCheckBox.State.CHECKED);
+ }
+
+
+ // repeat the same recursively up the hierarchy
+ updateStateOfAncestors(parentTriStateNode.getParent());
+ }
+ }
+
+ /**
+ * Recursively traverses all descendants of the <code>parentNode</code>
+ * to set their checking state to the value of the state of the <code>parentNode</code>.
+ *
+ * @param parentNode Initially - the tree node for which the state
+ * change was made; then updated for recursive calls.
+ */
+ private void updateStateOfDescendants(TriStateTreeNode parentNode)
+ {
+ // explicitly fetch children into a new enumeration - this is
+ // to make sure that we work with the same enumeration, rather
+ // than obtaining a fresh one with every reference to 'parentNode.children()'
+ Enumeration childNodes = parentNode.children();
+
+ // for all child nodes do 2 things:
+ // - set their state as that of the parent;
+ // - repeat the same recursively with their children
+ while(childNodes.hasMoreElements()) {
+ Object node = childNodes.nextElement();
+ if (node instanceof TriStateTreeNode) {
+ TriStateTreeNode currentNode = (TriStateTreeNode) node;
+ currentNode.setState(parentNode.getState());
+ currentNode.updateStateOfDescendants(currentNode);
+ }
+ }
+ }
+
+ /*
+ * === End of CHECKING MODEL implementation.
+ */
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java
new file mode 100644
index 0000000..ac170dc
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java
@@ -0,0 +1,70 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class BioCataloguePerspective implements PerspectiveSPI
+{
+ private MainComponent perspectiveMainComponent;
+ private boolean visible = true;
+
+ public ImageIcon getButtonIcon()
+ {
+ return ResourceManager.getImageIcon(ResourceManager.FAVICON);
+ }
+
+ public InputStream getLayoutInputStream() {
+ return getClass().getResourceAsStream("biocatalogue-perspective.xml");
+ }
+
+ public String getText() {
+ return "Service Catalogue";
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public int positionHint()
+ {
+ // this determines position of perspective in the
+ // bar with perspective buttons (currently makes it the last in
+ // the list)
+ return 40;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+
+ }
+
+ public void update(Element layoutElement) {
+ // TODO Auto-generated method stub
+
+ // Not sure what to do here
+ }
+
+ public void setMainComponent(MainComponent component)
+ {
+ this.perspectiveMainComponent = component;
+ }
+
+ /**
+ * Returns the instance of the main component of this perspective.
+ */
+ public MainComponent getMainComponent()
+ {
+ return this.perspectiveMainComponent;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java
new file mode 100644
index 0000000..eeb1cba
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java
@@ -0,0 +1,285 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.GridLayout;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.ToolTipManager;
+import javax.swing.border.LineBorder;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.ui.BioCatalogueExplorationTab;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+import org.apache.log4j.Logger;
+
+/*
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public final class MainComponent extends JPanel implements UIComponentSPI //, ChangeListener
+{
+// private static final String windowBaseTitle = "BioCatalogue API Demo";
+// private HashMap<String, String> windowTitleMap;
+
+ private MainComponent pluginPerspectiveMainComponent;
+ private final Logger logger = Logger.getLogger(MainComponent.class);
+
+ //private JTabbedPane tpMainTabs;
+ private BioCatalogueExplorationTab jpBioCatalogueExplorationTab;
+// private BioCataloguePluginAbout jpAboutTab;
+
+ public static JFrame dummyOwnerJFrame;
+ static {
+ // this is only to have a nice icon on all Dialog boxes - can be removed at any time
+ dummyOwnerJFrame = new JFrame();
+ dummyOwnerJFrame.setIconImage(ResourceManager.getImageIcon(ResourceManager.FAVICON).getImage());
+ }
+
+
+ /**
+ * This constructor is protected, and so is only available to the classes in its package -
+ * i.e. Taverna integration classes. Other parts of the plugin must use <code>MainComponentFactory.getSharedInstance()</code>
+ * to get the shared instance of this class.
+ */
+ protected MainComponent()
+ {
+ super();
+ initialiseEnvironment();
+ initialisePerspectiveUI();
+ }
+
+
+ // *** Methods implementing UIComponentSPI interface ***
+
+ public ImageIcon getIcon() {
+ return WorkbenchIcons.databaseIcon;
+ }
+
+ @Override
+ public String getName() {
+ return "Service Catalogue Perspective Main Component";
+ }
+
+ public void onDisplay() {
+ // TODO Auto-generated method stub
+ }
+
+ public void onDispose() {
+ // TODO Auto-generated method stub
+ }
+
+ // *** End of methods implementing UIComponentSPI interface ***
+
+
+ private void initialiseEnvironment()
+ {
+ // before anything else - store a reference to self for use during
+ // initialisation of other components
+ pluginPerspectiveMainComponent = this;
+
+
+ // pre-load classes for FlyingSaucer XHTML renderer - this will make sure
+ // that the first time it is used, there will be no delay while classes
+ // are loaded by Java
+ new Thread("class pre-loading") {
+ public void run() {
+ try {
+ Class.forName("org.xhtmlrenderer.simple.FSScrollPane");
+ Class.forName("org.xhtmlrenderer.simple.XHTMLPanel");
+ }
+ catch (ClassNotFoundException e) {
+ logger.error("Problem while pre-loading classes for FlyingSaucer XHTML renderer", e);
+ }
+ }
+ }.start();
+
+
+ // determine what folder is to be used for config files
+ if (!Util.isRunningInTaverna()) {
+ // running outside Taverna, place config file and log into the user's home directory
+ BioCataloguePluginConstants.CONFIG_FILE_FOLDER =
+ new java.io.File(System.getProperty("user.home"), BioCataloguePluginConstants.CONFIG_FILE_FOLDER_WHEN_RUNNING_STANDALONE);
+ BioCataloguePluginConstants.LOG_FILE_FOLDER =
+ new java.io.File(System.getProperty("user.home"), BioCataloguePluginConstants.CONFIG_FILE_FOLDER_WHEN_RUNNING_STANDALONE);
+ }
+
+
+ // this makes sure that tooltips will stay displayed for longer than default
+ ToolTipManager.sharedInstance().setDismissDelay(BioCataloguePluginConstants.DEFAULT_TOOLTIP_DURATION);
+
+ // these components must be accessed by all other components, hence need
+ // to be initialised before any other initialisation is done
+
+// windowTitleMap = new HashMap<String,String>();
+ }
+
+ private void initialisePerspectiveUI()
+ {
+ // set the loader icon to show that the perspective is loading
+ this.setLayout(new GridLayout(1,1));
+ this.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE)));
+
+ new Thread("Initialise Service Catalogue Perspective UI")
+ {
+ public void run() {
+ // create all tabs prior to putting them inside the tabbed pane
+ jpBioCatalogueExplorationTab = new BioCatalogueExplorationTab();
+// jpServiceFilteringTab = new ServiceFilteringTab(pluginPerspectiveMainComponent, client, logger);
+// jpSearchTab = new SearchTab(pluginPerspectiveMainComponent, client, logger);
+// jpAboutTab = new BioCataloguePluginAbout(pluginPerspectiveMainComponent, client, logger);
+
+ // create main tabs
+// tpMainTabs = new JTabbedPane();
+// tpMainTabs.add("Explore BioCatalogue", jpBioCatalogueExplorationTab);
+// tpMainTabs.add("Search", jpSearchTab);
+// tpMainTabs.add("Filter Services", jpServiceFilteringTab);
+// tpMainTabs.add("About", jpAboutTab);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run()
+ {
+ // add main tabs and the status bar into the perspective
+ pluginPerspectiveMainComponent.removeAll();
+ pluginPerspectiveMainComponent.setLayout(new BorderLayout());
+ pluginPerspectiveMainComponent.setBorder(new LineBorder(Color.BLACK));
+ pluginPerspectiveMainComponent.add(jpBioCatalogueExplorationTab, BorderLayout.CENTER);
+
+ // everything is prepared -- need to focus default component on the first tab
+ // (artificially generate change event on the tabbed pane to perform focus request)
+// tpMainTabs.setSelectedIndex(1);
+// tpMainTabs.addChangeListener(pluginPerspectiveMainComponent);
+// tpMainTabs.setSelectedIndex(0);
+ }
+ });
+ }
+ }.start();
+ }
+
+ /**
+ * Determines whether the specified tab is currently active in the main tabbed pane.
+ * @param strTabClassName Class name of the tab which is tested for being active.
+ * @return True if specified tab is currently active.
+ */
+/*
+ private boolean isTabActive(String strTabClassName)
+ {
+ if (tpMainTabs == null) return (false);
+
+ // if an anonymous thread within the main tab component class will call
+ // this method, we want to store the main tab's class, rather than
+ // the full class name of the anonymous worker thread
+ String strCurSelectedTabClassName = tpMainTabs.getSelectedComponent().getClass().getName();
+
+ // get the real class name to match
+ String strBaseClassName = Util.getBaseClassName(strTabClassName);
+
+ // compare the two class names
+ return (strBaseClassName.equals(strCurSelectedTabClassName));
+ }
+ */
+
+
+ /**
+ * This method "selects" the tab represented by the component provided as a parameter.
+ *
+ * @param c Component that represents one of the tabs in the main tabbed pane.
+ * If <code>c</code> is not found within the components of the tabbed pane, nothing will happen.
+ */
+/*
+ public void setTabActive(Component c)
+ {
+ try {
+ tpMainTabs.setSelectedComponent(c);
+ }
+ catch (IllegalArgumentException e) {
+ // do nothing, can't activate component which is not in the tabbed pane
+ }
+ }
+ */
+
+
+ /**
+ * Sets title of the main perspective window for a specified tab,
+ * thus supporting different window titles for different tabs;
+ * new title will only be displayed immediately if the specified tab.
+ *
+ * @param strTabClassName Class name of the tab, for which window title should be updated.
+ * @param strTitle New title to set.
+ */
+/*
+ public void setWindowTitle(String strTabClassName, String strTitle)
+ {
+ // if an anonymous thread within the main tab component class will call
+ // this method, we want to store the main tab's class, rather than
+ // the full class name of the anonymous worker thread
+ String strBaseClassName = Util.getBaseClassName(strTabClassName);
+
+ // store the new title for for the specified tab
+ windowTitleMap.put(strBaseClassName, strTitle);
+
+ // if the specified tab is active, update window title immediately
+ if (isTabActive(strBaseClassName)) displayWindowTitle(strBaseClassName);
+ }
+ */
+
+
+ /**
+ * Displays window title that corresponds to specified tab.
+ *
+ * @param strTabClassName Class name of the tab for which the window title is to be set.
+ */
+/*
+ public void displayWindowTitle(String strTabClassName)
+ {
+ // if an anonymous thread within the main tab component class will call
+ // this method, we want to store the main tab's class, rather than
+ // the full class name of the anonymous worker thread
+ String strBaseClassName = Util.getBaseClassName(strTabClassName);
+
+ if (windowTitleMap.containsKey(strBaseClassName)) {
+ // title for specified tab found - show it
+ // TODO - disabled until this info will be shown in the status bar
+ //this.setTitle(windowBaseTitle + " :: " + windowTitleMap.get(strBaseClassName));
+ }
+ else {
+ // tab not found - display standard title
+ // TODO - disabled until this info will be shown in the status bar
+ //this.setTitle(windowBaseTitle);
+ }
+ }
+ */
+
+ // *** Getters for various components ***
+
+ /**
+ * @return Reference to the component that represents the BioCatalogue Exploration
+ * tab in the BioCatalogue Perspective.
+ */
+ public BioCatalogueExplorationTab getBioCatalogueExplorationTab() {
+ return (this.jpBioCatalogueExplorationTab);
+ }
+
+ // *** Callbacks for ChangeListener interface ***
+
+/* public void stateChanged(ChangeEvent e)
+ {
+ if (e.getSource().equals(tpMainTabs)) {
+ // will get called when selected tab changes - need to focus default component in the active tab
+ // and also set a correct window title
+ if (tpMainTabs.getSelectedComponent() instanceof HasDefaultFocusCapability) {
+ ((HasDefaultFocusCapability)tpMainTabs.getSelectedComponent()).focusDefaultComponent();
+ this.displayWindowTitle(tpMainTabs.getSelectedComponent().getClass().getName());
+ }
+ }
+ }*/
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java
new file mode 100644
index 0000000..c213600
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java
@@ -0,0 +1,46 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class MainComponentFactory implements UIComponentFactorySPI
+{
+ // this is to ensure that the whole perspective is not re-created
+ // each time it is being activated in Taverna, rather it will only
+ // happen once during the execution
+ private static MainComponent mainPerspectiveComponent = null;
+
+ public static MainComponent getSharedInstance()
+ {
+ // double-check on existence of the 'mainPerspectiveComponent' ensures
+ // that it is really created only once
+ if (mainPerspectiveComponent == null) {
+ synchronized(MainComponentFactory.class) {
+ if (mainPerspectiveComponent == null) {
+ mainPerspectiveComponent = new MainComponent();
+ }
+ }
+ }
+ return (mainPerspectiveComponent);
+ }
+
+ public UIComponentSPI getComponent() {
+ return (getSharedInstance());
+ }
+
+
+ public ImageIcon getIcon() {
+ return WorkbenchIcons.databaseIcon;
+ }
+
+ public String getName() {
+ return "Service Catalogue Main Component Factory";
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java
new file mode 100644
index 0000000..6c2e844
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java
@@ -0,0 +1,49 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import com.thoughtworks.xstream.XStream;
+
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config.BioCataloguePluginConfiguration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.BioCatalogueServiceProvider;
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class MainComponentShutdownHook implements ShutdownSPI
+{
+ public int positionHint()
+ {
+ // all custom plugins are suggested to return a value of > 100;
+ // this affects when in the termination process will this plugin
+ // be shutdown;
+ return 100;
+ }
+
+ public boolean shutdown()
+ {
+ // Do not save service providers in BioCatalogue's conf file - they should be saved by Taverna together with
+ // other service providers
+
+// // store services that were added to the Service Panel - both REST and SOAP
+// XStream xstream = new XStream();
+//
+// BioCataloguePluginConfiguration configuration = BioCataloguePluginConfiguration.getInstance();
+//
+// configuration.setProperty(
+// BioCataloguePluginConfiguration.SOAP_OPERATIONS_IN_SERVICE_PANEL,
+// xstream.toXML(BioCatalogueServiceProvider.getRegisteredSOAPOperations()));
+// configuration.setProperty(
+// BioCataloguePluginConfiguration.REST_METHODS_IN_SERVICE_PANEL,
+// xstream.toXML(BioCatalogueServiceProvider.getRegisteredRESTMethods()));
+//
+// // save all the plugin's configuration
+// configuration.store();
+//
+//
+// // close API operation log
+// MainComponentFactory.getSharedInstance().getBioCatalogueClient().getAPILogWriter().close();
+//
+ return true;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java
new file mode 100644
index 0000000..28a52c7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java
@@ -0,0 +1,68 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+
+import javax.swing.JFrame;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class TestJFrameForLocalLaunch extends JFrame implements ComponentListener
+{
+ private static final int DEFAULT_POSITION_X = 225;
+ private static final int DEFAULT_POSITION_Y = 150;
+
+ private static final int DEFAULT_WIDTH = 800;
+ private static final int DEFAULT_HEIGHT = 500;
+
+
+ private TestJFrameForLocalLaunch()
+ {
+ // set window title and icon
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setTitle("Service Catalogue API Demo"/* TODO - windowBaseTitle */);
+ this.setIconImage(ResourceManager.getImageIcon(ResourceManager.FAVICON).getImage());
+ this.addComponentListener(this);
+
+ // get content pane
+ Container contentPane = this.getContentPane();
+ contentPane.setLayout(new BorderLayout());
+
+ // put main tabs into the content pane
+ contentPane.add(MainComponentFactory.getSharedInstance(), BorderLayout.CENTER);
+
+ this.pack();
+ }
+
+
+ // *** Callbacks for ComponentListener interface ***
+
+ public void componentResized(ComponentEvent e) { /* do nothing */ }
+ public void componentMoved(ComponentEvent e) { /* do nothing */ }
+ public void componentHidden(ComponentEvent e) { /* do nothing */ }
+ public void componentShown(ComponentEvent e) {
+ this.setLocation(DEFAULT_POSITION_X, DEFAULT_POSITION_Y);
+ }
+
+
+ /**
+ * This is a simple test class for launching BioCatalogue perspective
+ * from outside Taverna. At some point it will be not usable anymore,
+ * when proper integration of BioCatalogue plugin is made.
+ *
+ * @author Sergejs Aleksejevs
+ */
+ public static void main(String[] args)
+ {
+ TestJFrameForLocalLaunch standaloneFrame = new TestJFrameForLocalLaunch();
+ standaloneFrame.setMinimumSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
+ standaloneFrame.setVisible(true);
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/Integration.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/Integration.java
new file mode 100644
index 0000000..ec792d4
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/Integration.java
@@ -0,0 +1,518 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestService;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapService;
+import org.biocatalogue.x2009.xml.rest.Service.Variants;
+
+import net.sf.taverna.biocatalogue.model.HTTPMethodInterpreter;
+import net.sf.taverna.biocatalogue.model.HTTPMethodInterpreter.UnsupportedHTTPMethodException;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.SoapOperationPortIdentity;
+import net.sf.taverna.biocatalogue.model.SoapProcessorIdentity;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.activities.rest.RESTActivity;
+import net.sf.taverna.t2.activities.rest.RESTActivity.HTTP_METHOD;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivity;
+import net.sf.taverna.t2.activities.wsdl.servicedescriptions.WSDLServiceDescription;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.BioCatalogueRESTServiceProvider;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.BioCatalogueWSDLOperationServiceProvider;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.RESTFromBioCatalogueServiceDescription;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.WSDLOperationFromBioCatalogueServiceDescription;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.Port;
+import net.sf.taverna.t2.workflowmodel.Processor;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityInputPort;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+import net.sf.taverna.t2.workflowmodel.utils.Tools;
+
+/**
+ * This class contains helpers for deeper integration with Taverna UI.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class Integration
+{
+ private static final Logger logger = Logger.getLogger(Integration.class);
+
+
+ // deny instantiation of this class
+ private Integration() { }
+
+
+ /**
+ * Adds a processor to the current workflow.
+ * The processor is specified by WSDL location and the operation name.
+ *
+ * @param processorResource Resource to add to the current workflow.
+ * @return Outcome of inserting the processor into the current workflow as a
+ * HTML-formatted string (with no opening and closing HTML tags).
+ */
+ public static JComponent insertProcessorIntoCurrentWorkflow(ResourceLink processorResource)
+ {
+ // check if this type of resource can be added to workflow diagram
+ TYPE resourceType = Resource.getResourceTypeFromResourceURL(processorResource.getHref());
+ if (resourceType.isSuitableForAddingToWorkflowDiagram()) {
+ switch (resourceType) {
+ case SOAPOperation:
+ SoapOperation soapOp = (SoapOperation) processorResource;
+ try {
+ SoapService soapService = BioCatalogueClient.getInstance().
+ getBioCatalogueSoapService(soapOp.getAncestors().getSoapService().getHref());
+
+ try {
+ WSDLServiceDescription myServiceDescription = new WSDLServiceDescription();
+ myServiceDescription.setOperation(soapOp.getName());
+ myServiceDescription.setUse("literal"); // or "encoded"
+ myServiceDescription.setStyle("document"); // or "rpc"
+ myServiceDescription.setURI(new URI(soapService.getWsdlLocation()));
+ myServiceDescription.setDescription(StringEscapeUtils.escapeHtml(soapService.getDescription())); // TODO - not sure where this is used
+
+ if (WorkflowView.importServiceDescription(myServiceDescription, false) != null) {
+ return (new JLabel("Selected " + TYPE.SOAPOperation.getTypeName() + " was successfully added to the current workflow",
+ ResourceManager.getImageIcon(ResourceManager.TICK_ICON), JLabel.CENTER));
+ }
+ else {
+ return (new JLabel("<html><center>Taverna was unable to add selected " + TYPE.SOAPOperation.getTypeName() +
+ " as a service to the current workflow.<br>This could be because the service is currently not accessible.</center></html>",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+ }
+ catch (URISyntaxException e)
+ {
+ logger.error("Couldn't add " + TYPE.SOAPOperation + " to the current workflow", e);
+ return (new JLabel("<html>Could not add the selected " + TYPE.SOAPOperation.getTypeName() + " to the current workflow.<br>" +
+ "Log file will containt additional details about this error.</html>",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+ }
+ catch (Exception e) {
+ logger.error("Failed to fetch required details to add this " + TYPE.SOAPOperation + " into the current workflow.", e);
+ return (new JLabel("<html>Failed to fetch required details to add this<br>" +
+ TYPE.SOAPOperation.getTypeName() + " into the current workflow.</html>",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+ case RESTMethod:
+ // received object may only contain limited data, therefore need to fetch full details first
+ try {
+ RestMethod restMethod = BioCatalogueClient.getInstance().
+ getBioCatalogueRestMethod(processorResource.getHref());
+
+ // actual import of the service into the workflow
+ RESTFromBioCatalogueServiceDescription restServiceDescription = createRESTServiceDescriptionFromRESTMethod(restMethod);
+ WorkflowView.importServiceDescription(restServiceDescription, false);
+
+ // prepare result of the operation to be shown in the the waiting dialog window
+ String warnings = extractWarningsFromRESTServiceDescription(restServiceDescription, false);
+ JLabel outcomes = new JLabel("<html>Selected " + TYPE.RESTMethod.getTypeName() + " was successfully added to the current workflow" + warnings + "</html>",
+ ResourceManager.getImageIcon(warnings.length() > 0 ? ResourceManager.WARNING_ICON : ResourceManager.TICK_ICON),
+ JLabel.CENTER);
+ outcomes.setIconTextGap(20);
+ return (outcomes);
+ }
+ catch (UnsupportedHTTPMethodException e) {
+ logger.error(e);
+ return (new JLabel(e.getMessage(), ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+ catch (Exception e) {
+ logger.error("Failed to fetch required details to add this " + TYPE.RESTMethod + " as a service to the current workflow.", e);
+ return (new JLabel("<html>Failed to fetch required details to add this " + TYPE.RESTMethod.getTypeName() + "<br>" +
+ "as a service to the current workflow.</html>",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+ // type not currently supported, but maybe in the future?
+ default: return (new JLabel("Adding " + resourceType.getCollectionName() + " to the current workflow is not yet possible",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+ }
+
+ // definitely not supported type
+ return (new JLabel("<html>It is not possible to add resources of the provided type<br>" +
+ "into the current workflow.</html>",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+
+ /**
+ *
+ * @param processorResource
+ * @return Outcome of inserting the processor into the current workflow as a
+ * HTML-formatted string (with no opening and closing HTML tags).
+ */
+ public static JComponent insertProcesorIntoServicePanel(ResourceLink processorResource)
+ {
+ // check if this type of resource can be added to Service Panel
+ TYPE resourceType = Resource.getResourceTypeFromResourceURL(processorResource.getHref());
+ if (resourceType.isSuitableForAddingToServicePanel()) {
+ switch (resourceType) {
+ case SOAPOperation:
+ SoapOperation soapOp = (SoapOperation) processorResource;
+ try {
+ SoapService soapService = BioCatalogueClient.getInstance().
+ getBioCatalogueSoapService(soapOp.getAncestors().getSoapService().getHref());
+ SoapOperationIdentity soapOpId = new SoapOperationIdentity(soapService.getWsdlLocation(), soapOp.getName(), StringEscapeUtils.escapeHtml(soapOp.getDescription()));
+ WSDLOperationFromBioCatalogueServiceDescription wsdlOperationDescription = new WSDLOperationFromBioCatalogueServiceDescription(soapOpId);
+ BioCatalogueWSDLOperationServiceProvider.registerWSDLOperation(wsdlOperationDescription, null);
+
+ return (new JLabel("Selected SOAP operation has been successfully added to the Service Panel.",
+ ResourceManager.getImageIcon(ResourceManager.TICK_ICON), JLabel.CENTER));
+ }
+ catch (Exception e) {
+ logger.error("Failed to fetch required details to add this SOAP service into the Service Panel.", e);
+ return (new JLabel("Failed to fetch required details to add this " +
+ "SOAP service into the Service Panel.", ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+ case RESTMethod:
+ try {
+ // received object may only contain limited data, therefore need to fetch full details first
+ RestMethod restMethod = BioCatalogueClient.getInstance().
+ getBioCatalogueRestMethod(processorResource.getHref());
+ RESTFromBioCatalogueServiceDescription restServiceDescription = createRESTServiceDescriptionFromRESTMethod(restMethod);
+
+ // actual insertion of the REST method into Service Panel
+ BioCatalogueRESTServiceProvider.registerNewRESTMethod(restServiceDescription, null);
+
+ // prepare result of the operation to be shown in the the waiting dialog window
+ String warnings = extractWarningsFromRESTServiceDescription(restServiceDescription, true);
+ JLabel outcomes = new JLabel("<html>Selected REST method has been successfully added to the Service Panel" + warnings + "</html>",
+ ResourceManager.getImageIcon(warnings.length() > 0 ? ResourceManager.WARNING_ICON : ResourceManager.TICK_ICON),
+ JLabel.CENTER);
+ outcomes.setIconTextGap(20);
+ return (outcomes);
+ }
+ catch (UnsupportedHTTPMethodException e) {
+ logger.error(e);
+ return (new JLabel(e.getMessage(), ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+ catch (Exception e) {
+ logger.error("Failed to fetch required details to add this REST service into the Service Panel.", e);
+ return (new JLabel("Failed to fetch required details to add this " +
+ "REST service into the Service Panel.", ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+ // type not currently supported, but maybe in the future?
+ default: return (new JLabel("Adding " + resourceType.getCollectionName() + " to the Service Panel is not yet possible",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+ }
+
+ // definitely not supported type
+ return (new JLabel("<html>It is not possible to add resources of the provided type<br>" +
+ "into the Service Panel.</html>",
+ ResourceManager.getImageIcon(ResourceManager.ERROR_ICON), JLabel.CENTER));
+ }
+
+ /**
+ * Inserts all operations of the given parent SOAP or REST Web service resource link
+ * into Service Panel. Works for SOAP operations only at the moment.
+ *
+ * @return Outcome of inserting operations into Service Panel as a
+ * HTML-formatted string (with no opening and closing HTML tags).
+ */
+ public static JComponent insertAllOperationsIntoServicePanel(ResourceLink serviceResource)
+ {
+ // Check if this type of resource is a parent SOAP Web service
+ // whose operations can be added to the Service Panel
+ TYPE resourceType = Resource
+ .getResourceTypeFromResourceURL(serviceResource.getHref());
+
+ Service service = null;
+ if (resourceType == TYPE.SOAPOperation) {
+ SoapService soapService = ((SoapOperation) serviceResource)
+ .getAncestors().getSoapService();
+
+ // Get the WSDL URL of the SOAP service
+ String wsdlURL = soapService.getWsdlLocation();
+
+ // Import this WSDL into Service panel - it will add all
+ // of
+ // its operations
+ if (BioCatalogueWSDLOperationServiceProvider.registerWSDLService(
+ wsdlURL, null)) {
+ return (new JLabel(
+ "Operation(s) of the SOAP service have been successfully added to the Service Panel.",
+ ResourceManager.getImageIcon(ResourceManager.TICK_ICON),
+ JLabel.CENTER));
+ } else {
+ return (new JLabel(
+ "Failed to insert the operations of the SOAP service "
+ + " to the Service Panel.", ResourceManager
+ .getImageIcon(ResourceManager.ERROR_ICON),
+ JLabel.CENTER));
+ }
+ } else if (resourceType == TYPE.RESTMethod) {
+ RestService restService = ((RestMethod) serviceResource)
+ .getAncestors().getRestService();
+ for (RestMethod method : restService.getMethods().getRestMethodList()) {
+
+ }
+ }
+
+ return (new JLabel(
+ "<html>It is not possible to add resources of the provided type<br>"
+ + "into the Service Panel.</html>", ResourceManager
+ .getImageIcon(ResourceManager.ERROR_ICON),
+ JLabel.CENTER));
+ }
+
+ /**
+ * Instantiates a {@link RESTFromBioCatalogueServiceDescription} object from the {@link RestMethod}
+ * XML data obtained from BioCatalogue API.
+ *
+ * @param restMethod
+ * @return
+ */
+ public static RESTFromBioCatalogueServiceDescription createRESTServiceDescriptionFromRESTMethod(RestMethod restMethod) throws UnsupportedHTTPMethodException
+ {
+ // if the type of the HTTP method is not supported, an exception will be throws
+ HTTP_METHOD httpMethod = HTTPMethodInterpreter.getHTTPMethodForRESTActivity(restMethod.getHttpMethodType());
+
+ RESTFromBioCatalogueServiceDescription restServiceDescription = new RESTFromBioCatalogueServiceDescription();
+ restServiceDescription.setServiceName(Resource.getDisplayNameForResource(restMethod));
+ restServiceDescription.setDescription(StringEscapeUtils.escapeHtml(restMethod.getDescription()));
+ restServiceDescription.setHttpMethod(httpMethod);
+ restServiceDescription.setURLSignature(restMethod.getUrlTemplate());
+
+ int outputRepresentationCount = restMethod.getOutputs().getRepresentations().getRestRepresentationList().size();
+ if (outputRepresentationCount > 0) {
+ if (outputRepresentationCount > 1) {
+ restServiceDescription.getDataWarnings().add(RESTFromBioCatalogueServiceDescription.AMBIGUOUS_ACCEPT_HEADER_VALUE);
+ }
+ restServiceDescription.setAcceptHeaderValue(restMethod.getOutputs().getRepresentations().getRestRepresentationList().get(0).getContentType());
+ }
+ else {
+ restServiceDescription.getDataWarnings().add(RESTFromBioCatalogueServiceDescription.DEFAULT_ACCEPT_HEADER_VALUE);
+ }
+
+ int inputRepresentationCount = restMethod.getInputs().getRepresentations().getRestRepresentationList().size();
+ if (inputRepresentationCount > 0) {
+ if (inputRepresentationCount > 1) {
+ restServiceDescription.getDataWarnings().add(RESTFromBioCatalogueServiceDescription.AMBIGUOUS_CONTENT_TYPE_HEADER_VALUE);
+ }
+ restServiceDescription.setOutgoingContentType(restMethod.getInputs().getRepresentations().getRestRepresentationList().get(0).getContentType());
+ }
+ else if (RESTActivity.hasMessageBodyInputPort(httpMethod)) {
+ restServiceDescription.getDataWarnings().add(RESTFromBioCatalogueServiceDescription.DEFAULT_CONTENT_TYPE_HEADER_VALUE);
+ }
+
+ return (restServiceDescription);
+ }
+
+
+ /**
+ * @param restServiceDescription {@link RESTFromBioCatalogueServiceDescription} to process.
+ * @param addingToServicePanel <code>true</code> indicates that the warning messages
+ * will assume that the processor is added to the service panel;
+ * <code>false</code> would mean that the processor is added to
+ * the current workflow.
+ * @return An HTML-formatted string (with no opening-closing HTML tags) that lists
+ * any warnings that have been recorded during the {@link RESTFromBioCatalogueServiceDescription}
+ * object creation. Empty string will be returned if there are no warnings.
+ */
+ public static String extractWarningsFromRESTServiceDescription(RESTFromBioCatalogueServiceDescription restServiceDescription,
+ boolean addingToServicePanel)
+ {
+ String messageSuffix = addingToServicePanel ?
+ " once you add it into the workflow" :
+ "";
+
+ String warnings = "";
+ if (restServiceDescription.getDataWarnings().contains(RESTFromBioCatalogueServiceDescription.AMBIGUOUS_ACCEPT_HEADER_VALUE)) {
+ warnings += "<br><br>Service Catalogue description of this REST method contains more than one<br>" +
+ "representation of the method's outputs - the first one was used.<br>" +
+ "Please check value of the 'Accept' header in the configuration<br>" +
+ "of the imported service" + messageSuffix + ".";
+ }
+ else if (restServiceDescription.getDataWarnings().contains(RESTFromBioCatalogueServiceDescription.DEFAULT_ACCEPT_HEADER_VALUE)) {
+ warnings += "<br><br>Service Catalogue description of this REST method does not contain any<br>" +
+ "representations of the method's outputs - default value was used.<br>" +
+ "Please check value of the 'Accept' header in the configuration<br>" +
+ "of the imported service" + messageSuffix + ".";
+ }
+
+ if (restServiceDescription.getDataWarnings().contains(RESTFromBioCatalogueServiceDescription.AMBIGUOUS_CONTENT_TYPE_HEADER_VALUE)) {
+ warnings += "<br><br>Service Catalogue description of this REST method contains more than one<br>" +
+ "representation of the method's input data - the first one was used.<br>" +
+ "Please check value of the 'Content-Type' header in the configuration<br>" +
+ "of the imported service" + messageSuffix + ".";
+ }
+ else if (restServiceDescription.getDataWarnings().contains(RESTFromBioCatalogueServiceDescription.DEFAULT_CONTENT_TYPE_HEADER_VALUE)) {
+ warnings += "<br><br>Service Catalogue description of this REST method does not contain any<br>" +
+ "representations of the method's input data - default value was used.<br>" +
+ "Please check value of the 'Content-Type' header in the configuration<br>" +
+ "of the imported service" + messageSuffix + ".";
+ }
+
+ if (warnings.length() > 0) {
+ warnings = "<br><br>WARNINGS:" + warnings;
+ }
+
+ return (warnings);
+ }
+
+
+
+ /**
+ * @param activityPort Probably comes from contextual selection - must be either
+ * ActivityInputPort or ActivityOutputPort.
+ * @return SOAP input / output port details (WSDL location, operation name, port name) from
+ * ActivityInputPort/ActivityOutputPort which is obtained from contextual selection in the Dataflow.
+ */
+ public static <T extends Port> SoapOperationPortIdentity extractSoapOperationPortDetailsFromActivityInputOutputPort(T activityPort)
+ {
+ // check that we have the correct instance of Port here - either ActivityInputPort or ActivityOutputPort
+ boolean hasInputPort;
+ if (activityPort instanceof ActivityInputPort) {
+ hasInputPort = true;
+ }
+ else if (activityPort instanceof ActivityOutputPort) {
+ hasInputPort = false;
+ }
+ else {
+ // ERROR - wrong type supplied
+ return new SoapOperationPortIdentity("Activity port from the contextual selection was not of correct type. Impossible to create preview.");
+ }
+
+ // get parent processor details
+ Dataflow currentDataflow = FileManager.getInstance().getCurrentDataflow();
+ Collection<Processor> processors = null;
+ if (hasInputPort) {
+ processors = Tools.getProcessorsWithActivityInputPort(currentDataflow, (ActivityInputPort)activityPort);
+ }
+ else {
+ processors = Tools.getProcessorsWithActivityOutputPort(currentDataflow, (ActivityOutputPort)activityPort);
+ }
+
+ // TODO - doesn't take into account that it's possible to have several
+ SoapOperationIdentity soapOperationDetails = extractSoapOperationDetailsFromProcessor(processors.toArray(new Processor[]{})[0]);
+
+ // if no error happened, add port details and return
+ if (!soapOperationDetails.hasError()) {
+ return (new SoapOperationPortIdentity(soapOperationDetails.getWsdlLocation(),
+ soapOperationDetails.getOperationName(),
+ activityPort.getName(), hasInputPort));
+ }
+ else {
+ // error...
+ return (new SoapOperationPortIdentity(soapOperationDetails.getErrorDetails()));
+ }
+ }
+
+
+ /**
+ * Uses contextual selection to extract WSDL location and operation name of the
+ * currently selected processor within the Design view of current workflow.
+ *
+ * @param contextualSelection Selection that was made in the Design view.
+ * @return Details of the SOAP operation that acts as a processor wrapped into
+ * this single instance. If any problems occurred while performing
+ * contextual selection analysis, these are also recorded into the same
+ * instance - before using the returned value the caller must check
+ * <code>SoapOperationIdentity.hasError()</code> value.
+ */
+ public static SoapOperationIdentity extractSoapOperationDetailsFromProcessorContextualSelection(ContextualSelection contextualSelection)
+ {
+ if (!(contextualSelection.getSelection() instanceof Processor)) {
+ return (new SoapOperationIdentity("ERROR: It is only possible to extract " +
+ "SOAP operation details from the service."));
+ }
+
+ // now we know it's a Processor
+ Processor processor = (Processor)contextualSelection.getSelection();
+ return (extractSoapOperationDetailsFromProcessor(processor));
+ }
+
+
+ /**
+ * Worker method for <code>extractSoapOperationDetailsFromProcessorContextualSelection()</code>.
+ *
+ * @param processor
+ * @return
+ */
+ public static SoapOperationIdentity extractSoapOperationDetailsFromProcessor(Processor processor)
+ {
+ List<? extends Activity> activityList = (List<? extends Activity>) processor.getActivityList();
+
+ if (activityList == null || activityList.size() == 0) {
+ return (new SoapOperationIdentity("ERROR: Selected processor doesn't have any activities - " +
+ "impossible to extract SOAP operation details."));
+ }
+ else {
+ // take only the first activity - TODO: figure out what should be done here...
+ Activity activity = activityList.get(0);
+ if (activity instanceof WSDLActivity) {
+ WSDLActivity a = (WSDLActivity)activity;
+ return (new SoapOperationIdentity(a.getConfiguration().getWsdl(), a.getConfiguration().getOperation(), null));
+ }
+ else {
+ return (new SoapOperationIdentity("Service Catalogue integration only works with WSDL Activities at the moment"));
+ }
+ }
+ }
+
+
+ /**
+ * @param contextualSelection
+ * @return A list of all WSDL activities (the only supported processors by BioCatalogue plugin for now).
+ */
+ public static List<SoapProcessorIdentity> extractSupportedProcessorsFromDataflow(ContextualSelection contextualSelection)
+ {
+ // check that there was a correct contextual selection
+ if (!(contextualSelection.getSelection() instanceof Dataflow)) {
+ logger.error("It is only possible to extract supported services from the workflow.");
+ return (new ArrayList<SoapProcessorIdentity>());
+ }
+
+ // first extract all processors
+ Dataflow dataflow = (Dataflow)contextualSelection.getSelection();
+ List<? extends Processor> allProcessors = dataflow.getEntities(Processor.class);
+
+ // now filter out any processors that are not WSDL activities
+ List<SoapProcessorIdentity> supportedProcessors = new ArrayList<SoapProcessorIdentity>();
+ for (Processor proc : allProcessors) {
+ List<? extends Activity> activityList = (List<? extends Activity>) proc.getActivityList();
+ if (activityList != null && activityList.size() > 0) {
+ // take only the first activity - TODO: figure out what should be done here...
+ Activity activity = activityList.get(0);
+ if (activity instanceof WSDLActivity) {
+ WSDLActivity a = (WSDLActivity)activity;
+ supportedProcessors.add(new SoapProcessorIdentity(a.getConfiguration().getWsdl(),
+ a.getConfiguration().getOperation(),
+ proc.getLocalName()));
+ }
+ }
+ }
+
+ // return all found processors
+ return (supportedProcessors);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfiguration.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfiguration.java
new file mode 100644
index 0000000..3985ac5
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfiguration.java
@@ -0,0 +1,68 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+
+/**
+ *
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCataloguePluginConfiguration extends AbstractConfigurable
+{
+ public static final String SERVICE_CATALOGUE_BASE_URL = "ServiceCatalogue_Base_URL";
+ public static final String SOAP_OPERATIONS_IN_SERVICE_PANEL = "SOAP_Operations_in_Service_Panel";
+ public static final String REST_METHODS_IN_SERVICE_PANEL = "REST_Methods_in_Service_Panel";
+
+
+ private static class Singleton {
+ private static BioCataloguePluginConfiguration instance = new BioCataloguePluginConfiguration();
+ }
+
+ // private static Logger logger = Logger.getLogger(MyExperimentConfiguration.class);
+
+ private Map<String, String> defaultPropertyMap;
+
+
+ public static BioCataloguePluginConfiguration getInstance() {
+ return Singleton.instance;
+ }
+
+ public String getCategory() {
+ return "general";
+ }
+
+ public Map<String,String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<String,String>();
+ defaultPropertyMap.put(SERVICE_CATALOGUE_BASE_URL, BioCatalogueClient.DEFAULT_API_LIVE_SERVER_BASE_URL);
+ }
+ return defaultPropertyMap;
+ }
+
+ public String getDisplayName() {
+ return "Service catalogue";
+ }
+
+ public String getFilePrefix() {
+ return "ServiceCatalogue";
+ }
+
+ public String getUUID() {
+ return "4daac25c-bd56-4f90-b909-1e49babe5197";
+ }
+
+
+ /**
+ * Just a "proxy" method - {@link AbstractConfigurable#store()}
+ * is not visible to the users of instances of this class otherwise.
+ */
+ public void store() {
+ super.store();
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfigurationPanel.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfigurationPanel.java
new file mode 100644
index 0000000..a83a223
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfigurationPanel.java
@@ -0,0 +1,448 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProxySelector;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+//import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.log4j.Logger;
+import org.jdom.Attribute;
+import org.jdom.Document;
+import org.jdom.input.SAXBuilder;
+
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class BioCataloguePluginConfigurationPanel extends JPanel
+{
+ public static final String APPLICATION_XML_MIME_TYPE = "application/xml";
+
+ public static String PROXY_HOST = "http.proxyHost";
+ public static String PROXY_PORT = "http.proxyPort";
+ public static String PROXY_USERNAME = "http.proxyUser";
+ public static String PROXY_PASSWORD = "http.proxyPassword";
+
+ // 1.0.0b and higher until the first digit changes, as according to "Semantic Versioning"
+ // from http://www.biocatalogue.org/wiki/doku.php?id=public:api:changelog
+ // "Major version X (X.y.z | X > 0) MUST be incremented if any backwards
+ // incompatible changes are introduced to the public API. It MAY include minor and patch level changes."
+ public static String[] MIN_SUPPORTED_BIOCATALOGUE_API_VERSION = {"1", "1", "0"}; // major, minor and patch versions
+ public static String API_VERSION = "apiVersion";
+
+ private BioCataloguePluginConfiguration configuration =
+ BioCataloguePluginConfiguration.getInstance();
+
+
+ // UI elements
+ JTextField tfBioCatalogueAPIBaseURL;
+
+ private Logger logger = Logger.getLogger(BioCataloguePluginConfigurationPanel.class);
+
+ public BioCataloguePluginConfigurationPanel() {
+ initialiseUI();
+ resetFields();
+ }
+
+ private void initialiseUI()
+ {
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.weightx = 1.0;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ JTextArea taDescription = new JTextArea("Configure the Service Catalogue integration functionality");
+ taDescription.setFont(taDescription.getFont().deriveFont(Font.PLAIN, 11));
+ taDescription.setLineWrap(true);
+ taDescription.setWrapStyleWord(true);
+ taDescription.setEditable(false);
+ taDescription.setFocusable(false);
+ taDescription.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ this.add(taDescription, c);
+
+
+ c.gridy++;
+ c.insets = new Insets(20, 0, 0, 0);
+ JLabel jlBioCatalogueAPIBaseURL = new JLabel("Base URL of the Service Catalogue instance to connect to:");
+ this.add(jlBioCatalogueAPIBaseURL, c);
+
+ c.gridy++;
+ c.insets = new Insets(0, 0, 0, 0);
+ tfBioCatalogueAPIBaseURL = new JTextField();
+ this.add(tfBioCatalogueAPIBaseURL, c);
+
+
+ c.gridy++;
+ c.insets = new Insets(30, 0, 0, 0);
+ // We are not removing BioCatalogue services from its config panel any more -
+ // they are being handled by the Taverna's Service Registry
+// JButton bForgetStoredServices = new JButton("Forget services added to Service Panel by BioCatalogue Plugin");
+// bForgetStoredServices.addActionListener(new ActionListener() {
+// public void actionPerformed(ActionEvent e)
+// {
+// int response = JOptionPane.showConfirmDialog(null, // no way T2ConfigurationFrame instance can be obtained to be used as a parent...
+// "Are you sure you want to clear all SOAP operations and REST methods\n" +
+// "that were added to the Service Panel by the BioCatalogue Plugin?\n\n" +
+// "This action is permanent is cannot be undone.\n\n" +
+// "Do you want to proceed?", "BioCatalogue Plugin", JOptionPane.YES_NO_OPTION);
+//
+// if (response == JOptionPane.YES_OPTION)
+// {
+// BioCatalogueServiceProvider.clearRegisteredServices();
+// JOptionPane.showMessageDialog(null, // no way T2ConfigurationFrame instance can be obtained to be used as a parent...
+// "Stored services have been successfully cleared, but will remain\n" +
+// "being shown in Service Panel during this session.\n\n" +
+// "They will not appear in the Service Panel after you restart Taverna.",
+// "BioCatalogue Plugin", JOptionPane.INFORMATION_MESSAGE);
+// }
+// }
+// });
+// this.add(bForgetStoredServices, c);
+
+
+ JButton bLoadDefaults = new JButton("Load Defaults");
+ bLoadDefaults.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ loadDefaults();
+ }
+ });
+
+ JButton bReset = new JButton("Reset");
+ bReset.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ resetFields();
+ }
+ });
+
+ JButton bApply = new JButton("Apply");
+ bApply.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ applyChanges();
+ }
+ });
+
+ JPanel jpActionButtons = new JPanel();
+ jpActionButtons.add(bLoadDefaults);
+ jpActionButtons.add(bReset);
+ jpActionButtons.add(bApply);
+ c.insets = new Insets(30, 0, 0, 0);
+ c.gridy++;
+ c.weighty = 1.0;
+ this.add(jpActionButtons, c);
+ }
+
+
+ /**
+ * Resets all fields to the last saved configuration.
+ */
+ private void resetFields() {
+ tfBioCatalogueAPIBaseURL.setText(configuration.getProperty(BioCataloguePluginConfiguration.SERVICE_CATALOGUE_BASE_URL));
+ }
+
+ /**
+ * Resets all fields to the default values.
+ */
+ private void loadDefaults() {
+ tfBioCatalogueAPIBaseURL.setText(configuration.getDefaultProperty(BioCataloguePluginConfiguration.SERVICE_CATALOGUE_BASE_URL));
+ }
+
+ /**
+ * Saves recent changes to the configuration parameter map.
+ * Some input validation is performed as well.
+ */
+ private void applyChanges() {
+ // --- BioCatalogue BASE URL ---
+
+ String candidateBaseURL = tfBioCatalogueAPIBaseURL.getText();
+ if (candidateBaseURL.length() == 0) {
+ JOptionPane.showMessageDialog(this,
+ "Service Catalogue base URL must not be blank",
+ "Service Catalogue Configuration", JOptionPane.WARNING_MESSAGE);
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ } else {
+ try {
+ new URL(candidateBaseURL);
+ } catch (MalformedURLException e) {
+ JOptionPane
+ .showMessageDialog(
+ this,
+ "Currently set Service Catalogue instance URL is not valid\n." +
+ "Please check the URL and try again.",
+ "Service Catalogue Configuration",
+ JOptionPane.WARNING_MESSAGE);
+ tfBioCatalogueAPIBaseURL.selectAll();
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ }
+
+ // check if the base URL has changed from the last saved state
+ if (!candidateBaseURL
+ .equals(configuration
+ .getProperty(BioCataloguePluginConfiguration.SERVICE_CATALOGUE_BASE_URL))) {
+ // Perform various checks on the new URL
+
+ // Do a GET with "Accept" header set to "application/xml"
+ // We are expecting a 200 OK and an XML doc in return that
+ // contains the BioCataogue version number element.
+ DefaultHttpClient httpClient = new DefaultHttpClient();
+
+ // Set the proxy settings, if any
+ if (System.getProperty(PROXY_HOST) != null
+ && !System.getProperty(PROXY_HOST).equals("")) {
+ // Instruct HttpClient to use the standard
+ // JRE proxy selector to obtain proxy information
+ ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
+ httpClient.getConnectionManager().getSchemeRegistry(), ProxySelector
+ .getDefault());
+ httpClient.setRoutePlanner(routePlanner);
+ // Do we need to authenticate the user to the proxy?
+ if (System.getProperty(PROXY_USERNAME) != null
+ && !System.getProperty(PROXY_USERNAME).equals("")) {
+ // Add the proxy username and password to the list of credentials
+ httpClient.getCredentialsProvider().setCredentials(
+ new AuthScope(System.getProperty(PROXY_HOST),Integer.parseInt(System.getProperty(PROXY_PORT))),
+ new UsernamePasswordCredentials(System.getProperty(PROXY_USERNAME), System.getProperty(PROXY_PASSWORD)));
+ }
+ }
+
+ HttpGet httpGet = new HttpGet(candidateBaseURL);
+ httpGet.setHeader("Accept", APPLICATION_XML_MIME_TYPE);
+
+ // Execute the request
+ HttpContext localContext = new BasicHttpContext();
+ HttpResponse httpResponse;
+ try {
+ httpResponse = httpClient.execute(httpGet, localContext);
+ } catch (Exception ex1) {
+ logger.error("Service Catalogue preferences configuration: Failed to do "
+ + httpGet.getRequestLine(), ex1);
+ // Warn the user
+ JOptionPane.showMessageDialog(this,
+ "Failed to connect to the URL of the Service Catalogue instance.\n"
+ + "Please check the URL and try again.",
+ "Service Catalogue Configuration",
+ JOptionPane.INFORMATION_MESSAGE);
+
+ // Release resource
+ httpClient.getConnectionManager().shutdown();
+
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ }
+
+ if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) { // HTTP/1.1 200 OK
+ HttpEntity httpEntity = httpResponse.getEntity();
+ String contentType = httpEntity.getContentType().getValue()
+ .toLowerCase().trim();
+ logger
+ .info("Service Catalogue preferences configuration: Got 200 OK when testing the Service Catalogue instance by doing "
+ + httpResponse.getStatusLine()
+ + ". Content type of response "
+ + contentType);
+ if (contentType.startsWith(APPLICATION_XML_MIME_TYPE)) {
+ String value = null;
+ Document doc = null;
+ try {
+ value = readResponseBodyAsString(httpEntity)
+ .trim();
+ // Try to read this string into an XML document
+ SAXBuilder builder = new SAXBuilder();
+ byte[] bytes = value.getBytes("UTF-8");
+ doc = builder.build(new ByteArrayInputStream(bytes));
+ } catch (Exception ex2) {
+ logger.error("Service Catalogue preferences configuration: Failed to build an XML document from the response.", ex2);
+ // Warn the user
+ JOptionPane.showMessageDialog(this,
+ "Failed to get the expected response body when testing the Service Catalogue instance.\n"
+ + "The URL is probably wrong. Please check it and try again.",
+ "Service Catalogue Configuration",
+ JOptionPane.INFORMATION_MESSAGE);
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ }
+ finally{
+ // Release resource
+ httpClient.getConnectionManager().shutdown();
+ }
+ // Get the version element from the XML document
+ Attribute apiVersionAttribute = doc.getRootElement().getAttribute(API_VERSION);
+ if (apiVersionAttribute != null){
+ String apiVersion = apiVersionAttribute.getValue();
+ String versions[] = apiVersion.split("[.]");
+ String majorVersion = versions[0];
+ String minorVersion = versions[1];
+ try {
+ //String patchVersion = versions[2]; // we are not comparing the patch versions
+ String supportedMajorVersion = MIN_SUPPORTED_BIOCATALOGUE_API_VERSION[0];
+ String supportedMinorVersion = MIN_SUPPORTED_BIOCATALOGUE_API_VERSION[1];
+ Integer iSupportedMajorVersion = Integer.parseInt(supportedMajorVersion);
+ Integer iMajorVersion = Integer.parseInt(majorVersion);
+ Integer iSupportedMinorVersion = Integer.parseInt(supportedMinorVersion);
+ Integer iMinorVersion = Integer.parseInt(minorVersion);
+ if (!(iSupportedMajorVersion == iMajorVersion &&
+ iSupportedMinorVersion <= iMinorVersion)){
+ // Warn the user
+ JOptionPane
+ .showMessageDialog(
+ this,
+ "The version of the Service Catalogue instance you are trying to connect to is not supported.\n"
+ + "Please change the URL and try again.",
+ "Service Catalogue Configuration",
+ JOptionPane.INFORMATION_MESSAGE);
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ }
+ } catch (Exception e) {
+ logger.error(e);
+ }
+ } // if null - we'll try to do our best to connect to BioCatalogue anyway
+ } else {
+ logger
+ .error("Service Catalogue preferences configuration: Failed to get the expected response content type when testing the Service Catalogue instance. "
+ + httpGet.getRequestLine()
+ + " returned content type '"
+ + contentType
+ + "'; expected response content type is 'application/xml'.");
+ // Warn the user
+ JOptionPane
+ .showMessageDialog(
+ this,
+ "Failed to get the expected response content type when testing the Service Catalogue instance.\n"
+ + "The URL is probably wrong. Please check it and try again.",
+ "Service Catalogue Plugin",
+ JOptionPane.INFORMATION_MESSAGE);
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ }
+ }
+ else{
+ logger
+ .error("Service Catalogue preferences configuration: Failed to get the expected response status code when testing the Service Catalogue instance. "
+ + httpGet.getRequestLine()
+ + " returned the status code "
+ + httpResponse.getStatusLine()
+ .getStatusCode() + "; expected status code is 200 OK.");
+ // Warn the user
+ JOptionPane
+ .showMessageDialog(
+ this,
+ "Failed to get the expected response status code when testing the Service Catalogue instance.\n"
+ + "The URL is probably wrong. Please check it and try again.",
+ "Service Catalogue Configuration",
+ JOptionPane.INFORMATION_MESSAGE);
+ tfBioCatalogueAPIBaseURL.requestFocusInWindow();
+ return;
+ }
+
+ // Warn the user of the changes in the BioCatalogue base URL
+ JOptionPane
+ .showMessageDialog(
+ this,
+ "You have updated the Service Catalogue base URL.\n"
+ + "This does not take effect until you restart Taverna.",
+ "Service catalogue Configuration",
+ JOptionPane.INFORMATION_MESSAGE);
+
+ }
+
+ // the new base URL seems to be valid - can save it into config
+ // settings
+ configuration.setProperty(
+ BioCataloguePluginConfiguration.SERVICE_CATALOGUE_BASE_URL,
+ candidateBaseURL);
+
+/* // also update the base URL in the BioCatalogueClient
+ BioCatalogueClient.getInstance()
+ .setBaseURL(candidateBaseURL);*/
+ }
+
+ }
+
+
+ /**
+ * For testing only.
+ */
+ public static void main(String[] args) {
+ JFrame theFrame = new JFrame();
+ theFrame.add(new BioCataloguePluginConfigurationPanel());
+ theFrame.pack();
+ theFrame.setLocationRelativeTo(null);
+ theFrame.setVisible(true);
+ }
+
+ /**
+ * Worker method that extracts the content of the received HTTP message as a
+ * string. It also makes use of the charset that is specified in the
+ * Content-Type header of the received data to read it appropriately.
+ *
+ * @param entity
+ * @return
+ * @throws IOException
+ */
+ // Taken from HTTPRequestHandler in rest-activity by Sergejs Aleksejevs
+ private static String readResponseBodyAsString(HttpEntity entity)
+ throws IOException {
+ // get charset name
+ String charset = null;
+ String contentType = entity.getContentType().getValue().toLowerCase();
+
+ String[] contentTypeParts = contentType.split(";");
+ for (String contentTypePart : contentTypeParts) {
+ contentTypePart = contentTypePart.trim();
+ if (contentTypePart.startsWith("charset=")) {
+ charset = contentTypePart.substring("charset=".length());
+ }
+ }
+
+ // read the data line by line
+ StringBuilder responseBodyString = new StringBuilder();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(entity
+ .getContent(), charset));
+
+ String str;
+ while ((str = reader.readLine()) != null) {
+ responseBodyString.append(str + "\n");
+ }
+
+ return (responseBodyString.toString());
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfigurationUIFactory.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfigurationUIFactory.java
new file mode 100644
index 0000000..f5baa70
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/config/BioCataloguePluginConfigurationUIFactory.java
@@ -0,0 +1,27 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCataloguePluginConfigurationUIFactory implements ConfigurationUIFactory
+{
+
+ public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ public Configurable getConfigurable() {
+ return BioCataloguePluginConfiguration.getInstance();
+ }
+
+ public JPanel getConfigurationPanel() {
+ return new BioCataloguePluginConfigurationPanel();
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginInputPortContextViewFactory.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginInputPortContextViewFactory.java
new file mode 100644
index 0000000..d067cce
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginInputPortContextViewFactory.java
@@ -0,0 +1,45 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationPortIdentity;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityInputPort;
+
+
+public class BioCataloguePluginInputPortContextViewFactory implements
+ ContextualViewFactory<ActivityInputPort> {
+
+ public boolean canHandle(Object selection)
+ {
+ // TODO - HACK: this would stop showing the contextual view in case of any error,
+ // not just in case of unsupported contextual selection; this needs to be
+ // changed, so that useful error messages are still displayed in the
+ // contextual view
+ if (selection instanceof ActivityInputPort)
+ {
+ SoapOperationPortIdentity portDetails = Integration.
+ extractSoapOperationPortDetailsFromActivityInputOutputPort((ActivityInputPort)selection);
+ boolean canHandleSelection = !portDetails.hasError();
+ if (!canHandleSelection) {
+ Logger.getLogger(BioCataloguePluginProcessorContextViewFactory.class).debug(
+ "Input port contextual view not shown due to some condition: " + portDetails.getErrorDetails());
+ }
+
+ return (canHandleSelection);
+ }
+ else {
+ return (false);
+ }
+ }
+
+ public List<ContextualView> getViews(ActivityInputPort selection) {
+ return Arrays.<ContextualView>asList(new ProcessorInputPortView(selection));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginOutputPortContextViewFactory.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginOutputPortContextViewFactory.java
new file mode 100644
index 0000000..1014a54
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginOutputPortContextViewFactory.java
@@ -0,0 +1,45 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationPortIdentity;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+
+public class BioCataloguePluginOutputPortContextViewFactory implements
+ ContextualViewFactory<ActivityOutputPort> {
+
+ public boolean canHandle(Object selection)
+ {
+ // TODO - HACK: this would stop showing the contextual view in case of any error,
+ // not just in case of unsupported contextual selection; this needs to be
+ // changed, so that useful error messages are still displayed in the
+ // contextual view
+ if (selection instanceof ActivityOutputPort)
+ {
+ SoapOperationPortIdentity portDetails = Integration.
+ extractSoapOperationPortDetailsFromActivityInputOutputPort((ActivityOutputPort)selection);
+ boolean canHandleSelection = !portDetails.hasError();
+ if (!canHandleSelection) {
+ Logger.getLogger(BioCataloguePluginProcessorContextViewFactory.class).debug(
+ "Output port contextual view not shown due to some condition: " + portDetails.getErrorDetails());
+ }
+
+ return (canHandleSelection);
+ }
+ else {
+ return (false);
+ }
+ }
+
+ public List<ContextualView> getViews(ActivityOutputPort selection) {
+ return Arrays.<ContextualView>asList(new ProcessorOutputPortView(selection));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginProcessorContextViewFactory.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginProcessorContextViewFactory.java
new file mode 100644
index 0000000..05380d3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/BioCataloguePluginProcessorContextViewFactory.java
@@ -0,0 +1,43 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+public class BioCataloguePluginProcessorContextViewFactory implements
+ ContextualViewFactory<Processor> {
+
+ public boolean canHandle(Object selection)
+ {
+ // TODO - HACK: this would stop showing the contextual view in case of any error,
+ // not just in case of unsupported contextual selection; this needs to be
+ // changed, so that useful error messages are still displayed in the
+ // contextual view
+ if (selection instanceof Processor)
+ {
+ SoapOperationIdentity opId = Integration.extractSoapOperationDetailsFromProcessor((Processor) selection);
+ boolean canHandleSelection = !opId.hasError();
+ if (!canHandleSelection) {
+ Logger.getLogger(BioCataloguePluginProcessorContextViewFactory.class).debug(
+ "Service's contextual view not shown due to some condition: " + opId.getErrorDetails());
+ }
+
+ return (canHandleSelection);
+ }
+ else {
+ return (false);
+ }
+ }
+
+ public List<ContextualView> getViews(Processor selection) {
+ return Arrays.<ContextualView>asList(new ProcessorView(selection));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorInputPortView.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorInputPortView.java
new file mode 100644
index 0000000..4e307f1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorInputPortView.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityInputPort;
+
+
+public class ProcessorInputPortView extends ContextualView
+{
+ private final ActivityInputPort inputPort;
+ private JPanel jPanel;
+
+ public ProcessorInputPortView(ActivityInputPort inputPort) {
+ this.inputPort = inputPort;
+
+ jPanel = new JPanel();
+
+ // NB! This is required to have the body of this contextual
+ // view added to the main view; otherwise, body will be
+ // blank
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame()
+ {
+ return jPanel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service Catalogue Information";
+ }
+
+ @Override
+ public void refreshView()
+ {
+ // this actually causes the parent container to validate itself,
+ // which is what is needed here
+ this.revalidate();
+ this.repaint();
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return BioCataloguePluginConstants.CONTEXTUAL_VIEW_PREFERRED_POSITION;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorOutputPortView.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorOutputPortView.java
new file mode 100644
index 0000000..feff0f4
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorOutputPortView.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+
+public class ProcessorOutputPortView extends ContextualView
+{
+ private final ActivityOutputPort outputPort;
+ private JPanel jPanel;
+
+ public ProcessorOutputPortView(ActivityOutputPort outputPort) {
+ this.outputPort = outputPort;
+
+ jPanel = new JPanel();
+
+ // NB! This is required to have the body of this contextual
+ // view added to the main view; otherwise, body will be
+ // blank
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame()
+ {
+ return jPanel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service Catalogue Information";
+ }
+
+ @Override
+ public void refreshView()
+ {
+ // this actually causes the parent container to validate itself,
+ // which is what is needed here
+ this.revalidate();
+ this.repaint();
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return BioCataloguePluginConstants.CONTEXTUAL_VIEW_PREFERRED_POSITION;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorView.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorView.java
new file mode 100644
index 0000000..ea41805
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/contextual_views/ProcessorView.java
@@ -0,0 +1,229 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views;
+
+import java.awt.Component;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URI;
+import java.rmi.activation.UnknownObjectException;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.t2.lang.ui.DeselectingButton;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceHealthChecker;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+
+
+public class ProcessorView extends ContextualView {
+ private final Processor processor;
+ private JPanel jPanel;
+
+ private static Logger logger = Logger.getLogger(ProcessorView.class);
+
+
+
+ public ProcessorView(Processor processor) {
+ this.processor = processor;
+
+ jPanel = new JPanel();
+
+ // this is required to have the body of this contextual
+ // view added to the main view; otherwise, body will be
+ // blank
+ initView();
+ }
+
+
+
+ @Override
+ public JComponent getMainFrame()
+ {
+ Thread t = new Thread("loading processor data") {
+ public void run() {
+ final SoapOperationIdentity operationDetails = Integration.extractSoapOperationDetailsFromProcessor(processor);
+
+ if (operationDetails.hasError()) {
+ SwingUtilities.invokeLater(new RefreshThread(new JLabel(operationDetails.getErrorDetails().toString(),
+ UIManager.getIcon("OptionPane.warningIcon"), JLabel.CENTER)));
+ return;
+ }
+ else {
+ BioCatalogueClient client = BioCatalogueClient.getInstance();
+
+ if (client != null) {
+ try {
+ final SoapOperation soapOperation = client.lookupSoapOperation(operationDetails);
+ if (soapOperation == null) {
+ SwingUtilities.invokeLater(new RefreshThread(new JLabel("This service is not registered in the Service Catalogue",
+ UIManager.getIcon("OptionPane.warningIcon"), JLabel.CENTER)));
+ return;
+ }
+
+ Service parentService = client.getBioCatalogueService(soapOperation.getAncestors().getService().getHref());
+ if (parentService == null) {
+ SwingUtilities.invokeLater(new RefreshThread(new JLabel("Problem while fetching monitoring data from the Service Catalogue",
+ UIManager.getIcon("OptionPane.warningIcon"), JLabel.CENTER)));
+ return;
+ }
+
+
+ // *** managed to get all necessary data successfully - present it ***
+
+ // create status update panel
+ JButton jclServiceStatus = new DeselectingButton(
+ new AbstractAction("Check monitoring status") {
+ public void actionPerformed(ActionEvent e) {
+ ServiceHealthChecker.checkWSDLProcessor(operationDetails);
+ }
+ });
+ jclServiceStatus.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JLabel jlStatusMessage = new JLabel(parentService.getLatestMonitoringStatus().getMessage());
+ jlStatusMessage.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ // operation description
+ String operationDescription = (soapOperation.getDescription().length() > 0 ?
+ soapOperation.getDescription() :
+ "No description is available for this service");
+
+ ReadOnlyTextArea jlOperationDescription = new ReadOnlyTextArea(operationDescription);
+
+ jlOperationDescription.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ // a button to open preview of the service
+ JButton jbLaunchProcessorPreview = new DeselectingButton("Show on the Service Catalogue",
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (!operationDetails.hasError()) {
+ String hrefString = soapOperation.getHref();
+ try {
+ Desktop.getDesktop().browse(new URI(hrefString));
+ }
+ catch (Exception ex) {
+ logger.error("Failed while trying to open the URL in a standard browser; URL was: " +
+ hrefString, ex);
+ };
+ }
+ else {
+ // this error message comes from Integration class extracting SOAP operation details from the contextual selection
+ JOptionPane.showMessageDialog(null, operationDetails.getErrorDetails(), "Service Catalogue Error", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ },
+ "View this service on the Service Catalogue");
+
+ JPanel jpPreviewButtonPanel = new JPanel();
+ jpPreviewButtonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ jbLaunchProcessorPreview.setAlignmentX(Component.LEFT_ALIGNMENT);
+ // jpPreviewButtonPanel.add(jbLaunchProcessorPreview);
+ // put everything together
+ JPanel jpInnerPane = new JPanel();
+ jpInnerPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ jpInnerPane.setLayout(new BoxLayout(jpInnerPane, BoxLayout.Y_AXIS));
+ jpInnerPane.add(jlOperationDescription);
+ jpInnerPane.add(Box.createVerticalStrut(10));
+ jpInnerPane.add(jlStatusMessage);
+ jpInnerPane.add(Box.createVerticalStrut(10));
+ jpInnerPane.add(jclServiceStatus);
+ jpInnerPane.add(Box.createVerticalStrut(10));
+ jpInnerPane.add(jbLaunchProcessorPreview);
+
+ JScrollPane spInnerPane = new JScrollPane(jpInnerPane);
+
+ SwingUtilities.invokeLater(new RefreshThread(spInnerPane));
+ return;
+ }
+ catch (UnknownObjectException e) {
+ SwingUtilities.invokeLater(new RefreshThread(new JLabel(e.getMessage(),
+ UIManager.getIcon("OptionPane.informationIcon"), JLabel.CENTER)));
+ return;
+ }
+ catch (Exception e) {
+ // a real error occurred while fetching data about selected processor
+ logger.error("ERROR: unexpected problem while trying to ", e);
+ SwingUtilities.invokeLater(new RefreshThread(new JLabel("An unknown problem has prevented Taverna from loading this preview",
+ UIManager.getIcon("OptionPane.errorIcon"), JLabel.CENTER)));
+ return;
+ }
+ }
+ else {
+ SwingUtilities.invokeLater(new RefreshThread(new JLabel("Service Catalogue integration has not initialised yet. Please wait and try again.",
+ UIManager.getIcon("OptionPane.warningIcon"), JLabel.CENTER)));
+ return;
+ }
+ }
+ }
+ };
+
+ jPanel.removeAll();
+ jPanel.setPreferredSize(new Dimension(200,200));
+ jPanel.setLayout(new GridLayout());
+ jPanel.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE), JLabel.CENTER));
+ t.start();
+ return jPanel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service Catalogue Information";
+ }
+
+ @Override
+ public void refreshView()
+ {
+ // this actually causes the parent container to validate itself,
+ // which is what is needed here
+ this.revalidate();
+ this.repaint();
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return BioCataloguePluginConstants.CONTEXTUAL_VIEW_PREFERRED_POSITION;
+ }
+
+ class RefreshThread extends Thread {
+ private final Component component;
+
+ public RefreshThread (Component component) {
+ this.component = component;
+ }
+
+ public void run() {
+ jPanel.removeAll();
+ if (component != null) {
+ jPanel.add(component);
+ }
+ refreshView();
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthCheck.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthCheck.java
new file mode 100644
index 0000000..2461c2c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthCheck.java
@@ -0,0 +1,40 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check;
+
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.Visitor;
+
+/**
+ * A {@link BioCatalogueWSDLActivityHealthCheck} is a kind of visit that determines
+ * if the corresponding WSDL activity in a workflow will work during a workflow run -
+ * checks will be made based on the monitoring status held about that service in BioCatalogue.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCatalogueWSDLActivityHealthCheck extends VisitKind
+{
+ // The following values indicate the type of results that can be associated
+ // with a VisitReport generated by a health-checking visitor.
+ public static final int MESSAGE_IN_VISIT_REPORT = 0;
+
+
+ // property names to be placed into VisitReport generated by BioCatalogueWSDLActivityHealthChecker
+ public static final String WSDL_LOCATION_PROPERTY = "wsdlLocation";
+ public static final String OPERATION_NAME_PROPERTY = "soapOperationName";
+ public static final String EXPLANATION_MSG_PROPERTY = "fullExplanationMessage";
+
+
+
+
+ @Override
+ public Class<? extends Visitor> getVisitorClass() {
+ return BioCatalogueWSDLActivityHealthChecker.class;
+ }
+
+ private static class Singleton {
+ private static BioCatalogueWSDLActivityHealthCheck instance = new BioCatalogueWSDLActivityHealthCheck();
+ }
+
+ public static BioCatalogueWSDLActivityHealthCheck getInstance() {
+ return Singleton.instance;
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthCheckVisitExplainer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthCheckVisitExplainer.java
new file mode 100644
index 0000000..8bee663
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthCheckVisitExplainer.java
@@ -0,0 +1,111 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.workbench.report.explainer.VisitExplainer;
+
+// import status constants
+import static net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.BioCatalogueWSDLActivityHealthCheck.*;
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCatalogueWSDLActivityHealthCheckVisitExplainer implements VisitExplainer
+{
+
+ public boolean canExplain(VisitKind vk, int resultId) {
+ return (vk instanceof BioCatalogueWSDLActivityHealthCheck);
+ }
+
+
+ /**
+ * This class only handles {@link VisitReport} instances that are of
+ * {@link BioCatalogueWSDLActivityHealthCheck} kind. Therefore, decisions on
+ * the explanations / solutions are made solely by visit result IDs.
+ */
+ public JComponent getExplanation(final VisitReport vr)
+ {
+ int resultId = vr.getResultId();
+ String explanation = null;
+
+ switch (resultId) {
+ case MESSAGE_IN_VISIT_REPORT:
+ explanation = (String) vr.getProperty(EXPLANATION_MSG_PROPERTY); break;
+
+ default:
+ explanation = "Unknown issue - no expalanation available"; break;
+ }
+
+
+ JButton bRunBioCatalogueHealthCheck = new JButton("View monitoring status details");
+ bRunBioCatalogueHealthCheck.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ SoapOperationIdentity soapOpIdentity =
+ new SoapOperationIdentity((String)vr.getProperty(BioCatalogueWSDLActivityHealthCheck.WSDL_LOCATION_PROPERTY),
+ (String)vr.getProperty(BioCatalogueWSDLActivityHealthCheck.OPERATION_NAME_PROPERTY), null);
+
+ ServiceHealthChecker.checkWSDLProcessor(soapOpIdentity);
+ }
+ });
+ JPanel jpButton = new JPanel();
+ jpButton.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
+ jpButton.add(bRunBioCatalogueHealthCheck);
+ jpButton.setOpaque(false);
+
+ JPanel jpExplanation = new JPanel(new BorderLayout());
+ jpExplanation.add(new ReadOnlyTextArea(explanation), BorderLayout.CENTER);
+ jpExplanation.add(jpButton, BorderLayout.SOUTH);
+
+ return (jpExplanation);
+ }
+
+
+
+ /**
+ * This class only handles {@link VisitReport} instances that are of
+ * {@link BioCatalogueWSDLActivityHealthCheck} kind. Therefore, decisions on
+ * the explanations / solutions are made solely by visit result IDs.
+ */
+ public JComponent getSolution(VisitReport vr)
+ {
+ String explanation = null;
+
+ // instead of switching between possible health check resultIDs,
+ // simply choose from possible statuses: for all failures there's
+ // nothing specific that can be done, so no need to differentiate
+ // displayed messages
+ switch (vr.getStatus()) {
+ case OK:
+ explanation = "This WSDL service works fine - no change necessary"; break;
+
+ case WARNING:
+ case SEVERE:
+ explanation = "This remote WSDL service appears to have an internal problem. There is nothing " +
+ "specific that can be done to fix it locally.\n\n" +
+ "It is possible that the current state of the service will still allow to execute " +
+ "the workflow successfully. Also, the service may have already recovered since the " +
+ "last time it's monitoring status has been checked.\n\n" +
+ "If this problem does affect the current workflow, it may be resolved by the " +
+ "service provider. It may be worth contacting them to report the issue.";
+ break;
+
+ default:
+ explanation = "Unknown issue - no solution available"; break;
+ }
+
+ return (new ReadOnlyTextArea(explanation));
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthChecker.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthChecker.java
new file mode 100644
index 0000000..c30ec8f
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/BioCatalogueWSDLActivityHealthChecker.java
@@ -0,0 +1,199 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.MonitoringStatusLabel;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTest;
+import org.biocatalogue.x2009.xml.rest.TestScript;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivity;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivityConfigurationBean;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.workflowmodel.health.HealthChecker;
+
+
+/**
+ * A {@link HealthChecker} for a {@link WSDLActivity}.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class BioCatalogueWSDLActivityHealthChecker implements HealthChecker<WSDLActivity>
+{
+ private static final int MILLIS_IN_THE_PAST_FOR_OLDEST_MONITORING_DATA = 48 * 60 * 60 * 1000; // 48hrs
+
+
+ private Logger logger;
+
+ public BioCatalogueWSDLActivityHealthChecker() {
+ logger = Logger.getLogger(BioCatalogueWSDLActivityHealthChecker.class);
+ }
+
+
+ public boolean canVisit(Object subject) {
+ return (subject instanceof WSDLActivity);
+ }
+
+
+ public VisitReport visit(WSDLActivity activity, List<Object> ancestors)
+ {
+ WSDLActivityConfigurationBean configBean = activity.getConfiguration();
+ SoapOperationIdentity soapOpIdentity = new SoapOperationIdentity(configBean.getWsdl(), configBean.getOperation(), null);
+
+ try {
+ // make BioCatalogue API request to fetch the data
+ Service serviceWithMonitoringData = BioCatalogueClient.getInstance().lookupParentServiceMonitoringData(soapOpIdentity);
+ MonitoringStatusLabel.Enum serviceStatusLabel = null;
+
+
+ VisitReport.Status status = null;
+ String visitReportLabel = null;
+ String visitReportExplanation = null;
+ List<VisitReport> subReports = new ArrayList<VisitReport>();
+
+
+ if (serviceWithMonitoringData == null) {
+ // BioCatalogue doesn't "know" about this service - it appears not to be registered;
+ // --> nothing to report to Taverna
+ return (null);
+ }
+ else if (serviceWithMonitoringData.getLatestMonitoringStatus() == null) {
+ // BioCatalogue "knows" this service, but for some reason there was no monitoring data available;
+ // possibly an API change? either way --> nothing to report to Taverna
+ return (null);
+ }
+ else
+ {
+ Calendar lastCheckedAt = serviceWithMonitoringData.getLatestMonitoringStatus().getLastChecked();
+ String agoString = Util.getAgoString(lastCheckedAt, Calendar.getInstance(), MILLIS_IN_THE_PAST_FOR_OLDEST_MONITORING_DATA);
+ if (agoString == null) {
+ return (null);
+ }
+
+ serviceStatusLabel = serviceWithMonitoringData.getLatestMonitoringStatus().getLabel();
+ switch (serviceStatusLabel.intValue()) {
+ case MonitoringStatusLabel.INT_PASSED:
+ visitReportLabel = "Service Catalogue: all tests passed " + agoString;
+ visitReportExplanation = "The Service Catalogue reports that all available tests for this WSDL service have " +
+ "been successful. They have been last executed " + agoString;
+ status = Status.OK;
+ break;
+
+ case MonitoringStatusLabel.INT_WARNING:
+ case MonitoringStatusLabel.INT_FAILED:
+ visitReportLabel = "Service Catalogue: some tests failed " + agoString;
+ visitReportExplanation = "Some test scripts for this WSDL service have failed";
+
+ // only extract data about failing test scripts
+ subReports = createTestScriptSubReportsForFailingService(activity, serviceWithMonitoringData);
+ if (subReports.size() == 0) {
+ // failing tests must have been for endpoint / WSDL location - but not for scripts;
+ // Taverna doesn't need to know about the former, as it replicates internal checks
+ return (null);
+ }
+ else {
+ // determine the worst status and report as the one of the collection of subreports
+ status = VisitReport.getWorstStatus(subReports);
+ }
+ break;
+
+ case MonitoringStatusLabel.INT_UNCHECKED:
+ // monitoring record states that the status of this service was not (yet) checked;
+ // possibly monitoring on BioCatalogue was switched off before this service was registered;
+ // --> nothing to report to Taverna
+ return (null);
+
+ default:
+ visitReportLabel = "Service Catalogue: unknown monitoring status received - \"" + serviceStatusLabel.toString() + "\"";
+ visitReportExplanation = "The Service Catalogue has returned a new monitoring status for this service: \"" +
+ serviceStatusLabel.toString() + "\"\n\n" +
+ "It has never been used before and probably indicates a change in the Service Catalogue API. " +
+ "Please report this issue to the Service Catalogue developers.";
+ status = Status.WARNING;
+ break;
+ }
+ }
+
+ // wrap determined values into a single VisitReport object; then attach data to identify
+ // this service in associated VisitExplainer
+ VisitReport report = new VisitReport(BioCatalogueWSDLActivityHealthCheck.getInstance(), activity,
+ visitReportLabel, BioCatalogueWSDLActivityHealthCheck.MESSAGE_IN_VISIT_REPORT, status, subReports);
+ report.setProperty(BioCatalogueWSDLActivityHealthCheck.WSDL_LOCATION_PROPERTY, soapOpIdentity.getWsdlLocation());
+ report.setProperty(BioCatalogueWSDLActivityHealthCheck.OPERATION_NAME_PROPERTY, soapOpIdentity.getOperationName());
+ report.setProperty(BioCatalogueWSDLActivityHealthCheck.EXPLANATION_MSG_PROPERTY, visitReportExplanation);
+
+ return (report);
+ }
+ catch (Exception e) {
+ // not sure what could have happened - it will be visible in the logs
+ logger.error("Unexpected error while performing health check for " +
+ soapOpIdentity.getWsdlLocation() + " service.", e);
+ return (null);
+ }
+ }
+
+
+ private List<VisitReport> createTestScriptSubReportsForFailingService(WSDLActivity activity, Service serviceWithMonitoringData)
+ {
+ List<VisitReport> subReports = new ArrayList<VisitReport>();
+
+ try {
+ List<ServiceTest> serviceTests = serviceWithMonitoringData.getMonitoring().getTests().getServiceTestList();
+ for (ServiceTest test : serviceTests)
+ {
+ if (test.getTestType().getTestScript() != null &&
+ test.getTestType().getTestScript()instanceof TestScript)
+ {
+ TestScript testScript = test.getTestType().getTestScript();
+
+ String agoString = Util.getAgoString(test.getLatestStatus().getLastChecked(), Calendar.getInstance(),
+ MILLIS_IN_THE_PAST_FOR_OLDEST_MONITORING_DATA);
+
+ // only proceed if this test wasn't run too long ago
+ if (agoString != null) {
+ String label = "Service Catalogue: \"" + testScript.getName() + "\" test script " + test.getLatestStatus().getLabel();
+ VisitReport report = new VisitReport(BioCatalogueWSDLActivityHealthCheck.getInstance(), activity,
+ label, BioCatalogueWSDLActivityHealthCheck.MESSAGE_IN_VISIT_REPORT,
+ ServiceMonitoringStatusInterpreter.translateBioCatalogueStatusForTaverna(test.getLatestStatus().getLabel()));
+ report.setProperty(BioCatalogueWSDLActivityHealthCheck.WSDL_LOCATION_PROPERTY, activity.getConfiguration().getWsdl());
+ report.setProperty(BioCatalogueWSDLActivityHealthCheck.OPERATION_NAME_PROPERTY, activity.getConfiguration().getOperation());
+ report.setProperty(BioCatalogueWSDLActivityHealthCheck.EXPLANATION_MSG_PROPERTY,
+ "This test was last executed " + agoString + "." +
+ "\n\n" + StringEscapeUtils.escapeHtml(test.getLatestStatus().getMessage()) +
+ "\n\n---- Test script description ----\n" + StringEscapeUtils.escapeHtml(testScript.getDescription()));
+
+ subReports.add(report);
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ // log the error, but do not terminate the method - maybe some sub reports were successfully
+ // generated, in which case at least partial result can be returned
+ logger.error("Encountered unexpected problem while trying to generate a collection of sub-reports " +
+ "for a failing service: " + activity.getConfiguration().getWsdl(), e);
+ }
+
+ return (subReports);
+ }
+
+
+ /**
+ * Health check for the WSDL activities involves fetching
+ * the monitoring status of each activity from BioCatalogue -
+ * this *may* be time consuming.
+ */
+ public boolean isTimeConsuming() {
+ return true;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/ServiceHealthChecker.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/ServiceHealthChecker.java
new file mode 100644
index 0000000..8e85e61
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/ServiceHealthChecker.java
@@ -0,0 +1,280 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.border.EmptyBorder;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTest;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.SoapProcessorIdentity;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.ui.JClickableLabel;
+import net.sf.taverna.biocatalogue.ui.JWaitDialog;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+
+/**
+ * This class helps with "health checks" of individual Taverna processors
+ * (i.e. SOAP operations) and the workflows in general (by iterating through
+ * the processors).
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class ServiceHealthChecker
+{
+ private static Logger logger = Logger.getLogger(ServiceHealthChecker.class);
+
+ // deny creation of instances of this class
+ private ServiceHealthChecker() { };
+
+
+ // =====================================================================================================
+ // *** Health Check of Individual Service / Processor ***
+ // =====================================================================================================
+
+ /**
+ * @param serviceURL URL of SOAP service or REST service on BioCatalogue;
+ * URL should be of the
+ */
+ public static void checkServiceByURL(String serviceURL)
+ {
+ if (serviceURL != null) {
+ checkMonitoringStatusRoutine(serviceURL);
+ }
+ else {
+ // for some reason the URL of the service wasn't provided...
+ JOptionPane.showMessageDialog(null, "Cannot provide monitoring status for this service - " +
+ "unknown service URL", "Service Catalogue Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+
+ /**
+ * @param
+ */
+ public static void checkResource(ResourceLink serviceOrOperationOrMethod)
+ {
+ if (serviceOrOperationOrMethod != null) {
+ checkMonitoringStatusRoutine(serviceOrOperationOrMethod);
+ }
+ else {
+ // for some reason resource object wasn't provided...
+ JOptionPane.showMessageDialog(null, "Cannot provide monitoring status - " +
+ "null reference received", "Service Catalogue Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+
+ /**
+ * Used when invoked from the workflow diagram - e.g. when a URL of the specific
+ * resource on BioCatalogue is not known, but have enough of identifying data
+ * to proceed with health check.
+ *
+ * @param soapOperationDetails
+ */
+ public static void checkWSDLProcessor(SoapOperationIdentity soapOperationDetails)
+ {
+ if (!soapOperationDetails.hasError()) {
+ checkMonitoringStatusRoutine(soapOperationDetails);
+ }
+ else {
+ // this error message comes from Integration class extracting SOAP operation details from the contextual selection
+ JOptionPane.showMessageDialog(null, soapOperationDetails.getErrorDetails(), "Service Catalogue Error", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+
+
+ /**
+ * @param serviceOrSoapOperationToCheck Instance of SoapOperationIdentity representing Taverna processor
+ * or String representing a URL of the service to check health for.
+ */
+ private static void checkMonitoringStatusRoutine(final Object serviceOrSoapOperationToCheck)
+ {
+ // helper variable to determine the kind of check to perform - the difference is minimal:
+ // wording in the status messages ("Web Service" | "processor" | "REST Service") and which method to call on
+ // the BioCatalogue client to fetch monitoring data
+ final boolean bCheckingService = (serviceOrSoapOperationToCheck instanceof String);
+ final boolean bCheckingWSDLProcessor = (serviceOrSoapOperationToCheck instanceof SoapOperationIdentity);
+ final boolean bCheckingResource = (serviceOrSoapOperationToCheck instanceof ResourceLink);
+
+ final StringBuilder itemToCheck = new StringBuilder();
+ if (bCheckingService) {
+ itemToCheck.append("service");
+ }
+ else if (bCheckingWSDLProcessor) {
+ itemToCheck.append("WSDL service");
+ }
+ else if (bCheckingResource) {
+ TYPE resourceType = Resource.getResourceTypeFromResourceURL(((ResourceLink)serviceOrSoapOperationToCheck).getHref());
+ itemToCheck.append(resourceType.getTypeName());
+ }
+
+ // create the wait dialog, but don't make it visible - first need to start the background processing thread
+ final JWaitDialog jwd = new JWaitDialog(MainComponent.dummyOwnerJFrame, "Checking "+itemToCheck+" status",
+ "Please wait while status of selected "+itemToCheck+" is being checked...");
+
+ new Thread(itemToCheck + " lookup and health check operation") {
+ public void run() {
+ try
+ {
+ BioCatalogueClient client = BioCatalogueClient.getInstance();
+ Service serviceMonitoringData = null;
+
+ // attempt to get monitoring data from BioCatalogue - for this need to identify what type of
+ // item was provided as a parameter
+ if (bCheckingService) {
+ serviceMonitoringData = client.getBioCatalogueServiceMonitoringData((String)serviceOrSoapOperationToCheck);
+ }
+ else if (bCheckingWSDLProcessor) {
+ serviceMonitoringData = client.lookupParentServiceMonitoringData((SoapOperationIdentity)serviceOrSoapOperationToCheck);
+ }
+ else if (bCheckingResource) {
+ String resourceURL = ((ResourceLink)serviceOrSoapOperationToCheck).getHref();
+ TYPE resourceType = Resource.getResourceTypeFromResourceURL(resourceURL);
+
+// if (resourceType == TYPE.Service) {
+// serviceMonitoringData = client.getBioCatalogueServiceMonitoringData(resourceURL);
+// }
+// else
+ if (resourceType == TYPE.SOAPOperation) {
+ String parentServiceURL = ((SoapOperation)serviceOrSoapOperationToCheck).getAncestors().getService().getHref();
+ serviceMonitoringData = client.getBioCatalogueServiceMonitoringData(parentServiceURL);
+ }
+ else if (resourceType == TYPE.RESTMethod) {
+ String parentServiceURL = ((RestMethod)serviceOrSoapOperationToCheck).getAncestors().getService().getHref();
+ serviceMonitoringData = client.getBioCatalogueServiceMonitoringData(parentServiceURL);
+ }
+ else {
+ JOptionPane.showMessageDialog(jwd, "Unexpected resource type - can't execute health check for this",
+ "Service Catalogue Error", JOptionPane.ERROR_MESSAGE);
+ logger.error("Service Catalogue: Could not perform health check for" + resourceType);
+ }
+ }
+
+
+ // need to make this assignment to make the variable final - otherwise unavailable inside the new thread...
+ final Service serviceWithMonitoringData = serviceMonitoringData;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (serviceWithMonitoringData == null) {
+ jwd.setTitle("Service Catalogue - Information");
+ jwd.waitFinished(new JLabel("There is no information about this "+itemToCheck+" in the Service Catalogue",
+ UIManager.getIcon("OptionPane.informationIcon"), JLabel.CENTER));
+ }
+ else if (serviceWithMonitoringData.getLatestMonitoringStatus() == null) {
+ jwd.setTitle("Service Catalogue Warning");
+ jwd.waitFinished(new JLabel("This "+itemToCheck+" is known to the Service Catalogue, but no monitoring data was available.",
+ UIManager.getIcon("OptionPane.warningIcon"), JLabel.CENTER));
+ }
+ else
+ {
+ // set the overall status message
+ String overallStatusLabel = "<html><b>Overall status:</b><br>" +
+ serviceWithMonitoringData.getLatestMonitoringStatus().getMessage() + "<br>" +
+ "(last checked";
+ if (serviceWithMonitoringData.getLatestMonitoringStatus().getLastChecked() == null) {
+ overallStatusLabel += ": never";
+ }
+ else {
+ overallStatusLabel += " at " + BioCatalogueClient.getShortDateFormatter().format(
+ serviceWithMonitoringData.getLatestMonitoringStatus().getLastChecked().getTime());
+ }
+ overallStatusLabel += ")</html>";
+ JLabel jlOverallStatus = new JLabel(overallStatusLabel);
+
+ // create panel for additional status messages (e.g. endpoint, wsdl location, etc)
+ JPanel jpStatusMessages = new JPanel();
+ jpStatusMessages.setLayout(new BoxLayout(jpStatusMessages, BoxLayout.Y_AXIS));
+
+ for (ServiceTest test : serviceWithMonitoringData.getMonitoring().getTests().getServiceTestList())
+ {
+ // First get the service type
+ String testLabel = "<html><br><b>";
+ if (test.getTestType().getUrlMonitor() != null)
+ {
+ if (test.getTestType().getUrlMonitor().getUrl().endsWith("wsdl")) {
+ // WSDL location test
+ testLabel += "WSDL Location Availability:</b><br>" +
+ "URL: " + test.getTestType().getUrlMonitor().getUrl();
+ }
+ else {
+ // Endpoint availability test
+ testLabel += "Endpoint Availability:</b><br>" +
+ "URL: " + test.getTestType().getUrlMonitor().getUrl();
+ }
+ }
+ else if (test.getTestType().getTestScript() != null) {
+ // test script
+ testLabel += "Test Script: " + test.getTestType().getTestScript().getName() + "</b>";
+ }
+ else {
+ testLabel += "Unknown test type</b>";
+ }
+ testLabel += "<br>";
+
+ // Add service results
+ testLabel += test.getLatestStatus().getMessage() + "</html>";
+
+ // Add the current test into the test messages panel
+ jpStatusMessages.add(new JLabel(testLabel));
+ }
+
+ // either way add the overall status on top of everything
+ jpStatusMessages.add(jlOverallStatus, 0);
+ jpStatusMessages.setBorder(new EmptyBorder(10,10,10,10));
+ JScrollPane jspStatusMessages = new JScrollPane(jpStatusMessages);
+ jspStatusMessages.setBorder(BorderFactory.createEmptyBorder());
+
+ // *** Put everything together ***
+ JPanel jpHealthCheckStatus = new JPanel(new BorderLayout(15, 10));
+ jpHealthCheckStatus.add(new JLabel(ServiceMonitoringStatusInterpreter.getStatusIcon(serviceWithMonitoringData, false)),
+ BorderLayout.WEST);
+ jpHealthCheckStatus.add(jspStatusMessages, BorderLayout.CENTER);
+
+ jwd.setTitle("Service Catalogue - Monitoring Status");
+ jwd.waitFinished(jpHealthCheckStatus);
+ }
+ }
+ });
+ }
+ catch (Exception e) {
+ logger.error("Service Catalogue: Error occurred while checking status of selected", e);
+ jwd.setTitle("Service Catalogue - Error");
+ jwd.waitFinished(new JLabel("<html>An unexpected error occurred while checking status of selected " +
+ itemToCheck + "<br>Please see error log for details...",
+ UIManager.getIcon("OptionPane.errorIcon"), JLabel.CENTER));
+ }
+ }
+ }.start();
+
+ jwd.setVisible(true);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/ServiceMonitoringStatusInterpreter.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/ServiceMonitoringStatusInterpreter.java
new file mode 100644
index 0000000..0742cef
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/health_check/ServiceMonitoringStatusInterpreter.java
@@ -0,0 +1,77 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check;
+
+import java.net.URL;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+
+import org.biocatalogue.x2009.xml.rest.MonitoringStatus;
+import org.biocatalogue.x2009.xml.rest.MonitoringStatusLabel;
+import org.biocatalogue.x2009.xml.rest.Service;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class ServiceMonitoringStatusInterpreter
+{
+ // prevent instantiation of this class
+ private ServiceMonitoringStatusInterpreter() { /* do nothing */ }
+
+
+ /**
+ * @param serviceWithMonitoringData
+ * @param listingIconRequired True to get a small icon suitable for a JList entry;
+ * false to get a larger icon.
+ * @return
+ */
+ public static ImageIcon getStatusIcon(Service serviceWithMonitoringData, boolean listingIconRequired)
+ {
+ MonitoringStatus latestMonitoringStatus = serviceWithMonitoringData.getLatestMonitoringStatus();
+ if (latestMonitoringStatus == null) {
+ return ResourceManager.getImageIcon((listingIconRequired ?
+ ResourceManager.SERVICE_STATUS_UNCHECKED_ICON :
+ ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+ }
+ MonitoringStatusLabel.Enum serviceStatusLabel = latestMonitoringStatus.getLabel();
+
+ switch (serviceStatusLabel.intValue()) {
+ case MonitoringStatusLabel.INT_PASSED:
+ return ResourceManager.getImageIcon((listingIconRequired ?
+ ResourceManager.SERVICE_STATUS_PASSED_ICON :
+ ResourceManager.SERVICE_STATUS_PASSED_ICON_LARGE));
+ case MonitoringStatusLabel.INT_WARNING:
+ return ResourceManager.getImageIcon((listingIconRequired ?
+ ResourceManager.SERVICE_STATUS_WARNING_ICON :
+ ResourceManager.SERVICE_STATUS_WARNING_ICON_LARGE));
+ case MonitoringStatusLabel.INT_FAILED:
+ return ResourceManager.getImageIcon((listingIconRequired ?
+ ResourceManager.SERVICE_STATUS_FAILED_ICON :
+ ResourceManager.SERVICE_STATUS_FAILED_ICON_LARGE));
+ case MonitoringStatusLabel.INT_UNCHECKED:
+ return ResourceManager.getImageIcon((listingIconRequired ?
+ ResourceManager.SERVICE_STATUS_UNCHECKED_ICON :
+ ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+ default:
+ return (ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNKNOWN_ICON));
+ }
+
+ }
+
+
+ public static VisitReport.Status translateBioCatalogueStatusForTaverna(MonitoringStatusLabel.Enum monitoringStatusLabelEnum)
+ {
+ switch (monitoringStatusLabelEnum.intValue()) {
+ case MonitoringStatusLabel.INT_PASSED: return Status.OK;
+ case MonitoringStatusLabel.INT_WARNING: return Status.WARNING;
+ case MonitoringStatusLabel.INT_FAILED: return Status.SEVERE;
+ case MonitoringStatusLabel.INT_UNCHECKED: return Status.OK; // not really OK, but Taverna isn't interested in missing data anyway
+ default: return Status.WARNING; // could be worth to pop up a warning in this case, as it may mean something has changed
+ }
+ }
+
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/BioCatalogueContextualMenuSection.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/BioCatalogueContextualMenuSection.java
new file mode 100644
index 0000000..a5fb7a8
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/BioCatalogueContextualMenuSection.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.menus;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DefaultContextualMenu;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.InputPort;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+
+public class BioCatalogueContextualMenuSection extends AbstractMenuSection implements ContextualMenuComponent
+{
+ // TODO - this shouldn't be here, must reference this field in AbstractMenuSection!!
+ public static final String SECTION_COLOR = "sectionColor";
+
+
+ public static final URI BIOCATALOGUE_MENU_SECTION_ID = URI.create("http://biocatalogue.org/2010/contextMenu/biocatalogue_section");
+ private static final String SECTION_TITLE = "Service Catalogue";
+
+ private ContextualSelection contextualSelection;
+
+
+ public BioCatalogueContextualMenuSection() {
+ super(DefaultContextualMenu.DEFAULT_CONTEXT_MENU, 100000, BIOCATALOGUE_MENU_SECTION_ID);
+ }
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ public void setContextualSelection(ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && (getContextualSelection().getSelection() instanceof Dataflow ||
+ getContextualSelection().getSelection() instanceof Processor ||
+ getContextualSelection().getSelection() instanceof InputPort);
+ }
+
+ @SuppressWarnings("serial")
+ protected Action createAction()
+ {
+ Action action = new AbstractAction(SECTION_TITLE, ResourceManager.getImageIcon(ResourceManager.FAVICON)) {
+ public void actionPerformed(ActionEvent e) {
+ }
+ };
+ action.putValue(SECTION_COLOR, ShadedLabel.GREEN);
+ return (action);
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/MenuActionInputPort.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/MenuActionInputPort.java
new file mode 100644
index 0000000..f94b0e6
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/MenuActionInputPort.java
@@ -0,0 +1,43 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.menus;
+
+import java.awt.event.ActionEvent;
+import java.net.URISyntaxException;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+
+import net.sf.taverna.t2.workflowmodel.InputPort;
+
+
+/**
+ * This class currently won't be used, as an entry for it was removed from
+ * META-INF/services/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
+ *
+ * This is because no useful action is yet available for input/output ports.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class MenuActionInputPort extends AbstractContextualMenuAction {
+
+ public MenuActionInputPort() throws URISyntaxException {
+ super(BioCatalogueContextualMenuSection.BIOCATALOGUE_MENU_SECTION_ID, 15);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("InputPort") {
+ public void actionPerformed(ActionEvent e) {
+ JOptionPane.showMessageDialog(getContextualSelection().getRelativeToComponent(), "Hoho!");
+ }
+ };
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (super.isEnabled() && getContextualSelection().getSelection() instanceof InputPort);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/MenuActionProcessorHealthCheck.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/MenuActionProcessorHealthCheck.java
new file mode 100644
index 0000000..c66d72b
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/menus/MenuActionProcessorHealthCheck.java
@@ -0,0 +1,51 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.menus;
+
+import java.awt.event.ActionEvent;
+import java.net.URISyntaxException;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceHealthChecker;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+
+public class MenuActionProcessorHealthCheck extends AbstractContextualMenuAction {
+
+ public MenuActionProcessorHealthCheck() throws URISyntaxException {
+ super(BioCatalogueContextualMenuSection.BIOCATALOGUE_MENU_SECTION_ID, 20);
+ }
+
+ @SuppressWarnings("serial")
+@Override
+ protected Action createAction()
+ {
+ Action action = new AbstractAction("Service Health Check") {
+ public void actionPerformed(ActionEvent e) {
+ SoapOperationIdentity soapOperationDetails = Integration.extractSoapOperationDetailsFromProcessorContextualSelection(getContextualSelection());
+ ServiceHealthChecker.checkWSDLProcessor(soapOperationDetails);
+ }
+ };
+ action.putValue(Action.SHORT_DESCRIPTION, "Check monitoring status of this service");
+ return (action);
+ }
+
+ @Override
+ public boolean isEnabled()
+ {
+ // FIXME - this will only work for SOAP processors for now..
+ boolean isEnabled = super.isEnabled() && getContextualSelection().getSelection() instanceof Processor;
+
+ if (isEnabled) {
+ SoapOperationIdentity soapOperationDetails = Integration.extractSoapOperationDetailsFromProcessorContextualSelection(getContextualSelection());
+ isEnabled = !soapOperationDetails.hasError();
+ }
+
+ return isEnabled;
+ }
+
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueRESTServiceProvider.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueRESTServiceProvider.java
new file mode 100644
index 0000000..3c39b1d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueRESTServiceProvider.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.perspectives.biocatalogue.integration.service_panel;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.Icon;
+
+import org.apache.log4j.Logger;
+
+//import net.sf.taverna.t2.activities.rest.ui.servicedescription.RESTActivityIcon;
+import net.sf.taverna.t2.servicedescriptions.AbstractConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionRegistryImpl;
+
+/**
+ * Service provider for REST service added to the Service Panel through the
+ * BioCatalogue perspective.
+ *
+ * @author Alex Nenadic
+ */
+public class BioCatalogueRESTServiceProvider extends
+ AbstractConfigurableServiceProvider<RESTFromBioCatalogueServiceDescription> {
+
+ public static final String PROVIDER_NAME = "Service Catalogue - selected services";
+
+ private static final URI providerId = URI
+ .create("http://taverna.sf.net/2010/service-provider/servicecatalogue/rest");
+
+ private static Logger logger = Logger.getLogger(BioCatalogueRESTServiceProvider.class);
+
+ public BioCatalogueRESTServiceProvider(
+ RESTFromBioCatalogueServiceDescription restServiceDescription) {
+ super(restServiceDescription);
+ }
+
+ public BioCatalogueRESTServiceProvider() {
+ super(new RESTFromBioCatalogueServiceDescription());
+ }
+
+ @Override
+ protected List<? extends Object> getIdentifyingData() {
+ return getConfiguration().getIdentifyingData();
+ }
+
+ @Override
+ public void findServiceDescriptionsAsync(
+ FindServiceDescriptionsCallBack callBack) {
+ callBack.status("Starting Service Catalogue REST Service Provider");
+ registerNewRESTMethod(getConfiguration(), callBack);
+ }
+
+ @Override
+ public Icon getIcon() {
+// return RESTActivityIcon.getRESTActivityIcon();
+ return getConfiguration().getIcon();
+ }
+
+ @Override
+ public String getId() {
+ return providerId.toString();
+ }
+
+ @Override
+ public String getName() {
+ return "Service Catalogue REST";
+ }
+
+ @Override
+ public String toString() {
+ return "Service Catalogue REST service " + getConfiguration().getName();
+ }
+
+ public static boolean registerNewRESTMethod(
+ RESTFromBioCatalogueServiceDescription restServiceDescription,
+ FindServiceDescriptionsCallBack callBack) {
+ if (callBack == null) {
+ // We are not adding service through a callback and
+ // findServiceDescriptionsAsync() -
+ // we are adding directly from the BioCatalogue perspective.
+ ServiceDescriptionRegistryImpl serviceDescriptionRegistry = ServiceDescriptionRegistryImpl
+ .getInstance();
+ serviceDescriptionRegistry
+ .addServiceDescriptionProvider(new BioCatalogueRESTServiceProvider(
+ restServiceDescription));
+ return true;
+ } else {
+ {
+ // Add the REST method to the Service Panel through the callback
+ callBack.partialResults(Collections
+ .singletonList(restServiceDescription));
+ callBack.finished();
+ return (true);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueServiceProvider.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueServiceProvider.java
new file mode 100644
index 0000000..87518c3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueServiceProvider.java
@@ -0,0 +1,274 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.wsdl.Operation;
+import javax.wsdl.WSDLException;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.Logger;
+import org.xml.sax.SAXException;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivityHealthChecker;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.BioCataloguePerspective;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config.BioCataloguePluginConfiguration;
+import net.sf.taverna.wsdl.parser.UnknownOperationException;
+import net.sf.taverna.wsdl.parser.WSDLParser;
+
+public class BioCatalogueServiceProvider implements ServiceDescriptionProvider
+{
+ public static final String PROVIDER_NAME = "Service Catalogue - selected services";
+
+ private static BioCatalogueServiceProvider instanceOfSelf = null;
+ private static FindServiceDescriptionsCallBack callBack;
+
+ private static List<SoapOperationIdentity> registeredSOAPOperations;
+ private static List<RESTFromBioCatalogueServiceDescription> registeredRESTMethods;
+
+ private static Logger logger = Logger.getLogger(BioCatalogueServiceProvider.class);
+
+
+ public BioCatalogueServiceProvider() {
+ BioCatalogueServiceProvider.instanceOfSelf = this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void findServiceDescriptionsAsync(FindServiceDescriptionsCallBack callBack)
+ {
+ BioCatalogueServiceProvider.callBack = callBack;
+ callBack.status("Starting Service Catalogue Service Provider");
+
+ // --- Initilise the service provider with stored services ---
+
+ // read stored settings
+ // NB! it's crucial to set the custom classloader, otherwise XStream would fail,
+ // as it would attempt to use the default one, which wouldn't know about the
+ // plugin's classes
+ logger.info("Starting to deserialise the list of services stored in the configuration file");
+ XStream xstream = new XStream(new DomDriver());
+ xstream.setClassLoader(BioCataloguePerspective.class.getClassLoader());
+
+ BioCataloguePluginConfiguration configuration = BioCataloguePluginConfiguration.getInstance();
+
+ // *** load stored SOAP operations ***
+ String loadedSOAPServicesXMLString = configuration.getProperty(BioCataloguePluginConfiguration.SOAP_OPERATIONS_IN_SERVICE_PANEL);
+
+ Object loadedSOAPServices = (loadedSOAPServicesXMLString == null ?
+ null :
+ xstream.fromXML(loadedSOAPServicesXMLString));
+
+ registeredSOAPOperations = (loadedSOAPServices == null || !(loadedSOAPServices instanceof List<?>) ?
+ new ArrayList<SoapOperationIdentity>() :
+ (List<SoapOperationIdentity>)loadedSOAPServices
+ );
+ logger.info("Deserialised " + registeredSOAPOperations.size() + Util.pluraliseNoun("SOAP operation", registeredSOAPOperations.size()));
+
+ // prepare the correct format of data for initialisation
+ List<ServiceDescription> results = new ArrayList<ServiceDescription>();
+ for (SoapOperationIdentity opId : registeredSOAPOperations) {
+ results.add(new WSDLOperationFromBioCatalogueServiceDescription(opId));
+ }
+
+
+ // *** load stored REST methods ***
+ String loadedRESTMethodsXMLString = configuration.getProperty(BioCataloguePluginConfiguration.REST_METHODS_IN_SERVICE_PANEL);
+
+ Object loadedRESTMethods = (loadedRESTMethodsXMLString == null ?
+ null :
+ xstream.fromXML(loadedRESTMethodsXMLString));
+
+ registeredRESTMethods = (loadedRESTMethods == null || !(loadedRESTMethods instanceof List<?>) ?
+ new ArrayList<RESTFromBioCatalogueServiceDescription>() :
+ (List<RESTFromBioCatalogueServiceDescription>)loadedRESTMethods);
+ logger.info("Deserialised " + registeredRESTMethods.size() + Util.pluraliseNoun("REST method", registeredRESTMethods.size()));
+
+ results.addAll(registeredRESTMethods);
+
+
+ // *** send the services to the Service Panel ***
+ callBack.partialResults(results);
+
+
+ // NB! This is to be called when it is known that no more items will be added -
+ // it's never true for this provider, as items may be added on user request
+ // at any time!
+ //
+ // callBack.finished();
+ }
+
+ public Icon getIcon() {
+ return null;
+ }
+
+ public String getName(){
+ // TODO - not sure where this is used
+ return "My dummy service";
+ }
+
+ public String getId() {
+ return "http://www.taverna.org.uk/2010/services/servicecatalogue";
+ }
+
+
+ /**
+ * Adds a new "processor" - i.e. a WSDL operation into the main Service Panel.
+ *
+ * @param wsdlLocation URL of the WSDL location of the operation to add.
+ * @param operationName Name of the operation within specified WSDL document.
+ * @return True if the operation was added;
+ * false if the service provided was not yet initiliased (unlikely) or
+ * when supplied strings were empty/null.
+ */
+ public static boolean registerNewWSDLOperation(SoapOperationIdentity soapOperationDetails)
+ {
+ if (BioCatalogueServiceProvider.instanceOfSelf == null || soapOperationDetails == null ||
+ soapOperationDetails.getWsdlLocation() == null || soapOperationDetails.getWsdlLocation().length() == 0 ||
+ soapOperationDetails.getOperationName() == null || soapOperationDetails.getOperationName().length() == 0)
+ {
+ // the service provider hasn't been initialised yet
+ // OR not all details available
+ return (false);
+ }
+ else
+ {
+ // record the newly added operation in the internal list
+ registeredSOAPOperations.add(soapOperationDetails);
+
+ // add the provided operation to the Service Panel
+ ServiceDescription service = new WSDLOperationFromBioCatalogueServiceDescription(soapOperationDetails);
+ BioCatalogueServiceProvider.callBack.partialResults(Collections.singletonList(service));
+ return (true);
+ }
+
+ }
+
+ /**
+ * Adds a SOAP/WSDL service and all of its operations into the Taverna's Service Panel.
+ */
+ public static boolean registerNewWSDLService(String wsdlURL)
+ {
+ if (BioCatalogueServiceProvider.instanceOfSelf == null || wsdlURL == null)
+ {
+ // the service provider hasn't been initialised yet
+ // OR not all details available
+ return (false);
+ }
+ else
+ {
+ // Do the same thing as in the WSDL service provider
+ callBack.status("Service Catalogue service provider: Parsing wsdl: " + wsdlURL);
+ WSDLParser parser = null;
+ try {
+ parser = new WSDLParser(wsdlURL);
+ List<Operation> operations = parser.getOperations();
+ callBack.status("Found " + operations.size() + " WSDL operations of service "
+ + wsdlURL);
+ List<WSDLOperationFromBioCatalogueServiceDescription> items = new ArrayList<WSDLOperationFromBioCatalogueServiceDescription>();
+ for (Operation operation : operations) {
+ WSDLOperationFromBioCatalogueServiceDescription item;
+ try {
+ String operationName = operation.getName();
+ String operationDesc = parser.getOperationDocumentation(operationName);
+ String use = parser.getUse(operationName);
+ String style = parser.getStyle();
+ if (!WSDLActivityHealthChecker.checkStyleAndUse(style, use)) {
+ logger.warn("Unsupported style and use combination " + style + "/" + use + " for operation " + operationName + " from " + wsdlURL);
+ continue;
+ }
+ item = new WSDLOperationFromBioCatalogueServiceDescription(wsdlURL, operationName, operationDesc);
+ items.add(item);
+
+ // Record the newly added operation in the internal list
+ SoapOperationIdentity soapOperationDetails = new SoapOperationIdentity(wsdlURL, operationName, operationDesc);
+ registeredSOAPOperations.add(soapOperationDetails);
+ } catch (UnknownOperationException e) {
+ String message = "Encountered an unexpected operation name:"
+ + operation.getName();
+ callBack.fail(message, e);
+ return false;
+ }
+ }
+ callBack.partialResults(items);
+ callBack.finished();
+ return true;
+ } catch (ParserConfigurationException e) {
+ String message = "Error configuring the WSDL parser";
+ callBack.fail(message, e);
+ return false;
+ } catch (WSDLException e) {
+ String message = "There was an error with the wsdl: " + wsdlURL;
+ callBack.fail(message, e);
+ return false;
+ } catch (IOException e) {
+ String message = "There was an IO error parsing the wsdl: " + wsdlURL
+ + " Possible reason: the wsdl location was incorrect.";
+ callBack.fail(message, e);
+ return false;
+ } catch (SAXException e) {
+ String message = "There was an error with the XML in the wsdl: "
+ + wsdlURL;
+ callBack.fail(message, e);
+ return false;
+ } catch (IllegalArgumentException e) { // a problem with the wsdl url
+ String message = "There was an error with the wsdl: " + wsdlURL + " "
+ + "Possible reason: the wsdl location was incorrect.";
+ callBack.fail(message, e);
+ return false;
+ } catch (Exception e) { // anything else we did not expect
+ String message = "There was an error with the wsdl: " + wsdlURL;
+ callBack.fail(message, e);
+ return false;
+ }
+ }
+
+ }
+
+
+ public static boolean registerNewRESTMethod(RESTFromBioCatalogueServiceDescription restServiceDescription)
+ {
+ if (restServiceDescription == null) {
+ return (false);
+ }
+ else
+ {
+ // record the newly added method in the internal list
+ registeredRESTMethods.add(restServiceDescription);
+
+ // add the provided method to the Service Panel
+ BioCatalogueServiceProvider.callBack.partialResults(Collections.singletonList(restServiceDescription));
+ return (true);
+ }
+ }
+
+
+ public static List<SoapOperationIdentity> getRegisteredSOAPOperations() {
+ return (registeredSOAPOperations);
+ }
+
+ public static List<RESTFromBioCatalogueServiceDescription> getRegisteredRESTMethods() {
+ return (registeredRESTMethods);
+ }
+
+
+ /**
+ * Clears internal lists of stored SOAP operations / REST methods.
+ * Therefore, once Taverna is restarted, the stored services will
+ * be effectively "forgotten".
+ */
+ public static void clearRegisteredServices() {
+ registeredRESTMethods.clear();
+ registeredSOAPOperations.clear();
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueWSDLOperationServiceProvider.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueWSDLOperationServiceProvider.java
new file mode 100644
index 0000000..62a39d7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/BioCatalogueWSDLOperationServiceProvider.java
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.perspectives.biocatalogue.integration.service_panel;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.wsdl.Operation;
+import javax.wsdl.WSDLException;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.Logger;
+import org.xml.sax.SAXException;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivityHealthChecker;
+import net.sf.taverna.t2.servicedescriptions.AbstractConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionRegistryImpl;
+import net.sf.taverna.wsdl.parser.UnknownOperationException;
+import net.sf.taverna.wsdl.parser.WSDLParser;
+
+/**
+ * Service provider for WSDL operations added to the Service Panel through the
+ * BioCatalogue perspective.
+ *
+ * @author Alex Nenadic
+ */
+public class BioCatalogueWSDLOperationServiceProvider extends
+ AbstractConfigurableServiceProvider<WSDLOperationFromBioCatalogueServiceDescription> {
+
+ public BioCatalogueWSDLOperationServiceProvider(
+ WSDLOperationFromBioCatalogueServiceDescription wsdlOperationDescription) {
+ super(wsdlOperationDescription);
+ }
+
+ public BioCatalogueWSDLOperationServiceProvider() {
+ super(new WSDLOperationFromBioCatalogueServiceDescription(new SoapOperationIdentity("", "", "")));
+ }
+
+ public static final String PROVIDER_NAME = "Service Catalogue - selected services";
+
+ private static final URI providerId = URI
+ .create("http://taverna.sf.net/2010/service-provider/servicecatalogue/wsdl");
+
+ private static Logger logger = Logger.getLogger(BioCatalogueWSDLOperationServiceProvider.class);
+
+ @Override
+ protected List<? extends Object> getIdentifyingData() {
+ return getConfiguration().getIdentifyingData();
+ }
+
+ @Override
+ public void findServiceDescriptionsAsync(
+ FindServiceDescriptionsCallBack callBack) {
+ callBack.status("Starting Service Catalogue WSDL Service Provider");
+ registerWSDLOperation(getConfiguration(), callBack);
+ }
+
+ @Override
+ public Icon getIcon() {
+ return getConfiguration().getIcon();
+ }
+
+ @Override
+ public String getId() {
+ return providerId.toString();
+ }
+
+ @Override
+ public String getName() {
+ return "Service Catalogue WSDL";
+ }
+
+ @Override
+ public String toString() {
+ return "Service Catalogue WSDL service " + getConfiguration().getName();
+ }
+
+ public static boolean registerWSDLOperation(
+ WSDLOperationFromBioCatalogueServiceDescription wsdlOperationDescription,
+ FindServiceDescriptionsCallBack callBack) {
+
+ if (callBack == null) {
+ // We are not adding service through Taverna service registry's callback and
+ // findServiceDescriptionsAsync() -
+ // we are adding directly from the BioCatalogue perspective.
+ ServiceDescriptionRegistryImpl serviceDescriptionRegistry = ServiceDescriptionRegistryImpl
+ .getInstance();
+ serviceDescriptionRegistry
+ .addServiceDescriptionProvider(new BioCatalogueWSDLOperationServiceProvider(
+ wsdlOperationDescription));
+ return true;
+ } else {
+ // Add the WSDL operation to the Service Panel through the callback
+ callBack.partialResults(Collections
+ .singletonList(wsdlOperationDescription));
+ callBack.finished();
+ return (true);
+ }
+ }
+
+ /**
+ * Adds a SOAP/WSDL service and all of its operations into the Taverna's Service Panel.
+ */
+ public static boolean registerWSDLService(String wsdlURL, FindServiceDescriptionsCallBack callBack)
+ {
+ String errorMessage = null;
+ Exception ex = null;
+
+ List<Operation> operations = null;
+ List<WSDLOperationFromBioCatalogueServiceDescription> items = null;
+
+ // Do the same thing as in the WSDL service provider
+ WSDLParser parser = null;
+ try {
+ parser = new WSDLParser(wsdlURL);
+ operations = parser.getOperations();
+ items = new ArrayList<WSDLOperationFromBioCatalogueServiceDescription>();
+ for (Operation operation : operations) {
+ WSDLOperationFromBioCatalogueServiceDescription item;
+ try {
+ String operationName = operation.getName();
+ String operationDesc = parser.getOperationDocumentation(operationName);
+ String use = parser.getUse(operationName);
+ String style = parser.getStyle();
+ if (!WSDLActivityHealthChecker.checkStyleAndUse(style, use)) {
+ logger.warn("Unsupported style and use combination " + style + "/" + use + " for operation " + operationName + " from " + wsdlURL);
+ continue;
+ }
+ item = new WSDLOperationFromBioCatalogueServiceDescription(wsdlURL, operationName, operationDesc);
+ items.add(item);
+ } catch (UnknownOperationException e) {
+ errorMessage = "Encountered an unexpected operation name:"
+ + operation.getName();
+ ex = e;
+ }
+ }
+ } catch (ParserConfigurationException e) {
+ errorMessage = "Error configuring the WSDL parser";
+ ex = e;
+ } catch (WSDLException e) {
+ errorMessage = "There was an error with the wsdl: " + wsdlURL;
+ ex = e;
+ } catch (IOException e) {
+ errorMessage = "There was an IO error parsing the wsdl: " + wsdlURL
+ + " Possible reason: the wsdl location was incorrect.";
+ ex = e;
+ } catch (SAXException e) {
+ errorMessage = "There was an error with the XML in the wsdl: "
+ + wsdlURL;
+ ex = e;
+ } catch (IllegalArgumentException e) { // a problem with the wsdl url
+ errorMessage = "There was an error with the wsdl: " + wsdlURL + " "
+ + "Possible reason: the wsdl location was incorrect.";
+ ex = e;
+ } catch (Exception e) { // anything else we did not expect
+ errorMessage = "There was an error with the wsdl: " + wsdlURL;
+ ex = e;
+ }
+
+ if (callBack == null) {
+ if (errorMessage != null){
+ logger.error(errorMessage, ex);
+ return false;
+ }
+ else{
+ // We are not adding service through Taverna service registry's callback and
+ // findServiceDescriptionsAsync() -
+ // we are adding directly from the BioCatalogue perspective.
+ ServiceDescriptionRegistryImpl serviceDescriptionRegistry = ServiceDescriptionRegistryImpl
+ .getInstance();
+ for (WSDLOperationFromBioCatalogueServiceDescription item : items) {
+ serviceDescriptionRegistry
+ .addServiceDescriptionProvider(new BioCatalogueWSDLOperationServiceProvider(
+ item));
+ }
+ return true;
+ }
+ } else {
+ if (errorMessage != null){
+ callBack.fail(errorMessage, ex);
+ return false;
+ }
+ else{
+ callBack.status("Found " + operations.size() + " WSDL operations of service "
+ + wsdlURL);
+ callBack.partialResults(items);
+ callBack.finished();
+ return true;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/RESTFromBioCatalogueServiceDescription.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/RESTFromBioCatalogueServiceDescription.java
new file mode 100644
index 0000000..1551294
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/RESTFromBioCatalogueServiceDescription.java
@@ -0,0 +1,194 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.t2.lang.beans.PropertyAnnotation;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+
+import net.sf.taverna.t2.activities.rest.RESTActivity;
+import net.sf.taverna.t2.activities.rest.RESTActivity.DATA_FORMAT;
+import net.sf.taverna.t2.activities.rest.RESTActivity.HTTP_METHOD;
+import net.sf.taverna.t2.activities.rest.RESTActivityConfigurationBean;
+import net.sf.taverna.t2.activities.rest.ui.servicedescription.RESTActivityIcon;
+
+/**
+ * This class is solely intended to support import of REST services from BioCatalogue.
+ *
+ * @author Sergejs Aleksejevs
+ */
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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
+ ******************************************************************************/
+public class RESTFromBioCatalogueServiceDescription extends ServiceDescription<RESTActivityConfigurationBean>
+{
+ private static final int SHORT_DESCRIPTION_MAX_LENGTH = 200;
+
+ private static final String FULL_DESCRIPTION = "Full description";
+
+ public static final int AMBIGUOUS_ACCEPT_HEADER_VALUE = 100;
+ public static final int DEFAULT_ACCEPT_HEADER_VALUE = 110;
+ public static final int AMBIGUOUS_CONTENT_TYPE_HEADER_VALUE = 200;
+ public static final int DEFAULT_CONTENT_TYPE_HEADER_VALUE = 210;
+
+
+ private RESTActivityConfigurationBean serviceConfigBean;
+ private String serviceName;
+ private String description;
+
+ private List<Integer> dataWarnings;
+
+
+ /**
+ * Constructor instantiates config bean and pre-populates
+ * it with default values.
+ */
+ public RESTFromBioCatalogueServiceDescription()
+ {
+ // apply default name in case it won't be set manually later
+ this.serviceName = "REST Service";
+ this.serviceConfigBean = RESTActivityConfigurationBean.getDefaultInstance();
+ this.dataWarnings = new ArrayList<Integer>();
+ }
+
+ /**
+ * The subclass of Activity which should be instantiated when adding a service
+ * for this description.
+ */
+ @Override
+ public Class<? extends Activity<RESTActivityConfigurationBean>> getActivityClass() {
+ return RESTActivity.class;
+ }
+
+ /**
+ * The configuration bean which is to be used for configuring the instantiated activity.
+ *
+ * Values are to be set through individual setters provided in this class.
+ */
+ @Override
+ public RESTActivityConfigurationBean getActivityConfiguration() {
+ return serviceConfigBean;
+ }
+
+ /**
+ * An icon to represent this service type in the service palette.
+ */
+ @Override
+ public Icon getIcon() {
+ return RESTActivityIcon.getRESTActivityIcon();
+ }
+
+ /**
+ * The display name that will be shown in service palette and will
+ * be used as a template for processor name when added to workflow.
+ */
+ @Override
+ public String getName() {
+ return serviceName;
+ }
+
+
+ /**
+ * Truncates the description if necessary to {@link WSDLOperationFromBioCatalogueServiceDescription#SHORT_DESCRIPTION_MAX_LENGTH} --
+ * to get full description, use {@link WSDLOperationFromBioCatalogueServiceDescription#getFullDescription()}
+ */
+ public String getDescription() {
+ if (this.description != null && this.description.length() > SHORT_DESCRIPTION_MAX_LENGTH) {
+ return (this.description.substring(0, SHORT_DESCRIPTION_MAX_LENGTH) + "(...)");
+ }
+ else {
+ return this.description;
+ }
+ }
+
+ @PropertyAnnotation(displayName = FULL_DESCRIPTION)
+ public String getFullDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * The path to this service description in the service palette. Folders
+ * will be created for each element of the returned path.
+ *
+ * (Shouldn't really be ever used, as instances of different type are
+ * added into the Service Panel).
+ */
+ @Override
+ public List<String> getPath() {
+ // For deeper paths you may return several strings
+ return Arrays.asList(BioCatalogueRESTServiceProvider.PROVIDER_NAME, "REST @ " + serviceConfigBean.getUrlSignature());
+ }
+
+ /**
+ * Return a list of data values uniquely identifying this service
+ * description (to avoid duplicates). Include only primary key like fields,
+ * ie. ignore descriptions, icons, etc.
+ */
+ @Override
+ protected List<? extends Object> getIdentifyingData() {
+ return Arrays.<Object>asList(serviceConfigBean.getUrlSignature(), serviceConfigBean.getHttpMethod());
+ }
+
+
+ public List<Integer> getDataWarnings() {
+ return dataWarnings;
+ }
+
+
+ public void setURLSignature(String urlSignature) {
+ this.serviceConfigBean.setUrlSignature(urlSignature);
+ }
+
+
+ public void setHttpMethod(HTTP_METHOD httpMethod) {
+ this.serviceConfigBean.setHttpMethod(httpMethod);
+ }
+
+
+ public void setAcceptHeaderValue(String acceptHeaderValue) {
+ this.serviceConfigBean.setAcceptsHeaderValue(acceptHeaderValue);
+ }
+
+
+ public void setOutgoingContentType(String outgoingContentType)
+ {
+ this.serviceConfigBean.setUrlSignature(outgoingContentType);
+
+ // automatically infer data format - string/binary from the content type
+ if (outgoingContentType.startsWith("text")) { this.serviceConfigBean.setOutgoingDataFormat(DATA_FORMAT.String); }
+ else { this.serviceConfigBean.setOutgoingDataFormat(DATA_FORMAT.Binary); }
+ }
+
+
+ public void setServiceName(String name) {
+ this.serviceName = name;
+ }
+
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/WSDLOperationFromBioCatalogueServiceDescription.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/WSDLOperationFromBioCatalogueServiceDescription.java
new file mode 100644
index 0000000..f5de088
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/integration/service_panel/WSDLOperationFromBioCatalogueServiceDescription.java
@@ -0,0 +1,116 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivity;
+import net.sf.taverna.t2.activities.wsdl.WSDLActivityConfigurationBean;
+import net.sf.taverna.t2.activities.wsdl.servicedescriptions.WSDLActivityIcon;
+import net.sf.taverna.t2.lang.beans.PropertyAnnotation;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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
+ ******************************************************************************/
+public class WSDLOperationFromBioCatalogueServiceDescription extends ServiceDescription<WSDLActivityConfigurationBean>
+{
+ private static final int SHORT_DESCRIPTION_MAX_LENGTH = 200;
+
+ private static final String FULL_DESCRIPTION = "Full description";
+
+
+ private final String wsdlLocation;
+ private final String operationName;
+ private final String description;
+
+
+ public WSDLOperationFromBioCatalogueServiceDescription(String wsdlLocation, String operationName, String description)
+ {
+ this.wsdlLocation = wsdlLocation;
+ this.operationName = operationName;
+ this.description = description;
+ }
+
+ public WSDLOperationFromBioCatalogueServiceDescription(SoapOperationIdentity soapOpearationIdentity)
+ {
+ this.wsdlLocation = soapOpearationIdentity.getWsdlLocation();
+ this.operationName = soapOpearationIdentity.getOperationName();
+ this.description = soapOpearationIdentity.getDescription();
+ }
+
+
+ @Override
+ public Class getActivityClass() {
+ return WSDLActivity.class;
+ }
+
+ @Override
+ public WSDLActivityConfigurationBean getActivityConfiguration() {
+ WSDLActivityConfigurationBean bean = new WSDLActivityConfigurationBean();
+ bean.setOperation(operationName);
+ bean.setWsdl(wsdlLocation);
+ return bean;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return WSDLActivityIcon.getWSDLIcon();
+ }
+
+ @Override
+ public String getName() {
+ return (this.operationName);
+ }
+
+ /**
+ * Truncates the description if necessary to {@link WSDLOperationFromBioCatalogueServiceDescription#SHORT_DESCRIPTION_MAX_LENGTH} --
+ * to get full description, use {@link WSDLOperationFromBioCatalogueServiceDescription#getFullDescription()}
+ */
+ public String getDescription() {
+ if (this.description != null && this.description.length() > SHORT_DESCRIPTION_MAX_LENGTH) {
+ return (this.description.substring(0, SHORT_DESCRIPTION_MAX_LENGTH) + "(...)");
+ }
+ else {
+ return this.description;
+ }
+ }
+
+ @PropertyAnnotation(displayName = FULL_DESCRIPTION)
+ public String getFullDescription() {
+ return this.description;
+ }
+
+ @Override
+ public List<String> getPath() {
+ return Arrays.asList(BioCatalogueWSDLOperationServiceProvider.PROVIDER_NAME, "WSDL @ " + this.wsdlLocation);
+ }
+
+ @Override
+ protected List<? extends Object> getIdentifyingData()
+ {
+ // This is important - Taverna won't add identical operations
+ // into the Service Panel. These tokens distinguish added items.
+ return Arrays.asList(wsdlLocation, operationName);
+ }
+
+}
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider
new file mode 100644
index 0000000..44cf82b
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.BioCatalogueWSDLOperationServiceProvider
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.BioCatalogueRESTServiceProvider
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..8bdcfa5
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.menus.BioCatalogueContextualMenuSection
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.menus.MenuActionProcessorHealthCheck
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..b036654
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+#net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentShutdownHook
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..bc9b362
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config.BioCataloguePluginConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.report.explainer.VisitExplainer b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.report.explainer.VisitExplainer
new file mode 100644
index 0000000..5b64699
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.report.explainer.VisitExplainer
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.BioCatalogueWSDLActivityHealthCheckVisitExplainer
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..a26e638
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views.BioCataloguePluginProcessorContextViewFactory
+#net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views.BioCataloguePluginInputPortContextViewFactory
+#net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.contextual_views.BioCataloguePluginOutputPortContextViewFactory
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
new file mode 100644
index 0000000..eb7f253
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.BioCataloguePerspective
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..1c7d751
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..40b15c7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker
new file mode 100644
index 0000000..f24a3dd
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.BioCatalogueWSDLActivityHealthChecker
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-grey-bert2-still.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-grey-bert2-still.png
new file mode 100644
index 0000000..12abdf6
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-grey-bert2-still.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-grey-bert2.gif b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-grey-bert2.gif
new file mode 100644
index 0000000..c4793ec
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-grey-bert2.gif
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-orange-bert2-still.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-orange-bert2-still.png
new file mode 100644
index 0000000..9b6784e
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-orange-bert2-still.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-orange-bert2.gif b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-orange-bert2.gif
new file mode 100644
index 0000000..c6745b9
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-orange-bert2.gif
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-still.gif b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-still.gif
new file mode 100644
index 0000000..b100470
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader-still.gif
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader.gif b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader.gif
new file mode 100644
index 0000000..078b55f
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/ajax-loader.gif
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/biocatalogue-perspective.xml b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/biocatalogue-perspective.xml
new file mode 100644
index 0000000..8145d87
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/biocatalogue-perspective.xml
@@ -0,0 +1,19 @@
+<basepane>
+ <child>
+ <znode classname="net.sf.taverna.zaria.ZRavenComponent">
+ <component scroll="false">
+ <raven>
+ <group>net.sf.taverna.t2.ui-exts</group>
+ <artifact>perspective-biocatalogue</artifact>
+ </raven>
+ <classname>
+ net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory
+ </classname>
+ <interface>
+ net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
+ </interface>
+ </component>
+ </znode>
+ </child>
+ <namedcomponents />
+</basepane>
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/biocatalogue_styles.css b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/biocatalogue_styles.css
new file mode 100644
index 0000000..3f7246a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/biocatalogue_styles.css
@@ -0,0 +1,2673 @@
+@charset "utf-8";
+/**
+ * BioCatalogue: app/public/stylesheets/styles.css
+ *
+ * Copyright (c) 2009, University of Manchester, The European Bioinformatics
+ * Institute (EMBL-EBI) and the University of Southampton.
+ * See license.txt for details
+ */
+
+/* CSS Document */
+
+/**
+ * NOTE: this relies on the Yahoo IU reset-fonts CSS to normalise styles for HTML elements and fonts
+ * ALl font sizes MUST be specified in %ages as per the table here: http://developer.yahoo.com/yui/fonts/
+ */
+
+/*
+** Markup free clearing
+** Details: http://www.positioniseverything.net/easyclearing.html
+*/
+.clear-block:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+.clear-block {
+ display: inline-block;
+}
+
+/* Hides from IE-mac \*/
+* html .clear-block {
+ height: 1%;
+}
+.clear-block {
+ display: block;
+}
+/* End hide from IE-mac */
+
+
+
+/**
+ *
+ * This css is a variation of the Garland theme, for Drupal 5.0
+ *
+ * Credits to:
+ * Stefan Nagtegaal, iStyledThis [dot] nl
+ * Steven Wittens, acko [dot] net`
+ *
+ */
+
+/**
+ * Generic elements
+ */
+
+html {
+ background-color: #e9f7cc;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ background-color: #e9f7cc;
+ font-family: arial;
+ color: #333333;
+ min-width: 950px;
+}
+
+p {
+ font-family: arial;
+ margin: 1em 0;
+ padding: 0;
+ line-height: 1.5;
+}
+
+input {
+ color: #333333;
+ vertical-align: middle;
+}
+
+textarea, select {
+ color: #333333;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+ font-family: arial;
+ line-height: 1.5;
+ color: #333333;
+}
+
+h1 {
+ font-size: 153.9%;
+ color: #000033;
+ margin: 0.5em 0;
+}
+
+h2 {
+ font-size: 138.5%;
+}
+
+h3 {
+ font-size: 123.1%;
+}
+
+h4 {
+ margin: 0.5em 0;
+}
+
+h5 {
+ margin: 0.8em 0 0.5em 0;
+ padding-bottom: 0.3em;
+ border-bottom: 1px solid #CCC;
+}
+
+h6 {
+}
+
+table {
+ margin: 0;
+ border-collapse: separate;
+}
+
+th,td {
+ text-align: left;
+ border: none;
+ vertical-align: top;
+}
+
+ul, quote, code, fieldset {
+ margin: 0.5em 0;
+}
+
+pre {
+ background-color: #EEEEEE;
+ padding: 1em;
+ font-size: 85%;
+}
+
+small {
+ line-height: 1.3;
+ color: #555555;
+}
+
+fieldset {
+ border: 1px solid #cdeb8b;
+ padding: 0.3em 1.3em 0.8em 1.3em;
+}
+
+legend {
+ padding: 0.3em 0.6em;
+ text-align: left;
+ font-weight: bold;
+ margin: 0;
+ margin-bottom: 0.2em;
+}
+
+strong {
+ font-weight: bold;
+}
+
+em {
+ font-style: italic;
+}
+
+a:link, a:visited {
+ color: #333333;
+ text-decoration: underline;
+}
+
+a:hover {
+ color: #ff7400;
+ text-decoration: underline;
+}
+
+a:active, a.active {
+ color: #ff7400;
+ text-decoration: underline;
+}
+
+a img {
+ text-decoration: none;
+}
+
+hr {
+ margin: 0.5em 0;
+ *margin: 0;
+ padding: 0;
+ border: none;
+ height: 1px;
+ background: #5294c1;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+}
+
+ul li {
+ margin: 0;
+ padding: 0;
+ line-height: 1.4;
+}
+
+img, a img {
+ border: none;
+}
+
+.center {
+ text-align: center;
+}
+
+.nomargin {
+ margin: 0;
+}
+
+.medium_linespaced {
+ line-height: 1.5;
+}
+
+.high_linespaced {
+ line-height: 2.0;
+}
+
+.inline-block {
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+}
+
+.faded {
+ color: #999;
+}
+
+.faded_plus {
+ color: #AAA;
+}
+
+.faded_plus_plus {
+ color: #BBB;
+}
+
+.green_box {
+ border: 1px dotted #999999;
+ background-color: #e9f7cc;
+ padding: 2px;
+}
+
+.green_highlight {
+ background-color: #e9f7cc;
+ padding: 3px 5px 3px 5px;
+}
+
+.orange_highlight {
+ background-color: #ffbb7f;
+ padding: 4px 6px 4px 6px;
+}
+
+.yellow_highlight {
+ background-color: #FFC;
+ padding: 3px 5px 3px 5px;
+}
+
+.standout_link {
+ color: #333333;
+ border: 1px solid #EED799;
+ background: #FFC;
+ padding: 0.3em 0.5em;
+ line-height: 1.3;
+}
+
+.ago {
+ margin-left: 0.2em;
+ color: #666;
+}
+
+.container_middled * {
+ vertical-align: middle;
+}
+
+a:link.login, a:visited.login {
+ padding-left: 2px;
+ padding-right: 20px;
+ background: url(../images/login.gif) right no-repeat;
+ white-space: nowrap;
+}
+
+a:link.signup, a:visited.signup {
+ padding-left: 2px;
+ padding-right: 20px;
+ background: url(../images/pencil.gif) right no-repeat;
+ white-space: nowrap;
+}
+
+a:link.green_arrow, a:visited.green_arrow {
+ padding-left: 2px;
+ padding-right: 18px;
+ background: url(../images/green_arrow.gif) right no-repeat;
+ white-space: nowrap;
+}
+
+a:link.red_arrow, a:visited.red_arrow {
+ padding-left: 2px;
+ padding-right: 18px;
+ background: url(../images/red_arrow.gif) right no-repeat;
+ white-space: nowrap;
+}
+
+a:link.red_arrow_left, a:visited.red_arrow_left {
+ padding-right: 2px;
+ padding-left: 18px;
+ background: url(../images/red_arrow_left.gif) left no-repeat;
+ white-space: nowrap;
+}
+
+a:link.write_email, a:visited.write_email {
+ padding-left: 2px;
+ padding-right: 22px;
+ background: url(../images/write_email.gif) right no-repeat;
+ white-space: nowrap;
+}
+
+a:link.edit_profile, a:visited.edit_profile {
+ padding-right: 2px;
+ padding-left: 22px;
+ background: url(../images/user_edit.gif) left no-repeat;
+ white-space: nowrap;
+}
+
+a:link.change_pwd, a:visited.change_pwd {
+ padding-right: 2px;
+ padding-left: 22px;
+ background: url(../images/key.gif) left no-repeat;
+ white-space: nowrap;
+}
+
+a:hover.login {
+ background: url(../images/login_hover.gif) right no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.signup {
+ background: url(../images/pencil_hover.gif) right no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.green_arrow {
+ background: url(../images/green_arrow_hover.gif) right no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.red_arrow {
+ background: url(../images/red_arrow_hover.gif) right no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.red_arrow_left {
+ background: url(../images/red_arrow_left_hover.gif) left no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.write_email {
+ background: url(../images/write_email_hover.gif) right no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.edit_profile {
+ background: url(../images/user_edit_hover.gif) left no-repeat;
+ text-decoration: underline;
+}
+
+a:hover.change_pwd {
+ background: url(../images/key_hover.gif) left no-repeat;
+ text-decoration: underline;
+}
+
+#error_flash .rcontainer,
+#notice_flash .rcontainer {
+ padding: 0;
+ margin: auto;
+ margin-top: 0;
+ margin-bottom: 1em;
+ text-align: center;
+ max-width: 800px;
+ line-height: 1.3;
+}
+
+#error_flash .rcontain,
+#notice_flash .rcontain {
+ padding: 0.1em 0.5em;
+}
+
+#last_search_notice {
+ text-align: right;
+ line-height: 1.0;
+ padding: 0;
+ margin: 0 1em;
+}
+
+#last_search_notice * {
+ vertical-align: middle;
+}
+
+#last_search_notice_text {
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+ color: #333333;
+ border: 1px solid #99CCFF;
+ background-color: #EEF6FF;
+ padding: 0.2em 0.4em;
+}
+
+.flash_header {
+ font-weight: bold;
+ font-size: 108%;
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
+
+.flash_body {
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
+
+/**
+ * Layout
+ */
+#header-region {
+ height: 0.75em;
+}
+
+#wrapper {
+ background: #e9f7cc url(../images/central_part_small.gif) repeat-x top center;
+}
+
+#wrapper #container {
+ margin: 0 auto;
+ padding: 0 20px;
+ max-width: 1270px;
+}
+
+#wrapper #container #header {
+ padding-top: 1.1em;
+ height: 5em;
+}
+
+#wrapper #container #header #logo-floater {
+ position: absolute;
+ /*padding-top: 8px;*/
+}
+
+/* With 2 columns, require a minimum width of 800px. */
+body.sidebar-right {
+ min-width: 850px;
+}
+
+/* We must define 100% width to avoid the body being too narrow for near-empty pages */
+#wrapper #container #center {
+ float: left;
+ width: 100%;
+}
+
+/* So we move the #center container over the sidebars to compensate */
+body.sidebar-right #center {
+ margin-right: -210px;
+}
+
+/* And add blanks right for the sidebars to fill */
+body.sidebar-right #squeeze {
+ margin-right: 210px;
+}
+
+/* We ensure the sidebars are still clickable using z-index */
+#wrapper #container .sidebar {
+ margin: 60px 0 5em;
+ width: 210px;
+ float: left;
+ z-index: 2;
+ position: relative;
+}
+
+/* Now we add the backgrounds for the main content shading */
+#wrapper #container #center #squeeze {
+ background: #fff url(../images/central_bar_big.png) repeat-x 50% 0;
+ position: relative;
+ /*min-width: 600px;*/
+ min-width: 900px;
+}
+
+#wrapper #container #center .right-corner {
+ background: transparent url(../images/right_corner.gif) no-repeat 100% 0;
+ position: relative;
+ /*left: 10px;*/
+ left: 5px;
+ /*border: 1px #ff0000 solid;*/
+ /*min-width: 600px;*/
+ min-width: 900px;
+}
+
+#wrapper #container #center .right-corner .left-corner {
+ padding: 50px 25px 3em 25px;
+ background: transparent url(../images/left_corner.gif) no-repeat 0 0;
+ margin-left: -10px;
+ position: relative;
+ /*left: 1px;*/
+ min-height: 400px;
+}
+
+#footer {
+ float: none;
+ clear: both;
+ text-align: center;
+ padding-top: 1em;
+ padding-bottom: 1em;
+ /*margin: 4em 0 -3em;*/
+ /*color: #898989;*/
+}
+
+body.sidebar-right #footer {
+ width: 75%;
+}
+
+#footer_links {
+ float:left;
+ position:relative;
+ left:50%;
+ text-align:left;
+}
+
+#footer_links ul {
+ list-style:none;
+ position:relative;
+ left:-50%;
+}
+
+#footer_links ul li {
+ list-style-type: none;
+ list-style-image: none;
+ margin: 0;
+ padding: 0;
+ float: left;
+ position:relative;
+}
+
+#footer_links ul li.separator {
+ border-left: 1px solid #333333;
+}
+
+#footer_links ul li a, #footer_links ul li a:link, #footer_links ul li a:visited {
+ display: block;
+ margin: 0 1em;
+ color: #333333;
+}
+
+#footer_links ul li a:hover, #footer_links ul li a.active {
+ color: #ff7400;
+}
+
+#footer .logos {
+ margin: 1.5em 0;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+ border: 1px solid #DDD;
+ width: 950px;
+ color: #333;
+ background-color: #FFF;
+}
+
+#footer .logos * {
+ vertical-align: middle;
+}
+
+#footer .logos p {
+ text-align: center;
+}
+
+#footer .logos a {
+ margin: 0 0.8em;
+}
+
+#footer .copyright {
+ text-align: center;
+ font-weight: bold;
+ font-size: 93%;
+ margin-top: 1.5em;
+}
+
+#wrapper #container #content {
+ margin-top: 2em;
+}
+
+#wrapper #container #action_bar {
+ position: absolute;
+ top: 10px;
+ left: 30px;
+ z-index: 3;
+ font-size: 123.1%;
+ font-weight: bolder;
+ vertical-align: middle;
+}
+
+#wrapper #container #action_bar #action_links {
+ padding-left: 15px;
+}
+
+#wrapper #container #action_bar #action_links * {
+ vertical-align: middle;
+}
+
+#wrapper #container #action_bar #action_links a {
+ padding-left: 15px;
+ padding-right: 14px;
+ background: transparent url(../images/green_separator.gif) no-repeat left;
+ vertical-align: middle;
+ text-align: center;
+}
+
+#wrapper #container #action_icons {
+ position: absolute;
+ top: 14px;
+ right: 10px;
+ z-index: 3;
+ vertical-align: middle;
+}
+
+#wrapper #container #action_icons * {
+ vertical-align: middle;
+}
+
+#wrapper #container #action_icons a {
+ padding: 0 5px 0 10px;
+ background: transparent url(../images/green_separator.gif) no-repeat left;
+ vertical-align: middle;
+}
+
+#wrapper #container #action_bar img {
+ vertical-align: middle;
+}
+
+body.sidebar-right #footer {
+ margin-right: -210px;
+}
+
+/* BEGIN breadcrumbs */
+
+#breadcrumbs_bar {
+ z-index: 3;
+ font-size: 85%;
+ color: #666666;
+ margin: 3px -10px 0 -5px;
+}
+
+#breadcrumbs_bar table {
+ width: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#breadcrumbs_bar td {
+ text-align: left;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+}
+
+#breadcrumbs_bar td * {
+ vertical-align: middle;
+}
+
+ul.breadcrumb_list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ border: none;
+ vertical-align: middle;
+}
+
+ul.breadcrumb_list li {
+ display: inline;
+}
+
+/* END breadcrumbs */
+
+/**
+ * Primary navigation
+ */
+ul.primary-links {
+ margin: 0;
+ padding: 0;
+ float: right;
+ position: relative;
+ z-index: 4;
+ font-size: 108%;
+ font-weight: bold;
+}
+
+ul.primary-links li {
+ margin: 0;
+ padding: 0;
+ float: left;
+ list-style-type: none;
+ list-style-image: none;
+}
+
+ul.primary-links li.separator {
+ /*background: transparent url(../images/green_separator.gif) no-repeat bottom left;*/
+ border-left: 1px solid #999;
+}
+
+ul.primary-links li a, ul.primary-links li a:link, ul.primary-links li a:visited {
+ display: block;
+ margin: 0 1em;
+ /*padding: .75em 0 0;*/
+ color: #333333;
+ /*background: transparent url(images/bg-navigation-item.png) no-repeat 50% 0;*/
+}
+
+ul.primary-links li a:hover, ul.primary-links li a.active {
+ color: #ff7400;
+ /*background: transparent url(images/bg-navigation-item-hover.png) no-repeat 50% 0;*/
+}
+
+/**
+ * Secondary navigation
+ */
+ul.secondary-links {
+ margin: 0;
+ padding: 18px 0 0;
+ float: right;
+ clear: right;
+ position: relative;
+ z-index: 4;
+}
+
+ul.secondary-links li {
+ margin: 0;
+ padding: 0;
+ float: left;
+ background-image: none;
+}
+
+ul.secondary-links li a, ul.secondary-links li a:link, ul.secondary-links li a:visited {
+ display: block;
+ margin: 0 1em;
+ padding: .75em 0 0;
+ color: #cde3f1;
+ background: transparent;
+}
+
+ul.secondary-links li a:hover, ul.secondary-links li a.active {
+ color: #cde3f1;
+ background: transparent;
+}
+
+/**
+ * Local tasks
+ */
+ul.primary, ul.primary li, ul.secondary, ul.secondary li {
+ border: 0;
+ background: none;
+ margin: 0;
+ padding: 0;
+}
+
+#tabs-wrapper {
+ margin: 0 -26px 1em;
+ padding: 0 26px;
+ border-bottom: 1px solid #e9eff3;
+ position: relative;
+}
+ul.primary {
+ padding: 0.5em 0 10px;
+ float: left;
+}
+ul.secondary {
+ clear: both;
+ text-align: left;
+ border-bottom: 1px solid #e9eff3;
+ margin: -0.2em -26px 1em;
+ padding: 0 26px 0.6em;
+}
+h2.with-tabs {
+ float: left;
+ margin: 0 2em 0 0;
+ padding: 0;
+}
+
+ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited,
+ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited {
+ border: 0;
+ background: transparent;
+ padding: 4px 1em;
+ margin: 0 0 0 1px;
+ height: auto;
+ text-decoration: none;
+ position: relative;
+ top: -1px;
+}
+ul.primary li.active a, ul.primary li.active a:link, ul.primary li.active a:visited, ul.primary li a:hover,
+ul.secondary li.active a, ul.secondary li.active a:link, ul.secondary li.active a:visited, ul.secondary li a:hover {
+ background: url(images/bg-tab.png) repeat-x 0 50%;
+ color: #fff;
+}
+ul.primary li.active a,
+ul.secondary li.active a {
+ font-weight: bold;
+}
+
+/**
+ * CSS support
+ */
+span.clear {
+ display: block;
+ clear: both;
+ height: 1px;
+ line-height: 0px;
+ font-size: 0px;
+ margin-bottom: -1px;
+}
+
+/* BEGIN Login box styles */
+#wrapper #container #header #login_links {
+ clear: both;
+ float: right;
+ padding: 2.1em 0 0 0;
+ margin-bottom: 0;
+ position: relative;
+ z-index: 4;
+ color: #333;
+ font-size: 93%;
+}
+
+#wrapper #container #header #login_links .sep {
+ color: #999;
+ margin: 0 0.4em;
+}
+
+body.sidebar-right #wrapper #container #header #login_links {
+ margin-right: 210px;
+}
+
+#login_box {
+ position: absolute;
+ top: 3px;
+ right: 3px;
+ background-color: #e9f7cc;
+ border: 2px solid #CCC;
+ width: 320px;
+ height: auto;
+ margin: 0 0 auto auto;
+ z-index: 4;
+ padding: 7px;
+}
+
+body.sidebar-right #login_box {
+ margin-right: 210px;
+ top: 3px;
+ right: -15.6em;
+}
+
+#login_box form {
+ margin: 10px 0 1.5em 0;
+}
+
+#login_box span label {
+ float: left;
+ font-weight: bold;
+ padding-top: 0.2em;
+}
+
+#login_box span input.small_login {
+ clear: both;
+ float: right;
+ height: 1.4em;
+ line-height: 1.4;
+ width: 200px;
+ /*background-color: #feffcf;*/
+ margin-bottom: 5px;
+}
+
+#login_box input.small_submit_button {
+ clear: both;
+ float: right;
+ position: relative;
+ top: 5px;
+ width: 80px;
+}
+
+#login_box .rpx {
+ margin: 0;
+ padding: 0.8em 0;
+ border-top: 1px dotted #CCC;
+ text-align: center;
+ line-height: 1.8;
+}
+
+#login_box_other_links {
+ clear: both;
+ font-size: 93%;
+ width: 100%;
+ padding-top: 1.2em;
+ border-top: 1px dotted #CCC;
+ text-align: right;
+}
+
+#login_box_other_links li {
+ margin-bottom: 0.7em;
+}
+
+/* END Login box styles */
+
+/* BEGIN sidebar box styles */
+.sidebar_box {
+ border: solid 1px #cccccc;
+ width: 200px;
+ height: 270px;
+ margin-left: 1em;
+}
+
+.sidebar_box .box_title {
+ text-align: center;
+ font-weight: bold;
+ font-size: 123.1%;
+ height: 30px;
+ line-height: 28px;
+ background: url(../images/box_title_bkgd_green_14.gif) repeat-x;
+ /*color: #ff7400;*/
+ /*color: #ffffff;*/
+}
+
+.sidebar_box .box_body {
+ background-color: #fdfdfd;
+ height: 240px;
+ /*background: url(../images/green_body_bkgd2.gif) repeat-x;*/
+}
+
+.sidebar_box .box_body .box_text {
+ padding: 1em 1em 1em 1em;
+}
+
+.sidebar_box .box_body .separator {
+ border-bottom: 1px solid #cccccc;
+ height: 1px;
+ margin: 0 1em 0 1em;
+}
+
+.sidebar_box .box_body .box_subtitle {
+ text-align: center;
+ font-weight: bold;
+ font-size: 108%;
+ position: relative;
+}
+
+/* END sidebar box styles */
+
+/* BEGIN css tooltips/boxovers */
+
+.boxoverTooltipHeader {
+ z-index: 100000;
+ display: none;
+ border: 0;
+ height: 0;
+}
+
+.boxoverTooltipBody {
+ z-index: 100000;
+ border: solid 1px Gray;
+ color: #000000;
+ background-color: #FFFF66;
+ font-size: 93%;
+ font-weight: normal;
+ padding: 0.3em 0.6em;
+ max-width: 500px;
+ text-align: left;
+ line-height: 1.4;
+}
+
+.boxoverInfoHeader {
+ z-index: 100000;
+ display: none;
+ border: 0;
+ height: 0;
+}
+
+.boxoverInfoBody {
+ z-index: 100000;
+ border: solid 1px Gray;
+ color: #333333;
+ padding: 0.3em 0.6em;
+ background-color: #ebf3ff;
+ font-size: 93%;
+ text-align: left;
+ max-width: 500px;
+ line-height: 1.4;
+}
+
+/* END css tooltips/boxovers */
+
+/* BEGIN liquid round corners box styles */
+.liquid-round {
+ /*width: 70%;*/
+ margin: 25px auto;
+ background: url(../images/round_box_2_left.gif) repeat-y left top;
+}
+
+.liquid-round .top {
+ width: 100%;
+ height: 12px;
+ background: url(../images/round_box_2_top.gif) no-repeat left top;
+}
+
+.liquid-round .top span {
+ display: block;
+ position: relative;
+ height: 12px;
+ background: url(../images/round_box_2_top_right.gif) no-repeat right top;
+}
+
+.liquid-round .center-content {
+ position: relative;
+ background: #f8f8f8 url(../images/round_box_2_right.gif) repeat-y right top;
+ padding: 1px 15px 1px 15px;
+ margin-left: 3px;
+}
+
+.liquid-round .center-content img.round_box_title {
+ margin-top: -38px;
+}
+
+.liquid-round .center-content p {
+ margin-top: 0px;
+ text-align: justify;
+ *line-height: 1.5;
+}
+
+.liquid-round .bottom {
+ width: 100%;
+ height: 13px;
+ background: url(../images/round_box_2_bottom.gif) no-repeat left bottom;
+}
+
+.liquid-round .bottom span {
+ display: block;
+ position: relative;
+ height: 13px;
+ background: url(../images/round_box_2_bottom_right.gif) no-repeat right bottom;
+}
+/* END liquid round corners box styles */
+
+.step_text {
+ font-weight: bold;
+ margin-top: 2em;
+ color: #660000;
+ /* border-top: 1px dotted #CCC;
+ padding-top: 0.5em; */
+}
+
+.none_text {
+ font-style: italic;
+ color: #666;
+}
+
+.error_text {
+ color: red;
+ font-weight: bold;
+}
+
+.additional_info_text {
+ font-size: 93%;
+ color: #666666;
+ font-weight: normal;
+}
+
+.required {
+ color: red;
+ font-weight: bold;
+}
+
+.framed {
+ vertical-align: middle;
+ padding: 3px;
+ background-color: #FFF;
+ border: 1px solid #DDD;
+}
+
+.field {
+ border: 1px solid #EEE;
+ background-color: #FFF;
+ padding: 0.2em 0.5em;
+}
+
+.flag {
+ vertical-align: middle;
+}
+
+/* BEGIN pagination styles */
+
+.pagination {
+ background: white;
+ /* self-clearing method: */
+ }
+
+.pagination a,
+.pagination span {
+ padding: .2em .5em;
+ display: block;
+ float: left;
+ margin-right: 1px;
+}
+
+.pagination span.disabled {
+ color: #999;
+ border: 1px solid #DDD;
+}
+
+.pagination span.current {
+ font-weight: bold;
+ background: #E9F7CC;
+ color: #333333;
+ border: 1px solid #CDEB8B;
+}
+
+.pagination a {
+ text-decoration: none;
+ color: #333333;
+ border: 1px solid #CDEB8B;
+}
+
+.pagination a:hover,
+.pagination a:focus {
+ background: #E9F7CC;
+}
+
+.pagination .page_info {
+ background: #E9F7CC;
+ color: #333333;
+ padding: .4em .6em;
+ width: 25em;
+ margin-bottom: 0.5em;
+ text-align: center;
+}
+
+.pagination .page_info b {
+ color: #003;
+ padding: .1em .25em;
+}
+
+.pagination:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+* html .pagination {
+ height: 1%;
+}
+
+*:first-child+html .pagination {
+ overflow: hidden;
+}
+
+/* END pagination styles
+
+/* BEGIN listing styles */
+
+.listings {
+ padding: 0;
+ margin: 0;
+}
+
+.listing_item {
+ margin-bottom: 1.5em;
+ padding: 0.6em 1em;
+ border-left: 1px solid #CCC;
+ border-top: 1px solid #CCC;
+ border-right: 2px solid #BBB;
+ border-bottom: 2px solid #BBB;
+ background-color: #F5F5F5;
+}
+
+.listing_item p {
+ margin: 0;
+}
+
+.listing_item p * {
+ vertical-align: middle;
+}
+
+.listing_item .name_section td {
+ vertical-align: middle;
+}
+
+.listing_item .name {
+ font-size: 138.5%;
+ font-weight: bold;
+ line-height: 1.8;
+}
+
+.listing_item .simple_listing .name {
+ font-size: 123.1%;
+ font-weight: bold;
+ line-height: 1.6;
+}
+
+.listing_item .desc {
+ color: #333333;
+ padding: 0.6em 1em;
+ margin: 1em 0;
+ background-color: #F3F3F3;
+ border: 1px dotted #DDD;
+}
+
+.listing_item .detail {
+ color: #333333;
+ font-size: 85%;
+ margin-top: 0.6em;
+}
+
+.listing_item .detail_simple {
+ color: #333333;
+ font-size: 93%;
+ margin-top: 0.6em;
+}
+
+.listing_item .detail_simple * {
+ vertical-align: baseline;
+}
+
+.listing_item .extra_detail_box {
+ color: #333333;
+ font-size: 85%;
+ margin-top: 1.2em;
+ padding-top: 0.8em;
+ border-top: 1px solid #DDD;
+}
+
+/* END listing styles */
+
+.submitter_info {
+ color: #666;
+ vertical-align: baseline;
+}
+
+.submitter_info * {
+ vertical-align: baseline;
+}
+
+a.service_type_badge {
+ font-size: 85%;
+ font-weight: bold;
+ background-color: #FFF;
+ border: 1px solid #DDD;
+ padding: 2px 3px;
+ text-decoration: none;
+ line-height: 1.0;
+}
+
+a.service_type_badge:hover {
+ color: #FFF;
+ background-color: #999;
+}
+
+.service_type_badge_special {
+ font-size: 85%;
+ font-weight: bold;
+ background-color: #FFEFEF;
+ border: 1px solid #DDD;
+ padding: 2px 3px;
+ text-decoration: none;
+ line-height: 1.0;
+}
+
+a.service_location_flag {
+ margin-left: 1em;
+ line-height: 1.0;
+ vertical-align: middle;
+ padding: 0px;
+ line-height: 1.0;
+}
+
+.operation_box {
+ margin: 2em 0;
+ line-height: 1.5;
+ padding-bottom: 1em;
+ border: 0px solid #DDD;
+ border-width: 2px 0 2px 0;
+}
+
+.operation_heading {
+ margin: 0;
+ padding: 1em 0;
+ font-size: 116%;
+}
+
+.operation_name {
+ background-color: #FFBB7F;
+ font-weight: bold;
+ padding: 0.3em 0.5em;
+}
+
+.port {
+ margin-bottom: 1.5em;
+ padding: 0.7em 0.7em 0.3em 0.7em;
+ border: 1px solid #DDE;
+ background-color: #F7F7F7;
+}
+
+.monitoring_section {
+ margin-bottom: 1em;
+ padding: 0.7em 1em;
+ border: 1px solid #DDE;
+ background-color: #F7F7F7;
+}
+
+ul.simple_list {
+ padding: 0;
+ margin: 0;
+}
+
+ul.simple_list li {
+ padding: 0;
+ margin: 0 0 0.5em 2em;
+ list-style: disc;
+}
+
+.home_heading {
+ font-weight: bold;
+ font-size: 153.9%;
+ line-height: 1.5;
+ text-align: center;
+ padding: 0;
+ margin: 0;
+ margin-top: 1em;
+}
+
+#home_content {
+ width: 950px;
+ /*padding-top: 1em;*/
+ margin: 1em auto auto auto;
+}
+
+#home_content em {
+ color: #ff7400;
+ font-weight: bold;
+}
+
+#key_points {
+ clear:both;
+ margin: 0 0 1em 0;
+}
+
+#key_points li {
+ margin-top: 5px;
+ padding-left: 20px;
+ background: url(../images/bullet_green2.gif) left top no-repeat;
+ /*list-style-type: circle;
+ list-style-image: url("../images/bullet_green2.gif");*/
+ font-size: 108%;
+ line-height: 1.5;
+}
+
+#key_points em {
+ color: #ff7400;
+ font-weight: bold;
+}
+
+div.search_widget {
+ text-align: center;
+}
+
+div.search_widget * {
+ vertical-align: middle;
+}
+
+/* BEGIN Tabber tab styles */
+
+/*--------------------------------------------------
+ REQUIRED to hide the non-active tab content.
+ But do not hide them in the print stylesheet!
+ --------------------------------------------------*/
+.tabberlive .tabbertabhide {
+ display: none;
+}
+
+/*--------------------------------------------------
+ .tabber = before the tabber interface is set up
+ .tabberlive = after the tabber interface is set up
+ --------------------------------------------------*/
+.tabber {
+}
+
+.tabberlive {
+ margin-top: 1em;
+}
+
+/*--------------------------------------------------
+ ul.tabbernav = the tab navigation list
+ li.tabberactive = the active tab
+ --------------------------------------------------*/
+ul.tabbernav
+{
+ margin: 0;
+ border-bottom: 1px solid #CCC;
+ font-weight: bold;
+ font-size: 123.1%;
+ line-height: 24px;
+ padding-bottom: 1px;
+}
+
+ul.tabbernav li
+{
+ list-style: none;
+ margin: 0;
+ display: inline;
+}
+
+ul.tabbernav li a
+{
+ padding: 3px 0.5em;
+ margin-right: 4px;
+ border: 1px solid #CCC;
+ border-bottom: none;
+ background: #E9F7CC;
+ text-decoration: none;
+}
+
+ul.tabbernav li a:link { color: #333; }
+
+ul.tabbernav li a:hover
+{
+ color: #000;
+ background: #BFE76F;
+ border-color: #CCC;
+}
+
+ul.tabbernav li.tabberactive a
+{
+ background-color: #fff;
+ border-bottom: 1px solid #fff;
+}
+
+ul.tabbernav li.tabberactive a:hover
+{
+ color: #000;
+ background: white;
+ border-bottom: 1px solid white;
+}
+
+/*--------------------------------------------------
+ .tabbertab = the tab content
+ Add style only after the tabber interface is set up (.tabberlive)
+ --------------------------------------------------*/
+.tabberlive .tabbertab {
+ padding: 1em;
+ border: 1px solid #CCC;
+ border-top: 0;
+
+ /* If you don't want the tab size changing whenever a tab is changed
+ you can set a fixed height */
+
+ /* height:200px; */
+
+ /* If you set a fix height set overflow to auto and you will get a
+ scrollbar when necessary */
+
+ /* overflow:auto; */
+}
+
+/* If desired, hide the heading since a heading is provided by the tab */
+.tabberlive .tabbertab h3 {
+ display: none;
+}
+.tabberlive .tabbertab h3 {
+ display:none;
+}
+
+/* Example of using an ID to set different styles for the tabs on the page */
+.tabberlive#tab1 {
+}
+.tabberlive#tab2 {
+}
+.tabberlive#tab2 .tabbertab {
+ height:200px;
+ overflow:auto;
+}
+
+/* END Tabber tab styles */
+
+.search_button {
+ border: none;
+ height: 31px;
+ width: 38px;
+ background: url('/images/go_button.png') 0 0 no-repeat;
+ cursor: pointer;
+}
+
+.search_button:hover { }
+
+/* BEGIN tables styles */
+.biocat_table {
+ border: 1px solid #cccccc;
+}
+
+.biocat_table th {
+ font-weight: bold;
+ white-space: nowrap;
+ vertical-align: middle;
+ color: #333333;
+ height: 1.8em;
+ background: url(../images/box_title_bkgd_green_14.gif) repeat-x;
+ background-position: bottom;
+ padding-left: 6px;
+ padding-right: 6px;
+ border-right: 1px solid #cccccc;
+}
+
+.biocat_table th.center {
+ text-align: center;
+}
+
+.biocat_table .small_center {
+ text-align: center;
+ width: 40px;
+}
+
+.biocat_table tr.odd {
+ background-color: #f2fbe1;
+}
+
+.biocat_table td {
+ white-space: nowrap;
+ padding: 3px 6px 3px 6px;
+ border-left: 1px solid #cccccc;
+ border-right: 1px solid #cccccc;
+}
+
+.biocat_table td.center {
+ text-align: center;
+}
+
+.biocat_table td.action_links a:link, .biocat_table td.action_links a:visited {
+ text-align: center;
+ text-decoration: underline;
+ font-size: 93%;
+}
+
+.biocat_table td.action_links a:hover {
+ background-color: #feffcf;
+}
+/* END tables styles */
+
+/* BEGIN ActiveRecord errors box */
+
+/*.fieldWithErrors {
+ padding: 2px;
+ background-color: #ffaaaa;
+ display: table;
+}*/
+
+.fieldWithErrors label {
+ padding: 2px;
+ background-color: #ffaaaa;
+}
+
+.fieldWithErrors input, .fieldWithErrors select {
+ padding: 2px;
+ background-color: #ffaaaa;
+}
+
+#errorExplanation {
+ width: 400px;
+ border: 2px solid #ffaaaa;
+ margin: 1.5em 0;
+ background-color: #f5f5f5;
+ padding-bottom: 1em;
+}
+
+#errorExplanation h2 {
+ text-align: center;
+ font-weight: bold;
+ padding: 0.5em;
+ font-size: 93%;
+ margin: 0;
+ background-image: none;
+ background-color: #ffaaaa;
+ border: none;
+}
+
+#errorExplanation p {
+ font-size: 93%;
+ margin-bottom: 0;
+ margin-top: 0.5em;
+ padding: 0.5em;
+}
+
+#errorExplanation ul {
+ padding-left: 2em;
+}
+
+#errorExplanation ul li {
+ font-size: 93%;
+ list-style: square;
+}
+
+/* END ActiveRecord errors box */
+
+.box_description {
+ padding: 0.9em 1.3em;
+ background-color: #F7F7F7;
+ border: 1px solid #DDD;
+ line-height: 1.4;
+}
+
+.box_info {
+ background: #E9F7CC;
+ color: #333333;
+ padding: 0.6em 1em;
+ margin: 0;
+ margin-bottom: 1em;
+ text-align: center;
+}
+
+.box_info_standout {
+ color: #333333;
+ border: 1px solid #DDD;
+ background: #FFFFCC;
+ padding: 0.3em 0.6em;
+ margin-bottom: 1em;
+ line-height: 1.5;
+}
+
+.box_indented_with_bar {
+ border: 0;
+ border-left: 4px solid #DDD;
+ padding: 1em 0 1em 1em;
+ margin-left: 1em;
+}
+
+.box_currentuser_specific {
+ padding: 1em 1.5em;
+ border: 1px solid #99CCFF;
+ background-color: #EEF6FF;
+}
+
+.box_currentuser_specific * {
+ vertical-align: middle;
+}
+
+.box_form {
+ margin: 1em 0;
+ border: 1px solid #CCC;
+ padding: 0.7em 1.2em;
+ background-color: #EEE;
+}
+
+.box_form fieldset {
+ border: 1px solid #cccccc;
+}
+
+.box_form fieldset legend {
+ font-size: 108%;
+ color: #660000;
+}
+
+.box_form hr {
+ background: #CCC;
+}
+
+/* BEGIN annotations styles */
+
+.annotations_container { }
+
+.annotations_container .inactive {
+ color: #BBB;
+ line-height: 1.0;
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+}
+
+.annotations_container .active {
+ line-height: 1.0;
+ text-decoration: none;
+ color: #333;
+ display: none;
+}
+
+.annotations_container:hover .inactive {
+ display: none;
+}
+
+.annotations_container:hover .active {
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+}
+
+.box_annotations {
+ padding: 0;
+ line-height: 1.4;
+}
+
+.box_annotations .rcontain {
+ padding: 2px;
+}
+
+.box_annotations div.text {
+ padding: 0.1em 0.5em 0.2em 0.5em;
+ overflow-x: auto;
+}
+
+.box_annotations div.text p {
+ margin: 0.5em 0;
+ line-height: 1.4;
+}
+
+.box_annotations div.other {
+ padding: 0.1em 0.5em 0.2em 0.5em;
+}
+
+.box_annotations .actions {
+ margin: 0.5em 0 0 0;
+ font-size: 85%;
+ color: #666;
+ text-align: right;
+}
+
+.annotations_container .add_box {
+ margin-top: 0.5em;
+ text-align: center;
+}
+
+.annotations_container .add_box form {
+ text-align: center;
+}
+
+.annotations_container .add_box form div {
+ width: 100%;
+ text-align: center;
+}
+.annotations_container .add_box form textarea {
+ width: 99%;
+}
+
+.annotations_container .add_box .submit_button_div {
+ margin-top: 0.4em;
+ text-align: right;
+}
+
+.annotations_container .add_box .submit_button_div input {
+ width: 80px;
+}
+
+.annotations_container .login_link_box {
+ text-align: right;
+ margin-top: 0.7em;
+}
+
+.annotation_source_text {
+ float: right;
+ color: #666;
+ background-color: #FFC;
+ font-size: 85%;
+ padding: 0.1em 0.5em;
+ line-height: 1.3;
+}
+
+.annotation_source_text * {
+ vertical-align: middle;
+}
+
+.annotation_source_text a {
+ font-weight: bold;
+}
+
+.annotation_source_provider {
+ background-color: #FFDFDF;
+}
+
+.annotation_source_member {
+ background-color: #FFEFBF;
+}
+
+.annotation_source_registry {
+ background-color: #DDFFDD;
+}
+
+.aliases {
+ font-size: 100%;
+ margin: 0.3em 0 0.2em 0;
+ margin-left: 1em;
+ line-height: 1.6;
+}
+
+.annotations_counts_outer_box {
+ display: -moz-inline-block;
+ display: -moz-inline-box;
+ display: -moz-inline-stack;
+ display: inline-block;
+ *display: inline;
+ vertical-align: top;
+ white-space: nowrap;
+ line-height: 1.0;
+ zoom: 1;
+}
+
+.annotations_counts_outer_box .rtop,
+.annotations_counts_outer_box .rbottom {
+ *display: none;
+}
+
+.annotations_counts_outer_box .rcontain {
+ padding: 0px 3px;
+ *padding: 6px 3px 4px 3px;
+}
+
+.annotations_counts_box {
+ font-weight: bold;
+ font-size: 93%;
+ color: #333333;
+ vertical-align: middle;
+ line-height: 22px;
+}
+
+.annotations_counts_box span {
+ padding: 4px 3px 4px 5px;
+ vertical-align: middle;
+}
+
+.annotations_counts_box img {
+ vertical-align: middle;
+ margin-left: 2px;
+ *margin-left: 0;
+}
+
+.annotations_counts_box span.total {
+ background-color: #DDD;
+}
+
+.annotations_counts_box img {
+ vertical-align: middle;
+}
+
+.annotations_counts_box span.end {
+}
+
+/* END annotations styles */
+
+/* BEGIN terms of use page styles */
+#terms_of_use ul, #terms_of_use ol {
+ padding-left: 3em;
+}
+
+#terms_of_use ul li {
+ padding-top: 1em;
+ list-style: square;
+}
+
+#terms_of_use ol li {
+ padding-top: 1em;
+ list-style: decimal;
+}
+/* END terms of use page styles */
+
+/* BEGIN generic horizontal buttons classes */
+/* Should be used with <ul> elements */
+
+.buttons {
+ margin: 2em 0;
+ font-weight: bold;
+ text-align: center;
+}
+
+.buttons li {
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+ margin: 0;
+ margin-left: 8px;
+ line-height: 2.0;
+}
+
+.buttons li.first {
+ margin-left: 0;
+}
+
+.buttons li:hover {
+}
+
+.buttons li * {
+ vertical-align: middle;
+}
+
+.buttons li a {
+ width: 100%;
+ padding: 6px 0.5em;
+ line-height: 2.0;
+ font-weight: bold;
+ text-decoration: none;
+ border: 1px solid #BBBBBB;
+ background-image: url('/images/button_slim_bg.png');
+ background-position: top;
+ background-repeat: repeat-x;
+ background-color: #EEF6FF;
+}
+
+.buttons li a:hover {
+ background-image: none;
+ background-color: #0099FF;
+ color: #F5F5F5;
+}
+
+.buttons li a * {
+ vertical-align: middle;
+}
+
+a.button_slim {
+ font-weight: bold;
+ line-height: 1.6;
+ text-decoration: none;
+ border: 1px solid #BBBBBB;
+ background-image: url('/images/button_slim_bg.png');
+ background-position: top;
+ background-repeat: repeat-x;
+ background-color: #EEF6FF;
+ vertical-align: middle;
+ padding: 0.1em 0.5em;
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+}
+
+a.button_slim:hover {
+ background-image: none;
+ background-color: #0099FF;
+ color: #F5F5F5;
+}
+
+a.button_slim * {
+ vertical-align: middle;
+}
+
+/* END generic horizontal buttons classes */
+
+/* BEGIN tag cloud styles */
+
+.tag_cloud {
+ text-align: center;
+ line-height: 1.7;
+}
+
+.tag_cloud ul {
+ margin: 0;
+ padding: 0;
+}
+
+.tag_cloud ul li {
+ display: inline;
+ text-decoration: none;
+ margin: 0 0.25em;
+}
+
+.tag_cloud li a {
+ color: #000099;
+ padding: 0.1em 0;
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+}
+
+span.namespace,
+.tag_cloud li a.namespace {
+ color: #999;
+ font-size: 10px;
+}
+
+span.ontology_term,
+.tag_cloud li a.ontology_term {
+ color: #6600CC;
+}
+
+/* END tag cloud styles */
+
+/* BEGIN Section Boxes (usually for inner sidebars) */
+
+.box_section {
+ margin: 0;
+ text-align: center;
+ margin-bottom: 1em;
+}
+
+.box_section .content {
+ padding: 0.5em 0.8em;
+ border-top: none;
+ border-right: 1px solid #DDD;
+ border-bottom: none;
+ border-left: 1px solid #DDD;
+ background-image: url('/images/box-bg1.png');
+ background-position: top;
+ background-repeat: repeat-x;
+ background-color: #FFC;
+ overflow: hidden;
+ line-height: 1.5;
+}
+
+.box_section .xtop,
+.box_section .xbottom {
+ display: block;
+ background: transparent;
+ font-size: 1px;
+}
+
+.box_section .xb1,
+.box_section .xb2,
+.box_section .xb3,
+.box_section .xb4,
+.box_section .xb5,
+.box_section .xb6,
+.box_section .xb7 {
+ display: block;
+ overflow: hidden;
+}
+
+.box_section .xb1,
+.box_section .xb2,
+.box_section .xb3,
+.box_section .xb6,
+.box_section .xb7 {
+ height: 1px;
+}
+
+.box_section .xb2,
+.box_section .xb3,
+.box_section .xb4 {
+ background: #f4f0b0;
+ border-left: 1px solid #DDD;
+ border-right: 1px solid #DDD;
+}
+
+.box_section .xb5,
+.box_section .xb6,
+.box_section .xb7 {
+ background: #ffffcb;
+ border-left: 1px solid #DDD;
+ border-right: 1px solid #DDD;
+}
+
+.box_section .xb1 {
+ margin: 0 5px;
+ background: #DDD;
+}
+
+.box_section .xb2,
+.box_section .xb7 {
+ margin: 0 3px;
+ border-width: 0 2px;
+}
+
+.box_section .xb3,
+.box_section .xb6 {
+ margin: 0 2px;
+}
+
+.box_section .xb4,
+.box_section .xb5 {
+ height: 2px;
+ margin: 0 1px;
+}
+
+.box_section .heading {
+ font-size: 108%;
+ font-weight: bold;
+ line-height: 1.0;
+ margin-top: 0.2em;
+}
+
+.box_section .footer {
+ text-align: center;
+ margin-top: 1em;
+ border-top: 1px solid #DDD;
+ padding-top: 0.7em;
+}
+
+.box_section ul.items {
+ margin: 0 0 0.8em 1em;
+ text-align: left;
+}
+
+.box_section ul.items li {
+ list-style-type: none;
+ margin: 0.6em 0;
+ padding-left: 0.8em;
+ line-height: 1.1;
+ /*border-left: 3px solid #DDDDBB;*/
+ text-align: center;
+}
+
+.box_section ul.items li * {
+ vertical-align: middle;
+}
+
+/* END Section Boxes (usually for inner sidebars) */
+
+
+/* BEGIN ratings table styles */
+
+.ratings_table {
+ border: 1px solid #cccccc;
+ margin-bottom: 0.5em;
+ background-color: #FFC;
+ border-collapse: collapse;
+ font-size: 93%;
+}
+
+.ratings_table th {
+ font-weight: bold;
+ white-space: nowrap;
+ vertical-align: middle;
+ color: #333333;
+ height: 2em;
+ background: url(../images/box_title_bkgd_green_14.gif) repeat-x;
+ background-position: bottom;
+ padding-left: 6px;
+ padding-right: 6px;
+ border-right: 1px solid #cccccc;
+ text-align: center;
+}
+
+.ratings_table td {
+ white-space: nowrap;
+ padding: 4px 6px 4px 6px;
+ border-left: 1px solid #cccccc;
+ border-right: 1px solid #cccccc;
+ vertical-align: middle;
+ text-align: center;
+}
+
+.ratings_table tr:hover {
+ background-color: #F7F7F7;
+}
+
+.ratings_table .your_rating .delete_rating_faded {
+ width: 10px;
+ height: 10px;
+ margin-left: 0.5em;
+ vertical-align: middle;
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+ background: url('/images/delete_faded.png') no-repeat center;
+}
+
+.ratings_table .your_rating .delete_rating a {
+ text-decoration: none;
+}
+
+.ratings_table .your_rating:hover .delete_rating {
+ background: url('/images/delete.png') no-repeat center;
+}
+
+/* END ratings table styles */
+
+/* BEGIN filtering styles */
+
+#filters_header {
+ width: auto;
+ color: #000;
+ line-height: 1.2;
+ padding: 0.5em 0;
+ text-align: center;
+ margin-bottom: 0.5em;
+}
+
+#filters_header .text {
+ font-size: 108%;
+ font-weight: bold;
+ vertical-align: middle;
+}
+
+div.filters {
+ padding: 0 0.3em;
+ line-height: 1.5;
+}
+
+div.filters p.type {
+ font-weight: bold;
+ text-align: center;
+ line-height: 1.2;
+ padding: 0;
+ margin: 0;
+ margin-bottom: 0.8em;
+ color: #555;
+}
+
+div.filters p.subtype {
+ font-size: 93%;
+ font-weight: bold;
+ text-align: center;
+ line-height: 1.2;
+ padding: 0;
+ margin: 0;
+ margin-top: 0.7em;
+ margin-bottom: 0.6em;
+ color: #666;
+ border-bottom: 1px dotted #DDD;
+ padding-bottom: 0.3em;
+}
+
+div.filters p.type_small {
+ font-size: 85%;
+ line-height: 1.2;
+ font-weight: bold;
+ margin: 0;
+ margin-bottom: 0.7em;
+ text-align: center;
+ color: #555;
+}
+
+div.filters div.filter_type_box {
+ margin-bottom: 1em;
+ padding: 0;
+}
+
+div.filters div.current_filters_box {
+ margin-bottom: 1em;
+ padding: 0;
+}
+
+div.filters div.current_filters_box .rcontain {
+ padding: 2px 3px;
+}
+
+div.filters div.current_filters_box .and {
+ font-weight: bold;
+ font-size: 100%;
+ text-align: center;
+ margin: 0;
+ padding-right: 12px;
+}
+
+div.filters div.selected_filters_type_box {
+ margin: 0 0.1em;
+ padding: 0;
+}
+
+div.filters div.selected_filters_type_box .or {
+ font-weight: bold;
+ font-size: 77%;
+ text-align: center;
+ margin: 2px 0;
+ padding-right: 12px;
+}
+
+div.filters ul.top_level {
+ font-size: 85%;
+}
+
+div.filters li {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+div.filters li .container {
+
+}
+
+div.filters li .container a {
+ display: block;
+ vertical-align: top;
+ margin-bottom: 0.3em;
+ padding: 1px 3px;
+ text-decoration: none;
+ border: 1px dotted transparent;
+ vertical-align: baseline;
+}
+
+div.filters li .container a * {
+ vertical-align: baseline;
+}
+
+div.filters li .container a:hover {
+ border: 1px dotted #CCC;
+ background-color: #DDE;
+}
+
+div.filters li .container a.selected {
+ border: 1px dotted #CCC;
+ background-color: #DDE;
+}
+
+div.filters li .container a:hover.selected { }
+
+div.filters li .container a span.text {
+ text-decoration: underline;
+}
+
+div.filters li .container a span.count {
+ margin-left: 0.2em;
+}
+
+div.filters li .container a span.deselect {
+ float: right;
+ width: 12px;
+ background: url('/images/delete_faded_darker.png') no-repeat left center;
+}
+
+div.filters li .container a:hover span.deselect {
+ background: url('/images/delete.png') no-repeat left center;
+}
+
+div.filters li.category_root .container a {
+ margin-bottom: 0.5em;
+}
+
+div.filters li.sub_category .container a {
+ /*border-left: 1px dotted #CCC;*/
+ margin-left: 0.2em;
+}
+
+/* END filtering styles */
+
+/* BEGIN autocomplete styles */
+
+
+div.auto_complete {
+ position: absolute;
+ background-color: white;
+ border: 1px solid #999;
+ margin-top: -2px;
+ padding: 0px;
+ z-index: 10000;
+ font-size: 85%;
+ font-weight: normal;
+}
+
+div.auto_complete * {
+ vertical-align: baseline;
+}
+
+div.auto_complete ul {
+ list-style-type: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+div.auto_complete ul li.selected {
+ background-color: #ffb;
+}
+
+div.auto_complete ul li {
+ text-align: left;
+ list-style-type: none;
+ display: block;
+ margin: 0;
+ padding: 3px;
+ border-bottom: 1px solid #DDD;
+ cursor: pointer;
+ overflow: hidden;
+}
+
+div.auto_complete ul li.selected {
+ background-color: #ffb;
+}
+
+div.auto_complete ul strong.highlight {
+ color: #800;
+ margin: 0px;
+ padding: 0px;
+ font-weight: normal;
+}
+
+/* END autocomplete styles */
+
+/* BEGIN stats page styles */
+
+#stats_page {
+ margin: 0 2em;
+}
+
+#stats_page p {
+ margin: 0.6em 0;
+}
+
+#stats_page h4 {
+ padding: 0.2em 0.6em;
+ background-color: #EEE;
+ margin-top: 1.5em;
+}
+
+#stats_page .left_column {
+ float: left;
+ width: 80%;
+}
+
+#stats_page .right_column {
+ float: right;
+ width: 18%;
+}
+
+#stats_page .box_indented_with_bar {
+ padding: 0.5em 1.5em;
+}
+
+#stats_page .box_info_standout {
+ width: 550px;
+ font-size: 85%;
+}
+
+#stats_page .services {
+ margin: 0.5em 0 0.5em 1.5em;
+ line-height: 1.5;
+ font-size: 85%;
+}
+
+#stats_page ul.contents {
+ text-align: left;
+}
+
+#stats_page ul.contents li {
+ padding: 0.2em 0;
+}
+
+#stats_page ul.contents li ul {
+ margin-left: 1em;
+}
+
+/* END stats page styles */
+
+/* BEGIN styles for rounded corner boxes */
+
+.rcontain {
+ padding: 0 0.3em;
+}
+
+/* END styles for rounded corner boxes */
+
+p.more_less_links {
+ font-weight: bold;
+ text-align: right;
+ font-size: 85%;
+ margin: 0;
+}
+
+p.more_less_links a {
+ text-decoration: none;
+}
+
+.search_box .rcontain {
+ padding: 0.4em 1em;
+}
+
+.form_field_section {
+ margin-top: 1.5em;
+}
+
+.form_selected_values {
+ padding: 0.3em 0.5em;
+ border: 1px solid #DDD;
+ background-color: #FFF;
+ color: #333;
+}
+
+/* BEGIN categories styles */
+
+.categories_box {
+ font-size: 85%;
+ line-height: 16px;
+}
+
+ul.categories {
+ float: left;
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+ul.categories li {
+ display: inline;
+}
+
+ul.categories li span.main {
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+ background-color: #FFC;
+ margin-bottom: 0.4em;
+ margin-right: 0.5em;
+ padding: 0 3px;
+ border-top: 1px solid #DEDEDE;
+ border-left: 1px solid #DEDEDE;
+ border-right: 1px solid #999;
+ border-bottom: 1px solid #999;
+}
+
+ul.categories li span.main a.category {
+ line-height: 1.6;
+ text-decoration: none;
+ vertical-align: middle;
+}
+
+ul.categories li span.main a.category:hover {
+ color: #000;
+}
+
+ul.categories li span.main:hover {
+ background-color: #F0E68C;
+}
+
+/* END categories styles */
+
+/* BEGIN stats bar styles */
+
+.stats_bar {
+ color: #555;
+ font-weight: bold;
+}
+
+.stats_bar * {
+ vertical-align: middle;
+}
+
+.stats_bar img {
+ margin-right: 0.3em;
+}
+
+.stats_bar .each {
+ margin-left: 1em;
+}
+
+/* END stats bar styles */
+
+#feedback_help {
+ margin: 1.5em;
+ font-size: 93%;
+ text-align: left;
+}
+
+#feedback_help ul {
+ margin-left: 1.5em;
+ margin-bottom: 1em;
+}
+
+#search_within {
+ margin-bottom: 1em;
+}
+
+#search_within .rcontain {
+ padding: 0 1.5em;
+ text-align: right;
+}
+
+.service_properties p,
+.service_properties .property {
+ margin: 0;
+ margin-top: 1.5em;
+}
+
+#filter_message {
+ text-align: left;
+ font-size: 93%;
+ margin-bottom: 0.8em;
+ color: #333333;
+ /**border: 1px solid #DDD;
+ background: #FFFFCC;
+ padding: 0.4em 0.6em; */
+ line-height: 1.5;
+}
+
+#filter_message .rcontain {
+ padding: 1px 0.8em;
+}
+
+#filter_message .rtop,
+#filter_message .contain,
+#filter_message .rbottom {
+ margin: 0;
+}
+
+#filter_message .rtop,
+#filter_message .rbottom {
+ height: 4px;
+ line-height: 1.0;
+}
+
+#filter_message * {
+ vertical-align: middle;
+}
+
+#subscribe_results_link {
+ float: right;
+ text-decoration: none;
+ vertical-align: middle;
+ font-weight: bold;
+ margin-left: 2em;
+}
+
+#subscribe_results_link img {
+ margin-right: 0.4em;
+}
+
+/* Style for default texts in inputs */
+.ex {
+ color: #999;
+}
+
+/* BEGIN login page styles */
+
+.login_form {
+ padding: 1.2em 0.8em;
+}
+
+.login_form label {
+ float: left;
+ font-weight: bold;
+ margin-bottom: 1em;
+}
+
+.login_form input.login_field {
+ clear: both;
+ float: right;
+ height: 1.4em;
+ line-height: 1.4;
+ width: 220px;
+ margin-bottom: 1em;
+}
+
+.login_form input.sign_in_button {
+ clear: both;
+ float: right;
+ width: 80px;
+}
+
+.login_form .other_links {
+ clear: both;
+ width: 100%;
+ padding-top: 1.2em;
+ border-top: 1px dotted #CCC;
+ text-align: right;
+}
+
+.login_form .other_links li {
+ margin-bottom: 0.7em;
+}
+
+/* END login page styles */
+
+img.account_provider_logo {
+ margin: 0 2px;
+}
+
+.side_link {
+ color: #999;
+ margin-left: 3em;
+ font-size: 85%;
+}
+
+.side_link a {
+ color: #999;
+}
+
+.toolbar {
+ float: right;
+}
+
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/blue-sphere-50.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/blue-sphere-50.png
new file mode 100644
index 0000000..0b3626d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/blue-sphere-50.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/cross-sphere-35.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/cross-sphere-35.png
new file mode 100644
index 0000000..2553bd9
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/cross-sphere-35.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/cross-sphere-50.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/cross-sphere-50.png
new file mode 100644
index 0000000..2bcba73
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/cross-sphere-50.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/accept.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/accept.png
new file mode 100644
index 0000000..89c8129
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/accept.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add - tick.pdn b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add - tick.pdn
new file mode 100644
index 0000000..04ee9be
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add - tick.pdn
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add - tick.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add - tick.png
new file mode 100644
index 0000000..5d2d4af
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add - tick.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add.png
new file mode 100644
index 0000000..6332fef
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/add.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/application_form_add.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/application_form_add.png
new file mode 100644
index 0000000..28c2175
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/application_form_add.png
Binary files differ
diff --git "a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_join \050flipped vertically\051.png" "b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_join \050flipped vertically\051.png"
new file mode 100644
index 0000000..7858d14
--- /dev/null
+++ "b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_join \050flipped vertically\051.png"
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_left.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_left.png
new file mode 100644
index 0000000..2bed329
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_left.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_refresh.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_refresh.png
new file mode 100644
index 0000000..0de2656
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_refresh.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_right.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_right.png
new file mode 100644
index 0000000..2cf15f1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/arrow_right.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/chart_organisation.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/chart_organisation.png
new file mode 100644
index 0000000..c32d25c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/chart_organisation.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/cross.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/cross.png
new file mode 100644
index 0000000..1514d51
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/cross.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/disk.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/disk.png
new file mode 100644
index 0000000..99d532e
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/disk.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/error.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/error.png
new file mode 100644
index 0000000..628cf2d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/error.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/exclamation.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/exclamation.png
new file mode 100644
index 0000000..c37bd06
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/exclamation.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/external_link_listing_small.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/external_link_listing_small.png
new file mode 100644
index 0000000..a22b1f7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/external_link_listing_small.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/folder_explore.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/folder_explore.png
new file mode 100644
index 0000000..0ba9391
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/folder_explore.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/grey_circle.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/grey_circle.png
new file mode 100644
index 0000000..69ede6c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/grey_circle.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/help.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/help.png
new file mode 100644
index 0000000..5c87017
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/help.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/information.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/information.png
new file mode 100644
index 0000000..12cd1ae
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/information.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lightbulb.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lightbulb.png
new file mode 100644
index 0000000..d22fde8
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lightbulb.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lock.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lock.png
new file mode 100644
index 0000000..2ebc4f6
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lock.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lock_open.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lock_open.png
new file mode 100644
index 0000000..a471765
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/lock_open.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/magnifier.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/magnifier.png
new file mode 100644
index 0000000..cf3d97f
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/magnifier.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/multiple_star.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/multiple_star.png
new file mode 100644
index 0000000..c663d73
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/multiple_star.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/page_white_code.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/page_white_code.png
new file mode 100644
index 0000000..0c76bd1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/page_white_code.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/plugin.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/plugin.png
new file mode 100644
index 0000000..6187b15
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/plugin.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/remote_resource.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/remote_resource.png
new file mode 100644
index 0000000..b8edc12
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/remote_resource.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/server.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/server.png
new file mode 100644
index 0000000..720a237
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/server.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/star.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/star.png
new file mode 100644
index 0000000..b88c857
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/star.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/style.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/style.png
new file mode 100644
index 0000000..81e41de
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/style.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/sum.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/sum.png
new file mode 100644
index 0000000..fd7b32e
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/sum.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/tag_blue.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/tag_blue.png
new file mode 100644
index 0000000..9757fc6
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/tag_blue.png
Binary files differ
diff --git "a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_linespacing \050collapse\051.png" "b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_linespacing \050collapse\051.png"
new file mode 100644
index 0000000..ff09e31
--- /dev/null
+++ "b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_linespacing \050collapse\051.png"
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_linespacing.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_linespacing.png
new file mode 100644
index 0000000..1a91cbd
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_linespacing.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_list_numbers.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_list_numbers.png
new file mode 100644
index 0000000..33b0b8d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/text_list_numbers.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/tick.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/tick.png
new file mode 100644
index 0000000..a9925a0
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/tick.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/user.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/user.png
new file mode 100644
index 0000000..79f35cc
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/famfamfam_silk/user.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/favicon.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/favicon.png
new file mode 100644
index 0000000..1ac1bde
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/favicon.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/fold.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/fold.png
new file mode 100644
index 0000000..a13d280
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/fold.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/fold_16x16.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/fold_16x16.png
new file mode 100644
index 0000000..5a37b67
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/fold_16x16.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/unfold.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/unfold.png
new file mode 100644
index 0000000..589e2c9
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/unfold.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/unfold_16x16.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/unfold_16x16.png
new file mode 100644
index 0000000..dd2396c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/folds/unfold_16x16.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/info-sphere-35.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/info-sphere-35.png
new file mode 100644
index 0000000..cdbacd3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/info-sphere-35.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/info-sphere-50.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/info-sphere-50.png
new file mode 100644
index 0000000..8e8ac21
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/info-sphere-50.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/open_in_BioCatalogue.pdn b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/open_in_BioCatalogue.pdn
new file mode 100644
index 0000000..3357714
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/open_in_BioCatalogue.pdn
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/open_in_BioCatalogue.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/open_in_BioCatalogue.png
new file mode 100644
index 0000000..897b2c1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/open_in_BioCatalogue.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/pling-sphere-35.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/pling-sphere-35.png
new file mode 100644
index 0000000..3326aad
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/pling-sphere-35.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/pling-sphere-50.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/pling-sphere-50.png
new file mode 100644
index 0000000..1f47035
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/pling-sphere-50.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/query-sphere-35.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/query-sphere-35.png
new file mode 100644
index 0000000..8091ff3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/query-sphere-35.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/query-sphere-50.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/query-sphere-50.png
new file mode 100644
index 0000000..443a868
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/query-sphere-50.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_multitype.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_multitype.png
new file mode 100644
index 0000000..1a8e9cf
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_multitype.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_rest.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_rest.png
new file mode 100644
index 0000000..a4276c2
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_rest.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_soap.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_soap.png
new file mode 100644
index 0000000..aa80209
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_soap.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_unknown.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_unknown.png
new file mode 100644
index 0000000..92638f1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/service_type_unknown.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/soap_rest_multitype_unknown.pdn b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/soap_rest_multitype_unknown.pdn
new file mode 100644
index 0000000..cc7fcb9
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/service_icons/soap_rest_multitype_unknown.pdn
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/styles.css b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/styles.css
new file mode 100644
index 0000000..48bbdff
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/styles.css
@@ -0,0 +1,290 @@
+/* BEGIN service preview header styles */
+
+p.service_aka_names {
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+span.service_aka_name {
+ font-weight: bold;
+ margin-right: 0.5em;
+}
+
+a.service_status_details {
+ text-decoration: none;
+}
+
+a.service_status_details:hover {
+ color: #ff7400;
+ text-decoration: underline;
+}
+
+h1.service_title {
+ border: 1px solid #D7EFA4;
+ background-color: #E9F7CC;
+ color: #000033;
+ text-align: center;
+ font-size: 180%;
+ margin: 0.2em 0 0 0;
+ padding: 0;
+}
+
+
+h2.soap_operation_name {
+ border: 1px solid #D7EFA4;
+ background-color: #E9F7CC;
+ color: #000033;
+ text-align: center;
+ font-size: 120%;
+ margin: 0.2em 0 0 0;
+ padding: 0;
+}
+
+h3.soap_operation_inputs_or_outputs_section {
+ background-color: #EEEEEE;
+ border: 1px solid #CCCCCC;
+ padding: 0.1em 0.6em;
+ font-size: 110%;
+ margin-top: 2em;
+}
+
+div.soap_input_or_output {
+ border: 1px solid #C6961D;
+ margin: 0.5em 1em;
+}
+
+div.soap_input_or_output p.name {
+ background-color: #FFC526;
+ border: 1px solid #C6961D;
+ padding: 0.2em 1em;
+ margin: 0;
+}
+
+div.soap_input_or_output p.body {
+ border: 1px solid #C6961D;
+ padding: 0.5em 1em;
+ margin: 0;
+}
+
+
+.orange_highlight {
+ background-color: #ffbb7f;
+ padding: 1px 5px 1px 5px;
+ margin: 0 2px;
+}
+
+.none_text {
+ font-weight: normal;
+ font-style: italic;
+ color: #777777;
+}
+
+.left_indented {
+ margin-left: 2em;
+}
+
+/* END service preview header styles */
+
+
+/* BEGIN service preview annotation styles */
+
+div.annotation {
+ margin: 1em;
+ font-size: 100%;
+}
+
+div.annotation p.key {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
+
+div.annotation p.value {
+ margin: 0;
+ padding: 0 0.7em;
+}
+
+div.annotation p.value_with_submitter {
+ margin: 0.4em 0;
+ padding: 0.3em 0.7em;
+ background-color: #AAFFC1;
+ border: 1px solid #91D8A4;
+}
+
+div.annotation p.value_with_submitter span.submitter{
+ float: right;
+ font-size: 80%;
+ margin: 0 0.2em 0 1em;
+ padding: 2px 5px 1px 5px;
+ background-color: #D8FFE2;
+}
+
+div.annotation img.link_icon {
+ padding: 0 3px;
+ vertical-align: middle;
+}
+
+div.annotation a {
+ vertical-align: middle;
+ color: #000000;
+}
+
+div.annotation a:link, a:visited {
+ /*color: #333333;*/
+ text-decoration: underline;
+}
+
+div.annotation a:hover {
+ color: #ff7400;
+ text-decoration: underline;
+}
+
+div.annotation a:active {
+ color: #ff7400;
+ text-decoration: underline;
+}
+
+/* END service preview annotation styles */
+
+
+
+/* BEGIN service type styles */
+
+p.service_types {
+ margin-top: 0.5em;
+ margin-bottom: 0px;
+}
+
+a.service_type_badge {
+ font-size: 85%;
+ font-weight: bold;
+ background-color: #FFF;
+ border: 1px solid #DDD;
+ padding: 2px 3px;
+ text-decoration: none;
+ line-height: 1.0;
+}
+
+a.service_type_badge:hover {
+ color: #FFF;
+ background-color: #999;
+}
+
+/* END service type styles */
+
+
+/* BEGIN categories styles */
+
+.categories_box {
+ font-size: 90%;
+}
+
+ul.categories {
+ float: left;
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+ul.categories li {
+ display: inline;
+}
+
+ul.categories li span.title {
+ line-height: 1.6;
+ font-weight: bold;
+ margin-right: 0.7em;
+ vertical-align: middle;
+}
+
+ul.categories li span.main {
+ display:-moz-inline-block;
+ display:-moz-inline-box;
+ display: inline-block;
+ background-color: #FFC;
+ margin-bottom: 0.4em;
+ margin-right: 0.5em;
+ padding: 0;
+ border-top: 1px solid #DEDEDE;
+ border-left: 1px solid #DEDEDE;
+ border-right: 1px solid #999;
+ border-bottom: 1px solid #999;
+}
+
+ul.categories li span.main a.category {
+ line-height: 1.6;
+ text-decoration: none;
+ vertical-align: middle;
+ padding: 3px 5px 2px 5px;
+}
+
+ul.categories li span.main a.category:hover {
+ color: #000;
+ background-color: #F0E68C;
+}
+
+ul.categories li span.main:hover {
+ background-color: #F0E68C;
+}
+
+/* END categories styles */
+
+
+
+/* BEGIN tag cloud styles */
+
+div.tag_cloud a {
+ font-family:Arial,Helvetica,sans-serif;
+ color: #000099; /* font color as on BioCatalogue */
+ margin: 0 10px; /* vertical margin doesn't seem to work, so adding line-height to make sure that selection borders on different lines do not overlap */
+ line-height: 1.9em;
+ padding: 0 4px 2px 4px;
+ text-decoration: none;
+
+ vertical-align: middle;
+}
+
+div.tag_cloud a:hover {
+ color: orange;
+}
+
+div.tag_cloud a.unselected {
+ /*
+ * this style is used to make sure that equal space
+ * around anchor tags is taken by the transparent border
+ * even when the tag is not "selected"
+ */
+ border-style: solid;
+ border-width: 2px;
+ border-color: transparent;
+}
+
+div.tag_cloud a.unselected_ontological_term {
+ /*
+ * this style is used to make sure that equal space
+ * around anchor tags is taken by the transparent border
+ * even when the tag is not "selected"
+ */
+ border-style: solid solid double solid;
+ border-width: 2px 2px 3px 2px;
+ border-color: transparent transparent #000099 transparent;
+}
+
+#ontological_term{
+ color:#3090C7 ; /*Deep Sky Blue3*/
+}
+
+div.tag_cloud a.selected {
+ border-style: solid;
+ border-width: 2px;
+ background: #D6E9FF;
+}
+
+div.tag_cloud a.selected_ontological_term {
+ border-style: double double double double;
+ border-width: 3px 3px 3px 3px;
+ background: #D6E9FF;
+}
+
+/* END tag cloud styles */
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/test.html b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/test.html
new file mode 100644
index 0000000..e23f24c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/test.html
@@ -0,0 +1,78 @@
+<html>
+ <head>
+ <link href="styles.css" media="screen" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <h1 class="service_title">
+ Service:
+ <span class="orange_highlight">Blast</span>
+ <a class="service_type_badge" style="margin-left: 1.5em" href="">SOAP</a>
+ </h1>
+ <p class="left_indented service_aka_names"><span class="none_text">aka</span><span class="service_aka_name"> BLAST(DDBJ)</span></p>
+
+ <div class="categories_box">
+ <ul class="categories">
+ <li>
+ <span class="title">
+ Categories:
+ </span>
+ </li>
+ <li>
+ <span class="main">
+ <a href="/services?cat=%5B18%5D" class="category">Nucleotide Sequence Similarity</a>
+ </span>
+ </li>
+ <li>
+ <span class="main">
+ <a href="/services?cat=%5B6%5D" class="category">Protein Sequence Similarity</a>
+ </span>
+ </li>
+ </ul>
+ </div>
+
+ <p style="border-top: 1px solid #000000; margin-top: 3em;"></p>
+
+ <div class="annotation">
+ <p class="key">Provider</p>
+ <p class="value"><a href="">xml.nig.ac.jp</a></p>
+ </div>
+
+ <div class="annotation">
+ <p class="key">Location</p>
+ <p class="value">Japan</p>
+ </div>
+
+ <div class="annotation">
+ <p class="key">Submitter / Source</p>
+ <p class="value">
+ <img src="famfamfam_silk/user.png" class="link_icon"/>
+ <a href="">Franck</a>
+ <span class="none_text" style="margin-left: 0.7em; vertical-align: middle;">(2009-01-01)</span>
+ </p>
+ </div>
+
+ <div class="annotation">
+ <p class="key">Endpoint</p>
+ <p class="value"><a href="">http://xml.nig.ac.jp/xddbj/Blast</a></p>
+ </div>
+
+ <div class="annotation">
+ <p class="key">WSDL Location</p>
+ <p class="value"><a href="">http://xml.nig.ac.jp/xddbj/Blast.wsdl</a></p>
+ </div>
+
+ <div class="annotation">
+ <p class="key">Documentation URL(s)</p>
+ <p class="value_with_submitter">
+ <span class="submitter">
+ <span style="vertical-align: middle;">by</span>
+ <img src="famfamfam_silk/remote_resource.png" class="link_icon"/>
+ <a href="">The EMBRACE Registry</a>
+ <span class="none_text" style="margin-left: 0.3em;">(2009-02-03)</span>
+ </span>
+ <a href="">http://xml.nig.ac.jp/index.html</a>
+ </p>
+ </div>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tick-sphere-35.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tick-sphere-35.png
new file mode 100644
index 0000000..3593666
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tick-sphere-35.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tick-sphere-50.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tick-sphere-50.png
new file mode 100644
index 0000000..f93f269
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tick-sphere-50.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/trash.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/trash.png
new file mode 100644
index 0000000..80f5624
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/trash.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_checked.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_checked.png
new file mode 100644
index 0000000..af679a2
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_checked.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_partial.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_partial.png
new file mode 100644
index 0000000..9a61aa4
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_partial.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_partial_green.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_partial_green.png
new file mode 100644
index 0000000..9d1eeea
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_partial_green.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_unchecked.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_unchecked.png
new file mode 100644
index 0000000..d9b5c92
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/tristate_checkbox/tristate_checkbox_unchecked.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/unchecked.png b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/unchecked.png
new file mode 100644
index 0000000..c131d07
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/resources/net/sf/taverna/t2/ui/perspectives/biocatalogue/unchecked.png
Binary files differ
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/xsd/dc.xsd b/taverna-workbench-perspective-biocatalogue/src/main/xsd/dc.xsd
new file mode 100644
index 0000000..815d1a2
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/xsd/dc.xsd
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="http://purl.org/dc/elements/1.1/"
+ targetNamespace="http://purl.org/dc/elements/1.1/"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ Simplified XML Schema for http://purl.org/dc/elements/1.1/
+
+ Created 2007-06-28
+
+ Created by
+
+ Stian Soiland (ssoiland@cs.man.ac.uk)
+
+ This simplification removes SimpleLiteral and specifies dc:any as
+ an xs:string, except for dc:date which is specified as an
+ xs:dateTime. This makes the schema more usable for tools like
+ XMLBeans.
+
+
+
+ Based on
+ http://dublincore.org/schemas/xmls/qdc/2006/01/06/dc.xsd :
+
+
+ DCMES 1.1 XML Schema
+ XML Schema for http://purl.org/dc/elements/1.1/ namespace
+
+ Created 2003-04-02
+
+ Created by
+
+ Tim Cole (t-cole3@uiuc.edu)
+ Tom Habing (thabing@uiuc.edu)
+ Jane Hunter (jane@dstc.edu.au)
+ Pete Johnston (p.johnston@ukoln.ac.uk),
+ Carl Lagoze (lagoze@cs.cornell.edu)
+
+ This schema declares XML elements for the 15 DC elements from the
+ http://purl.org/dc/elements/1.1/ namespace.
+
+ It defines a complexType SimpleLiteral which permits mixed content
+ and makes the xml:lang attribute available. It disallows child elements by
+ use of minOcccurs/maxOccurs.
+
+ However, this complexType does permit the derivation of other complexTypes
+ which would permit child elements.
+
+ All elements are declared as substitutable for the abstract element any,
+ which means that the default type for all elements is dc:SimpleLiteral.
+
+ </xs:documentation>
+
+ </xs:annotation>
+
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd">
+
+ </xs:import>
+
+
+ <xs:element name="any" type="xs:string" abstract="true"/>
+
+ <xs:element name="title" substitutionGroup="any"/>
+ <xs:element name="creator" substitutionGroup="any"/>
+ <xs:element name="subject" substitutionGroup="any"/>
+ <xs:element name="description" substitutionGroup="any"/>
+ <xs:element name="publisher" substitutionGroup="any"/>
+
+ <xs:element name="contributor" substitutionGroup="any"/>
+ <xs:element name="date" type="xs:dateTime" />
+ <xs:element name="type" substitutionGroup="any"/>
+ <xs:element name="format" substitutionGroup="any"/>
+ <xs:element name="identifier" substitutionGroup="any"/>
+ <xs:element name="source" substitutionGroup="any"/>
+ <xs:element name="language" substitutionGroup="any"/>
+ <xs:element name="relation" substitutionGroup="any"/>
+ <xs:element name="coverage" substitutionGroup="any"/>
+
+ <xs:element name="rights" substitutionGroup="any"/>
+
+ <xs:group name="elementsGroup">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ This group is included as a convenience for schema authors
+ who need to refer to all the elements in the
+ http://purl.org/dc/elements/1.1/ namespace.
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:sequence>
+
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="any"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:complexType name="elementContainer">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+
+ This complexType is included as a convenience for schema authors who need to define a root
+ or container element for all of the DC elements.
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:choice>
+ <xs:group ref="elementsGroup"/>
+ </xs:choice>
+ </xs:complexType>
+
+
+</xs:schema>
+
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/xsd/dcterms.xsd b/taverna-workbench-perspective-biocatalogue/src/main/xsd/dcterms.xsd
new file mode 100644
index 0000000..205bf48
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/xsd/dcterms.xsd
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ targetNamespace="http://purl.org/dc/terms/"
+ xmlns="http://purl.org/dc/terms/"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ Simplified XML Schema for http://purl.org/dc/terms/
+
+ Created 2007-06-28
+
+ Created by
+
+ Stian Soiland (ssoiland@cs.man.ac.uk)
+
+ This simplification removes SimpleLiteral references
+ so that it can be used with the simplified dc.xsd.
+ This makes the schema more usable for tools like
+ XMLBeans.
+
+
+ Based on
+ http://dublincore.org/schemas/xmls/qdc/2006/01/06/dcterms.xsd :
+
+
+ DCterms XML Schema
+ XML Schema for http://purl.org/dc/terms/ namespace
+
+ Created 2006-01-06
+
+ Created by
+
+ Tim Cole (t-cole3@uiuc.edu)
+ Tom Habing (thabing@uiuc.edu)
+ Jane Hunter (jane@dstc.edu.au)
+ Pete Johnston (p.johnston@ukoln.ac.uk),
+ Carl Lagoze (lagoze@cs.cornell.edu)
+
+ This schema declares XML elements for the DC elements and
+ DC element refinements from the http://purl.org/dc/terms/ namespace.
+
+ It reuses the complexType dc:SimpleLiteral, imported from the dc.xsd
+ schema, which permits simple element content, and makes the xml:lang
+ attribute available.
+
+ This complexType permits the derivation of other complexTypes
+ which would permit child elements.
+
+ DC elements are declared as substitutable for the abstract element dc:any, and
+ DC element refinements are defined as substitutable for the base elements
+ which they refine.
+
+ This means that the default type for all XML elements (i.e. all DC elements and
+ element refinements) is dc:SimpleLiteral.
+
+ Encoding schemes are defined as complexTypes which are restrictions
+ of the dc:SimpleLiteral complexType. These complexTypes restrict
+ values to an appropriates syntax or format using data typing,
+ regular expressions, or enumerated lists.
+
+ In order to specify one of these encodings an xsi:type attribute must
+ be used in the instance document.
+
+ Also, note that one shortcoming of this approach is that any type can be
+ applied to any of the elements or refinements. There is no convenient way
+ to restrict types to specific elements using this approach.
+
+ </xs:documentation>
+
+ </xs:annotation>
+
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd">
+
+ </xs:import>
+
+ <xs:import namespace="http://purl.org/dc/elements/1.1/"
+ schemaLocation="dc.xsd"/>
+
+ <xs:element name="alternative" substitutionGroup="dc:title"/>
+
+ <xs:element name="tableOfContents" substitutionGroup="dc:description"/>
+ <xs:element name="abstract" substitutionGroup="dc:description"/>
+
+ <xs:element name="created" substitutionGroup="dc:date"/>
+ <xs:element name="valid" substitutionGroup="dc:date"/>
+
+ <xs:element name="available" substitutionGroup="dc:date"/>
+ <xs:element name="issued" substitutionGroup="dc:date"/>
+ <xs:element name="modified" substitutionGroup="dc:date"/>
+ <xs:element name="dateAccepted" substitutionGroup="dc:date"/>
+ <xs:element name="dateCopyrighted" substitutionGroup="dc:date"/>
+ <xs:element name="dateSubmitted" substitutionGroup="dc:date"/>
+
+ <xs:element name="extent" substitutionGroup="dc:format"/>
+ <xs:element name="medium" substitutionGroup="dc:format"/>
+
+ <xs:element name="isVersionOf" substitutionGroup="dc:relation"/>
+ <xs:element name="hasVersion" substitutionGroup="dc:relation"/>
+ <xs:element name="isReplacedBy" substitutionGroup="dc:relation"/>
+ <xs:element name="replaces" substitutionGroup="dc:relation"/>
+ <xs:element name="isRequiredBy" substitutionGroup="dc:relation"/>
+ <xs:element name="requires" substitutionGroup="dc:relation"/>
+ <xs:element name="isPartOf" substitutionGroup="dc:relation"/>
+ <xs:element name="hasPart" substitutionGroup="dc:relation"/>
+
+ <xs:element name="isReferencedBy" substitutionGroup="dc:relation"/>
+ <xs:element name="references" substitutionGroup="dc:relation"/>
+ <xs:element name="isFormatOf" substitutionGroup="dc:relation"/>
+ <xs:element name="hasFormat" substitutionGroup="dc:relation"/>
+ <xs:element name="conformsTo" substitutionGroup="dc:relation"/>
+
+ <xs:element name="spatial" substitutionGroup="dc:coverage"/>
+ <xs:element name="temporal" substitutionGroup="dc:coverage"/>
+
+ <xs:element name="audience" substitutionGroup="dc:any"/>
+
+ <xs:element name="accrualMethod" substitutionGroup="dc:any"/>
+ <xs:element name="accrualPeriodicity" substitutionGroup="dc:any"/>
+ <xs:element name="accrualPolicy" substitutionGroup="dc:any"/>
+ <xs:element name="instructionalMethod" substitutionGroup="dc:any"/>
+ <xs:element name="provenance" substitutionGroup="dc:any"/>
+ <xs:element name="rightsHolder" substitutionGroup="dc:any"/>
+
+ <xs:element name="mediator" substitutionGroup="audience"/>
+ <xs:element name="educationLevel" substitutionGroup="audience"/>
+
+ <xs:element name="accessRights" substitutionGroup="dc:rights"/>
+ <xs:element name="license" substitutionGroup="dc:rights"/>
+
+ <xs:element name="bibliographicCitation" substitutionGroup="dc:identifier"/>
+
+</xs:schema>
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/xsd/schema-v1.xsd b/taverna-workbench-perspective-biocatalogue/src/main/xsd/schema-v1.xsd
new file mode 100644
index 0000000..6e57dd1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/xsd/schema-v1.xsd
@@ -0,0 +1,3557 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema targetNamespace="http://www.biocatalogue.org/2009/xml/rest"
+ xml:lang="en" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.biocatalogue.org/2009/xml/rest"
+ elementFormDefault="qualified" xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/">
+
+ <xsd:import namespace="http://purl.org/dc/elements/1.1/"
+ schemaLocation="dc.xsd" />
+ <xsd:import namespace="http://purl.org/dc/terms/"
+ schemaLocation="dcterms.xsd" />
+ <xsd:import namespace="http://www.w3.org/1999/xlink"
+ schemaLocation="xlink.xsd" />
+
+ <xsd:simpleType name="SearchScopeUrlValue">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="all"></xsd:enumeration>
+ <xsd:enumeration value="service_providers"></xsd:enumeration>
+
+ <xsd:enumeration value="services"></xsd:enumeration>
+ <xsd:enumeration value="users"></xsd:enumeration>
+ <xsd:enumeration value="registries"></xsd:enumeration>
+ <xsd:enumeration value="soap_operations"></xsd:enumeration>
+ <xsd:enumeration value="rest_methods"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:element name="search" type="Search"></xsd:element>
+
+ <xsd:complexType name="Search">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="SearchParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="SearchStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="results" type="SearchResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="SearchRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="SearchParameters">
+ <xsd:sequence>
+
+ <xsd:element name="query" type="SearchQueryParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="scope" type="SearchScopeParameter"
+ maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="SearchStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ <xsd:sequence>
+ <xsd:element name="scopedResults" minOccurs="0"
+ maxOccurs="unbounded">
+ <xsd:complexType>
+
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:nonNegativeInteger">
+ <xsd:attribute name="scope" type="SearchScopeName">
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="biocatalogue" type="BioCatalogue" />
+
+ <xsd:complexType name="BioCatalogue">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+
+ <xsd:element name="documentation" maxOccurs="1"
+ minOccurs="1" type="ResourceLink">
+ </xsd:element>
+ <xsd:element name="collections" maxOccurs="1"
+ minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="agents"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="annotationAttributes"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="categories"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="registries"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restMethods"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restResources"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="restServices"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="search"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="serviceProviders"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="services"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="soapOperations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="soapServices"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="tags"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="testResults"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="users"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="filters"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="annotations" type="ResourceLink" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element
+ name="restMethods" type="ResourceLink" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="services"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element
+ name="soapOperations" type="ResourceLink" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="version" type="xsd:string"
+ use="required">
+ </xsd:attribute>
+ <xsd:attribute name="apiVersion" type="xsd:string" use="required"></xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="SearchRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="searches" minOccurs="1" maxOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scoped" type="ScopedSearch"
+ minOccurs="0" maxOccurs="unbounded">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="CollectionCoreRelatedLinks">
+
+ <xsd:sequence>
+ <xsd:element name="previous" type="ResourceLink"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="next" type="ResourceLink" maxOccurs="1"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ResourceLink">
+
+ <xsd:attribute ref="xlink:href" use="required"></xsd:attribute>
+ <xsd:attribute ref="xlink:title" use="optional"></xsd:attribute>
+ <xsd:attribute name="resourceType" type="ResourceType"
+ use="optional"></xsd:attribute>
+ <xsd:attribute name="resourceName" type="xsd:string"
+ use="optional">
+ </xsd:attribute>
+ </xsd:complexType>
+
+
+ <xsd:complexType name="ScopedSearch">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:attribute name="scope" type="SearchScopeName"></xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="SearchScopeName">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="All"></xsd:enumeration>
+
+ <xsd:enumeration value="Services"></xsd:enumeration>
+ <xsd:enumeration value="Service Providers"></xsd:enumeration>
+ <xsd:enumeration value="Users"></xsd:enumeration>
+ <xsd:enumeration value="Registries"></xsd:enumeration>
+ <xsd:enumeration value="SOAP Operations"></xsd:enumeration>
+ <xsd:enumeration value="REST Endpoints"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="SearchScopeParameter">
+ <xsd:simpleContent>
+ <xsd:extension base="SearchScopeName">
+ <xsd:attribute name="urlKey" type="xsd:string"></xsd:attribute>
+ <xsd:attribute name="urlValue" type="SearchScopeUrlValue"
+ use="required">
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="SearchResults">
+ <xsd:sequence>
+ <xsd:choice maxOccurs="unbounded" minOccurs="0">
+ <xsd:element name="service" type="Service"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="soapOperation" type="SoapOperation"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="serviceProvider"
+ type="ServiceProvider" maxOccurs="unbounded" minOccurs="0">
+
+ </xsd:element>
+ <xsd:element name="user" type="User"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="registry" type="Registry"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="restMethod" type="RestMethod"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:choice>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:element name="service" type="Service"></xsd:element>
+
+ <xsd:complexType name="Service">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="originalSubmitter"
+ type="ResourceLink" minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="serviceTechnologyTypes"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+
+ <xsd:sequence>
+ <xsd:element name="type"
+ type="ServiceTechnologyType" maxOccurs="unbounded"
+ minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="latestMonitoringStatus"
+ type="MonitoringStatus" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" minOccurs="1"
+ maxOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="summary" minOccurs="0"
+ maxOccurs="1" type="ServiceSummary">
+ </xsd:element>
+ <xsd:element name="deployments" minOccurs="0"
+ maxOccurs="1">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+
+ <xsd:sequence>
+ <xsd:element
+ name="serviceDeployment" type="ServiceDeployment"
+ minOccurs="1" maxOccurs="unbounded">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="variants" minOccurs="0"
+ maxOccurs="1">
+
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:choice
+ maxOccurs="unbounded" minOccurs="1">
+ <xsd:element
+ name="soapService" type="SoapService" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ <xsd:element
+ name="restService" type="RestService" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="monitoring" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="tests"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="serviceTest" type="ServiceTest"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related"
+ type="ServiceRelatedLinks" minOccurs="0" maxOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServiceSummary">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="counts" minOccurs="1" maxOccurs="1">
+
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="deployments" type="xsd:positiveInteger"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="variants" type="xsd:positiveInteger"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="metadata" type="MetadataCount"
+ minOccurs="0" maxOccurs="unbounded">
+ </xsd:element>
+ <xsd:element name="favourites" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="views" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="alternativeName" minOccurs="0"
+ maxOccurs="unbounded" type="xsd:string">
+ </xsd:element>
+ <xsd:element name="category" minOccurs="0" maxOccurs="unbounded"
+ type="ResourceLinkWithString">
+
+ </xsd:element>
+ <xsd:element name="provider" minOccurs="1" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="endpoint" minOccurs="1" maxOccurs="unbounded"
+ type="xsd:anyURI">
+ </xsd:element>
+ <xsd:element name="wsdl" minOccurs="0" maxOccurs="unbounded"
+ type="xsd:anyURI">
+ </xsd:element>
+ <xsd:element name="location" minOccurs="0" maxOccurs="unbounded"
+ type="Location">
+
+ </xsd:element>
+ <xsd:element name="documentationUrl" maxOccurs="unbounded"
+ minOccurs="0" type="xsd:string">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ <xsd:element name="tag" minOccurs="0" maxOccurs="unbounded"
+ type="ResourceLinkWithString">
+ </xsd:element>
+ <xsd:element name="cost" maxOccurs="unbounded"
+ minOccurs="0" type="xsd:string">
+ </xsd:element>
+
+ <xsd:element name="license" type="xsd:string"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="usageCondition" maxOccurs="unbounded"
+ minOccurs="0" type="xsd:string">
+ </xsd:element>
+ <xsd:element name="contact" maxOccurs="unbounded"
+ minOccurs="0" type="xsd:string">
+ </xsd:element>
+ <xsd:element name="publication" maxOccurs="unbounded"
+ minOccurs="0" type="xsd:string">
+ </xsd:element>
+ <xsd:element name="citation" maxOccurs="unbounded"
+ minOccurs="0" type="xsd:string">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="serviceProvider" type="ServiceProvider"></xsd:element>
+
+ <xsd:complexType name="ServiceProvider">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string" minOccurs="1"
+ maxOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="hostnames" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="hostname" type="xsd:string"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="related" type="ServiceProviderRelatedLinks"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="user" type="User"></xsd:element>
+
+ <xsd:complexType name="User">
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="affiliation" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="location" type="Location"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="publicEmail" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="joined" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="savedSearches" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="savedSearch"
+ type="SavedSearch" maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="related" type="UserRelatedLinks"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="registry" type="Registry"></xsd:element>
+
+ <xsd:complexType name="Registry">
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string" minOccurs="1"
+ maxOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="homepage" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="RegistryRelatedLinks"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:element name="services" type="Services"></xsd:element>
+
+ <xsd:complexType name="Services">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="ServicesParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="statistics" type="ServicesStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="ServicesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="ServicesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="ServicesParameters">
+ <xsd:sequence>
+ <xsd:element name="filters" maxOccurs="1" minOccurs="1"
+ type="FiltersParameters">
+
+ </xsd:element>
+ <xsd:element name="query" maxOccurs="1" minOccurs="1"
+ type="SearchQueryParameter">
+ </xsd:element>
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+
+ <xsd:element ref="sortOrder" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="FiltersParameters">
+ <xsd:sequence>
+ <xsd:element name="group" type="FilterGroupParameter"
+ minOccurs="0" maxOccurs="unbounded">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServicesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="ServicesResults">
+ <xsd:sequence>
+ <xsd:element name="service" type="Service" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServicesRelatedLinks">
+
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="filters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="filtersOnCurrentResults" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="withSummaries" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="withDeployments" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="withVariants" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="withMonitoring" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="withAllSections" type="ResourceLink"
+ maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="Filters">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="group" type="FilterGroup" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+
+ </xsd:sequence>
+ <xsd:attribute name="for" type="ResourceType" use="required"></xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="filters" type="Filters"></xsd:element>
+
+ <xsd:complexType name="FilterType">
+ <xsd:sequence>
+
+ <xsd:element name="filter" type="Filter" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="FilterTypeName" use="required">
+ </xsd:attribute>
+ <xsd:attribute name="urlKey" type="FilterTypeUrlKey"
+ use="required">
+ </xsd:attribute>
+ <xsd:attribute name="description" type="xsd:string" use="required"></xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:simpleType name="FilterTypeName">
+ <xsd:restriction base="xsd:string">
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="FilterTypeUrlKey">
+ <xsd:restriction base="xsd:string">
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="Filter">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="filter" type="Filter" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="urlValue" type="xsd:string" use="required">
+
+ </xsd:attribute>
+ <xsd:attribute name="name" type="xsd:string" use="required"></xsd:attribute>
+ <xsd:attribute name="count" type="xsd:nonNegativeInteger"
+ use="required">
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="CollectionCoreStatistics">
+
+ <xsd:sequence>
+ <xsd:element name="pages" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+ <xsd:annotation>
+ <xsd:documentation>The total number of pages available for this
+ result set</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="results" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+ <xsd:annotation>
+
+ <xsd:documentation>The total number of results available for this
+ result set</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="total" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>When present, this gives the total number of
+ resources of the particular resource type that the result set is
+ referring to. Eg: for /services, this would be the total number of
+ services available regardless of how many are in the result set
+ (which may have been filtered down).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+
+ <xsd:simpleType name="SortBy">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="created"></xsd:enumeration>
+ <xsd:enumeration value="activated"></xsd:enumeration>
+ <xsd:enumeration value="name"></xsd:enumeration>
+
+ <xsd:enumeration value="modified"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="SortOrder">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="asc"></xsd:enumeration>
+ <xsd:enumeration value="desc"></xsd:enumeration>
+ </xsd:restriction>
+
+ </xsd:simpleType>
+
+ <xsd:element name="tags" type="Tags"></xsd:element>
+
+ <xsd:complexType name="Tags">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="TagsParameters"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="statistics" type="TagsStatistics"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="TagsResults"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="TagsRelated"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="TagsParameters">
+ <xsd:sequence>
+ <xsd:element name="sort" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="urlKey" use="required">
+
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="sort">
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="urlValue">
+ <xsd:simpleType>
+
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="counts">
+ </xsd:enumeration>
+ <xsd:enumeration value="name">
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:extension>
+
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element name="limit" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1" nillable="true"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="TagsStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="TagsResults">
+ <xsd:sequence>
+
+ <xsd:element name="tag" type="Tag" minOccurs="0"
+ maxOccurs="unbounded">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="TagsRelated">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="Tag">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="name" type="xsd:string" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="displayName" type="xsd:string"
+ maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element name="totalItemsCount" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="TagRelatedLinks"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="SubmitterType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="User"></xsd:enumeration>
+ <xsd:enumeration value="Registry"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="AnnotationSourceType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="User"></xsd:enumeration>
+ <xsd:enumeration value="ServiceProvider"></xsd:enumeration>
+ <xsd:enumeration value="Registry"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <xsd:complexType name="ServiceDeployment">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="endpoint" type="xsd:anyURI"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="serviceProvider" type="ServiceProvider"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="location" minOccurs="1" maxOccurs="1"
+ type="Location">
+
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="providedVariant" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element name="soapService" type="SoapService"
+ maxOccurs="1" minOccurs="0">
+
+ </xsd:element>
+ <xsd:element name="restService" type="RestService"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="annotations" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="ServiceTechnologyType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="SOAP"></xsd:enumeration>
+ <xsd:enumeration value="REST"></xsd:enumeration>
+ <xsd:enumeration value="Soaplab"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="Annotation">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="annotatable" type="ResourceLink"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="source" type="ResourceLink"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="version" type="xsd:positiveInteger"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="annotationAttribute" type="AnnotationAttribute"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="value" minOccurs="1" maxOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="resource" type="ResourceLink"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="type" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="content" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element ref="dcterms:created" minOccurs="1"
+ maxOccurs="1">
+ </xsd:element>
+
+ <xsd:element ref="dcterms:modified" minOccurs="0"
+ maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType></xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationAttribute">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element ref="dc:identifier" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="annotations" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+
+
+ <xsd:complexType name="ServiceRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="withSummary" type="ResourceLink"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="withMonitoring" type="ResourceLink"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ <xsd:element name="withAllSections" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="summary" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="deployments" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="variants" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="monitoring" type="ResourceLink"
+ maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element name="annotations" type="ResourceLink"
+ minOccurs="1" maxOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+
+ <xsd:simpleType name="MetadataBy">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="all"></xsd:enumeration>
+ <xsd:enumeration value="users"></xsd:enumeration>
+ <xsd:enumeration value="providers"></xsd:enumeration>
+ <xsd:enumeration value="registries"></xsd:enumeration>
+ </xsd:restriction>
+
+ </xsd:simpleType>
+
+ <xsd:complexType name="MetadataCount">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:nonNegativeInteger">
+ <xsd:attribute name="by" type="MetadataBy"></xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="SearchQueryParameter">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="urlKey" type="xsd:string" use="required">
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="PageParameter">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:nonNegativeInteger">
+ <xsd:attribute name="urlKey" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="page"></xsd:enumeration>
+ </xsd:restriction>
+
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+
+
+ <xsd:complexType name="SortByParameter">
+ <xsd:simpleContent>
+
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="urlKey" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="sort_by"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="urlValue" type="SortBy" use="required">
+
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="SortOrderParameter">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="urlKey" use="required">
+
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="sort_order"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="urlValue" type="SortOrder" use="required"></xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+
+ </xsd:complexType>
+
+ <xsd:element name="categories" type="Categories"></xsd:element>
+
+ <xsd:element name="category" type="Category"></xsd:element>
+
+ <xsd:complexType name="Category">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element name="name" type="xsd:string" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="totalItemsCount" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="broader" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="category" type="Category"
+ maxOccurs="unbounded" minOccurs="0">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="narrower" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="category" type="Category"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="services" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="Categories">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="CategoriesParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="CategoriesStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="CategoriesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="CategoriesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="CategoriesParameters">
+ <xsd:sequence>
+ <xsd:element name="rootsOnly" maxOccurs="1" minOccurs="1">
+
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:boolean">
+ <xsd:attribute name="urlKey" type="xsd:string" use="required">
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="CategoriesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="CategoriesResults">
+ <xsd:sequence>
+ <xsd:element name="category" type="Category" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="CategoriesRelatedLinks">
+ <xsd:sequence>
+
+ <xsd:element name="serviceFilters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ResourceLinkWithString">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute ref="xlink:href" use="required"></xsd:attribute>
+
+ <xsd:attribute ref="xlink:title" use="optional"></xsd:attribute>
+ <xsd:attribute name="resourceType" type="ResourceType"
+ use="optional"></xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <xsd:complexType name="MonitoringStatus">
+ <xsd:sequence>
+ <xsd:element name="label" maxOccurs="1" minOccurs="1"
+ type="MonitoringStatusLabel">
+
+ </xsd:element>
+ <xsd:element name="message" type="xsd:string" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="symbol" type="ResourceLink" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="smallSymbol" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="lastChecked" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="1" nillable="true"></xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="TagRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="services" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="soapOperations" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restMethods" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:element name="tag" type="Tag"></xsd:element>
+
+ <xsd:complexType name="PageSizeParameter">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:nonNegativeInteger">
+ <xsd:attribute name="urlKey" use="required">
+
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="per_page"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:element name="serviceProviders" type="ServiceProviders"></xsd:element>
+
+ <xsd:complexType name="ServiceProviders">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="ServiceProvidersParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="ServiceProvidersStatistics"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="results" type="ServiceProvidersResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="ServiceProvidersRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServiceProviderRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="annotations" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="annotationsBy" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="services" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServiceProvidersParameters">
+ <xsd:sequence>
+ <xsd:element name="filters" type="FiltersParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="query" type="SearchQueryParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServiceProvidersStatistics">
+ <xsd:complexContent>
+
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServiceProvidersResults">
+ <xsd:sequence>
+ <xsd:element name="serviceProvider" type="ServiceProvider"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ServiceProvidersRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="filters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="filtersOnCurrentResults"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="sortBy" type="SortByParameter"></xsd:element>
+
+ <xsd:element name="sortOrder" type="SortOrderParameter"></xsd:element>
+
+ <xsd:element name="page" type="PageParameter"></xsd:element>
+
+ <xsd:element name="pageSize" type="PageSizeParameter"></xsd:element>
+
+ <xsd:element name="soapService" type="SoapService"></xsd:element>
+
+ <xsd:complexType name="SoapService">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="wsdlLocation" type="xsd:anyURI"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="documentationUrl" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="deployments" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+
+ <xsd:sequence>
+ <xsd:element name="serviceDeployment" type="ServiceDeployment"
+ maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="operations" maxOccurs="1" minOccurs="0">
+
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="soapOperation" type="SoapOperation"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="deployments" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="operations" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="annotations" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="restService" type="RestService"></xsd:element>
+
+ <xsd:complexType name="RestService">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="documentationUrl"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="deployments" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element
+ name="serviceDeployment" type="ServiceDeployment"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resources" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+
+ <xsd:element name="restResource"
+ type="RestResource" maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="methods" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="restMethod"
+ type="RestMethod" maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="deployments"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="resources"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="methods"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+
+ <xsd:element name="serviceDeployment" type="ServiceDeployment"></xsd:element>
+
+
+ <xsd:complexType name="FilterTypeParameter">
+ <xsd:sequence>
+ <xsd:element name="filter" type="FilterParameter"
+ maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ <xsd:attribute name="name" type="FilterTypeName" use="required">
+ </xsd:attribute>
+ <xsd:attribute name="urlKey" type="FilterTypeUrlKey"
+ use="required">
+ </xsd:attribute>
+ <xsd:attribute name="description" type="xsd:string" use="required"></xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="FilterParameter">
+ <xsd:simpleContent>
+
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="urlValue" type="xsd:string" use="required">
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="Location">
+ <xsd:sequence>
+
+ <xsd:element name="city" type="xsd:string" maxOccurs="1"
+ minOccurs="0">
+ </xsd:element>
+ <xsd:element name="country" type="xsd:string" maxOccurs="1"
+ minOccurs="0">
+ </xsd:element>
+ <xsd:element name="iso3166CountryCode" type="xsd:string"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="flag" type="ResourceLink" maxOccurs="1"
+ minOccurs="0"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:element name="users" type="Users"></xsd:element>
+
+ <xsd:complexType name="Users">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="UsersParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="UsersStatistics"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="results" type="UsersResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="UsersRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="UsersParameters">
+ <xsd:sequence>
+ <xsd:element name="filters" type="FiltersParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="query" type="SearchQueryParameter" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="UsersStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="UsersResults">
+ <xsd:sequence>
+ <xsd:element name="user" type="User" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="UsersRelatedLinks">
+
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="filters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="filtersOnCurrentResults"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="UserRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="annotationsBy" type="ResourceLink"
+ maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element name="services" type="ResourceLink"
+ maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:element name="registries" type="Registries"></xsd:element>
+
+ <xsd:complexType name="Registries">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="RegistriesParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="RegistriesStatistics"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="results" type="RegistriesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="RegistriesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RegistriesParameters">
+ <xsd:sequence>
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RegistriesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RegistriesResults">
+ <xsd:sequence>
+
+ <xsd:element name="registry" type="Registry" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RegistriesRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks"></xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="RegistryRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="annotationsBy" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="services" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+
+ <xsd:simpleType name="MonitoringStatusLabel">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="PASSED"></xsd:enumeration>
+ <xsd:enumeration value="WARNING"></xsd:enumeration>
+ <xsd:enumeration value="FAILED"></xsd:enumeration>
+ <xsd:enumeration value="UNCHECKED"></xsd:enumeration>
+ </xsd:restriction>
+
+ </xsd:simpleType>
+
+
+ <xsd:complexType name="SoapOperation">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="parameterOrder" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+
+ <xsd:element name="inputs" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="soapInput"
+ type="SoapInput" maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputs" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="soapOutput"
+ type="SoapOutput" maxOccurs="unbounded" minOccurs="0">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="soapService"
+ type="SoapService" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+
+ <xsd:sequence>
+ <xsd:element name="inputs"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="outputs"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="annotationsOnAll"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="soapOperation" type="SoapOperation"></xsd:element>
+
+ <xsd:element name="soapInput" type="SoapInput"></xsd:element>
+
+ <xsd:complexType name="SoapInput">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="computationalType"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="computationalTypeDetails"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="soapService"
+ type="SoapService" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="soapOperation"
+ type="SoapOperation" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="soapOutput" type="SoapOutput"></xsd:element>
+
+ <xsd:complexType name="SoapOutput">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="computationalType"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="computationalTypeDetails"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="soapService"
+ type="SoapService" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="soapOperation"
+ type="SoapOperation" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="ResourceType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Annotation"></xsd:enumeration>
+ <xsd:enumeration value="AnnotationAttribute"></xsd:enumeration>
+ <xsd:enumeration value="Annotations"></xsd:enumeration>
+ <xsd:enumeration value="BioCatalogue"></xsd:enumeration>
+ <xsd:enumeration value="Categories"></xsd:enumeration>
+ <xsd:enumeration value="Category"></xsd:enumeration>
+
+ <xsd:enumeration value="Filters"></xsd:enumeration>
+ <xsd:enumeration value="Registries"></xsd:enumeration>
+ <xsd:enumeration value="Registry"></xsd:enumeration>
+ <xsd:enumeration value="RestService"></xsd:enumeration>
+ <xsd:enumeration value="Search"></xsd:enumeration>
+ <xsd:enumeration value="Service"></xsd:enumeration>
+ <xsd:enumeration value="ServiceDeployment"></xsd:enumeration>
+ <xsd:enumeration value="ServiceProvider"></xsd:enumeration>
+ <xsd:enumeration value="ServiceProviders"></xsd:enumeration>
+
+ <xsd:enumeration value="Services"></xsd:enumeration>
+ <xsd:enumeration value="SoapInput"></xsd:enumeration>
+ <xsd:enumeration value="SoapOperation"></xsd:enumeration>
+ <xsd:enumeration value="SoapOutput"></xsd:enumeration>
+ <xsd:enumeration value="SoapService"></xsd:enumeration>
+ <xsd:enumeration value="Tag"></xsd:enumeration>
+ <xsd:enumeration value="Tags"></xsd:enumeration>
+ <xsd:enumeration value="Tags"></xsd:enumeration>
+ <xsd:enumeration value="User"></xsd:enumeration>
+
+ <xsd:enumeration value="Users"></xsd:enumeration>
+ <xsd:enumeration value="Annotations"></xsd:enumeration>
+ <xsd:enumeration value="AnnotationAttributes"></xsd:enumeration>
+ <xsd:enumeration value="ServiceTest"></xsd:enumeration>
+ <xsd:enumeration value="TestResult"></xsd:enumeration>
+ <xsd:enumeration value="TestResults"></xsd:enumeration>
+ <xsd:enumeration value="UrlMonitor"></xsd:enumeration>
+ <xsd:enumeration value="TestScript"></xsd:enumeration>
+ <xsd:enumeration value="Errors"></xsd:enumeration>
+
+ <xsd:enumeration value="SearchByData"></xsd:enumeration>
+ <xsd:enumeration value="SoapOperations"></xsd:enumeration>
+ <xsd:enumeration value="Agent"></xsd:enumeration>
+ <xsd:enumeration value="Agents"></xsd:enumeration>
+ <xsd:enumeration value="RestMethod"></xsd:enumeration>
+ <xsd:enumeration value="RestParameter"></xsd:enumeration>
+ <xsd:enumeration value="RestRepresentation"></xsd:enumeration>
+ <xsd:enumeration value="RestMethods"></xsd:enumeration>
+ <xsd:enumeration value="SoapServices"></xsd:enumeration>
+
+ <xsd:enumeration value="RestServices"></xsd:enumeration>
+ <xsd:enumeration value="RestResources"></xsd:enumeration>
+ <xsd:enumeration value="RestResource"></xsd:enumeration>
+ <xsd:enumeration value="SavedSearch"></xsd:enumeration>
+ <xsd:enumeration value="WsdlLocations"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:element name="annotationAttribute" type="AnnotationAttribute">
+
+ </xsd:element>
+
+ <xsd:element name="annotationAttributes" type="AnnotationAttributes">
+ </xsd:element>
+
+ <xsd:complexType name="AnnotationAttributes">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="AnnotationAttributesParameters"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="statistics" type="AnnotationAttributesStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="AnnotationAttributesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="AnnotationAttributesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationAttributesParameters">
+ <xsd:sequence>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationAttributesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationAttributesResults">
+ <xsd:sequence>
+ <xsd:element name="annotationAttribute" type="AnnotationAttribute"
+ maxOccurs="unbounded" minOccurs="0">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationAttributesRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="annotations" type="Annotations"></xsd:element>
+
+ <xsd:complexType name="Annotations">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="AnnotationsParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="AnnotationsStatistics"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="results" type="AnnotationsResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="AnnotationsRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationsParameters">
+ <xsd:sequence>
+ <xsd:element name="filters" type="FiltersParameters"
+ maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationsStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationsResults">
+ <xsd:sequence>
+
+ <xsd:element name="annotation" type="Annotation"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="AnnotationsRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+
+ <xsd:element name="filters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="filtersOnCurrentResults" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="annotation" type="Annotation"></xsd:element>
+
+ <xsd:element name="testResult" type="TestResult"></xsd:element>
+
+ <xsd:complexType name="TestResult">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="testAction" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="resultCode" type="xsd:integer"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="status" type="MonitoringStatus"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="serviceTest"
+ type="ServiceTest" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType></xsd:complexType>
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name="testResults" type="TestResults"></xsd:element>
+
+ <xsd:complexType name="TestResults">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="serviceTest" type="ResourceLink"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="statistics" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="results" maxOccurs="1" minOccurs="1">
+
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="testResult" type="TestResult"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:element name="serviceTest" type="ServiceTest"></xsd:element>
+
+ <xsd:complexType name="ServiceTest">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="testType" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+
+ <xsd:choice>
+ <xsd:element name="urlMonitor" type="UrlMonitor"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="testScript" type="TestScript"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="latestStatus" type="MonitoringStatus"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="results" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="UrlMonitor">
+ <xsd:sequence>
+ <xsd:element name="url" type="xsd:anyURI" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="resource" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="TestScript">
+ <xsd:sequence>
+ <xsd:element name="name" type="xsd:string" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="contentType" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="programmingLanguage" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="executableFilename" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="download" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="activatedAt" type="xsd:dateTime"
+ nillable="true" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+
+ <xsd:element name="errors" type="Errors"></xsd:element>
+
+ <xsd:complexType name="Errors">
+ <xsd:sequence>
+ <xsd:element name="error" type="xsd:string" maxOccurs="unbounded"
+ minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:element name="searchByData" type="SearchByData"></xsd:element>
+
+ <xsd:complexType name="SearchByData">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element name="data" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="searchType" maxOccurs="1"
+ minOccurs="1">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="input">
+ </xsd:enumeration>
+ <xsd:enumeration value="output">
+ </xsd:enumeration>
+
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="limit" type="xsd:nonNegativeInteger"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="statistics" maxOccurs="1" minOccurs="1">
+
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="results" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+
+ <xsd:sequence>
+ <xsd:element name="resultItem" maxOccurs="unbounded"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="soapOperation" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="port" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="annotation" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="related" maxOccurs="1" minOccurs="1">
+ <xsd:complexType></xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+
+ <xsd:element name="soapOperations" type="SoapOperations"></xsd:element>
+
+ <xsd:complexType name="SoapOperations">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element name="filters" maxOccurs="1" minOccurs="1"
+ type="FiltersParameters">
+ </xsd:element>
+ <xsd:element name="query" type="SearchQueryParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1"
+ minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="statistics" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ </xsd:element>
+ <xsd:element name="results" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="soapOperation" type="SoapOperation"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="related" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="filters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="filtersOnCurrentResults" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="FilterGroup">
+ <xsd:sequence>
+ <xsd:element name="type" type="FilterType" maxOccurs="unbounded"
+ minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"></xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:complexType name="FilterGroupParameter">
+
+ <xsd:sequence>
+ <xsd:element name="type" type="FilterTypeParameter"
+ maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"></xsd:attribute>
+ </xsd:complexType>
+
+ <xsd:element name="agents" type="Agents"></xsd:element>
+
+ <xsd:complexType name="Agents">
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="AgentsParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="AgentsStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="AgentsResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="related" type="AgentsRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AgentsParameters">
+ <xsd:sequence>
+
+ <xsd:element ref="sortBy" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="sortOrder" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="page" maxOccurs="1" minOccurs="1"></xsd:element>
+ <xsd:element ref="pageSize" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="AgentsStatistics">
+ <xsd:complexContent>
+
+ <xsd:extension base="CollectionCoreStatistics">
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AgentsResults">
+ <xsd:sequence>
+ <xsd:element name="agent" type="Agent" maxOccurs="unbounded"
+ minOccurs="0">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="AgentsRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="AgentRelatedLinks">
+
+ <xsd:sequence>
+ <xsd:element name="annotationsBy" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:element name="agent" type="Agent"></xsd:element>
+
+ <xsd:complexType name="Agent">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string" minOccurs="1"
+ maxOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="related" type="AgentRelatedLinks"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+
+ <xsd:element name="soapServices" type="SoapServices"></xsd:element>
+
+ <xsd:complexType name="SoapServices">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="SoapServicesParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="SoapServicesStatistics"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="results" type="SoapServicesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="SoapServicesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="SoapServicesParameters">
+ <xsd:sequence>
+ <xsd:element name="sortBy" type="SortByParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="sortOrder" type="SortOrderParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="page" type="PageParameter" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="pageSize" type="PageSizeParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="SoapServicesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics"></xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="SoapServicesResults">
+ <xsd:sequence>
+ <xsd:element name="soapService" type="SoapService"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="SoapServicesRelatedLinks">
+
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="restServices" type="RestServices"></xsd:element>
+
+ <xsd:complexType name="RestServices">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="RestServicesParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="RestServicesStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="RestServicesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="RestServicesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestServicesParameters">
+ <xsd:sequence>
+ <xsd:element name="sortBy" type="SortByParameter"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="sortOrder" type="SortOrderParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="page" type="PageParameter" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="pageSize" type="PageSizeParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestServicesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestServicesResults">
+ <xsd:sequence>
+ <xsd:element name="restService" type="RestService"
+ maxOccurs="unbounded" minOccurs="0">
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestServicesRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="restResources" type="RestResources"></xsd:element>
+
+ <xsd:complexType name="RestResources">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="RestResourcesParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="statistics" type="RestResourcesStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="RestResourcesResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="related" type="RestResourcesRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="RestResourcesParameters">
+ <xsd:sequence>
+ <xsd:element name="sortBy" type="SortByParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="sortOrder" type="SortOrderParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="page" type="PageParameter" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="pageSize" type="PageSizeParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestResourcesStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics"></xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestResourcesResults">
+ <xsd:sequence>
+ <xsd:element name="restResource" type="RestResource"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestResourcesRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks"></xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="restMethods" type="RestMethods"></xsd:element>
+
+ <xsd:complexType name="RestMethods">
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters" type="RestMethodsParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="statistics" type="RestMethodsStatistics"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="results" type="RestMethodsResults"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="related" type="RestMethodsRelatedLinks"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestMethodsParameters">
+ <xsd:sequence>
+
+ <xsd:element name="filters" type="FiltersParameters"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="query" type="SearchQueryParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="sortBy" type="SortByParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="sortOrder" type="SortOrderParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="page" type="PageParameter" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="pageSize" type="PageSizeParameter"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestMethodsStatistics">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreStatistics"></xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestMethodsResults">
+ <xsd:sequence>
+ <xsd:element name="restMethod" type="RestMethod"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestMethodsRelatedLinks">
+ <xsd:complexContent>
+ <xsd:extension base="CollectionCoreRelatedLinks">
+ <xsd:sequence>
+ <xsd:element name="filters" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="filtersOnCurrentResults"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="RestResource">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="path" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="archived" type="xsd:dateTime" maxOccurs="1" minOccurs="0"></xsd:element>
+
+ <xsd:element name="methods" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="restMethod"
+ type="RestMethod" maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restService"
+ type="RestService" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="restMethods"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="RestMethod">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="endpointLabel" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="httpMethodType" type="HttpVerb"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="urlTemplate" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="documentationUrl"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="inputs" maxOccurs="1"
+ minOccurs="0">
+
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="restParameter" type="RestParameter"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element
+ name="representations" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="restRepresentation" type="RestRepresentation"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputs" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="parameters"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="restParameter" type="RestParameter"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element
+ name="representations" maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="restRepresentation" type="RestRepresentation"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restService"
+ type="RestService" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restResource"
+ type="RestResource" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="inputs"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="outputs"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="restResource" type="RestResource"></xsd:element>
+
+ <xsd:element name="restMethod" type="RestMethod"></xsd:element>
+
+ <xsd:element name="restParameter" type="RestParameter"></xsd:element>
+
+ <xsd:complexType name="RestParameter">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="computationalType"
+ type="xsd:string" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="defaultValue" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="paramStyle"
+ type="RestParameterStyle" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="isOptional" type="xsd:boolean"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="constrainedValues" maxOccurs="1"
+ minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value"
+ type="xsd:string" maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restService"
+ type="RestService" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restResources"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+
+ <xsd:sequence>
+ <xsd:element
+ name="restResource" type="RestResource"
+ maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="restMethods"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+
+ <xsd:element
+ name="restMethod" type="RestMethod" maxOccurs="unbounded"
+ minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="RestParameterStyle">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="template"></xsd:enumeration>
+ <xsd:enumeration value="query"></xsd:enumeration>
+ <xsd:enumeration value="matrix"></xsd:enumeration>
+ <xsd:enumeration value="header"></xsd:enumeration>
+
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:element name="restRepresentation" type="RestRepresentation"></xsd:element>
+
+ <xsd:complexType name="RestRepresentation">
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+
+ </xsd:element>
+ <xsd:element ref="dc:description" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="contentType" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="submitter" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="archived" type="xsd:dateTime"
+ maxOccurs="1" minOccurs="0">
+ </xsd:element>
+ <xsd:element name="ancestors" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="service"
+ type="Service" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="restService"
+ type="RestService" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element name="restResources"
+ maxOccurs="1" minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="restResource" type="RestResource"
+ maxOccurs="unbounded" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="restMethods"
+ maxOccurs="1" minOccurs="1">
+
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="restMethod" type="RestMethod" maxOccurs="unbounded"
+ minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ </xsd:element>
+ <xsd:element name="related" maxOccurs="1"
+ minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="annotations"
+ type="ResourceLink" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="savedSearch" type="SavedSearch"></xsd:element>
+
+ <xsd:complexType name="SavedSearch">
+ <xsd:complexContent>
+
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element ref="dc:title" maxOccurs="1"
+ minOccurs="1">
+ </xsd:element>
+ <xsd:element name="name" type="xsd:string"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="allScopes" type="xsd:boolean"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element name="query" type="xsd:string"
+ maxOccurs="1" minOccurs="1" nillable="true">
+
+ </xsd:element>
+ <xsd:element name="scopes" maxOccurs="1"
+ minOccurs="1">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scope"
+ maxOccurs="unbounded" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element
+ name="scopeName" type="SearchScopeName" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+
+ <xsd:element
+ name="scopeUrlValue" type="SearchScopeUrlValue" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element
+ name="scopeResourceType" maxOccurs="1" minOccurs="1"
+ type="ResourceType">
+ </xsd:element>
+ <xsd:element name="filters"
+ type="FiltersParameters" maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="user" type="ResourceLink"
+ maxOccurs="1" minOccurs="1">
+ </xsd:element>
+ <xsd:element ref="dcterms:created" maxOccurs="1" minOccurs="1"></xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+
+ </xsd:complexType>
+
+ <xsd:simpleType name="HttpVerb">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="OPTIONS"></xsd:enumeration>
+ <xsd:enumeration value="GET"></xsd:enumeration>
+ <xsd:enumeration value="HEAD"></xsd:enumeration>
+ <xsd:enumeration value="POST"></xsd:enumeration>
+ <xsd:enumeration value="PUT"></xsd:enumeration>
+
+ <xsd:enumeration value="DELETE"></xsd:enumeration>
+ <xsd:enumeration value="TRACE"></xsd:enumeration>
+ <xsd:enumeration value="CONNECT"></xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <xsd:element name="wsdlLocations" type="WsdlLocations"></xsd:element>
+
+ <xsd:complexType name="WsdlLocations">
+
+ <xsd:complexContent>
+ <xsd:extension base="ResourceLink">
+ <xsd:sequence>
+ <xsd:element name="wsdlLocation" type="xsd:anyURI"
+ maxOccurs="unbounded" minOccurs="0">
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/xsd/xlink.xsd b/taverna-workbench-perspective-biocatalogue/src/main/xsd/xlink.xsd
new file mode 100644
index 0000000..6c2034b
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/xsd/xlink.xsd
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- METS XLink Schema, v. 2, Nov. 15, 2004 -->
+<!-- Downloaded from http://www.loc.gov/standards/mets/xlink.xsd at 2007-04-19 -->
+<!-- Downloaded from http://taverna.cvs.sf.net/viewvc/taverna/taverna-service/taverna-interface/src/main/resources/xlink.xsd?view=log at 2009-10-19 -->
+<schema targetNamespace="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xlink="http://www.w3.org/1999/xlink" elementFormDefault="qualified">
+ <!-- global attributes -->
+ <attribute name="href" type="anyURI"/>
+ <attribute name="role" type="string"/>
+ <attribute name="arcrole" type="string"/>
+ <attribute name="title" type="string" />
+ <attribute name="show">
+ <simpleType>
+
+ <restriction base="string">
+ <enumeration value="new" />
+ <enumeration value="replace" />
+ <enumeration value="embed" />
+ <enumeration value="other" />
+ <enumeration value="none" />
+ </restriction>
+ </simpleType>
+ </attribute>
+ <attribute name="actuate">
+ <simpleType>
+
+ <restriction base="string">
+ <enumeration value="onLoad" />
+ <enumeration value="onRequest" />
+ <enumeration value="other" />
+ <enumeration value="none" />
+ </restriction>
+ </simpleType>
+ </attribute>
+ <attribute name="label" type="string" />
+ <attribute name="from" type="string" />
+ <attribute name="to" type="string" />
+ <attributeGroup name="simpleLink">
+
+ <attribute name="type" type="string" fixed="simple" form="qualified" />
+ <attribute ref="xlink:href" use="optional" />
+ <attribute ref="xlink:role" use="optional" />
+ <attribute ref="xlink:arcrole" use="optional" />
+ <attribute ref="xlink:title" use="optional" />
+ <attribute ref="xlink:show" use="optional" />
+ <attribute ref="xlink:actuate" use="optional" />
+ </attributeGroup>
+ <attributeGroup name="extendedLink">
+ <attribute name="type" type="string" fixed="extended" form="qualified" />
+ <attribute ref="xlink:role" use="optional" />
+ <attribute ref="xlink:title" use="optional" />
+ </attributeGroup>
+ <attributeGroup name="locatorLink">
+
+ <attribute name="type" type="string" fixed="locator" form="qualified" />
+ <attribute ref="xlink:href" use="required" />
+ <attribute ref="xlink:role" use="optional" />
+ <attribute ref="xlink:title" use="optional" />
+ <attribute ref="xlink:label" use="optional" />
+ </attributeGroup>
+ <attributeGroup name="arcLink">
+ <attribute name="type" type="string" fixed="arc" form="qualified" />
+ <attribute ref="xlink:arcrole" use="optional" />
+ <attribute ref="xlink:title" use="optional" />
+ <attribute ref="xlink:show" use="optional" />
+ <attribute ref="xlink:actuate" use="optional" />
+ <attribute ref="xlink:from" use="optional" />
+ <attribute ref="xlink:to" use="optional" />
+ </attributeGroup>
+
+ <attributeGroup name="resourceLink">
+ <attribute name="type" type="string" fixed="resource" form="qualified" />
+ <attribute ref="xlink:role" use="optional" />
+ <attribute ref="xlink:title" use="optional" />
+ <attribute ref="xlink:label" use="optional" />
+ </attributeGroup>
+ <attributeGroup name="titleLink">
+ <attribute name="type" type="string" fixed="title" form="qualified" />
+ </attributeGroup>
+ <attributeGroup name="emptyLink">
+ <attribute name="type" type="string" fixed="none" form="qualified" />
+ </attributeGroup>
+
+</schema>
diff --git a/taverna-workbench-perspective-design/pom.xml b/taverna-workbench-perspective-design/pom.xml
new file mode 100644
index 0000000..cde090a
--- /dev/null
+++ b/taverna-workbench-perspective-design/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>perspective-design</artifactId>
+ <packaging>bundle</packaging>
+ <name>Design perspective</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>activity-palette-ui</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-explorer</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <!-- <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>report-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency> -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/DesignPerspective.java b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/DesignPerspective.java
new file mode 100644
index 0000000..eca98d4
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/DesignPerspective.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * 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.ui.perspectives.design;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.editIcon;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+
+public class DesignPerspective implements PerspectiveSPI {
+ private DesignPerspectiveComponent designPerspectiveComponent;
+ private UIComponentFactorySPI graphViewComponentFactory;
+ private UIComponentFactorySPI servicePanelComponentFactory;
+ private UIComponentFactorySPI contextualViewComponentFactory;
+ private UIComponentFactorySPI workflowExplorerFactory;
+ private UIComponentFactorySPI reportViewComponentFactory;
+ private FileManager fileManager;
+ private SelectionManager selectionManager;
+ private MenuManager menuManager;
+ private EditManager editManager;
+
+ @Override
+ public String getID() {
+ return DesignPerspective.class.getName();
+ }
+
+ @Override
+ public JComponent getPanel() {
+ if (designPerspectiveComponent == null)
+ designPerspectiveComponent = new DesignPerspectiveComponent(
+ graphViewComponentFactory, servicePanelComponentFactory,
+ contextualViewComponentFactory, workflowExplorerFactory,
+ reportViewComponentFactory, fileManager, selectionManager,
+ menuManager, editManager);
+ return designPerspectiveComponent;
+ }
+
+ @Override
+ public ImageIcon getButtonIcon() {
+ return editIcon;
+ }
+
+ @Override
+ public String getText() {
+ return "Design";
+ }
+
+ @Override
+ public int positionHint() {
+ return 10;
+ }
+
+ public void setGraphViewComponentFactory(
+ UIComponentFactorySPI graphViewComponentFactory) {
+ this.graphViewComponentFactory = graphViewComponentFactory;
+ }
+
+ public void setServicePanelComponentFactory(
+ UIComponentFactorySPI servicePanelComponentFactory) {
+ this.servicePanelComponentFactory = servicePanelComponentFactory;
+ }
+
+ public void setContextualViewComponentFactory(
+ UIComponentFactorySPI contextualViewComponentFactory) {
+ this.contextualViewComponentFactory = contextualViewComponentFactory;
+ }
+
+ public void setWorkflowExplorerFactory(
+ UIComponentFactorySPI workflowExplorerFactory) {
+ this.workflowExplorerFactory = workflowExplorerFactory;
+ }
+
+ public void setReportViewComponentFactory(
+ UIComponentFactorySPI reportViewComponentFactory) {
+ this.reportViewComponentFactory = reportViewComponentFactory;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+}
diff --git a/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/DesignPerspectiveComponent.java b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/DesignPerspectiveComponent.java
new file mode 100644
index 0000000..37df0ac
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/DesignPerspectiveComponent.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.ui.perspectives.design;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+
+/**
+ * @author David Withers
+ */
+public class DesignPerspectiveComponent extends JSplitPane {
+ private static final long serialVersionUID = 6199239532713982318L;
+
+ private final UIComponentFactorySPI graphViewComponentFactory;
+ private final UIComponentFactorySPI servicePanelComponentFactory;
+ private final UIComponentFactorySPI contextualViewComponentFactory;
+ private final UIComponentFactorySPI workflowExplorerFactory;
+ @SuppressWarnings("unused")
+ private final UIComponentFactorySPI reportViewComponentFactory;
+ private final SelectionManager selectionManager;
+
+ private final FileManager fileManager;
+ private final MenuManager menuManager;
+ private final EditManager editManager;
+
+ public DesignPerspectiveComponent(
+ UIComponentFactorySPI graphViewComponentFactory,
+ UIComponentFactorySPI servicePanelComponentFactory,
+ UIComponentFactorySPI contextualViewComponentFactory,
+ UIComponentFactorySPI workflowExplorerFactory,
+ UIComponentFactorySPI reportViewComponentFactory,
+ FileManager fileManager, SelectionManager selectionManager,
+ MenuManager menuManager, EditManager editManager) {
+ this.graphViewComponentFactory = graphViewComponentFactory;
+ this.servicePanelComponentFactory = servicePanelComponentFactory;
+ this.contextualViewComponentFactory = contextualViewComponentFactory;
+ this.workflowExplorerFactory = workflowExplorerFactory;
+ this.reportViewComponentFactory = reportViewComponentFactory;
+ this.fileManager = fileManager;
+ this.selectionManager = selectionManager;
+ this.menuManager = menuManager;
+ this.editManager = editManager;
+
+ setBorder(null);
+ setOrientation(HORIZONTAL_SPLIT);
+ setDividerLocation(300);
+ setLeftComponent(createLeftComponent());
+ setRightComponent(createRightComponent());
+ }
+
+ private Component createLeftComponent() {
+ JSplitPane leftComponent = new JSplitPane(VERTICAL_SPLIT);
+ leftComponent.setBorder(null);
+ leftComponent.setDividerLocation(400);
+
+ leftComponent.setLeftComponent((Component) servicePanelComponentFactory
+ .getComponent());
+
+ JTabbedPane rightComponent = new JTabbedPane();
+ rightComponent.addTab("Workflow explorer",
+ (Component) workflowExplorerFactory.getComponent());
+ rightComponent.addTab("Details",
+ (Component) contextualViewComponentFactory.getComponent());
+ // rightComponent.addTab("Validation report", (Component)
+ // reportViewComponentFactory.getComponent());
+ leftComponent.setRightComponent(rightComponent);
+
+ return leftComponent;
+ }
+
+ private Component createRightComponent() {
+ JPanel diagramComponent = new JPanel(new BorderLayout());
+ diagramComponent.add(new WorkflowSelectorComponent(selectionManager),
+ NORTH);
+ diagramComponent.add(
+ (Component) graphViewComponentFactory.getComponent(), CENTER);
+
+ JPanel rightComonent = new JPanel(new BorderLayout());
+ rightComonent.add(new WorkflowBundleSelectorComponent(selectionManager,
+ fileManager, menuManager, editManager), NORTH);
+ rightComonent.add(diagramComponent, CENTER);
+ return rightComonent;
+ }
+}
diff --git a/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowBundleSelectorComponent.java b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowBundleSelectorComponent.java
new file mode 100644
index 0000000..9fccf06
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowBundleSelectorComponent.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.design;
+
+import java.awt.Component;
+import java.net.URI;
+
+import javax.swing.Action;
+import javax.swing.JMenuItem;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.tabselector.Tab;
+import net.sf.taverna.t2.lang.ui.tabselector.TabSelectorComponent;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Component for managing selection of workflow bundles.
+ *
+ * @author David Withers
+ */
+public class WorkflowBundleSelectorComponent extends TabSelectorComponent<WorkflowBundle> {
+ private static final long serialVersionUID = 7291973052895544750L;
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileClose");
+
+ private final SelectionManager selectionManager;
+ private final FileManager fileManager;
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+
+ private Action closeMenuAction;
+
+ public WorkflowBundleSelectorComponent(SelectionManager selectionManager,
+ FileManager fileManager, MenuManager menuManager,
+ EditManager editManager) {
+ this.selectionManager = selectionManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.editManager = editManager;
+ fileManager.addObserver(new FileManagerObserver());
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ private class FileManagerObserver extends
+ SwingAwareObserver<FileManagerEvent> {
+ @Override
+ public void notifySwing(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) {
+ if (message instanceof ClosedDataflowEvent) {
+ ClosedDataflowEvent event = (ClosedDataflowEvent) message;
+ removeObject(event.getDataflow());
+ }
+ }
+ }
+
+ private class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent) {
+ WorkflowBundleSelectionEvent workflowBundleSelectionEvent = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle workflowBundle = workflowBundleSelectionEvent
+ .getSelectedWorkflowBundle();
+ selectObject(workflowBundle);
+ }
+ }
+ }
+
+ @Override
+ protected Tab<WorkflowBundle> createTab(WorkflowBundle workflowBundle) {
+ return new WorkflowTab(this, workflowBundle, selectionManager,
+ fileManager, editManager, getCloseMenuAction());
+ }
+
+ private Action getCloseMenuAction() {
+ if (closeMenuAction == null) {
+ Component component = menuManager.getComponentByURI(FILE_CLOSE_URI);
+ if (component instanceof JMenuItem) {
+ JMenuItem menuItem = (JMenuItem) component;
+ closeMenuAction = menuItem.getAction();
+ }
+ }
+ return closeMenuAction;
+ }
+}
diff --git a/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowSelectorComponent.java b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowSelectorComponent.java
new file mode 100644
index 0000000..6472828
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowSelectorComponent.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.perspectives.design;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowSelectionEvent;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Component for managing selection of workflows.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class WorkflowSelectorComponent extends JPanel {
+ private static final URI NESTED_WORKFLOW_TYPE = URI
+ .create("http://ns.taverna.org.uk/2010/activity/nested-workflow");
+
+ private final Scufl2Tools scufl2Tools = new Scufl2Tools();
+ private final SelectionManager selectionManager;
+
+ public WorkflowSelectorComponent(SelectionManager selectionManager) {
+ super(new FlowLayout(LEFT));
+ this.selectionManager = selectionManager;
+ update(selectionManager.getSelectedWorkflow());
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ private void update(Workflow workflow) {
+ removeAll();
+ if (workflow != null
+ && workflow.getParent().getMainWorkflow() != workflow)
+ update(workflow, selectionManager.getSelectedProfile());
+ revalidate();
+ repaint();
+ }
+
+ /** @see #update(Workflow) */
+ private void update(Workflow workflow, Profile profile) {
+ boolean first = true;
+ for (final Workflow workflowItem : getPath(
+ new NamedSet<>(profile.getActivities()), workflow, profile)) {
+ JButton button = new JButton(workflowItem.getName());
+// button.setBorder(null);
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ selectionManager.setSelectedWorkflow(workflowItem);
+ }
+ });
+ if (!first)
+ add(new JLabel(">"));
+ first = false;
+ add(button);
+ }
+ }
+
+ private List<Workflow> getPath(NamedSet<Activity> activities,
+ Workflow workflow, Profile profile) {
+ LinkedList<Workflow> path = new LinkedList<>();
+ for (Activity activity : activities) {
+ if (!activity.getType().equals(NESTED_WORKFLOW_TYPE))
+ continue;
+ if (getNestedWorkflow(workflow, profile, activity) != workflow)
+ continue;
+
+ List<ProcessorBinding> processorBindings = scufl2Tools
+ .processorBindingsToActivity(activity);
+ for (ProcessorBinding processorBinding : processorBindings) {
+ Processor processor = processorBinding.getBoundProcessor();
+ Workflow parentWorkflow = processor.getParent();
+ if (workflow.getParent().getMainWorkflow() == parentWorkflow)
+ path.add(parentWorkflow);
+ else {
+ activities.remove(activity);
+ path.addAll(getPath(activities, parentWorkflow, profile));
+ }
+ break;
+ }
+ break;
+ }
+ path.add(workflow);
+ return path;
+ }
+
+ private Workflow getNestedWorkflow(Workflow workflow, Profile profile,
+ Activity activity) {
+ for (Configuration configuration : scufl2Tools.configurationsFor(
+ activity, profile)) {
+ JsonNode nested = configuration.getJson().get("nestedWorkflow");
+ Workflow wf = workflow.getParent().getWorkflows()
+ .getByName(nested.asText());
+ if (wf != null)
+ return wf;
+ }
+ return null;
+ }
+
+ private class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent)
+ bundleSelected((WorkflowBundleSelectionEvent) message);
+ else if (message instanceof WorkflowSelectionEvent)
+ workflowSelected((WorkflowSelectionEvent) message);
+ }
+ }
+
+ private void workflowSelected(WorkflowSelectionEvent event) {
+ update(event.getSelectedWorkflow());
+ }
+
+ private void bundleSelected(WorkflowBundleSelectionEvent event) {
+ WorkflowBundle workflowBundle = event.getSelectedWorkflowBundle();
+ if (workflowBundle == null)
+ update((Workflow) null);
+ else
+ update(selectionManager.getSelectedWorkflow());
+ }
+}
diff --git a/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowTab.java b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowTab.java
new file mode 100644
index 0000000..e2f7bd6
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/java/net/sf/taverna/t2/ui/perspectives/design/WorkflowTab.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.ui.perspectives.design;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.tabselector.Tab;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Tab for selecting current workflow.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class WorkflowTab extends Tab<WorkflowBundle> {
+ private static final String SAVED_MARKER = "*";
+
+ private final Component component;
+ private final SelectionManager selectionManager;
+ private final FileManager fileManager;
+ private final EditManager editManager;
+ private final Action closeMenuAction;
+
+ private boolean saved = true;
+ private EditManagerObserver editManagerObserver;
+ private FileManagerObserver fileManagerObserver;
+
+ public WorkflowTab(Component component, final WorkflowBundle workflowBundle,
+ final SelectionManager selectionManager, final FileManager fileManager,
+ EditManager editManager, Action closeMenuAction) {
+ super(workflowBundle.getMainWorkflow().getName(), workflowBundle);
+ this.component = component;
+ this.selectionManager = selectionManager;
+ this.fileManager = fileManager;
+ this.editManager = editManager;
+ this.closeMenuAction = closeMenuAction;
+ editManagerObserver = new EditManagerObserver();
+ fileManagerObserver = new FileManagerObserver();
+ editManager.addObserver(editManagerObserver);
+ fileManager.addObserver(fileManagerObserver);
+ }
+
+ @Override
+ protected void clickTabAction() {
+ selectionManager.setSelectedWorkflowBundle(selection);
+ }
+
+ @Override
+ protected void closeTabAction() {
+ if (!saved && closeMenuAction != null) {
+ selectionManager.setSelectedWorkflowBundle(selection);
+ closeMenuAction.actionPerformed(new ActionEvent(component, 0, ""));
+ } else
+ try {
+ fileManager.closeDataflow(selection, false);
+ } catch (UnsavedException e) {
+ }
+ }
+
+ private class EditManagerObserver extends
+ SwingAwareObserver<EditManagerEvent> {
+ @Override
+ public void notifySwing(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) {
+ if (message instanceof AbstractDataflowEditEvent) {
+ AbstractDataflowEditEvent event = (AbstractDataflowEditEvent) message;
+ if (event.getDataFlow() == selection)
+ setSaved(false);
+ }
+ }
+ }
+
+ private class FileManagerObserver extends
+ SwingAwareObserver<FileManagerEvent> {
+ @Override
+ public void notifySwing(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) {
+ if (message instanceof ClosedDataflowEvent) {
+ ClosedDataflowEvent event = (ClosedDataflowEvent) message;
+ if (event.getDataflow() == selection) {
+ fileManager.removeObserver(fileManagerObserver);
+ editManager.removeObserver(editManagerObserver);
+ }
+ } else if (message instanceof SavedDataflowEvent) {
+ SavedDataflowEvent event = (SavedDataflowEvent) message;
+ if (event.getDataflow() == selection)
+ setSaved(true);
+ }
+ }
+ }
+
+ public void setSaved(boolean saved) {
+ this.saved = saved;
+ String name = getName();
+ if (saved && name.startsWith(SAVED_MARKER))
+ setName(name.substring(SAVED_MARKER.length()));
+ else if (!saved && !name.startsWith(SAVED_MARKER))
+ setName(SAVED_MARKER + name);
+ }
+}
diff --git a/taverna-workbench-perspective-design/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI b/taverna-workbench-perspective-design/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
new file mode 100644
index 0000000..db4397c
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.design.DesignPerspective
diff --git a/taverna-workbench-perspective-design/src/main/resources/META-INF/spring/perspective-design-context-osgi.xml b/taverna-workbench-perspective-design/src/main/resources/META-INF/spring/perspective-design-context-osgi.xml
new file mode 100644
index 0000000..3473a47
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/resources/META-INF/spring/perspective-design-context-osgi.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="DesignPerspective" auto-export="interfaces" />
+
+ <reference id="graphViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" bean-name="GraphViewComponentFactory" />
+ <reference id="servicePanelComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" bean-name="ServicePanelComponentFactory"/>
+ <reference id="contextualViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" bean-name="ContextualViewComponentFactory"/>
+ <reference id="workflowExplorerFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" bean-name="WorkflowExplorerFactory"/>
+ <!-- <reference id="reportViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" bean-name="ReportViewComponentFactory"/> -->
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-perspective-design/src/main/resources/META-INF/spring/perspective-design-context.xml b/taverna-workbench-perspective-design/src/main/resources/META-INF/spring/perspective-design-context.xml
new file mode 100644
index 0000000..9e4e383
--- /dev/null
+++ b/taverna-workbench-perspective-design/src/main/resources/META-INF/spring/perspective-design-context.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="DesignPerspective" class="net.sf.taverna.t2.ui.perspectives.design.DesignPerspective">
+ <property name="graphViewComponentFactory" ref="graphViewComponentFactory" />
+ <property name="servicePanelComponentFactory" ref="servicePanelComponentFactory" />
+ <property name="contextualViewComponentFactory" ref="contextualViewComponentFactory" />
+ <property name="workflowExplorerFactory" ref="workflowExplorerFactory" />
+ <!-- <property name="reportViewComponentFactory" ref="reportViewComponentFactory" /> -->
+ <property name="fileManager" ref="fileManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="editManager" ref="editManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-perspective-myexperiment/pom.xml b/taverna-workbench-perspective-myexperiment/pom.xml
new file mode 100644
index 0000000..9406580
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>perspective-myexperiment</artifactId>
+ <name>myExperiment perspective</name>
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository</url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>
+ http://www.mygrid.org.uk/maven/snapshot-repository
+ </url>
+ </repository>
+
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>dataflow-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ <version>${jdom.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/AddCommentDialog.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/AddCommentDialog.java
new file mode 100644
index 0000000..fc1a8e6
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/AddCommentDialog.java
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Component;
+import java.awt.Container;
+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.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.net.HttpURLConnection;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.ServerResponse;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+
+public class AddCommentDialog extends HelpEnabledDialog implements ActionListener, CaretListener, ComponentListener, KeyListener {
+ // components for accessing application's main elements
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ // COMPONENTS
+ private DialogTextArea taComment;
+ private JButton bPost;
+ private JButton bCancel;
+ private JLabel lStatusMessage;
+
+ // STORAGE
+ private Resource resource; // a resource for which the comment is being posted
+ private String strComment = null;
+ private boolean bPostingSuccessful = false;
+
+ public AddCommentDialog(JFrame owner, Resource resource, MainComponent component, MyExperimentClient client, Logger logger) {
+ super(owner, "Add comment for \"" + resource.getTitle() + "\" "
+ + resource.getItemTypeName(), true);
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // set the resource for which the comment is being added
+ this.resource = resource;
+
+ // set options of the 'add comment' dialog box
+ this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ //this.setIconImage(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("myexp_icon")).getImage());
+
+ this.initialiseUI();
+ }
+
+ private void initialiseUI() {
+ // get content pane
+ Container contentPane = this.getContentPane();
+
+ // set up layout
+ contentPane.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ // add all components
+ JLabel lInfo = new JLabel("Please type in you comment:");
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.insets = new Insets(10, 10, 5, 10);
+ contentPane.add(lInfo, c);
+
+ this.taComment = new DialogTextArea(5, 35);
+ this.taComment.setLineWrap(true);
+ this.taComment.setWrapStyleWord(true);
+ this.taComment.addKeyListener(this);
+ this.taComment.addCaretListener(this);
+
+ JScrollPane spComment = new JScrollPane(this.taComment);
+ c.gridy = 1;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.insets = new Insets(0, 10, 0, 10);
+ contentPane.add(spComment, c);
+
+ this.bPost = new JButton("Post Comment");
+ this.bPost.setEnabled(false);
+ this.bPost.setDefaultCapable(true);
+ this.getRootPane().setDefaultButton(this.bPost);
+ this.bPost.addActionListener(this);
+ this.bPost.addKeyListener(this);
+ c.gridy = 2;
+ c.anchor = GridBagConstraints.EAST;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.insets = new Insets(10, 5, 10, 5);
+ contentPane.add(bPost, c);
+
+ this.bCancel = new JButton("Cancel");
+ this.bCancel.setPreferredSize(this.bPost.getPreferredSize());
+ this.bCancel.addActionListener(this);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 0.5;
+ contentPane.add(bCancel, c);
+
+ this.pack();
+ this.setMinimumSize(this.getPreferredSize());
+ this.setMaximumSize(this.getPreferredSize());
+ this.addComponentListener(this);
+ }
+
+ /**
+ * Opens up a modal dialog where the user can enter the comment text.
+ *
+ * Window is simply closed if 'Cancel' button is pressed; on pressing 'Post
+ * Comment' button the window will turn into 'waiting' state, post the comment
+ * and return the resulting XML document (which would contain the newly added
+ * comment) back to the caller.
+ *
+ * @return String value of the non-empty comment text to be sent to
+ * myExperiment or null if action was cancelled.
+ */
+ public String launchAddCommentDialogAndPostCommentIfRequired() {
+ // makes the 'add comment' dialog visible, then waits until it is closed;
+ // control returns to this method when the dialog window is disposed
+ this.setVisible(true);
+ return (strComment);
+ }
+
+ // *** Callback for ActionListener interface ***
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bPost)) {
+ // 'Post' button is not active when text in the comment field is blank,
+ // so if it was pressed, there should be some text typed in
+ this.strComment = this.taComment.getText();
+
+ // the window will stay visible, but should turn into 'waiting' state
+ final JRootPane rootPane = this.getRootPane();
+ final Container contentPane = this.getContentPane();
+ contentPane.remove(this.bPost);
+ contentPane.remove(this.bCancel);
+ if (this.lStatusMessage != null)
+ contentPane.remove(this.lStatusMessage);
+ this.taComment.setEditable(false);
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 2;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.NONE;
+ c.insets = new Insets(10, 5, 10, 5);
+ lStatusMessage = new JLabel("Posting your comment...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.CENTER);
+ contentPane.add(lStatusMessage, c);
+
+ // disable the (X) button (ideally, would need to remove it, but there's no way to do this)
+ this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+
+ // revalidate the window
+ this.pack();
+ this.validate();
+ this.repaint();
+
+ new Thread("Posting comment") {
+ public void run() {
+ // *** POST THE COMMENT ***
+ final ServerResponse response = myExperimentClient.postComment(resource, Util.stripAllHTML(strComment));
+ bPostingSuccessful = (response.getResponseCode() == HttpURLConnection.HTTP_OK);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // *** REACT TO POSTING RESULT ***
+ if (bPostingSuccessful) {
+ // comment posted successfully
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ taComment.setEnabled(false);
+ contentPane.remove(lStatusMessage);
+
+ c.insets = new Insets(10, 5, 5, 5);
+ lStatusMessage = new JLabel("Your comment was posted successfully", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("success_icon")), SwingConstants.LEFT);
+ contentPane.add(lStatusMessage, c);
+
+ bCancel.setText("OK");
+ bCancel.setDefaultCapable(true);
+ rootPane.setDefaultButton(bCancel);
+ c.insets = new Insets(5, 5, 10, 5);
+ c.gridy += 1;
+ contentPane.add(bCancel, c);
+
+ pack();
+ bCancel.requestFocusInWindow();
+ } else {
+ // posting wasn't successful, notify the user
+ // and provide an option to close window or start again
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ taComment.setEditable(true);
+ contentPane.remove(lStatusMessage);
+
+ c.insets = new Insets(10, 5, 5, 5);
+ lStatusMessage = new JLabel("Error occurred while posting comment: "
+ + Util.retrieveReasonFromErrorXMLDocument(response.getResponseBody()), new ImageIcon(MyExperimentPerspective.getLocalResourceURL("failure_icon")), SwingConstants.LEFT);
+ contentPane.add(lStatusMessage, c);
+
+ bPost.setText("Try again");
+ bPost.setToolTipText("Please review your comment before trying to post it again");
+ c.anchor = GridBagConstraints.EAST;
+ c.insets = new Insets(5, 5, 10, 5);
+ c.gridwidth = 1;
+ c.weightx = 0.5;
+ c.gridx = 0;
+ c.gridy += 1;
+ contentPane.add(bPost, c);
+ rootPane.setDefaultButton(bPost);
+
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 1;
+ bCancel.setPreferredSize(bPost.getPreferredSize());
+ contentPane.add(bCancel, c);
+
+ pack();
+ validate();
+ repaint();
+ }
+ }
+ });
+
+ }
+ }.start();
+ } else if (e.getSource().equals(this.bCancel)) {
+ // cleanup the comment if it wasn't posted successfully + simply close and destroy the window
+ if (!this.bPostingSuccessful)
+ this.strComment = null;
+ this.dispose();
+ }
+
+ }
+
+ // *** Callbacks for KeyListener interface ***
+ public void keyPressed(KeyEvent e) {
+ // if TAB was pressed in the text area, need to move keyboard focus
+ if (e.getSource().equals(this.taComment)
+ && e.getKeyCode() == KeyEvent.VK_TAB) {
+ if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) == KeyEvent.SHIFT_DOWN_MASK) {
+ // SHIFT + TAB move focus backwards
+ ((Component) e.getSource()).transferFocusBackward();
+ } else {
+ // TAB moves focus forward
+ ((Component) e.getSource()).transferFocus();
+ }
+ e.consume();
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ // not in use
+ }
+
+ public void keyTyped(KeyEvent e) {
+ // not in use
+ }
+
+ // *** Callback for CaretListener interface ***
+ public void caretUpdate(CaretEvent e) {
+ // check whether the 'Post' button should be available or not
+ this.bPost.setEnabled(this.taComment.getText().length() > 0);
+ }
+
+ // *** Callbacks for ComponentListener interface ***
+ public void componentShown(ComponentEvent e) {
+ // center this dialog box within the preview browser window
+ Util.centerComponentWithinAnother(this.pluginMainComponent.getPreviewBrowser(), this);
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ // not in use
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ // not in use
+ }
+
+ public void componentResized(ComponentEvent e) {
+ // not in use
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/AddRemoveFavouriteDialog.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/AddRemoveFavouriteDialog.java
new file mode 100644
index 0000000..563d2b8
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/AddRemoveFavouriteDialog.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Container;
+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.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.net.HttpURLConnection;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.ServerResponse;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class AddRemoveFavouriteDialog extends HelpEnabledDialog implements ActionListener, ComponentListener {
+ // CONSTANTS
+ protected static final int OPERATION_SUCCESSFUL = 1;
+ protected static final int OPERATION_CANCELLED = 0;
+ protected static final int OPERATION_FAILED = -1;
+
+ // components for accessing application's main elements
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // COMPONENTS
+ private JButton bAddRemoveFavourite;
+ private JButton bCancel;
+
+ // STORAGE
+ private final Resource resource; // a resource which is being favourited / removed from favourites
+ private final boolean bIsFavouriteBeingAdded;
+ private int iOperationStatus = OPERATION_CANCELLED;
+ private ServerResponse response = null;
+
+ public AddRemoveFavouriteDialog(JFrame owner, boolean isFavouriteAdded, Resource resource, MainComponent component, MyExperimentClient client, Logger logger) {
+ super(owner, isFavouriteAdded ? "Add to" : "Remove from"
+ + " favourites - \"" + resource.getTitle() + "\" "
+ + resource.getItemTypeName(), true);
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // parameters
+ this.bIsFavouriteBeingAdded = isFavouriteAdded;
+ this.resource = resource;
+
+ // set options of the 'add/remove favourite' dialog box
+ this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ //this.setIconImage(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("myexp_icon")).getImage());
+
+ this.initialiseUI();
+ }
+
+ private void initialiseUI() {
+ // get content pane
+ Container contentPane = this.getContentPane();
+
+ // set up layout
+ contentPane.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ // add all components
+ JLabel lInfo = new JLabel("<html><center>You are about to "
+ + (this.bIsFavouriteBeingAdded ? "add" : "remove") + " \""
+ + resource.getTitle() + "\" " + resource.getItemTypeName()
+ + (this.bIsFavouriteBeingAdded ? " to" : " from")
+ + " your favourites.<br><br>"
+ + "Do you want to proceed?</center></html>");
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.insets = new Insets(10, 10, 10, 10);
+ contentPane.add(lInfo, c);
+
+ if (this.bIsFavouriteBeingAdded) {
+ this.bAddRemoveFavourite = new JButton("Add to Favourites");
+ } else {
+ this.bAddRemoveFavourite = new JButton("Remove from Favourites");
+ }
+ this.bAddRemoveFavourite.setDefaultCapable(true);
+ this.bAddRemoveFavourite.addActionListener(this);
+
+ c.gridy = 1;
+ c.anchor = GridBagConstraints.EAST;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.insets = new Insets(5, 5, 10, 5);
+ contentPane.add(this.bAddRemoveFavourite, c);
+
+ this.bCancel = new JButton("Cancel");
+ this.bCancel.setPreferredSize(this.bAddRemoveFavourite.getPreferredSize());
+ this.bCancel.addActionListener(this);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ contentPane.add(bCancel, c);
+
+ this.pack();
+ this.getRootPane().setDefaultButton(this.bAddRemoveFavourite);
+ this.setMinimumSize(this.getPreferredSize());
+ this.setMaximumSize(this.getPreferredSize());
+ this.addComponentListener(this);
+ }
+
+ /**
+ * Makes the dialog for adding / removing an item from favourites visible.
+ * Based on the user actions, it might execute the adding / removing
+ * operation.
+ *
+ * @return Returns an integer value which represents status of the operation:
+ * {@value #OPERATION_SUCCESSFUL} - favourite item was added / removed
+ * successfully; {@value #OPERATION_CANCELLED} - the user has
+ * cancelled operation; {@value #OPERATION_FAILED} - failed while
+ * adding / removing favourite item;
+ */
+ public int launchAddRemoveFavouriteDialogAndPerformNecessaryActionIfRequired() {
+ this.setVisible(true);
+ return (this.iOperationStatus);
+ }
+
+ // *** Callback for ActionListener interface ***
+ public void actionPerformed(ActionEvent e) {
+
+ if (e.getSource().equals(this.bAddRemoveFavourite)) {
+ // the window will stay visible, but should turn into 'waiting' state
+ final Container contentPane = this.getContentPane();
+ contentPane.removeAll();
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.NONE;
+ c.insets = new Insets(10, 5, 10, 5);
+ JLabel lInfo = new JLabel(this.bIsFavouriteBeingAdded ? "Adding to favourites..." : "Removing from favourites...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.CENTER);
+ contentPane.add(lInfo, c);
+
+ // disable the (X) button (ideally, would need to remove it, but there's no way to do this)
+ this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+
+ // revalidate the window
+ this.pack();
+ this.validate();
+ this.repaint();
+
+ new Thread("Execute add / remove favourite operation") {
+ @Override
+ public void run() {
+ // *** DO THE REQUIRED ACTION ***
+ response = (bIsFavouriteBeingAdded ? myExperimentClient.addFavourite(resource) : myExperimentClient.deleteFavourite(resource));
+ iOperationStatus = (response.getResponseCode() == HttpURLConnection.HTTP_OK) ? OPERATION_SUCCESSFUL : OPERATION_FAILED;
+
+ if (iOperationStatus == OPERATION_SUCCESSFUL) {
+ // update local list of favourite items - no data sync with the API is required at this point
+ if (bIsFavouriteBeingAdded) {
+ myExperimentClient.getCurrentUser().getFavourites().add(resource);
+ } else {
+ myExperimentClient.getCurrentUser().getFavourites().remove(resource);
+ }
+ } else {
+ // operation has failed - this might have been due to outdated data which is stored locally;
+ // sync favourite data with the API so that the UI can show more relevant data
+ // (e.g. replace 'add to favourites' with 'remove from favourites' if this item is already
+ // added to favourites - probably via another interface: web or another instance of the plugin)
+ myExperimentClient.updateUserFavourites(myExperimentClient.getCurrentUser());
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // *** REACT TO RESULT ***
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ contentPane.removeAll();
+ c.insets = new Insets(10, 5, 5, 5);
+
+ if (iOperationStatus == OPERATION_SUCCESSFUL) {
+ // favourite was added / removed successfully
+ contentPane.add(new JLabel("Item has been successfully "
+ + (bIsFavouriteBeingAdded ? "added to" : "removed from")
+ + " your favourites", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("success_icon")), SwingConstants.LEFT), c);
+ } else {
+ // favourite wasn't added / removed - operation failed;
+ // display error message
+ contentPane.add(new JLabel("<html><center>Error occurred while "
+ + (bIsFavouriteBeingAdded ? "adding" : "removing")
+ + " the item "
+ + (bIsFavouriteBeingAdded ? "to" : "from")
+ + " your favourites:<br>"
+ + Util.retrieveReasonFromErrorXMLDocument(response.getResponseBody())
+ + "</center></html>", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("failure_icon")), SwingConstants.LEFT), c);
+ }
+
+ bCancel.setText("OK");
+ bCancel.setPreferredSize(null); // resets preferred size to the automatic one
+ bCancel.setDefaultCapable(true);
+ c.insets = new Insets(5, 5, 10, 5);
+ c.gridy += 1;
+ contentPane.add(bCancel, c);
+
+ pack();
+ repaint();
+
+ bCancel.requestFocusInWindow();
+ getRootPane().setDefaultButton(bCancel);
+ }
+ });
+ }
+ }.start();
+
+ } else if (e.getSource().equals(this.bCancel)) {
+ // simply close and destroy the window
+ this.dispose();
+ }
+ }
+
+ // *** Callbacks for ComponentListener interface ***
+ public void componentShown(ComponentEvent e) {
+ // center this dialog box within the preview browser window
+ Util.centerComponentWithinAnother(this.pluginMainComponent.getPreviewBrowser(), this);
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ // not in use
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ // not in use
+ }
+
+ public void componentResized(ComponentEvent e) {
+ // not in use
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ExampleWorkflowsPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ExampleWorkflowsPanel.java
new file mode 100644
index 0000000..d3a58a0
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ExampleWorkflowsPanel.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.ResourceListPanel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+public class ExampleWorkflowsPanel extends JPanel implements ActionListener, ChangeListener {
+
+ private static final String ACTION_REFRESH = "refresh_example_workflows";
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ private JLabel statusLabel;
+ private JButton refreshButton;
+
+ private List<Workflow> workflows = new ArrayList<Workflow>();
+
+ private ResourceListPanel workflowsListPanel;
+
+ public ExampleWorkflowsPanel(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ this.initialiseUI();
+ }
+
+ public void actionPerformed(ActionEvent event) {
+ if (ACTION_REFRESH.equals(event.getActionCommand())) {
+ this.refresh();
+ }
+ }
+
+ public void stateChanged(ChangeEvent event) {
+
+ }
+
+ public void clear() {
+ this.statusLabel.setText("");
+ this.workflowsListPanel.clear();
+ }
+
+ public void refresh() {
+ this.pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), "Fetching example workflows from myExperiment");
+ this.statusLabel.setText("");
+
+ // Make call to myExperiment API in a different thread
+ // (then use SwingUtilities.invokeLater to update the UI when ready).
+ new Thread("Refresh for ExampleWorkflowsPanel") {
+ public void run() {
+ logger.debug("Refreshing Example Workflows tab");
+
+ try {
+ workflows = myExperimentClient.getExampleWorkflows();
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ repopulate();
+ }
+ });
+ } catch (Exception ex) {
+ logger.error("Failed to refresh Example Workflows panel", ex);
+ }
+ }
+ }.start();
+
+ }
+
+ public void repopulate() {
+ logger.debug("Repopulating Example Workflows tab");
+
+ this.pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), null);
+ this.statusLabel.setText("This will contain example resources to"
+ + " help get you started in Taverna. Coming Soon.");
+ this.statusLabel.setText(this.workflows.size() + " example workflows found");
+
+ // cannot cast a list of subclass items into a list of superclass items -
+ // hence a new list is created
+ this.workflowsListPanel.setListItems(new ArrayList<Resource>(this.workflows));
+
+ this.revalidate();
+ }
+
+ private void initialiseUI() {
+ this.setLayout(new BorderLayout());
+
+ JPanel topPanel = new JPanel(new BorderLayout());
+ topPanel.setBorder(BorderFactory.createEtchedBorder());
+ this.statusLabel = new JLabel();
+ this.statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 7, 0, 0));
+ topPanel.add(this.statusLabel, BorderLayout.CENTER);
+ this.refreshButton = new JButton("Refresh", WorkbenchIcons.refreshIcon);
+ this.refreshButton.setActionCommand(ACTION_REFRESH);
+ this.refreshButton.addActionListener(this);
+ this.refreshButton.setToolTipText("Click this button to refresh the Example Workflows list");
+ topPanel.add(this.refreshButton, BorderLayout.EAST);
+ this.add(topPanel, BorderLayout.NORTH);
+
+ this.workflowsListPanel = new ResourceListPanel(this.pluginMainComponent, this.myExperimentClient, this.logger);
+ JScrollPane spExampleWorkflowList = new JScrollPane(this.workflowsListPanel);
+ spExampleWorkflowList.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ this.add(spExampleWorkflowList, BorderLayout.CENTER);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/HistoryBrowserTabContentPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/HistoryBrowserTabContentPanel.java
new file mode 100644
index 0000000..3dfd2db
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/HistoryBrowserTabContentPanel.java
@@ -0,0 +1,541 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Base64;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine.QuerySearchInstance;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira
+ */
+public class HistoryBrowserTabContentPanel extends JPanel implements ActionListener {
+ // CONSTANTS
+ public static final int DOWNLOADED_ITEMS_HISTORY_LENGTH = 50;
+ public static final int OPENED_ITEMS_HISTORY_LENGTH = 50;
+ public static final int UPLOADED_ITEMS_HISTORY_LENGTH = 50;
+ public static final int COMMENTED_ON_ITEMS_HISTORY_LENGTH = 50;
+
+ public static final int PREVIEWED_ITEMS_HISTORY = 0;
+ public static final int DOWNLOADED_ITEMS_HISTORY = 1;
+ public static final int OPENED_ITEMS_HISTORY = 2;
+ public static final int UPLOADED_ITEMS_HISTORY = 4;
+ public static final int COMMENTED_ON_ITEMS_HISTORY = 3;
+
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // STORAGE
+ private ArrayList<Resource> lDownloadedItems;
+ private ArrayList<Resource> lOpenedItems;
+ private ArrayList<Resource> lUploadedItems;
+ private ArrayList<Resource> lCommentedOnItems;
+
+ // COMPONENTS
+ private JPanel jpPreviewHistory;
+ private JPanel jpSearchHistory;
+ private JPanel jpTagSearchHistory;
+ private JPanel jpDownloadedItemsHistory;
+ private JPanel jpOpenedItemsHistory;
+ private JPanel jpUploadedItemsHistory;
+ private JPanel jpCommentedOnHistory;
+ private JSplitPane spMain;
+ private JClickableLabel jclPreviewHistory;
+ private JClickableLabel jclSearchHistory;
+ private JClickableLabel jclTagSearchHistory;
+ private JClickableLabel jclDownloadedItemsHistory;
+ private JClickableLabel jclOpenedItemsHistory;
+ private JClickableLabel jclUploadedItemsHistory;
+ private JClickableLabel jclCommentedOnHistory;
+ private JPanel jpPreviewHistoryBox;
+ private JPanel jpSearchHistoryBox;
+ private JPanel jpTagSearchHistoryBox;
+ private JPanel jpDownloadedItemsHistoryBox;
+ private JPanel jpOpenedItemsHistoryBox;
+ private JPanel jpUploadedItemsHistoryBox;
+ private JPanel jpCommentedOnHistoryBox;
+
+ @SuppressWarnings("unchecked")
+ public HistoryBrowserTabContentPanel(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // initialise downloaded items history
+ String strDownloadedItemsHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_DOWNLOADED_ITEMS_HISTORY);
+ if (strDownloadedItemsHistory != null) {
+ Object oDownloadedItemsHistory = Base64.decodeToObject(strDownloadedItemsHistory);
+ this.lDownloadedItems = (ArrayList<Resource>) oDownloadedItemsHistory;
+ } else {
+ this.lDownloadedItems = new ArrayList<Resource>();
+ }
+
+ // initialise opened items history
+ String strOpenedItemsHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_OPENED_ITEMS_HISTORY);
+ if (strOpenedItemsHistory != null) {
+ Object oOpenedItemsHistory = Base64.decodeToObject(strOpenedItemsHistory);
+ this.lOpenedItems = (ArrayList<Resource>) oOpenedItemsHistory;
+ } else {
+ this.lOpenedItems = new ArrayList<Resource>();
+ }
+
+ // initialise uploaded items history
+ String strUploadedItemsHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_UPLOADED_ITEMS_HISTORY);
+ if (strUploadedItemsHistory != null) {
+ Object oUploadedItemsHistory = Base64.decodeToObject(strUploadedItemsHistory);
+ this.lUploadedItems = (ArrayList<Resource>) oUploadedItemsHistory;
+ } else {
+ this.lUploadedItems = new ArrayList<Resource>();
+ }
+
+ // initialise history of the items that were commented on
+ String strCommentedItemsHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_COMMENTED_ITEMS_HISTORY);
+ if (strCommentedItemsHistory != null) {
+ Object oCommentedItemsHistory = Base64.decodeToObject(strCommentedItemsHistory);
+ this.lCommentedOnItems = (ArrayList<Resource>) oCommentedItemsHistory;
+ } else {
+ this.lCommentedOnItems = new ArrayList<Resource>();
+ }
+
+ this.initialiseUI();
+ this.refreshAllData();
+ }
+
+ private void confirmHistoryDelete(final int id, String strBoxTitle) {
+ if (JOptionPane.showConfirmDialog(null, "This will the "
+ + strBoxTitle.toLowerCase() + " list.\nDo you want to proceed?", "myExperiment Plugin - Confirmation Required", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
+ switch (id) {
+ case 1:
+ pluginMainComponent.getPreviewBrowser().clearPreviewHistory();
+ break;
+ case 2:
+ pluginMainComponent.getSearchTab().getSearchHistory().clear();
+ pluginMainComponent.getSearchTab().updateSearchHistory();
+ break;
+ case 3:
+ pluginMainComponent.getTagBrowserTab().getTagSearchHistory().clear();
+ break;
+ case 4:
+ clearDownloadedItemsHistory();
+ break;
+ case 5:
+ clearOpenedItemsHistory();
+ break;
+ case 6:
+ clearCommentedOnItemsHistory();
+ break;
+ case 7:
+ clearUploadedItemsHistory();
+ break;
+ }
+ refreshAllData();
+ }
+ }
+
+ private JPanel addSpecifiedPanel(final int id, final String strBoxTitle, JPanel jPanel) {
+ JPanel jpTemp = new JPanel();
+ jpTemp.setLayout(new BorderLayout());
+ jpTemp.add(generateContentBox(strBoxTitle, jPanel), BorderLayout.CENTER);
+ JButton bClear = new JButton("Clear " + strBoxTitle, WorkbenchIcons.deleteIcon);
+ bClear.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ confirmHistoryDelete(id, strBoxTitle);
+ }
+ });
+
+ jpTemp.add(bClear, BorderLayout.SOUTH);
+ jpTemp.setMinimumSize(new Dimension(500, 0));
+ return jpTemp;
+ }
+
+ private void initialiseUI() {
+ // create helper text
+ ShadedLabel lHelper = new ShadedLabel("All history sections are local to myExperiment plugin usage."
+ + " Detailed history of your actions on myExperiment is available in your profile on myExperiment.", ShadedLabel.BLUE);
+
+ // create all individual content holder panels
+ this.jpPreviewHistory = new JPanel();
+ this.jpTagSearchHistory = new JPanel();
+ this.jpSearchHistory = new JPanel();
+ this.jpDownloadedItemsHistory = new JPanel();
+ this.jpOpenedItemsHistory = new JPanel();
+ this.jpUploadedItemsHistory = new JPanel();
+ this.jpCommentedOnHistory = new JPanel();
+
+ // create sidebar
+ JPanel jpSidebar = new JPanel();
+ jpSidebar.setLayout(new BoxLayout(jpSidebar, BoxLayout.Y_AXIS));
+ Border border = BorderFactory.createEmptyBorder(5, 3, 10, 3);
+ jclPreviewHistory = new JClickableLabel("Previewed Items", "preview_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclPreviewHistory.setBorder(border);
+ jpSidebar.add(jclPreviewHistory);
+
+ jclSearchHistory = new JClickableLabel("Search History", "search_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclSearchHistory.setBorder(border);
+ jpSidebar.add(jclSearchHistory);
+
+ jclTagSearchHistory = new JClickableLabel("Tag Searches Made", "tag_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclTagSearchHistory.setBorder(border);
+ jpSidebar.add(jclTagSearchHistory);
+
+ jclDownloadedItemsHistory = new JClickableLabel("Downloaded Items", "downloads_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclDownloadedItemsHistory.setBorder(border);
+ jpSidebar.add(jclDownloadedItemsHistory);
+
+ jclOpenedItemsHistory = new JClickableLabel("Opened Items", "opened_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclOpenedItemsHistory.setBorder(border);
+ jpSidebar.add(jclOpenedItemsHistory);
+
+ jclUploadedItemsHistory = new JClickableLabel("Updated Items", "uploaded_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclUploadedItemsHistory.setBorder(border);
+ jpSidebar.add(jclUploadedItemsHistory);
+
+ jclCommentedOnHistory = new JClickableLabel("Items Commented On", "comments_history", this, WorkbenchIcons.editIcon, SwingConstants.LEFT, "tooltip");
+ jclCommentedOnHistory.setBorder(border);
+ jpSidebar.add(jclCommentedOnHistory);
+
+ JPanel jpSidebarContainer = new JPanel();
+ jpSidebarContainer.add(jpSidebar, BorderLayout.NORTH);
+ JScrollPane spSidebar = new JScrollPane(jpSidebarContainer);
+ spSidebar.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+ spSidebar.setMinimumSize(new Dimension(245, 0));
+ spSidebar.setMaximumSize(new Dimension(300, 0));
+
+ // create standard boxes for each content holder panels
+ // only one of these will hold the right hand side of spMain at any given time
+ // arg 1: is the ID, which will be used by confirmHistoryDelete() to decide
+ // how to delete the history item
+ jpPreviewHistoryBox = addSpecifiedPanel(1, "Items you have previewed", jpPreviewHistory);
+ jpSearchHistoryBox = addSpecifiedPanel(2, "Terms you have searched for", jpSearchHistory);
+ jpTagSearchHistoryBox = addSpecifiedPanel(3, "Tags searches you have made", jpTagSearchHistory);
+ jpDownloadedItemsHistoryBox = addSpecifiedPanel(4, "Items you have downloaded", jpDownloadedItemsHistory);
+ jpOpenedItemsHistoryBox = addSpecifiedPanel(5, "Workflows you have opened in Taverna", jpOpenedItemsHistory);
+ jpCommentedOnHistoryBox = addSpecifiedPanel(6, "Items you have commented on in myExperiment", jpCommentedOnHistory);
+ jpUploadedItemsHistoryBox = addSpecifiedPanel(7, "Items you have updated on myExperiment", jpUploadedItemsHistory);
+
+ // put everything together
+ spMain = new JSplitPane();
+ spMain.setLeftComponent(spSidebar);
+ spMain.setRightComponent(jpPreviewHistoryBox);
+
+ spMain.setOneTouchExpandable(true);
+ spMain.setDividerLocation(247);
+ spMain.setDoubleBuffered(true);
+
+ // spMyStuff will be the only component in the Panel
+ this.setLayout(new BorderLayout());
+ this.add(spMain);
+ this.add(lHelper, BorderLayout.NORTH);
+
+ }
+
+ public ArrayList<Resource> getDownloadedItemsHistoryList() {
+ return (this.lDownloadedItems);
+ }
+
+ public void clearDownloadedItemsHistory() {
+ this.lDownloadedItems.clear();
+ }
+
+ public ArrayList<Resource> getOpenedItemsHistoryList() {
+ return (this.lOpenedItems);
+ }
+
+ public ArrayList<Resource> getUploadedItemsHistoryList() {
+ return (this.lUploadedItems);
+ }
+
+ public void clearOpenedItemsHistory() {
+ this.lOpenedItems.clear();
+ }
+
+ public void clearUploadedItemsHistory() {
+ this.lUploadedItems.clear();
+ }
+
+ public ArrayList<Resource> getCommentedOnItemsHistoryList() {
+ return (this.lCommentedOnItems);
+ }
+
+ public void clearCommentedOnItemsHistory() {
+ this.lCommentedOnItems.clear();
+ }
+
+ /**
+ * Used to refresh all boxes at a time (for example at launch time).
+ */
+ private void refreshAllData() {
+ this.refreshHistoryBox(PREVIEWED_ITEMS_HISTORY);
+ this.refreshHistoryBox(DOWNLOADED_ITEMS_HISTORY);
+ this.refreshHistoryBox(OPENED_ITEMS_HISTORY);
+ this.refreshHistoryBox(UPLOADED_ITEMS_HISTORY);
+ this.refreshHistoryBox(COMMENTED_ON_ITEMS_HISTORY);
+ this.refreshSearchHistory();
+ this.refreshTagSearchHistory();
+ }
+
+ /**
+ * This helper can be called externally to refresh the following history
+ * boxes: previewed items history, downloaded items history, opened items
+ * history and the history of items that were commented on.
+ *
+ * Is used inside ResourcePreviewBrowser and MainComponent every time a
+ * relevant action occurs. Also useful, when an option to 'clear preview
+ * history' is used in the Preferences window for. a particular history type.
+ */
+ public void refreshHistoryBox(int historyType) {
+ switch (historyType) {
+ case PREVIEWED_ITEMS_HISTORY:
+ this.jpPreviewHistory.removeAll();
+ populateHistoryBox(this.pluginMainComponent.getPreviewBrowser().getPreviewHistory(), this.jpPreviewHistory, "No items were previewed yet");
+ break;
+ case DOWNLOADED_ITEMS_HISTORY:
+ this.jpDownloadedItemsHistory.removeAll();
+ populateHistoryBox(this.lDownloadedItems, this.jpDownloadedItemsHistory, "No items were downloaded");
+ break;
+ case OPENED_ITEMS_HISTORY:
+ this.jpOpenedItemsHistory.removeAll();
+ populateHistoryBox(this.lOpenedItems, this.jpOpenedItemsHistory, "No items were opened");
+ break;
+ case UPLOADED_ITEMS_HISTORY:
+ this.jpUploadedItemsHistory.removeAll();
+ populateHistoryBox(this.lUploadedItems, this.jpUploadedItemsHistory, "No items were updated");
+ break;
+ case COMMENTED_ON_ITEMS_HISTORY:
+ this.jpCommentedOnHistory.removeAll();
+ populateHistoryBox(this.lCommentedOnItems, this.jpCommentedOnHistory, "You didn't comment on any items");
+ break;
+ }
+ }
+
+ /**
+ * Retrieves history data from a relevant list and populates the specified
+ * panel with it. All listed items will be resources that can be opened by
+ * Preview Browser.
+ */
+ private void populateHistoryBox(List<Resource> lHistory, JPanel jpPanelToPopulate, String strLabelIfNoItems) {
+ if (lHistory.size() > 0) {
+ for (int i = lHistory.size() - 1; i >= 0; i--) {
+ Resource r = lHistory.get(i);
+ if (r != null) {
+ JClickableLabel lResource = Util.generateClickableLabelFor(r, this.pluginMainComponent.getPreviewBrowser());
+ jpPanelToPopulate.add(lResource);
+ }
+ }
+ } else {
+ jpPanelToPopulate.add(Util.generateNoneTextLabel(strLabelIfNoItems));
+ }
+
+ // make sure that the component is updated after population
+ jpPanelToPopulate.revalidate();
+ jpPanelToPopulate.repaint();
+ }
+
+ /**
+ * This helper can be called externally to refresh the search history. Is used
+ * inside SearchTabContentPanel every time a new item is added to search
+ * history.
+ */
+ public void refreshSearchHistory() {
+ this.jpSearchHistory.removeAll();
+ populateSearchHistory();
+ }
+
+ /**
+ * Retrieves search history data from SearchTabContentPanel and populates the
+ * relevant panel.
+ */
+ private void populateSearchHistory() {
+ List<QuerySearchInstance> lSearchHistory = this.pluginMainComponent.getSearchTab().getSearchHistory();
+
+ // prepare layout
+ this.jpSearchHistory.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+
+ if (lSearchHistory.size() > 0) {
+ for (int i = lSearchHistory.size() - 1; i >= 0; i--) {
+ QuerySearchInstance qsiCurrent = lSearchHistory.get(i);
+ JClickableLabel jclCurrentEntryLabel = new JClickableLabel(qsiCurrent.getSearchQuery(), SearchTabContentPanel.SEARCH_FROM_HISTORY
+ + ":" + i, this, WorkbenchIcons.findIcon, SwingUtilities.LEFT, qsiCurrent.toString());
+ JLabel jlCurrentEntrySettings = new JLabel(qsiCurrent.detailsAsString());
+ jlCurrentEntrySettings.setBorder(BorderFactory.createEmptyBorder(3, 5, 0, 0));
+
+ JPanel jpCurrentSearchHistoryEntry = new JPanel();
+ jpCurrentSearchHistoryEntry.setLayout(new GridBagLayout());
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ jpCurrentSearchHistoryEntry.add(jclCurrentEntryLabel, c);
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 1.0;
+ jpCurrentSearchHistoryEntry.add(jlCurrentEntrySettings, c);
+
+ c.gridy = lSearchHistory.size() - 1 - i;
+ if (i == 0)
+ c.weighty = 1.0;
+ this.jpSearchHistory.add(jpCurrentSearchHistoryEntry, c);
+ }
+ } else {
+ c.weightx = 1.0;
+ c.weighty = 1.0;
+ this.jpSearchHistory.add(Util.generateNoneTextLabel(SearchResultsPanel.NO_SEARCHES_STATUS), c);
+ }
+
+ // make sure that the component is updated after population
+ this.jpSearchHistory.revalidate();
+ this.jpSearchHistory.repaint();
+ }
+
+ /**
+ * This helper can be called externally to refresh the tag search history. Is
+ * used inside TagBrowserTabContentPanel every time a new tag is searched for.
+ */
+ public void refreshTagSearchHistory() {
+ this.jpTagSearchHistory.removeAll();
+ populateTagSearchHistory();
+ }
+
+ /**
+ * Retrieves tag search history data from Tag Browser and populates the
+ * relevant panel.
+ */
+ private void populateTagSearchHistory() {
+ List<Tag> lTagSearchHistory = this.pluginMainComponent.getTagBrowserTab().getTagSearchHistory();
+
+ if (lTagSearchHistory.size() > 0) {
+ for (int i = lTagSearchHistory.size() - 1; i >= 0; i--) {
+ Tag t = lTagSearchHistory.get(i);
+ JClickableLabel lTag = new JClickableLabel(t.getTagName(), "tag:"
+ + t.getTagName(), this, new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.TAG)), // HACK: after deserialization t.getItemType() return "Unknown" type
+ SwingConstants.LEFT, "Tag: " + t.getTagName());
+ this.jpTagSearchHistory.add(lTag);
+ }
+ } else {
+ this.jpTagSearchHistory.add(Util.generateNoneTextLabel("No searches by tags have been made yet"));
+ }
+
+ // make sure that the component is updated after population
+ this.jpTagSearchHistory.revalidate();
+ this.jpTagSearchHistory.repaint();
+ }
+
+ /**
+ * @param strBoxTitle
+ * Title of the content box.
+ * @param jpContentPanel
+ * JPanel which will be populated with history listing.
+ * @return Prepared JPanel with a border, title and jpContentPanel wrapped
+ * into a scroll pane.
+ */
+ private static JPanel generateContentBox(String strBoxTitle, JPanel jpContentPanel) {
+ // set layout for the content panel
+ jpContentPanel.setLayout(new BoxLayout(jpContentPanel, BoxLayout.Y_AXIS));
+
+ // wrap content panel into a standard scroll pane
+ JScrollPane spContent = new JScrollPane(jpContentPanel);
+ spContent.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
+ spContent.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ spContent.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ // create the actual box stub with a border which will contain the scroll pane
+ JPanel jpBox = new JPanel();
+ jpBox.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2), BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " "
+ + strBoxTitle + " ")));
+
+ jpBox.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1.0;
+ c.weighty = 1.0;
+ jpBox.add(spContent, c);
+
+ return (jpBox);
+ }
+
+ // *** Callback for ActionListener interface ***
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() instanceof JClickableLabel) {
+ if (e.getActionCommand().startsWith(SearchTabContentPanel.SEARCH_FROM_HISTORY)) {
+ // open search tab and start the chosen search
+ this.pluginMainComponent.getSearchTab().actionPerformed(e);
+ this.pluginMainComponent.getMainTabs().setSelectedComponent(this.pluginMainComponent.getSearchTab());
+ } else if (e.getActionCommand().startsWith("tag:")) {
+ // open tag browser tab and start the chosen tag search
+ this.pluginMainComponent.getTagBrowserTab().actionPerformed(e);
+ this.pluginMainComponent.getMainTabs().setSelectedComponent(this.pluginMainComponent.getTagBrowserTab());
+ } else if (e.getActionCommand().contains("history")) {
+ if (e.getActionCommand() == "preview_history")
+ spMain.setRightComponent(jpPreviewHistoryBox);
+ else if (e.getActionCommand() == "search_history")
+ spMain.setRightComponent(jpSearchHistoryBox);
+ else if (e.getActionCommand() == "tag_history")
+ spMain.setRightComponent(jpTagSearchHistoryBox);
+ else if (e.getActionCommand() == "downloads_history")
+ spMain.setRightComponent(jpDownloadedItemsHistoryBox);
+ else if (e.getActionCommand() == "opened_history")
+ spMain.setRightComponent(jpOpenedItemsHistoryBox);
+ else if (e.getActionCommand() == "uploaded_history")
+ spMain.setRightComponent(jpUploadedItemsHistoryBox);
+ else if (e.getActionCommand() == "comments_history")
+ spMain.setRightComponent(jpCommentedOnHistoryBox);
+ }
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/JClickableLabel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/JClickableLabel.java
new file mode 100644
index 0000000..53f901f
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/JClickableLabel.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.EventListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+
+public class JClickableLabel extends JLabel implements MouseListener
+{
+ // This will hold the data which is relevant to processing the 'click' event on this label
+ private String strData;
+
+ // This will hold a reference to ResourcePreviewBrowser instance that is supposed to process the clicks
+ // on JClickableLabels
+ private ActionListener clickHandler;
+
+
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler)
+ {
+ this(strLabel, strDataForAction, eventHandler, null);
+ }
+
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler, Icon icon)
+ {
+ this(strLabel, strDataForAction, eventHandler, icon, SwingUtilities.LEFT);
+ }
+
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler, Icon icon, int horizontalAlignment)
+ {
+ this(strLabel, strDataForAction, eventHandler, icon, horizontalAlignment, null);
+ }
+
+ /**
+ *
+ * @param strLabel Textual label that will be visible in the UI.
+ * @param strDataForAction Data that will be passed to eventHandler when click on the label is made.
+ * @param eventHandler ActionListener that will process clicks on this label.
+ * @param icon Icon to display in the label.
+ * @param horizontalAlignment This is one of SwingConstants: LEFT, CENTER, RIGHT, LEADING or TRAILING
+ * @param strTooltip Tooltip to show over the label - if none is provided (e.g. null value), the strLabel will be used as a tooltip.
+ */
+ public JClickableLabel(String strLabel, String strDataForAction, EventListener eventHandler, Icon icon, int horizontalAlignment, String strTooltip)
+ {
+ super(strLabel, icon, horizontalAlignment);
+
+ this.strData = strDataForAction;
+ this.clickHandler = (ActionListener)eventHandler;
+
+ // empty border at the top and bottom will simulate "line-spacing"
+ // (this is only needed when an icon is displayed)
+ if (icon != null) {
+ this.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0));
+ }
+
+ // the tooltip for now only shows the full label text
+ this.setToolTipText(strTooltip == null ? strLabel : strTooltip);
+ this.setForeground(Color.BLUE);
+ this.addMouseListener(this);
+ }
+
+
+ /* This class extends JLabel, so it can't extend MouseAdapter;
+ * therefore, empty methods will be added for not useful callbacks
+ * from the MouseListener interface.
+ */
+ public void mouseClicked(MouseEvent e)
+ {
+ // call 'actionPerfermed' method on the clickHandler instance that was supplied
+ // on creation of the JClickableLabel instance
+ this.clickHandler.actionPerformed(new ActionEvent(this, e.getID(), this.strData));
+ }
+
+ public void mouseEntered(MouseEvent e)
+ {
+ this.setCursor( Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ) ) ;
+ }
+
+ public void mouseExited(MouseEvent e)
+ {
+ this.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) ) ;
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ // do nothing
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ // do nothing
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponent.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponent.java
new file mode 100644
index 0000000..f4b6ace
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponent.java
@@ -0,0 +1,645 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.io.ByteArrayInputStream;
+import java.awt.Desktop;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.PerspectiveRegistry;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.importworkflow.gui.ImportWorkflowWizard;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.serialization.xml.impl.XMLSerializationConstants;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira, Jiten Bhagat
+ */
+public final class MainComponent extends JPanel implements UIComponentSPI, ChangeListener {
+ // myExperiment client, logger and the stylesheet will be made available
+ // throughout the whole perspective
+ private MyExperimentClient myExperimentClient;
+ private final Logger logger = Logger.getLogger(MainComponent.class);
+ private final StyleSheet css;
+ private final ResourcePreviewFactory previewFactory;
+ private final ResourcePreviewBrowser previewBrowser;
+
+ // components of the perspective
+ private JTabbedPane tpMainTabs;
+ private MyStuffTabContentPanel pMyStuffContainer;
+ private ExampleWorkflowsPanel pExampleWorkflows;
+ private TagBrowserTabContentPanel pTagBrowser;
+ private SearchTabContentPanel pSearchTab;
+ private HistoryBrowserTabContentPanel pHistoryBrowserTab;
+ private PluginStatusBar pStatusBar;
+ private PluginPreferencesDialog jdPreferences;
+
+ public static MainComponent MAIN_COMPONENT;
+ public static MyExperimentClient MY_EXPERIMENT_CLIENT;
+ public static Logger LOGGER;
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public MainComponent(EditManager editManager, FileManager fileManager) {
+ super();
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+
+ // create and initialise myExperiment client
+ try {
+ this.myExperimentClient = new MyExperimentClient(logger);
+ } catch (Exception e) {
+ this.logger.error("Couldn't initialise myExperiment client");
+ }
+
+ // x, y, z ARE NOT USED ANYWHERE ELSE
+ // HACK TO BE ABLE TO GET THE REFS FROM TAVERNA'S PREFERENCE PANEL
+ // TODO: refactor code for all the other classes to utilise the class
+ // vars
+ MainComponent x = this;
+ MAIN_COMPONENT = x;
+
+ MyExperimentClient y = this.myExperimentClient;
+ MY_EXPERIMENT_CLIENT = y;
+
+ Logger z = this.logger;
+ LOGGER = z;
+
+ // components to generate and display previews
+ previewFactory = new ResourcePreviewFactory(this, myExperimentClient, logger);
+ previewBrowser = new ResourcePreviewBrowser(this, myExperimentClient, logger, fileManager);
+
+ this.css = new StyleSheet();
+ this.css.importStyleSheet(MyExperimentPerspective.getLocalResourceURL("css_stylesheet"));
+ logger.debug("Stylesheet loaded: \n" + this.css.toString());
+
+ // check if values for default tabs are set, if not - set defaults;
+ // NB! This has to be done before initialising UI
+ if (myExperimentClient.getSettings().getProperty(
+ MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB) == null)
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB, "3"); // SEARCH
+ if (myExperimentClient.getSettings().getProperty(
+ MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB) == null)
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB, "0"); // STUFF
+
+ initialisePerspectiveUI();
+
+ // HACK for a weird stylesheet bug (where the first thing to use the
+ // stylesheet doesn't actually get the styles)
+ // NB! This has to be located after all ShadedLabels were initialized to
+ // prevent bad layout in them
+ HTMLEditorKit kit = new StyledHTMLEditorKit(this.css);
+
+ // determine which shutdown operations to use
+ if (Util.isRunningInTaverna()) {
+ // register the current instance of main component with the
+ // myExperiment
+ // perspective; this will be used later on when shutdown operation
+ // needs
+ // to be performed - e.g. this aids ShutdownSPI to find the running
+ // instance of the plugin
+ for (PerspectiveSPI perspective : PerspectiveRegistry.getInstance().getPerspectives()) {
+ if (perspective.getText().equals(MyExperimentPerspective.PERSPECTIVE_NAME)) {
+ ((MyExperimentPerspective) perspective).setMainComponent(this);
+ break;
+ }
+ }
+ }
+
+ // Do the rest in a separate thread to avoid hanging the GUI.
+ // Remember to use SwingUtilities.invokeLater to update the GUI
+ // directly.
+ new Thread("Data initialisation for Taverna 2 - myExperiment plugin") {
+ @Override
+ public void run() {
+ // load the data into the plugin
+ initialiseData();
+ }
+ }.start();
+
+ }
+
+ public ImageIcon getIcon() {
+ return WorkbenchIcons.databaseIcon;
+ }
+
+ @Override
+ public String getName() {
+ return "myExperiment Perspective Main Component";
+ }
+
+ public void onDisplay() {
+ }
+
+ public void onDispose() {
+ }
+
+ public MyExperimentClient getMyExperimentClient() {
+ return this.myExperimentClient;
+ }
+
+ public Logger getLogger() {
+ return this.logger;
+ }
+
+ public StyleSheet getStyleSheet() {
+ return this.css;
+ }
+
+ public PluginStatusBar getStatusBar() {
+ return this.pStatusBar;
+ }
+
+ public PluginPreferencesDialog getPreferencesDialog() {
+ return this.jdPreferences;
+ }
+
+ public ResourcePreviewFactory getPreviewFactory() {
+ return this.previewFactory;
+ }
+
+ public ResourcePreviewBrowser getPreviewBrowser() {
+ return this.previewBrowser;
+ }
+
+ public HistoryBrowserTabContentPanel getHistoryBrowser() {
+ return this.pHistoryBrowserTab;
+ }
+
+ public JTabbedPane getMainTabs() {
+ return (this.tpMainTabs);
+ }
+
+ public MyStuffTabContentPanel getMyStuffTab() {
+ return (this.pMyStuffContainer);
+ }
+
+ public ExampleWorkflowsPanel getExampleWorkflowsTab() {
+ return (this.pExampleWorkflows);
+ }
+
+ public TagBrowserTabContentPanel getTagBrowserTab() {
+ return (this.pTagBrowser);
+ }
+
+ public SearchTabContentPanel getSearchTab() {
+ return (this.pSearchTab);
+ }
+
+ private void initialisePerspectiveUI() {
+ // HACK: this is required to prevent some labels from having white
+ // non-transparent background
+ ShadedLabel testLabel = new ShadedLabel("test", ShadedLabel.BLUE);
+
+ // create instances of individual components
+ // (NB! Status bar needs to be initialised first, so that it is
+ // available to
+ // other components immediately!)
+ this.pStatusBar = new PluginStatusBar(this, myExperimentClient, logger);
+ this.pMyStuffContainer = new MyStuffTabContentPanel(this, myExperimentClient, logger, fileManager);
+ this.pExampleWorkflows = new ExampleWorkflowsPanel(this, myExperimentClient, logger);
+ this.pTagBrowser = new TagBrowserTabContentPanel(this, myExperimentClient, logger);
+ this.pSearchTab = new SearchTabContentPanel(this, myExperimentClient, logger);
+ this.pHistoryBrowserTab = new HistoryBrowserTabContentPanel(this, myExperimentClient,
+ logger);
+
+ // add the required ones into the main tabs
+ this.tpMainTabs = new JTabbedPane();
+ this.tpMainTabs.add("My Stuff", this.pMyStuffContainer);
+ // TODO: implement the starter pack
+ this.tpMainTabs.add("Starter Pack", this.pExampleWorkflows);
+ this.tpMainTabs.add("Tag Browser", this.pTagBrowser);
+ this.tpMainTabs.add("Search", this.pSearchTab);
+ this.tpMainTabs.add("Local History", this.pHistoryBrowserTab);
+
+ // add main tabs and the status bar into the perspective
+ this.setLayout(new BorderLayout());
+ this.add(this.tpMainTabs, BorderLayout.CENTER);
+ this.add(this.pStatusBar, BorderLayout.SOUTH);
+
+ // add listener to TabbedPane, so that the app "knows" when some tab was
+ // opened
+ this.tpMainTabs.addChangeListener(this);
+
+ // initialise the preferences dialog
+ /*
+ * NB! this has to be done after all tabs were created (Preview Browser
+ * is used as an owner of the preferences dialog because Preview Browser
+ * is the only JFrame in the application - in Java 1.5 it is only
+ * possible to set an icon to a JFrame and all 'children' dialog of it
+ * get the same icon - so essentially, this is only to set the
+ * myExperiment logo as an icon of preferences dialog.)
+ */
+ this.jdPreferences = new PluginPreferencesDialog(this.getPreviewBrowser(), this,
+ myExperimentClient, logger);
+ }
+
+ private void initialiseData() {
+ this.logger.debug("Initialising myExperiment Perspective data");
+
+ // check if 'auto-login' is required (NB! This requires the BASE_URL to
+ // be
+ // set correctly!)
+ Object oAutoLogin = this.myExperimentClient.getSettings().get(
+ MyExperimentClient.INI_AUTO_LOGIN);
+ if (oAutoLogin != null && oAutoLogin.equals("true")) {
+ this.getStatusBar().setStatus(this.getMyStuffTab().getClass().getName(),
+ "Performing autologin");
+ this.myExperimentClient.doLoginFromStoredCredentials();
+ this.getStatusBar().setStatus(this.getMyStuffTab().getClass().getName(),
+ "Autologin finished. Fetching user data");
+ }
+
+ // NB! This should only be done if the user is logged in -
+ // otherwise this component simply doesn't exist
+ // this.pMyStuffContainer.spMyStuff.setDividerLocation(0.3);
+
+ // load data into all tabs
+ this.pMyStuffContainer.createAndInitialiseInnerComponents();
+ if (this.myExperimentClient.isLoggedIn()) {
+ // set the default tab for logged in user (e.g. as a consequence of
+ // auto-login)
+ tpMainTabs.setSelectedIndex(Integer.parseInt(myExperimentClient.getSettings()
+ .getProperty(MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB)));
+
+ // auto-login was successful - can display user tags
+ // (no need to refresh this cloud on its own, because the whole tab
+ // is refreshed immediately after)
+ this.pTagBrowser.setMyTagsShown(true);
+ } else {
+ // set the default tab for anonymous user (auto-login failed or
+ // wasn't
+ // chosen)
+ tpMainTabs.setSelectedIndex(Integer.parseInt(myExperimentClient.getSettings()
+ .getProperty(MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB)));
+ }
+
+ this.pExampleWorkflows.refresh();
+ this.pTagBrowser.refresh();
+ }
+
+ public void stateChanged(ChangeEvent e) {
+ // invoked when a tab is opened
+ if (e.getSource().equals(this.tpMainTabs)) {
+ this.getStatusBar().displayStatus(
+ this.getMainTabs().getSelectedComponent().getClass().getName());
+ }
+ }
+
+ // ************** ACTIONS ***************
+ public class PreviewResourceAction extends AbstractAction {
+ private int iResourceType = Resource.UNKNOWN;
+ private String strResourceURI = "";
+
+ public PreviewResourceAction(int iResourceType, String strResourceURI) {
+ putValue(SMALL_ICON, WorkbenchIcons.zoomIcon);
+ putValue(NAME, "Preview");
+ putValue(SHORT_DESCRIPTION,
+ "Preview this " + Resource.getResourceTypeName(iResourceType).toLowerCase()
+ + " in the Preview Browser window");
+
+ this.iResourceType = iResourceType;
+ this.strResourceURI = strResourceURI;
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ getPreviewBrowser()
+ .preview("preview:" + this.iResourceType + ":" + this.strResourceURI);
+ }
+ }
+
+ public class DownloadResourceAction extends AbstractAction {
+ private Resource resource = null;
+
+ public DownloadResourceAction(Resource resource) {
+ this(resource, true);
+ }
+
+ public DownloadResourceAction(Resource resource, boolean bShowButtonLabel) {
+ this.resource = resource;
+ String strResourceType = resource.getItemTypeName().toLowerCase();
+
+ // in either case the icon is the same; label might be displayed -
+ // based
+ // on the parameter
+ putValue(SMALL_ICON, WorkbenchIcons.saveIcon);
+ if (bShowButtonLabel)
+ putValue(NAME, "Download");
+
+ String strTooltip = "Downloading " + strResourceType + "s is currently not possible";
+ boolean bDownloadAllowed = false;
+ if (resource.isDownloadable()) {
+ if (resource.isDownloadAllowed()) {
+ strTooltip = "Download this " + strResourceType + " and store it locally";
+ bDownloadAllowed = true;
+ } else {
+ strTooltip = "You don't have permissions to download this " + strResourceType;
+ }
+ }
+
+ setEnabled(bDownloadAllowed);
+ putValue(SHORT_DESCRIPTION, strTooltip);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ try {
+ Desktop.getDesktop().browse(new URI(resource.getResource() + "/download"));
+
+ // update downloaded items history making sure that:
+ // - there's only one occurrence of this item in the history;
+ // - if this item was in the history before, it is moved to the
+ // 'top'
+ // now;
+ // - predefined history size is not exceeded
+ getHistoryBrowser().getDownloadedItemsHistoryList().remove(resource);
+ getHistoryBrowser().getDownloadedItemsHistoryList().add(resource);
+ if (getHistoryBrowser().getDownloadedItemsHistoryList().size() > HistoryBrowserTabContentPanel.DOWNLOADED_ITEMS_HISTORY_LENGTH) {
+ getHistoryBrowser().getDownloadedItemsHistoryList().remove(0);
+ }
+
+ // now update the downloaded items history panel in 'History'
+ // tab
+ if (getHistoryBrowser() != null) {
+ getHistoryBrowser().refreshHistoryBox(
+ HistoryBrowserTabContentPanel.DOWNLOADED_ITEMS_HISTORY);
+ }
+ } catch (Exception ex) {
+ logger.error("Failed while trying to open download URL in a standard browser; URL was: "
+ + resource.getURI() + "\nException was: " + ex);
+ }
+ }
+ }
+
+ public class LoadResourceInTavernaAction extends AbstractAction {
+ private final Resource resource;
+
+ public LoadResourceInTavernaAction(Resource resource) {
+ this(resource, true);
+ }
+
+ public LoadResourceInTavernaAction(Resource resource, boolean bShowButtonLabel) {
+ this.resource = resource;
+ String strResourceType = resource.getItemTypeName().toLowerCase();
+
+ putValue(SMALL_ICON, WorkbenchIcons.openIcon);
+ if (bShowButtonLabel)
+ putValue(NAME, "Open");
+
+ boolean bLoadingAllowed = false;
+ String strTooltip = "Loading " + strResourceType
+ + "s into Taverna Workbench is currently not possible";
+ if (resource.getItemType() == Resource.WORKFLOW) {
+ if (((Workflow) resource).isTavernaWorkflow()) {
+ if (resource.isDownloadAllowed()) {
+ // Taverna workflow and download allowed - can load in
+ // Taverna
+ bLoadingAllowed = true;
+ strTooltip = "Download and load this workflow in Design mode of Taverna Workbench";
+ } else {
+ strTooltip = "You don't have permissions to download this workflow, and thus to load into Taverna Workbench";
+ }
+ } else {
+ strTooltip = "Loading workflow of unsupported type into Taverna Workbench is not possible.";
+ }
+ }
+
+ setEnabled(bLoadingAllowed);
+ putValue(SHORT_DESCRIPTION, strTooltip);
+
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ // if the preview browser window is opened, hide it beneath the main
+ // window
+ if (getPreviewBrowser().isActive())
+ getPreviewBrowser().toBack();
+
+ final String strCallerTabClassName = getMainTabs().getSelectedComponent().getClass()
+ .getName();
+ getStatusBar().setStatus(strCallerTabClassName, "Downloading and opening workflow...");
+ logger.debug("Downloading and opening workflow from URI: " + resource.getURI());
+
+ new Thread("Download and open workflow") {
+ @Override
+ public void run() {
+ try {
+ Workflow w = myExperimentClient.fetchWorkflowBinary(resource.getURI());
+ ByteArrayInputStream workflowDataInputStream = new ByteArrayInputStream(
+ w.getContent());
+
+ FileType fileTypeType = (w.isTaverna1Workflow() ? new ScuflFileType()
+ : new T2FlowFileType());
+ Dataflow openDataflow = fileManager.openDataflow(fileTypeType,
+ workflowDataInputStream);
+ } catch (Exception e) {
+ javax.swing.JOptionPane.showMessageDialog(null,
+ "An error has occurred while trying to load a workflow from myExperiment.\n\n"
+ + e, "Error", JOptionPane.ERROR_MESSAGE);
+ logger.error(
+ "Failed to open connection to URL to download and open workflow, from myExperiment.",
+ e);
+ }
+
+ getStatusBar().setStatus(strCallerTabClassName, null);
+
+ // update opened items history making sure that:
+ // - there's only one occurrence of this item in the
+ // history;
+ // - if this item was in the history before, it is moved to
+ // the 'top'
+ // now;
+ // - predefined history size is not exceeded
+ getHistoryBrowser().getOpenedItemsHistoryList().remove(resource);
+ getHistoryBrowser().getOpenedItemsHistoryList().add(resource);
+ if (getHistoryBrowser().getOpenedItemsHistoryList().size() > HistoryBrowserTabContentPanel.OPENED_ITEMS_HISTORY_LENGTH) {
+ getHistoryBrowser().getOpenedItemsHistoryList().remove(0);
+ }
+
+ // now update the opened items history panel in 'History'
+ // tab
+ if (getHistoryBrowser() != null) {
+ getHistoryBrowser().refreshHistoryBox(
+ HistoryBrowserTabContentPanel.OPENED_ITEMS_HISTORY);
+ }
+ }
+ }.start();
+ }
+ }
+
+ public class ImportIntoTavernaAction extends AbstractAction {
+ private final Resource resource;
+ private boolean importAsNesting;
+
+ public ImportIntoTavernaAction(Resource r) {
+ this.resource = r;
+
+ String strResourceType = resource.getItemTypeName().toLowerCase();
+
+ putValue(SMALL_ICON, WorkbenchIcons.importIcon);
+ putValue(NAME, "Import");
+
+ boolean bLoadingAllowed = false;
+ String strTooltip = "Loading " + strResourceType
+ + "s into Taverna Workbench is currently not possible";
+ if (resource.getItemType() == Resource.WORKFLOW) {
+ if (((Workflow) resource).isTavernaWorkflow()) {
+ if (resource.isDownloadAllowed()) {
+ // Taverna workflow and download allowed - can load in
+ // Taverna
+ bLoadingAllowed = true;
+ strTooltip = "Import this workflow into one that is currently open in the Design mode of Taverna Workbench";
+ } else {
+ strTooltip = "You don't have permissions to download this workflow, and thus to load into Taverna Workbench";
+ }
+ } else {
+ strTooltip = "Loading workflow of unsupported type into Taverna Workbench is not possible.";
+ }
+ }
+
+ setEnabled(bLoadingAllowed);
+ putValue(SHORT_DESCRIPTION, strTooltip);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ // if the preview browser window is opened, hide it beneath the main
+ // window
+ if (getPreviewBrowser().isActive())
+ getPreviewBrowser().toBack();
+
+ ImportWorkflowWizard importWorkflowDialog = new ImportWorkflowWizard(
+ getPreviewBrowser(), editManager, fileManager);
+
+ Workflow w;
+ try {
+ w = MY_EXPERIMENT_CLIENT.fetchWorkflowBinary(resource.getURI());
+ } catch (Exception e) {
+ JOptionPane.showMessageDialog(null, "An error has occurred "
+ + "while trying to load a " + "workflow from myExperiment.\n\n" + e,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ LOGGER.error("Failed to open connection to URL to "
+ + "download and open workflow, from myExperiment.", e);
+ return;
+ }
+ ByteArrayInputStream workflowDataInputStream = new ByteArrayInputStream(w.getContent());
+ FileType fileTypeType = (w.isTaverna1Workflow() ? new MainComponent.ScuflFileType()
+ : new MainComponent.T2FlowFileType());
+ Dataflow toBeImported;
+ try {
+ toBeImported = fileManager.openDataflowSilently(fileTypeType,
+ workflowDataInputStream).getDataflow();
+ } catch (OpenException e) {
+ JOptionPane.showMessageDialog(null, "An error has occurred"
+ + " while trying to load a " + "workflow from myExperiment.\n\n" + e,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ LOGGER.error("Failed to" + " open connection to URL "
+ + "to download and open workflow, from myExperiment.", e);
+ return;
+ }
+ importWorkflowDialog.setCustomSourceDataflow(toBeImported, "From myExperiment: "
+ + resource.getTitle());
+ importWorkflowDialog.setSourceEnabled(false);
+ importWorkflowDialog.setVisible(true);
+
+ // update opened items history making sure that:
+ // - there's only one occurrence of this item in the history;
+ // - if this item was in the history before, it is moved to the
+ // 'top' now;
+ // - predefined history size is not exceeded
+ getHistoryBrowser().getOpenedItemsHistoryList().remove(resource);
+ getHistoryBrowser().getOpenedItemsHistoryList().add(resource);
+ if (getHistoryBrowser().getOpenedItemsHistoryList().size() > HistoryBrowserTabContentPanel.OPENED_ITEMS_HISTORY_LENGTH) {
+ getHistoryBrowser().getOpenedItemsHistoryList().remove(0);
+ }
+
+ // now update the opened items history panel in 'History' tab
+ if (getHistoryBrowser() != null)
+ getHistoryBrowser().refreshHistoryBox(
+ HistoryBrowserTabContentPanel.OPENED_ITEMS_HISTORY);
+ }
+
+ }
+
+ // *** FileTypes for opening workflows inside Taverna
+
+ public static class ScuflFileType extends FileType {
+
+ @Override
+ public String getDescription() {
+ return "Taverna 1 SCUFL workflow";
+ }
+
+ @Override
+ public String getExtension() {
+ return "xml";
+ }
+
+ @Override
+ public String getMimeType() {
+ return "application/vnd.taverna.scufl+xml";
+ }
+ }
+
+ public static class T2FlowFileType extends FileType {
+ @Override
+ public String getDescription() {
+ return "Taverna 2 workflow";
+ }
+
+ @Override
+ public String getExtension() {
+ return "t2flow";
+ }
+
+ @Override
+ public String getMimeType() {
+ // "application/vnd.taverna.t2flow+xml";
+ return XMLSerializationConstants.WORKFLOW_DOCUMENT_MIMETYPE;
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponentFactory.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponentFactory.java
new file mode 100644
index 0000000..65f8875
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponentFactory.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+public class MainComponentFactory implements UIComponentFactorySPI {
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+ public UIComponentSPI getComponent() {
+ return new MainComponent(editManager, fileManager);
+ }
+
+ public ImageIcon getIcon() {
+ return WorkbenchIcons.databaseIcon;
+ }
+
+ public String getName() {
+ return "myExperiment Main Component Factory";
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponentShutdownHook.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponentShutdownHook.java
new file mode 100644
index 0000000..2214d5e
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MainComponentShutdownHook.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import net.sf.taverna.t2.ui.perspectives.PerspectiveRegistry;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+
+public class MainComponentShutdownHook implements ShutdownSPI {
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ public int positionHint() {
+ // all custom plugins are suggested to return a value of > 100;
+ // this affects when in the termination process will this plugin
+ // be shutdown;
+ return 100;
+ }
+
+ public boolean shutdown() {
+ // find instance of main component of the running myExperiment perspective
+ MainComponent mainComponent = null;
+ for (PerspectiveSPI perspective : PerspectiveRegistry.getInstance().getPerspectives()) {
+ if (perspective instanceof MyExperimentPerspective) {
+ mainComponent = ((MyExperimentPerspective) perspective).getMainComponent();
+ break;
+ }
+ }
+
+ // if myExperiment perspective wasn't initialised, no shutdown operations are required / possible
+ if (mainComponent != null) {
+ this.setLinks(mainComponent, mainComponent.getMyExperimentClient(), mainComponent.getLogger());
+ logger.debug("Starting shutdown operations for myExperiment plugin");
+
+ try {
+ myExperimentClient.storeHistoryAndSettings();
+ } catch (Exception e) {
+ logger.error("Failed while serializing myExperiment plugin settings:\n"
+ + e);
+ }
+
+ logger.debug("myExperiment plugin shutdown is completed; terminated...");
+ }
+
+ // "true" means that shutdown operations are complete and Taverna can terminate
+ return true;
+ }
+
+ /**
+ * Sets up links of this class with the rest of the plugin.
+ */
+ public void setLinks(MainComponent component, MyExperimentClient client, Logger logger) {
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyExperimentPerspective.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyExperimentPerspective.java
new file mode 100644
index 0000000..1982b75
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyExperimentPerspective.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+public class MyExperimentPerspective implements PerspectiveSPI {
+ // CONSTANTS
+ // this is where all icons, stylesheet, etc are located
+ private static final String BASE_RESOURCE_PATH = "/net/sf/taverna/t2/ui/perspectives/myexperiment/";
+ public static final String PERSPECTIVE_NAME = "myExperiment";
+ public static final String PLUGIN_VERSION = "0.2beta";
+
+ // COMPONENTS
+ private MainComponent perspectiveMainComponent;
+ private boolean visible = true;
+
+ public ImageIcon getButtonIcon() {
+ URL iconURL = MyExperimentPerspective.getLocalResourceURL("myexp_icon16x16");
+ if (iconURL == null) {
+ return null;
+ } else {
+ return new ImageIcon(iconURL);
+ }
+ }
+
+ public InputStream getLayoutInputStream() {
+ return getClass().getResourceAsStream("myexperiment-perspective.xml");
+ }
+
+ public String getText() {
+ return PERSPECTIVE_NAME;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public int positionHint() {
+ // this determines position of myExperiment perspective in the
+ // bar with perspective buttons (currently makes it the last in the
+ // list)
+ return 30;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+
+ }
+
+ public void update(Element layoutElement) {
+ // Not sure what to do here
+ }
+
+ public void setMainComponent(MainComponent component) {
+ this.perspectiveMainComponent = component;
+ }
+
+ /**
+ * Returns the instance of the main component of this perspective.
+ */
+ public MainComponent getMainComponent() {
+ return this.perspectiveMainComponent;
+ }
+
+ // a single point in the plugin where all resources are referenced
+ public static URL getLocalResourceURL(String strResourceName) {
+ String strResourcePath = MyExperimentPerspective.BASE_RESOURCE_PATH;
+
+ if (strResourceName.equals("not_authorized_icon"))
+ strResourcePath += "denied.png";
+ if (strResourceName.equals("failure_icon"))
+ strResourcePath += "denied.png";
+ else if (strResourceName.equals("success_icon"))
+ strResourcePath += "tick.png";
+ else if (strResourceName.equals("spinner"))
+ strResourcePath += "ajax-loader.gif";
+ else if (strResourceName.equals("spinner_stopped"))
+ strResourcePath += "ajax-loader-still.gif";
+ else if (strResourceName.equals("external_link_small_icon"))
+ strResourcePath += "external_link_listing_small.png";
+ else if (strResourceName.equals("back_icon"))
+ strResourcePath += "arrow_left.png";
+ else if (strResourceName.equals("forward_icon"))
+ strResourcePath += "arrow_right.png";
+ else if (strResourceName.equals("refresh_icon"))
+ strResourcePath += "arrow_refresh.png";
+ else if (strResourceName.equals("favourite_icon"))
+ strResourcePath += "star.png";
+ else if (strResourceName.equals("add_favourite_icon"))
+ strResourcePath += "favourite_add.png";
+ else if (strResourceName.equals("delete_favourite_icon"))
+ strResourcePath += "favourite_delete.png";
+ else if (strResourceName.equals("destroy_icon"))
+ strResourcePath += "cross.png";
+ else if (strResourceName.equals("add_comment_icon"))
+ strResourcePath += "comment_add.png";
+ else if (strResourceName.equals("myexp_icon"))
+ strResourcePath += "myexp_icon.png";
+ else if (strResourceName.equals("myexp_icon16x16"))
+ strResourcePath += "myexp_icon16x16.png";
+ else if (strResourceName.equals("open_in_my_experiment_icon"))
+ strResourcePath += "open_in_myExperiment.png";
+ else if (strResourceName.equals("login_icon"))
+ strResourcePath += "login.png";
+ else if (strResourceName.equals("logout_icon"))
+ strResourcePath += "logout.png";
+ else if (strResourceName.equals("css_stylesheet"))
+ strResourcePath += "styles.css";
+ else {
+ throw new java.lang.IllegalArgumentException(
+ "Unknown myExperiment plugin resource requested; requested resource name was: "
+ + strResourceName);
+ }
+
+ // no exception was thrown, therefore the supplied resource name was
+ // recognised;
+ // return the local URL of that resource
+ return (MyExperimentPerspective.class.getResource(strResourcePath));
+ }
+
+ // a single point in the plugin where all resources' icons are referenced
+ public static URL getLocalIconURL(int iResourceType) {
+ String strResourcePath = MyExperimentPerspective.BASE_RESOURCE_PATH;
+
+ switch (iResourceType) {
+ case Resource.WORKFLOW:
+ strResourcePath += "workflow.png";
+ break;
+ case Resource.FILE:
+ strResourcePath += "file.png";
+ break;
+ case Resource.PACK:
+ strResourcePath += "pack.png";
+ break;
+ case Resource.PACK_EXTERNAL_ITEM:
+ strResourcePath += "remote_resource.png";
+ break;
+ case Resource.USER:
+ strResourcePath += "user.png";
+ break;
+ case Resource.GROUP:
+ strResourcePath += "group.png";
+ break;
+ case Resource.TAG:
+ strResourcePath += "tag_blue.png";
+ break;
+ default:
+ throw new java.lang.IllegalArgumentException(
+ "Unknown myExperiment plugin resource requested; requested resource name was: "
+ + Resource.getResourceTypeName(iResourceType));
+ }
+
+ // no exception was thrown, therefore the supplied resource name was
+ // recognised;
+ // return the local URL of that resource
+ return (MyExperimentPerspective.class.getResource(strResourcePath));
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffContributionsPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffContributionsPanel.java
new file mode 100644
index 0000000..e7ef972
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffContributionsPanel.java
@@ -0,0 +1,370 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.File;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Pack;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira, Jiten Bhagat
+ */
+public class MyStuffContributionsPanel extends JPanel implements ActionListener {
+ // CONSTANTS
+ private static final int SHADED_LABEL_HEIGHT = 20;
+ private static final int SECTION_VSPACING = 5;
+ private static final int SCROLL_PANE_PADDING = 3;
+ private static final int TOTAL_SECTION_VSPACING = SHADED_LABEL_HEIGHT
+ + SECTION_VSPACING + SCROLL_PANE_PADDING + 5; // the last literal is to cover all paddings / margins / etc
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ // MAIN COMPONENTS of the view
+ JPanel jpMyWorkflows;
+ JPanel jpMyFiles;
+ JPanel jpMyPacks;
+
+ // HELPER COMPONENTS
+ // these are the individual content listings for 'my workfows', 'my files' and 'my packs' ..
+ ResourceListPanel jpMyWorkflowsContent = null;
+ ResourceListPanel jpMyFilesContent = null;
+ ResourceListPanel jpMyPacksContent = null;
+
+ // .. these will be wrapped into individual scroll panes to ensure correct sizes
+ JScrollPane spMyWorkflowsContent = null;
+ JScrollPane spMyFilesContent = null;
+ JScrollPane spMyPacksContent = null;
+
+ // STORAGE
+ private ArrayList<JPanel> alVisiblePanels;
+ private ArrayList<JComponent[]> alVisiblePanelsWithHelperElements;
+
+ public MyStuffContributionsPanel(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ alVisiblePanels = new ArrayList<JPanel>();
+ alVisiblePanelsWithHelperElements = new ArrayList<JComponent[]>();
+
+ // check that settings for this panel were set correctly in the INI file
+ // (if any record is missing - assume that this section is visible)
+ // (this will ensure that these values can be used with no further validity checks
+ // across the plugin; this is because this method will be executed at start of the plugin)
+ if (myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_WORKFLOWS) == null) {
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_WORKFLOWS, new Boolean(true).toString());
+ }
+ if (myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_FILES) == null) {
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_FILES, new Boolean(true).toString());
+ }
+ if (myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_PACKS) == null) {
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_PACKS, new Boolean(true).toString());
+ }
+
+ // create and initialise the UI of MyStuff tab
+ initialiseUI();
+ initialiseData();
+ }
+
+ private void initialiseUI() {
+ JPanel jpMyWorkflowsContainer = new JPanel();
+ jpMyWorkflowsContainer.setBorder(BorderFactory.createEmptyBorder());
+ if (Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_WORKFLOWS))) {
+ // "My Workflows" panel
+ jpMyWorkflowsContainer.setBorder(BorderFactory.createEtchedBorder());
+ jpMyWorkflowsContainer.setLayout(new BorderLayout());
+
+ ShadedLabel l0 = new ShadedLabel("My Workflows", ShadedLabel.BLUE);
+ jpMyWorkflowsContainer.add(l0, BorderLayout.NORTH);
+
+ jpMyWorkflows = new JPanel();
+ jpMyWorkflows.setLayout(new BorderLayout());
+ jpMyWorkflows.setBackground(Color.WHITE);
+ jpMyWorkflows.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
+ jpMyWorkflows.add(new JLabel("Loading...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.CENTER));
+
+ jpMyWorkflowsContainer.add(jpMyWorkflows, BorderLayout.CENTER);
+ alVisiblePanels.add(jpMyWorkflows);
+ }
+
+ JPanel jpMyFilesContainer = new JPanel();
+ if (Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_FILES))) {
+ // "My Files" panel
+ jpMyFilesContainer.setBorder(BorderFactory.createEtchedBorder());
+ jpMyFilesContainer.setLayout(new BorderLayout());
+
+ ShadedLabel l1 = new ShadedLabel("My Files", ShadedLabel.BLUE);
+ jpMyFilesContainer.add(l1, BorderLayout.NORTH);
+
+ jpMyFiles = new JPanel();
+ jpMyFiles.setLayout(new BorderLayout());
+ jpMyFiles.setBackground(Color.WHITE);
+ jpMyFiles.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
+ jpMyFiles.add(new JLabel("Loading...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.CENTER));
+
+ jpMyFilesContainer.add(jpMyFiles, BorderLayout.CENTER);
+ alVisiblePanels.add(jpMyFiles);
+ }
+
+ JPanel jpMyPacksContainer = new JPanel();
+ if (Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_PACKS))) {
+ // "My Packs" panel
+ jpMyPacksContainer.setBorder(BorderFactory.createEtchedBorder());
+ jpMyPacksContainer.setLayout(new BorderLayout());
+
+ ShadedLabel l2 = new ShadedLabel("My Packs", ShadedLabel.BLUE);
+ jpMyPacksContainer.add(l2, BorderLayout.NORTH);
+
+ jpMyPacks = new JPanel();
+ jpMyPacks.setLayout(new BorderLayout());
+ jpMyPacks.setBackground(Color.WHITE);
+ jpMyPacks.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
+ jpMyPacks.add(new JLabel("Loading...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.CENTER));
+
+ jpMyPacksContainer.add(jpMyPacks, BorderLayout.CENTER);
+ alVisiblePanels.add(jpMyPacks);
+ }
+
+ // ..putting everything together
+ JPanel jpEverything = new JPanel();
+ jpEverything.setLayout(new GridBagLayout());
+
+ GridBagConstraints gbConstraints = new GridBagConstraints();
+ gbConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gbConstraints.fill = GridBagConstraints.BOTH;
+ gbConstraints.gridx = 0;
+ gbConstraints.weightx = 1;
+ gbConstraints.weighty = 1;
+ int index = 0;
+
+ gbConstraints.gridy = index++;
+ jpEverything.add(jpMyWorkflowsContainer, gbConstraints);
+
+ gbConstraints.gridy = index++;
+ jpEverything.add(jpMyFilesContainer, gbConstraints);
+
+ gbConstraints.gridy = index++;
+ jpEverything.add(jpMyPacksContainer, gbConstraints);
+
+ this.setLayout(new BorderLayout());
+ this.add(jpEverything, BorderLayout.NORTH);
+ }
+
+ private void initialiseData() {
+ // Make call to myExperiment API in a different thread
+ // (then use SwingUtilities.invokeLater to update the UI when ready).
+ new Thread("Loading data about contributions of current user.") {
+ @SuppressWarnings("unchecked")
+ public void run() {
+ logger.debug("Loading contributions data for current user");
+
+ try {
+ final ArrayList<Workflow> alWorkflowInstances = new ArrayList<Workflow>();
+ if (alVisiblePanels.contains(jpMyWorkflows)) {
+ boolean anyMore = true;
+ for (int page = 1; anyMore; page++) {
+ // fetch all user workflows
+ Document doc = myExperimentClient.getUserContributions(myExperimentClient.getCurrentUser(), Resource.WORKFLOW, Resource.REQUEST_SHORT_LISTING, page);
+ if (doc != null) {
+ List<Element> foundElements = doc.getRootElement().getChildren();
+ anyMore = !foundElements.isEmpty();
+ for (Element e : foundElements) {
+ Workflow wfCurrent = Workflow.buildFromXML(e, logger);
+ alWorkflowInstances.add(wfCurrent);
+ }
+ }
+ }
+ }
+
+ final ArrayList<File> alFileInstances = new ArrayList<File>();
+ if (alVisiblePanels.contains(jpMyFiles)) {
+ boolean anyMore = true;
+ for (int page = 1; anyMore; page++) {
+ // fetch all user files
+ Document doc = myExperimentClient.getUserContributions(myExperimentClient.getCurrentUser(), Resource.FILE, Resource.REQUEST_SHORT_LISTING, page);
+ if (doc != null) {
+ List<Element> foundElements = doc.getRootElement().getChildren();
+ anyMore = !foundElements.isEmpty();
+ for (Element e : foundElements) {
+ File fCurrent = File.buildFromXML(e, logger);
+ alFileInstances.add(fCurrent);
+ }
+ }
+ }
+ }
+
+ final ArrayList<Pack> alPackInstances = new ArrayList<Pack>();
+ if (alVisiblePanels.contains(jpMyPacks)) {
+ boolean anyMore = true;
+ for (int page = 1; anyMore; page++) {
+ // fetch all user packs
+ Document doc = myExperimentClient.getUserContributions(myExperimentClient.getCurrentUser(), Resource.PACK, Resource.REQUEST_SHORT_LISTING, page);
+ if (doc != null) {
+ List<Element> foundElements = doc.getRootElement().getChildren();
+ anyMore = !foundElements.isEmpty();
+ for (Element e : foundElements) {
+ Pack pCurrent = Pack.buildFromXML(e, myExperimentClient, logger);
+ alPackInstances.add(pCurrent);
+ }
+ }
+ }
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // now create views for all user contributions
+ if (alVisiblePanels.contains(jpMyWorkflows)) {
+ // .. workflows ..
+ jpMyWorkflowsContent = new ResourceListPanel(pluginMainComponent, myExperimentClient, logger);
+ jpMyWorkflowsContent.setFullSizeItemsList(false);
+ jpMyWorkflowsContent.setListItems(new ArrayList<Resource>(alWorkflowInstances));
+
+ spMyWorkflowsContent = new JScrollPane(jpMyWorkflowsContent);
+ spMyWorkflowsContent.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ jpMyWorkflows.removeAll();
+ jpMyWorkflows.setBackground(null); // return background to default colour
+ jpMyWorkflows.setBorder(BorderFactory.createEmptyBorder()); // remove border that was added prior to loading
+ jpMyWorkflows.add(spMyWorkflowsContent);
+
+ alVisiblePanelsWithHelperElements.add(new JComponent[] { jpMyWorkflows, spMyWorkflowsContent, jpMyWorkflowsContent });
+ }
+
+ if (alVisiblePanels.contains(jpMyFiles)) {
+ // .. files ..
+ jpMyFilesContent = new ResourceListPanel(pluginMainComponent, myExperimentClient, logger);
+ jpMyFilesContent.setFullSizeItemsList(false);
+ jpMyFilesContent.setListItems(new ArrayList<Resource>(alFileInstances));
+
+ spMyFilesContent = new JScrollPane(jpMyFilesContent);
+ spMyFilesContent.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ jpMyFiles.removeAll();
+ jpMyFiles.setBackground(null); // return background to default colour
+ jpMyFiles.setBorder(BorderFactory.createEmptyBorder()); // remove border that was added prior to loading
+ jpMyFiles.add(spMyFilesContent);
+
+ alVisiblePanelsWithHelperElements.add(new JComponent[] { jpMyFiles, spMyFilesContent, jpMyFilesContent });
+ }
+
+ if (alVisiblePanels.contains(jpMyPacks)) {
+ // .. packs ..
+ jpMyPacksContent = new ResourceListPanel(pluginMainComponent, myExperimentClient, logger);
+ jpMyPacksContent.setFullSizeItemsList(false);
+ jpMyPacksContent.setListItems(new ArrayList<Resource>(alPackInstances));
+
+ spMyPacksContent = new JScrollPane(jpMyPacksContent);
+ spMyPacksContent.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ jpMyPacks.removeAll();
+ jpMyPacks.setBackground(null); // return background to default colour
+ jpMyPacks.setBorder(BorderFactory.createEmptyBorder()); // remove border that was added prior to loading
+ jpMyPacks.add(spMyPacksContent);
+
+ alVisiblePanelsWithHelperElements.add(new JComponent[] { jpMyPacks, spMyPacksContent, jpMyPacksContent });
+ }
+
+ // now work out correct sizes for each section - the goal is to maximize the usage of the space on the page
+
+ int iFullAvailableHeight = getSize().height;
+ ArrayList<Integer> alIndexesToScale = new ArrayList<Integer>();
+ for (int i = 0; i < alVisiblePanelsWithHelperElements.size(); i++) {
+ JScrollPane spContent = (JScrollPane) alVisiblePanelsWithHelperElements.get(i)[1];
+ JPanel jpContent = (JPanel) alVisiblePanelsWithHelperElements.get(i)[2];
+
+ if ((jpContent.getPreferredSize().height + TOTAL_SECTION_VSPACING) < (iFullAvailableHeight / alVisiblePanels.size())) {
+ Dimension d = jpContent.getPreferredSize();
+ d.height += SCROLL_PANE_PADDING;
+ spContent.setPreferredSize(d);
+
+ iFullAvailableHeight -= (jpContent.getPreferredSize().height)
+ + TOTAL_SECTION_VSPACING;
+ } else
+ alIndexesToScale.add(i);
+ }
+
+ if (alIndexesToScale.size() > 0) {
+ Dimension d = new Dimension();
+
+ for (Integer i : alIndexesToScale) {
+ d.height = (iFullAvailableHeight / alIndexesToScale.size())
+ - TOTAL_SECTION_VSPACING;
+ if (d.height > ((JPanel) alVisiblePanelsWithHelperElements.get(i)[2]).getPreferredSize().height
+ + SCROLL_PANE_PADDING)
+ d.height = ((JPanel) alVisiblePanelsWithHelperElements.get(i)[2]).getPreferredSize().height
+ + SCROLL_PANE_PADDING;
+
+ JScrollPane spCurrent = (JScrollPane) alVisiblePanelsWithHelperElements.get(i)[1];
+ spCurrent.setPreferredSize(d);
+ }
+ }
+
+ // report that this component has been loaded
+ pluginMainComponent.getMyStuffTab().cdlComponentLoadingDone.countDown();
+
+ validate();
+ repaint();
+ }
+ });
+ } catch (Exception ex) {
+ logger.error("Failed to populate some panel in My Stuff tab (User's files, workflows or packs)", ex);
+ }
+ }
+ }.start();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ javax.swing.JOptionPane.showMessageDialog(null, "button clicked");
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffSidebarPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffSidebarPanel.java
new file mode 100644
index 0000000..79ef03c
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffSidebarPanel.java
@@ -0,0 +1,359 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.User;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira, Jiten Bhagat
+ */
+public class MyStuffSidebarPanel extends JPanel implements ActionListener {
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // main components of the SidebarPanel
+ private final JPanel jpMyProfileBox;
+ private final JPanel jpMyFriendsBox;
+ private final JPanel jpMyGroupsBox;
+ private final JPanel jpMyFavouritesBox;
+ private final JPanel jpMyTagsBox;
+ private JButton bLogout;
+ protected JButton bRefreshMyStuff;
+ private JButton bUpload;
+
+ // icons which are used in several places in the sidebar
+ private final ImageIcon iconUser;
+ private final ImageIcon iconLogout;
+ private final FileManager fileManager;
+
+ public MyStuffSidebarPanel(MainComponent component, MyExperimentClient client, Logger logger, FileManager fileManager) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ this.fileManager = fileManager;
+
+ // prepare icons
+ iconUser = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.USER));
+ iconLogout = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("logout_icon"));
+
+ // add elements of the sidebar
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints gbConstraints = new GridBagConstraints();
+ gbConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+ gbConstraints.weightx = 1;
+ gbConstraints.gridx = 0;
+
+ gbConstraints.gridy = 0;
+ jpMyProfileBox = createMyProfileBox();
+ this.add(jpMyProfileBox, gbConstraints);
+
+ gbConstraints.gridy = 1;
+ jpMyFriendsBox = createMyFriendsBox();
+ this.add(jpMyFriendsBox, gbConstraints);
+
+ gbConstraints.gridy = 2;
+ jpMyGroupsBox = createMyGroupsBox();
+ this.add(jpMyGroupsBox, gbConstraints);
+
+ gbConstraints.gridy = 3;
+ jpMyFavouritesBox = createMyFavouritesBox();
+ repopulateFavouritesBox();
+ this.add(jpMyFavouritesBox, gbConstraints);
+
+ gbConstraints.gridy = 4;
+ jpMyTagsBox = createMyTagsBox();
+ this.add(jpMyTagsBox, gbConstraints);
+
+ // report that this component has been loaded
+ pluginMainComponent.getMyStuffTab().cdlComponentLoadingDone.countDown();
+ }
+
+ // creates a JPanel displaying the currently logged in user, logout button,
+ // etc
+ private JPanel createMyProfileBox() {
+ // panel containing name and avatar
+ JPanel jpAvatar = new JPanel();
+ jpAvatar.setLayout(new BoxLayout(jpAvatar, BoxLayout.X_AXIS));
+
+ User currentUser = this.myExperimentClient.getCurrentUser();
+ ImageIcon userAvatar = currentUser.getAvatar();
+ JLabel jlUserAvatar = new JLabel("No Profile Picture Found");
+ if (userAvatar != null)
+ jlUserAvatar = new JLabel(Util.getResizedImageIcon(userAvatar, 80, 80));
+
+ jlUserAvatar.setAlignmentX(LEFT_ALIGNMENT);
+ jpAvatar.add(jlUserAvatar);
+
+ String name = "<html>";
+ for (int x = 0; x < currentUser.getName().split(" ").length; x++)
+ name += currentUser.getName().split(" ")[x] + "<br>";
+ name += "</html>";
+
+ JClickableLabel jclUserName = new JClickableLabel(name, "preview:"
+ + Resource.USER + ":" + currentUser.getURI(), pluginMainComponent.getPreviewBrowser(), this.iconUser);
+ jpAvatar.add(jclUserName);
+
+ // panel containing everything in the profile box
+ JPanel jpEverythingInProfileBox = new JPanel();
+ jpEverythingInProfileBox.setMaximumSize(new Dimension(1024, 0));
+ jpEverythingInProfileBox.setLayout(new BoxLayout(jpEverythingInProfileBox, BoxLayout.X_AXIS));
+
+ jpEverythingInProfileBox.add(jpAvatar);
+
+ // action buttons
+ bLogout = new JButton("Logout", iconLogout);
+ bLogout.addActionListener(this);
+
+ bRefreshMyStuff = new JButton("Refresh", WorkbenchIcons.refreshIcon);
+ bRefreshMyStuff.addActionListener(this.pluginMainComponent.getMyStuffTab());
+
+ bUpload = new JButton("Upload Workflow", WorkbenchIcons.upArrowIcon);
+ bUpload.addActionListener(this);
+
+ // panel for the buttons
+ JPanel jpButtons = new JPanel();
+ jpButtons.setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridwidth = 1;
+ gbc.gridy = 0;
+ gbc.anchor = GridBagConstraints.NORTH;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.gridx = 0;
+
+ jpButtons.add(bUpload, gbc);
+ gbc.gridy++;
+ jpButtons.add(bRefreshMyStuff, gbc);
+ gbc.gridy++;
+ jpButtons.add(bLogout, gbc);
+
+ jpEverythingInProfileBox.add(jpButtons);
+
+ jpEverythingInProfileBox.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " My Profile "), BorderFactory.createEmptyBorder(1, 8, 8, 5)));
+
+ return (jpEverythingInProfileBox);
+ }
+
+ // creates a JPanel that displays a list of all friends of the current user
+ private JPanel createMyFriendsBox() {
+ JPanel jpFriends = new JPanel();
+ jpFriends.setLayout(new BoxLayout(jpFriends, BoxLayout.Y_AXIS));
+
+ // iterate through all friends and add all to the panel
+ Iterator<HashMap<String, String>> iFriends = this.myExperimentClient.getCurrentUser().getFriends().iterator();
+ if (iFriends.hasNext()) {
+ while (iFriends.hasNext()) {
+ HashMap<String, String> hmCurFriend = iFriends.next();
+ jpFriends.add(new JClickableLabel(hmCurFriend.get("name"), "preview:"
+ + Resource.USER + ":" + hmCurFriend.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconUser));
+ }
+ } else {
+ // known not to have any friends
+ JLabel lNone = new JLabel("None");
+ lNone.setFont(lNone.getFont().deriveFont(Font.ITALIC));
+ lNone.setForeground(Color.GRAY);
+ jpFriends.add(lNone);
+ }
+
+ jpFriends.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " My Friends "), BorderFactory.createEmptyBorder(1, 8, 8, 5)));
+
+ return (jpFriends);
+ }
+
+ // generates a JPanel that displays a list of groups for current user
+ // when they are logged in
+ private JPanel createMyGroupsBox() {
+ JPanel jpGroups = new JPanel();
+
+ jpGroups.setLayout(new BoxLayout(jpGroups, BoxLayout.Y_AXIS));
+
+ // prepare the icon for groups
+ ImageIcon iconGroup = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.GROUP));
+
+ // iterate through all groups and add all to the panel
+ Iterator<HashMap<String, String>> iGroups = this.myExperimentClient.getCurrentUser().getGroups().iterator();
+ if (iGroups.hasNext()) {
+ while (iGroups.hasNext()) {
+ HashMap<String, String> hmCurGroup = iGroups.next();
+ jpGroups.add(new JClickableLabel(hmCurGroup.get("name"), "preview:"
+ + Resource.GROUP + ":" + hmCurGroup.get("uri"), pluginMainComponent.getPreviewBrowser(), iconGroup));
+ }
+ } else {
+ // known not to have any groups
+ JLabel lNone = new JLabel("None");
+ lNone.setFont(lNone.getFont().deriveFont(Font.ITALIC));
+ lNone.setForeground(Color.GRAY);
+ jpGroups.add(lNone);
+ }
+
+ jpGroups.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " My Groups "), BorderFactory.createEmptyBorder(1, 8, 8, 5)));
+
+ return (jpGroups);
+ }
+
+ // generates a JPanel that displays a list of favourite items for current user
+ // when they are logged in
+ private JPanel createMyFavouritesBox() {
+ JPanel jpFavourites = new JPanel();
+
+ jpFavourites.setLayout(new BoxLayout(jpFavourites, BoxLayout.Y_AXIS));
+ jpFavourites.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " My Favourites "), BorderFactory.createEmptyBorder(1, 8, 8, 5)));
+
+ return (jpFavourites);
+ }
+
+ public void repopulateFavouritesBox() {
+ this.jpMyFavouritesBox.removeAll();
+
+ // iterate through all favourites and add all to the panel
+ Iterator<Resource> iFavourites = this.myExperimentClient.getCurrentUser().getFavourites().iterator();
+ if (iFavourites.hasNext()) {
+ while (iFavourites.hasNext()) {
+ Resource rFavourite = iFavourites.next();
+ this.jpMyFavouritesBox.add(new JClickableLabel(rFavourite.getTitle(), "preview:"
+ + rFavourite.getItemType() + ":" + rFavourite.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(rFavourite.getItemType()))));
+ }
+ } else {
+ // known not to have any favourites
+ JLabel lNone = new JLabel("None");
+ lNone.setFont(lNone.getFont().deriveFont(Font.ITALIC));
+ lNone.setForeground(Color.GRAY);
+ this.jpMyFavouritesBox.add(lNone);
+ }
+ }
+
+ // creates a Panel that shows all tags of the current user
+ private JPanel createMyTagsBox() {
+ JPanel jpTags = new JPanel();
+ jpTags.setLayout(new BoxLayout(jpTags, BoxLayout.Y_AXIS));
+
+ // prepare the icon for tags
+ ImageIcon iconTag = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.TAG));
+
+ // iterate through all tags and add all to the panel
+ Iterator<HashMap<String, String>> iTags = this.myExperimentClient.getCurrentUser().getTags().iterator();
+ if (iTags.hasNext()) {
+ while (iTags.hasNext()) {
+ String strCurTag = iTags.next().get("name");
+ jpTags.add(new JClickableLabel(strCurTag, "tag:" + strCurTag, pluginMainComponent.getPreviewBrowser(), iconTag));
+ }
+ } else {
+ // known not to have any tags
+ JLabel lNone = new JLabel("None");
+ lNone.setFont(lNone.getFont().deriveFont(Font.ITALIC));
+ lNone.setForeground(Color.GRAY);
+ jpTags.add(lNone);
+ }
+
+ jpTags.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " My Tags "), BorderFactory.createEmptyBorder(1, 8, 8, 5)));
+
+ return (jpTags);
+ }
+
+ public JPanel getMyProfileBox() {
+ return (this.jpMyProfileBox);
+ }
+
+ // listener of button clicks in the sidebar
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(bLogout)) {
+ // logout button was clicked
+
+ try {
+ // "forget" login details
+ this.myExperimentClient.doLogout();
+ } catch (Exception ex) {
+ logger.error("Error while trying to logout from myExperiment, exception:\n"
+ + ex);
+ }
+
+ // repaint "myStuff" tab to display the login box again
+ this.pluginMainComponent.getStatusBar().setStatus(this.pluginMainComponent.getMyStuffTab().getClass().getName(), "Logging out");
+ this.pluginMainComponent.getMyStuffTab().createAndInitialiseInnerComponents();
+ this.pluginMainComponent.getMyStuffTab().revalidate();
+ this.pluginMainComponent.getMyStuffTab().repaint();
+ this.pluginMainComponent.getStatusBar().setStatus(this.pluginMainComponent.getMyStuffTab().getClass().getName(), null);
+ this.pluginMainComponent.getStatusBar().setCurrentUser(null);
+
+ // remove "My Tags" from the tags browser tab and rerun last searches (tag
+ // & keyword)
+ // so that any "private" search results won't get shown anymore
+ this.pluginMainComponent.getTagBrowserTab().setMyTagsShown(false);
+ this.pluginMainComponent.getTagBrowserTab().getTagSearchResultPanel().clear();
+ this.pluginMainComponent.getTagBrowserTab().rerunLastTagSearch();
+
+ this.pluginMainComponent.getSearchTab().getSearchResultPanel().clear();
+ this.pluginMainComponent.getSearchTab().rerunLastSearch();
+
+ // TODO: also, update another tabs, so that they don't display any 'private' content?
+ } else if (e.getSource().equals(this.bUpload)) {
+ JFrame containingFrame = (JFrame) SwingUtilities.windowForComponent(this);
+ // UploadWorkflowDialog uploadWorkflowDialog = new UploadWorkflowDialog(containingFrame);
+
+ // File workflowFile = null;
+ // JFileChooser jfsSelectFile = new JFileChooser();
+ // if (jfsSelectFile.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
+ // workflowFile = jfsSelectFile.getSelectedFile();
+ // else
+ // return;
+
+ UploadWorkflowDialog uploadWorkflowDialog = new UploadWorkflowDialog(containingFrame, true, fileManager);
+
+ if (uploadWorkflowDialog.launchUploadDialogAndPostIfRequired())
+ // true was returned so refresh the whole of the mystuff content panel
+ this.actionPerformed(new ActionEvent(this.bRefreshMyStuff, 0, ""));
+
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffTabContentPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffTabContentPanel.java
new file mode 100644
index 0000000..56e2dcf
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/MyStuffTabContentPanel.java
@@ -0,0 +1,342 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+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.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira, Jiten Bhagat
+ */
+public class MyStuffTabContentPanel extends JPanel implements ActionListener, KeyListener {
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // components that should be accessible from anywhere in this class
+ private JButton bLogin;
+ private JCheckBox cbLoginAutomatically;
+ private MyStuffSidebarPanel jpSidebar;
+ public JSplitPane spMyStuff;
+
+ // synchronisation latch to let main thread know that all component-creation
+ // threads have been finished
+ protected CountDownLatch cdlComponentLoadingDone;
+ int NUMBER_OF_SUBCOMPONENTS = 2;
+
+ // "return to" type of thing, so that after a certain action has been done,
+ // it is possible to switch to another tab in this tabbed view
+ protected JComponent cTabContentComponentToSwitchToAfterLogin = null;
+ private final FileManager fileManager;
+
+ public MyStuffTabContentPanel(MainComponent component, MyExperimentClient client, Logger logger, FileManager fileManager) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ this.fileManager = fileManager;
+ }
+
+ public void createAndInitialiseInnerComponents() {
+ // if there are any components, these will be removed
+ this.removeAll();
+ cdlComponentLoadingDone = new CountDownLatch(NUMBER_OF_SUBCOMPONENTS);
+
+ // based on the current status (logged in / anonymous user), decide which
+ // components to create and display
+ if (this.myExperimentClient.isLoggedIn()) {
+ jpSidebar = new MyStuffSidebarPanel(pluginMainComponent, myExperimentClient, logger, fileManager);
+ JPanel jpSidebarContainer = new JPanel();
+ jpSidebarContainer.setLayout(new BorderLayout());
+ jpSidebarContainer.add(jpSidebar, BorderLayout.NORTH);
+ JScrollPane spSidebar = new JScrollPane(jpSidebarContainer);
+ spSidebar.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+ spSidebar.setMinimumSize(new Dimension(jpSidebar.getMyProfileBox().getPreferredSize().width + 30, 0)); // +30
+ // --> 10 for padding and 10 for vertical scroll bar + 10 extra current
+ // user is logged in to myExperiment, display all personal data
+
+ spMyStuff = new JSplitPane();
+ spMyStuff.setLeftComponent(spSidebar);
+ spMyStuff.setRightComponent(new MyStuffContributionsPanel(pluginMainComponent, myExperimentClient, logger));
+ this.pluginMainComponent.getStatusBar().setCurrentUser(myExperimentClient.getCurrentUser().getName());
+
+ // set proportional sizes of the two panes as 30/70 percents of the total
+ // width of the SplitPane
+ // this can only be done after the SplitPane is made visible - hence the
+ // need for the listener below
+ pluginMainComponent.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentShown(ComponentEvent e) {
+ javax.swing.JOptionPane.showMessageDialog(null, "component shown");
+ // NB! This is only needed for use with test class, not when Taverna
+ // calls perspective!!
+ // the SplitPane wouldn't have loaded yet - wait until it does
+ try {
+ Thread.sleep(50);
+ } // 50ms is a tiny delay -- acceptable
+ catch (Exception ex) { /* do nothing */}
+
+ // set the proportions in the SplitPane
+ spMyStuff.setDividerLocation(400);
+ }
+ });
+
+ // make sure that both panes will grow/shrink at the same rate if the
+ // size of the whole SplitPane is changed by resizing the window
+ spMyStuff.setResizeWeight(0.3);
+ spMyStuff.setOneTouchExpandable(true);
+ spMyStuff.setDividerLocation(400);
+ spMyStuff.setDoubleBuffered(true);
+
+ // spMyStuff will be the only component in the Panel
+ this.setLayout(new BorderLayout());
+ this.add(spMyStuff);
+
+ // wait until two of the components finish loading and set the status to 'ready'
+ // (done in a new thread so that this doesn't freeze the plugin window)
+ new Thread("Waiting for myStuff data to load") {
+ @Override
+ public void run() {
+ try {
+ cdlComponentLoadingDone.await();
+ pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), null);
+ } catch (InterruptedException ex) { /* do nothing for now */
+ }
+ }
+ }.start();
+ } else { // NOT logged in
+ // reset status in case of unsuccessful autologin
+ this.pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), null);
+
+ // user isn't logged in, display login box only
+ JPanel jpLoginBoxContainer = new JPanel();
+ jpLoginBoxContainer.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ jpLoginBoxContainer.add(createLoginBox(), c);
+
+ // put everything together (welcome banner + login box)
+ this.setLayout(new BorderLayout());
+ this.add(new ShadedLabel("Welcome to the myExperiment plugin. Please note that you can still use other tabs even "
+ + "if you don't have a user profile yet!", ShadedLabel.BLUE), BorderLayout.NORTH);
+ this.add(jpLoginBoxContainer, BorderLayout.CENTER);
+ }
+ }
+
+ // generates JPanel containing a login box
+ private JPanel createLoginBox() {
+ JPanel jpLoginBox = new JPanel();
+ jpLoginBox.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(10, 10, 10, 10)));
+
+ jpLoginBox.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ // label "Login to myExp"
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.insets = new Insets(0, 0, 15, 0);
+ c.gridx = 0;
+ c.gridy = 0;
+ JLabel jlHeader = new JLabel("<html><b>Log in to myExperiment</b></html>");
+ jlHeader.setFont(jlHeader.getFont().deriveFont((float) 13.0));
+ jpLoginBox.add(jlHeader, c);
+
+ // set values
+ c.weightx = 1;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = new Insets(0, 0, 3, 0);
+ c.ipadx = 10;
+
+ // autologin checkbox and label
+ c.gridy++;
+ c.insets = new Insets(0, 0, 0, 3);
+ cbLoginAutomatically = new JCheckBox("Log in automatically (next time)");
+ cbLoginAutomatically.setBorder(BorderFactory.createEmptyBorder()); // makes sure that this is aligned with text fields above
+ cbLoginAutomatically.addActionListener(this);
+ cbLoginAutomatically.addKeyListener(this);
+ jpLoginBox.add(cbLoginAutomatically, c);
+
+ // login button
+ c.gridy++;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.insets = new Insets(10, 0, 0, 0);
+ bLogin = new JButton("Login", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("login_icon")));
+ bLogin.setDefaultCapable(true);
+ bLogin.addKeyListener(this);
+ bLogin.addActionListener(this);
+ jpLoginBox.add(bLogin, c);
+
+ // wrap contents into another panel to allow for some extra border around the contents
+ return (jpLoginBox);
+ }
+
+ public MyStuffSidebarPanel getSidebar() {
+ return (this.jpSidebar);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bLogin)) {
+ // "Login" button clicked
+ pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), "Logging in");
+
+ // Make call to myExperiment API in a different thread
+ // (then use SwingUtilities.invokeLater to update the UI when ready).
+ new Thread("Login to myExperiment") {
+ @Override
+ public void run() {
+ logger.debug("Logging in to myExperiment");
+
+ try {
+ // do the actual "logging in"
+ boolean bLoginSuccessful = myExperimentClient.doLogin();
+
+ // check if need to store the login credentials and settings
+ if (bLoginSuccessful) {
+ // store the settings anyway (for instance, to clear stored login/password
+ // - when the 'remember me' tick is not checked anymore);
+ // however, need to check whether to store login details or not
+
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_AUTO_LOGIN, new Boolean(cbLoginAutomatically.isSelected()).toString());
+ myExperimentClient.storeHistoryAndSettings();
+
+ // if logging in was successful, set the status to the start of fetching the data
+ pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), "Fetching user data");
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (myExperimentClient.isLoggedIn()) {
+ // login successful, change view to "logged in" one
+ createAndInitialiseInnerComponents();
+
+ // ..also, load user's tag cloud
+ pluginMainComponent.getTagBrowserTab().setMyTagsShown(true);
+ pluginMainComponent.getTagBrowserTab().getMyTagPanel().refresh();
+
+ // ..also, refresh tag search results because these my include
+ // much more than
+ // during the previous search when the user was still not
+ // logged-in
+ pluginMainComponent.getTagBrowserTab().rerunLastTagSearch();
+
+ // ..also, refresh the keyword search results (as more items
+ // can now be found)
+ pluginMainComponent.getSearchTab().rerunLastSearch();
+
+ // if after logging it is needed to switch to other tab,
+ // that is done now
+ if (cTabContentComponentToSwitchToAfterLogin != null) {
+ pluginMainComponent.getMainTabs().setSelectedComponent(cTabContentComponentToSwitchToAfterLogin);
+ cTabContentComponentToSwitchToAfterLogin = null;
+ }
+
+ logger.debug("Logged in to myExperiment successfully");
+ } else {
+ // couldn't login - display error message
+ pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), null);
+ javax.swing.JOptionPane.showMessageDialog(null, "Unable to login to myExperiment - please check your login details", "myExperiment Plugin - Couldn't Login", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ });
+ }
+
+ } catch (Exception ex) {
+ logger.error("Exception on attempt to login to myExperiment:\n", ex);
+ }
+ }
+ }.start();
+
+ } else if (e.getSource().equals(this.jpSidebar.bRefreshMyStuff)) {
+ // this will re-fetch all user profile data and repopulate the whole of the 'My Stuff' tab
+ pluginMainComponent.getStatusBar().setStatus(this.getClass().getName(), "Refreshing user data");
+
+ new Thread("Refreshing myStuff tab data") {
+ @Override
+ public void run() {
+ // re-fetch user data first
+ myExperimentClient.setCurrentUser(myExperimentClient.fetchCurrentUser(myExperimentClient.getCurrentUser().getURI()));
+ createAndInitialiseInnerComponents();
+ revalidate();
+ }
+ }.start();
+ }
+ }
+
+ // *** Callbacks for KeyListener interface ***
+ public void keyPressed(KeyEvent e) {
+ // ENTER pressed - check which element is the source and determine what
+ // acion is to be taken
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ if (e.getSource().equals(this.cbLoginAutomatically)
+ || e.getSource().equals(this.bLogin)) {
+ // ENTER pressed when focus was on the login button, one of checkboxes or the password field - do logging in
+ actionPerformed(new ActionEvent(this.bLogin, 0, ""));
+ }
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ // do nothing
+ }
+
+ public void keyTyped(KeyEvent e) {
+ // do nothing
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/PluginPreferencesDialog.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/PluginPreferencesDialog.java
new file mode 100644
index 0000000..2ad8a42
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/PluginPreferencesDialog.java
@@ -0,0 +1,372 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+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.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira
+ */
+public class PluginPreferencesDialog extends HelpEnabledDialog implements ComponentListener, ActionListener {
+ // CONSTANTS
+
+ // components for accessing application's main elements
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // COMPONENTS
+ private JTextField tfMyExperimentURL;
+ private JComboBox cbDefaultLoggedInTab;
+ private JComboBox cbDefaultNotLoggedInTab;
+ private JCheckBox cbMyStuffWorkflows;
+ private JCheckBox cbMyStuffFiles;
+ private JCheckBox cbMyStuffPacks;
+ private JButton bSave;
+ private JButton bCancel;
+ private JClickableLabel jclClearPreviewHistory;
+ private JClickableLabel jclClearSearchHistory;
+ private JClickableLabel jclClearFavouriteSearches;
+
+ // DATA STORAGE
+ private final Component[] pluginTabComponents;
+ private final ArrayList<String> alPluginTabComponentNames;
+
+ public PluginPreferencesDialog(JFrame owner, MainComponent component, MyExperimentClient client, Logger logger) {
+ super(owner, "Plugin preferences", true);
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // set options of the preview dialog box
+ this.addComponentListener(this);
+ //this.setIconImage(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("myexp_icon")).getImage());
+
+ // prepare plugin tab names to display in the UI afterwards
+ this.alPluginTabComponentNames = new ArrayList<String>();
+ this.pluginTabComponents = this.pluginMainComponent.getMainTabs().getComponents();
+ for (int i = 0; i < this.pluginTabComponents.length; i++) {
+ alPluginTabComponentNames.add(this.pluginMainComponent.getMainTabs().getTitleAt(i));
+ }
+
+ this.initialiseUI();
+
+ // this is not computation-intensive method, so no need to run in a new thread
+ this.initialiseData();
+ }
+
+ private void initialiseUI() {
+ // this constraints instance will be shared among all components in the window
+ GridBagConstraints c = new GridBagConstraints();
+
+ // create the myExperiment API address box
+ JPanel jpApiLocation = new JPanel();
+ jpApiLocation.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " myExperiment Location "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+ jpApiLocation.setLayout(new GridBagLayout());
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 1.0;
+ c.anchor = GridBagConstraints.WEST;
+ jpApiLocation.add(new JLabel("Base URL of myExperiment instance to connect to"), c);
+
+ c.gridy = 1;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ this.tfMyExperimentURL = new JTextField();
+ this.tfMyExperimentURL.setToolTipText("<html>Here you can specify the base URL of the myExperiment "
+ + "instance that you wish to connect to.<br>This allows the plugin to connect not only to the "
+ + "<b>main myExperiment website</b> (default value:<br><b>http://www.myexperiment.org</b>) but "
+ + "also to any other myExperiment instance that might<br>exist elsewhere.<br><br>It is recommended "
+ + "that you only change this setting if you are certain in your actions.</html>");
+ jpApiLocation.add(this.tfMyExperimentURL, c);
+
+ // create startup tab choice box
+ JPanel jpStartupTabChoice = new JPanel();
+ jpStartupTabChoice.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Plugin Start-up Settings "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+ jpStartupTabChoice.setLayout(new GridBagLayout());
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(0, 0, 0, 10);
+ jpStartupTabChoice.add(new JLabel("Default startup tab for anonymous user"), c);
+
+ c.gridx = 1;
+ c.weightx = 1.0;
+ c.insets = new Insets(0, 0, 2, 0);
+ this.cbDefaultNotLoggedInTab = new JComboBox(this.alPluginTabComponentNames.toArray());
+ this.cbDefaultNotLoggedInTab.setToolTipText("<html>This tab will be automatically opened at plugin start up time if you are <b>not</b> logged id to myExperiment.</html>");
+ jpStartupTabChoice.add(this.cbDefaultNotLoggedInTab, c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.weightx = 0;
+ c.insets = new Insets(0, 0, 0, 10);
+ jpStartupTabChoice.add(new JLabel("Default startup tab after successful auto-login"), c);
+
+ c.gridx = 1;
+ c.weightx = 1.0;
+ c.insets = new Insets(2, 0, 0, 0);
+ this.cbDefaultLoggedInTab = new JComboBox(this.alPluginTabComponentNames.toArray());
+ this.cbDefaultLoggedInTab.setToolTipText("<html>This tab will be automatically opened at plugin start up time if you have chosen to use <b>auto logging in</b> to myExperiment.</html>");
+ jpStartupTabChoice.add(this.cbDefaultLoggedInTab, c);
+
+ // create 'my stuff' tab preference box
+ JPanel jpMyStuffPrefs = new JPanel();
+ jpMyStuffPrefs.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " 'My Stuff' Tab Settings "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+ jpMyStuffPrefs.setLayout(new GridBagLayout());
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(0, 0, 0, 0);
+ jpMyStuffPrefs.add(new JLabel("Sections to show in this tab:"), c);
+
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 1.0;
+ c.insets = new Insets(0, 10, 0, 0);
+ this.cbMyStuffWorkflows = new JCheckBox("My Workflows");
+ jpMyStuffPrefs.add(this.cbMyStuffWorkflows, c);
+
+ c.gridy = 1;
+ this.cbMyStuffFiles = new JCheckBox("My Files");
+ jpMyStuffPrefs.add(this.cbMyStuffFiles, c);
+
+ c.gridy = 2;
+ this.cbMyStuffPacks = new JCheckBox("My Packs");
+ jpMyStuffPrefs.add(this.cbMyStuffPacks, c);
+
+ // create privacy settings box
+ JPanel jpPrivacySettings = new JPanel();
+ jpPrivacySettings.setLayout(new BoxLayout(jpPrivacySettings, BoxLayout.Y_AXIS));
+ jpPrivacySettings.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Privacy Settings "), BorderFactory.createEmptyBorder(0, 7, 5, 5)));
+
+ this.jclClearPreviewHistory = new JClickableLabel("Clear browsing history", "clear_preview_history", this);
+ jpPrivacySettings.add(this.jclClearPreviewHistory);
+
+ this.jclClearSearchHistory = new JClickableLabel("Clear search history", "clear_search_history", this);
+ this.jclClearSearchHistory.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0));
+ jpPrivacySettings.add(this.jclClearSearchHistory);
+
+ this.jclClearFavouriteSearches = new JClickableLabel("Clear favourite searches", "clear_favourite_searches", this);
+ this.jclClearFavouriteSearches.setBorder(BorderFactory.createEmptyBorder(0, 0, 2, 0));
+ jpPrivacySettings.add(this.jclClearFavouriteSearches);
+
+ // create button panel
+ this.bSave = new JButton("Save");
+ this.bSave.addActionListener(this);
+
+ this.bCancel = new JButton("Cancel");
+ this.bCancel.addActionListener(this);
+
+ JPanel jpButtons = new JPanel();
+ jpButtons.setLayout(new GridBagLayout());
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(0, 0, 0, 2);
+ jpButtons.add(bSave, c);
+
+ c.gridx = 1;
+ c.insets = new Insets(0, 2, 0, 0);
+ jpButtons.add(bCancel, c);
+
+ // PUT EVERYTHING TOGETHER
+ this.setTitle("myExperiment Plugin Preferences");
+ BorderLayout layout = new BorderLayout();
+ JPanel jpEverything = new JPanel();
+ GridBagLayout jpEverythingLayout = new GridBagLayout();
+ jpEverything.setLayout(jpEverythingLayout);
+ this.getContentPane().setLayout(layout);
+
+ GridBagConstraints gbConstraints = new GridBagConstraints();
+ gbConstraints.fill = GridBagConstraints.BOTH;
+ gbConstraints.weightx = 1;
+ gbConstraints.gridx = 0;
+
+ gbConstraints.gridy = 0;
+ jpEverything.add(jpApiLocation, gbConstraints);
+
+ gbConstraints.gridy = 1;
+ jpEverything.add(jpStartupTabChoice, gbConstraints);
+
+ gbConstraints.gridy = 2;
+ jpEverything.add(jpMyStuffPrefs, gbConstraints);
+
+ gbConstraints.gridy = 3;
+ jpEverything.add(jpPrivacySettings, gbConstraints);
+
+ gbConstraints.gridy = 4;
+ jpEverything.add(jpButtons, gbConstraints);
+
+ this.add(jpEverything);
+ this.setResizable(false);
+
+ // pack() sets preferred size of the dialog box;
+ // after this, can set the minimum size to that value too
+ this.pack();
+ this.setMinimumSize(this.getPreferredSize());
+ }
+
+ private void initialiseData() {
+ // myExperiment Base URL
+ this.tfMyExperimentURL.setText(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_BASE_URL));
+
+ // default tabs
+ this.cbDefaultNotLoggedInTab.setSelectedIndex(Integer.parseInt(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB)));
+ this.cbDefaultLoggedInTab.setSelectedIndex(Integer.parseInt(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB)));
+
+ // components of "My Stuff" tab
+ this.cbMyStuffWorkflows.setSelected(Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_WORKFLOWS)));
+ this.cbMyStuffFiles.setSelected(Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_FILES)));
+ this.cbMyStuffPacks.setSelected(Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_PACKS)));
+ }
+
+ // *** Callbacks for ComponentListener interface ***
+
+ public void componentShown(ComponentEvent e) {
+ // every time the settings window is shown, make sure that the dialog box appears
+ // centered horizontally and vertically relatively to the main component
+ Util.centerComponentWithinAnother(this.pluginMainComponent, this);
+
+ // also, need to make sure that correct settings get shown
+ // (e.g. especially relevant when this window was last closed with 'cancel',
+ // but some options were changed prior to that)
+ this.initialiseData();
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ // do nothing
+ }
+
+ public void componentResized(ComponentEvent e) {
+ // do nothing
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ // do nothing
+ }
+
+ // *** Callback for ActionListener interface ***
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bSave)) {
+ // check if myExperiment address is present
+ String strNewMyExperimentURL = this.tfMyExperimentURL.getText().trim();
+ if (strNewMyExperimentURL.length() == 0) {
+ javax.swing.JOptionPane.showMessageDialog(null, "Please specify a base URL of myExperiment instance that you wish to connect to", "Error", JOptionPane.WARNING_MESSAGE);
+ this.tfMyExperimentURL.requestFocusInWindow();
+ return;
+ }
+
+ // check if at least one of the checkboxes (for sections in 'My Stuff' tab) is selected
+ if (!(this.cbMyStuffWorkflows.isSelected()
+ || this.cbMyStuffFiles.isSelected() || this.cbMyStuffPacks.isSelected())) {
+ javax.swing.JOptionPane.showMessageDialog(null, "Please choose at least one section to display in 'My Stuff' tab", "Error", JOptionPane.WARNING_MESSAGE);
+ this.cbMyStuffWorkflows.requestFocusInWindow();
+ return;
+ }
+
+ // NB! changed myExperiment location will not take action until the next application restart
+ if (!strNewMyExperimentURL.equals(myExperimentClient.getBaseURL())) {
+ // turn off auto-login
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_AUTO_LOGIN, new Boolean(false).toString());
+
+ javax.swing.JOptionPane.showMessageDialog(null, "You have selected a new Base URL for myExperiment. "
+ + "Your new setting has been saved,\nbut will not take effect until you restart Taverna.\n\n"
+ + "Auto-login feature has been switched off for you to check the login details at the next launch.", "myExperiment Plugin - Info", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ // all values should be present - store these into Properties object
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_BASE_URL, strNewMyExperimentURL);
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB, new Integer(cbDefaultNotLoggedInTab.getSelectedIndex()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB, new Integer(cbDefaultLoggedInTab.getSelectedIndex()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_WORKFLOWS, new Boolean(cbMyStuffWorkflows.isSelected()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_FILES, new Boolean(cbMyStuffFiles.isSelected()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_PACKS, new Boolean(cbMyStuffPacks.isSelected()).toString());
+
+ // close the window eventually
+ setVisible(false);
+ } else if (e.getSource().equals(this.bCancel)) {
+ // simply close the preferences window
+ setVisible(false);
+ } else if (e.getSource().equals(this.jclClearPreviewHistory)) {
+ // request user confirmation and clear browsing history (preview history)
+ if (JOptionPane.showConfirmDialog(null, "This will delete the browsing history - the lists of previously previewed,\n"
+ + "downloaded, opened and commented on items will be emptied.\n\nDo you want to proceed?", "myExperiment Plugin - Confirmation Required", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
+ pluginMainComponent.getPreviewBrowser().clearPreviewHistory();
+ pluginMainComponent.getHistoryBrowser().clearDownloadedItemsHistory();
+ pluginMainComponent.getHistoryBrowser().clearOpenedItemsHistory();
+ pluginMainComponent.getHistoryBrowser().clearCommentedOnItemsHistory();
+ pluginMainComponent.getHistoryBrowser().refreshHistoryBox(HistoryBrowserTabContentPanel.PREVIEWED_ITEMS_HISTORY);
+ pluginMainComponent.getHistoryBrowser().refreshHistoryBox(HistoryBrowserTabContentPanel.DOWNLOADED_ITEMS_HISTORY);
+ pluginMainComponent.getHistoryBrowser().refreshHistoryBox(HistoryBrowserTabContentPanel.OPENED_ITEMS_HISTORY);
+ pluginMainComponent.getHistoryBrowser().refreshHistoryBox(HistoryBrowserTabContentPanel.COMMENTED_ON_ITEMS_HISTORY);
+ }
+ } else if (e.getSource().equals(this.jclClearSearchHistory)) {
+ // request user confirmation and clear search history (tag search history + query search history)
+ if (JOptionPane.showConfirmDialog(null, "This will delete both query and tag search history.\nDo you want to proceed?", "myExperiment Plugin - Confirmation Required", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
+ pluginMainComponent.getSearchTab().getSearchHistory().clear();
+ pluginMainComponent.getTagBrowserTab().getTagSearchHistory().clear();
+ pluginMainComponent.getSearchTab().updateSearchHistory();
+ pluginMainComponent.getHistoryBrowser().refreshSearchHistory();
+ pluginMainComponent.getHistoryBrowser().refreshTagSearchHistory();
+ }
+ } else if (e.getSource().equals(this.jclClearFavouriteSearches)) {
+ // request user confirmation and clear favourite searches
+ if (JOptionPane.showConfirmDialog(null, "This will delete all your favourite search settings.\nDo you want to proceed?", "myExperiment Plugin - Confirmation Required", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
+ pluginMainComponent.getSearchTab().getSearchFavouritesList().clear();
+ pluginMainComponent.getSearchTab().updateFavouriteSearches();
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/PluginStatusBar.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/PluginStatusBar.java
new file mode 100644
index 0000000..0d68811
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/PluginStatusBar.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira
+ */
+public class PluginStatusBar extends JPanel implements ActionListener {
+ // CONSTANTS
+ private static final String STATUS_MESSAGE_READY = "Ready";
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ // all components that represent the status
+ private JLabel lSpinnerIcon;
+ private JLabel lStatusMsg;
+ private JLabel lCurrentUser;
+ // private JButton bPreferences;
+
+ // collections to keep the statuses for different tabs
+ ArrayList<String> alTabClassNames;
+ ArrayList<String> alTabStatuses;
+
+ // spinner icons
+ ImageIcon iconSpinner;
+ ImageIcon iconSpinnerStopped;
+
+ public PluginStatusBar(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // prepare status collections for different tabs
+ alTabClassNames = new ArrayList<String>();
+ alTabStatuses = new ArrayList<String>();
+
+ // load icons
+ this.iconSpinner = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner"));
+ this.iconSpinnerStopped = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner_stopped"));
+
+ // prepare main panel for the status bar
+ this.setLayout(new BorderLayout());
+ this.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 1)); // this will
+ // add a bit
+ // more spacing
+ // on the left
+ // - before the
+ // status
+ // message
+
+ // prepare status labels
+ this.lSpinnerIcon = new JLabel("", iconSpinnerStopped, SwingConstants.LEFT);
+ this.lStatusMsg = new JLabel("Ready");
+ this.lCurrentUser = new JLabel("Please log in to access your profile", SwingConstants.CENTER);
+
+ // 'Plugin Preferences' button
+ // this.bPreferences = new JButton("Plugin Preferences", WorkbenchIcons.configureIcon);
+ // this.bPreferences.addActionListener(this);
+
+ // put everything together
+ JPanel pWestStatusBarSection = new JPanel();
+ pWestStatusBarSection.add(lSpinnerIcon);
+ pWestStatusBarSection.add(lStatusMsg);
+
+ this.add(pWestStatusBarSection, BorderLayout.WEST);
+ this.add(this.lCurrentUser, BorderLayout.EAST);
+ // this.add(this.bPreferences, BorderLayout.EAST);
+ }
+
+ // updates the current user name in the middle of the status bar
+ public void setCurrentUser(String strUsername) {
+ // if "null" or "" is submitted as a parameter, the status will be set to
+ // "Ready"
+ if (strUsername == null || strUsername.length() == 0)
+ this.lCurrentUser.setText("Please log in to access your profile");
+ else
+ this.lCurrentUser.setText("<html>Logged in as <b>" + strUsername
+ + "</b></html>");
+ }
+
+ // sets the status message to the one that is relevant to the current tab
+ public void displayStatus(String strTabClassName) {
+ int iTabIdx = -1;
+ String strBaseClassName = Util.getBaseClassName(strTabClassName);
+
+ if ((iTabIdx = alTabClassNames.indexOf(strBaseClassName)) != -1) {
+ // tab found - show its status message
+ String strCurStatus = alTabStatuses.get(iTabIdx);
+ this.lStatusMsg.setText(strCurStatus);
+ startSpinner(!strCurStatus.equals(PluginStatusBar.STATUS_MESSAGE_READY));
+ } else {
+ // tab not found - assume no actions are happening
+ // (this will create the 'ready' status for the current tab,
+ // then return to display it)
+ setStatus(strBaseClassName, null);
+ }
+ }
+
+ // sets the status message for a particular tab;
+ // if this tab is currently active, the status will get displayed immediately
+ // (alternatively it will be displayed at the time when the tab becomes
+ // active)
+ public void setStatus(String strTabClassName, String strStatus) {
+ // PREPROCESSING - if "null" or "" is submitted as a parameter, the status
+ // will be set to "Ready"
+ if (strStatus == null || strStatus.length() == 0)
+ strStatus = PluginStatusBar.STATUS_MESSAGE_READY;
+ String strBaseClassName = Util.getBaseClassName(strTabClassName);
+
+ // STORING the status it in the collection
+ int iTabIdx = -1;
+ if ((iTabIdx = alTabClassNames.indexOf(strBaseClassName)) != -1) {
+ // only a change of status, already dealt with this tab before
+ alTabStatuses.set(iTabIdx, strStatus);
+ } else {
+ // never worked with this tab before, add new one
+ alTabClassNames.add(strBaseClassName);
+ alTabStatuses.add(strStatus);
+ }
+
+ // display the new status if the updated status is on the active tab
+ if (isTabActive(strBaseClassName))
+ displayStatus(strBaseClassName);
+ }
+
+ // helper to start / stop the spinner in the status bar that
+ // indicates that some action is currently in progress
+ // (action will be displayed by lStatusMsg label)
+ public void startSpinner(boolean bStart) {
+ this.lSpinnerIcon.setIcon(bStart ? this.iconSpinner : this.iconSpinnerStopped);
+ }
+
+ // Determine whether the tab in the parameter is currently active in the main
+ // tabbed pane.
+ private boolean isTabActive(String strTabClassName) {
+ // get the current active tab (this is a normal class name of the main tab
+ // content component)
+ String strCurSelectedTabClassName = this.pluginMainComponent.getMainTabs().getSelectedComponent().getClass().getName();
+
+ // get the real class name to match
+ String strBaseClassName = Util.getBaseClassName(strTabClassName);
+
+ // compare the two class names
+ return (strBaseClassName.equals(strCurSelectedTabClassName));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ // if (e.getSource().equals(this.bPreferences)) {
+ // // open preferences dialog box
+ // pluginMainComponent.getPreferencesDialog().setVisible(true);
+ // }
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourceListPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourceListPanel.java
new file mode 100644
index 0000000..d0b00a3
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourceListPanel.java
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.net.URI;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira, Jiten Bhagat
+ */
+public class ResourceListPanel extends JPanel implements HyperlinkListener {
+ // CONSTANTS
+ public final static int DESCRIPTION_TRUNCATE_LENGTH_FOR_SHORT_LIST_VIEW = 150;
+
+ public final static int THUMBNAIL_WIDTH_FOR_SHORT_LIST_VIEW = 60;
+ public final static int THUMBNAIL_HEIGHT_FOR_SHORT_LIST_VIEW = 45;
+
+ public final static int THUMBNAIL_WIDTH_FOR_FULL_LIST_VIEW = 90;
+ public final static int THUMBNAIL_HEIGHT_FOR_FULL_LIST_VIEW = 90;
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ private JPanel listPanel;
+ private GridBagConstraints gbConstraints;
+
+ private List<Resource> listItems;
+
+ // some of the components will not be shown in the item list if
+ // it's not of full size
+ private boolean bFullSizeItemsList = true;
+
+ public ResourceListPanel(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ this.initialiseUI();
+ }
+
+ public boolean isFullSizeItemsList() {
+ return this.bFullSizeItemsList;
+ }
+
+ public void setFullSizeItemsList(boolean bFullSizeItemsList) {
+ this.bFullSizeItemsList = bFullSizeItemsList;
+ }
+
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ try {
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ String strAction = e.getDescription().toString();
+
+ if (strAction.startsWith("preview:")) {
+ this.pluginMainComponent.getPreviewBrowser().preview(strAction);
+ } else {
+ Desktop.getDesktop().browse(new URI(strAction));
+ }
+ }
+ } catch (Exception ex) {
+ logger.error("Error occurred whilst clicking a hyperlink", ex);
+ }
+ }
+
+ public void setListItems(List<Resource> items) {
+ this.listItems = items;
+
+ this.repopulate();
+ }
+
+ public void clear() {
+ this.listPanel.removeAll();
+ this.invalidate();
+ }
+
+ public void refresh() {
+ if (this.listItems != null) {
+ this.repopulate();
+ }
+ }
+
+ public void repopulate() {
+ if (this.listItems != null) {
+ this.clear();
+
+ if (this.listItems.size() > 0) {
+ Resource res = null;
+ for (int i = 0; i < this.listItems.size(); i++) {
+ try {
+ // this will make the layout manager to push all extra space in
+ // Y-axis
+ // to go to the last element in the panel; essentially, this will
+ // push
+ // all list items to the top of the list view panel
+ if (i == listItems.size() - 1)
+ gbConstraints.weighty = 1.0;
+
+ res = this.listItems.get(i);
+ this.listPanel.add(res.createListViewPanel(bFullSizeItemsList, pluginMainComponent, this, logger), gbConstraints);
+ logger.debug("Added entry in resource list panel for the resource (Type: "
+ + res.getItemTypeName() + ", URI: " + res.getURI() + ")");
+ } catch (Exception e) {
+ logger.error("Failed to add item entry to ResourceListPanel (Item Type : "
+ + res.getItemTypeName() + ", URI: " + res.getURI() + ")", e);
+ }
+ }
+ } else {
+ // no items in the list
+ JLabel lNone = new JLabel("None");
+ lNone.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
+ lNone.setForeground(Color.GRAY);
+ lNone.setFont(lNone.getFont().deriveFont(Font.ITALIC));
+
+ gbConstraints.anchor = GridBagConstraints.WEST;
+ this.listPanel.add(lNone, gbConstraints);
+
+ this.listPanel.setPreferredSize(new Dimension(20, 40));
+ this.listPanel.setBackground(Color.WHITE);
+ }
+ }
+
+ this.validate();
+ this.repaint();
+ }
+
+ private void initialiseUI() {
+ this.listPanel = new JPanel();
+ this.listPanel.setBorder(BorderFactory.createEmptyBorder());
+
+ this.listPanel.setLayout(new GridBagLayout());
+ this.gbConstraints = new GridBagConstraints();
+ this.gbConstraints.anchor = GridBagConstraints.NORTH;
+ this.gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+ this.gbConstraints.gridx = GridBagConstraints.REMAINDER;
+ this.gbConstraints.gridy = GridBagConstraints.RELATIVE;
+ this.gbConstraints.weightx = 1.0;
+ this.gbConstraints.weighty = 0;
+
+ this.setLayout(new BorderLayout());
+ this.add(this.listPanel, BorderLayout.CENTER);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewBrowser.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewBrowser.java
new file mode 100644
index 0000000..bdef0e7
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewBrowser.java
@@ -0,0 +1,711 @@
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Desktop;
+import java.awt.Dimension;
+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.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Base64;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira
+ */
+public class ResourcePreviewBrowser extends JFrame implements ActionListener, HyperlinkListener, ComponentListener {
+ // CONSTANTS
+ protected static final int PREFERRED_WIDTH = 750;
+ protected static final int PREFERRED_HEIGHT = 600;
+ protected static final int PREFERRED_SCROLL = 10;
+ protected static final int PREVIEW_HISTORY_LENGTH = 50;
+
+ // navigation data
+ private int iCurrentHistoryIdx; // index within the current history
+ private final ArrayList<String> alCurrentHistory; // current history - e.g. if one
+ // opens Page1, then Page2; goes back and opens Page3 - current preview would hold only [Page1, Page3]
+ private ArrayList<Resource> alFullHistory; // all resources that were
+ // previewed since application started (will be used by ResourcePreviewHistoryBrowser)
+
+ // components for accessing application's main elements
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // holder of the data about currently previewed item
+ private ResourcePreviewContent rpcContent;
+ private Resource resource;
+
+ // components of the preview window
+ private JPanel jpMain;
+ private JPanel jpStatusBar;
+ private JLabel lSpinnerIcon;
+ private JButton bBack;
+ private JButton bForward;
+ private JButton bRefresh;
+ private JButton bOpenInMyExp;
+ private JButton bDownload;
+ private JButton bOpenInTaverna;
+ private JButton bImportIntoTaverna;
+ private JButton bAddComment;
+ private JButton bAddRemoveFavourite;
+ private JButton bUpload;
+ private JButton bEditMetadata;
+ private JScrollPane spContentScroller;
+
+ // icons
+ private final ImageIcon iconOpenInMyExp = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("open_in_my_experiment_icon"));
+ private final ImageIcon iconAddFavourite = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("add_favourite_icon"));
+ private final ImageIcon iconDeleteFavourite = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("delete_favourite_icon"));
+ private final ImageIcon iconAddComment = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("add_comment_icon"));
+ private final ImageIcon iconSpinner = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner"));
+ private final ImageIcon iconSpinnerStopped = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner_stopped"));
+ private final FileManager fileManager;
+
+ public ResourcePreviewBrowser(MainComponent component, MyExperimentClient client, Logger logger, FileManager fileManager) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ this.fileManager = fileManager;
+
+ // initialise previewed items history
+ String strPreviewedItemsHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_PREVIEWED_ITEMS_HISTORY);
+ if (strPreviewedItemsHistory != null) {
+ Object oPreviewedItemsHistory = Base64.decodeToObject(strPreviewedItemsHistory);
+ this.alFullHistory = (ArrayList<Resource>) oPreviewedItemsHistory;
+ } else {
+ this.alFullHistory = new ArrayList<Resource>();
+ }
+
+ // no navigation history at loading
+ this.iCurrentHistoryIdx = -1;
+ this.alCurrentHistory = new ArrayList<String>();
+
+ // set options of the preview dialog box
+ this.setIconImage(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("myexp_icon")).getImage());
+ this.addComponentListener(this);
+
+ this.initialiseUI();
+ }
+
+ /**
+ * Accessor method for getting a full history of previewed resources as a
+ * list.
+ */
+ public ArrayList<Resource> getPreviewHistory() {
+ return (this.alFullHistory);
+ }
+
+ /**
+ * As opposed to getPreviewHistory() which returns full history of previewed
+ * resources, this helper method only retrieves the current history stack.
+ *
+ * Example: if a user was to view the following items - A -> B -> C B <- C B
+ * -> D, the full history would be [A,C,B,D]; current history stack would be
+ * [A,B,D] - note how item C was "forgotten" (this works the same way as all
+ * web browsers do)
+ */
+ public List<String> getCurrentPreviewHistory() {
+ return (this.alCurrentHistory);
+ }
+
+ /**
+ * Deletes both 'current history' (the latest preview history stack) and the
+ * 'full preview history'. Also, resets the index in the current history, so
+ * that the preview browser would not allow using Back-Forward buttons until
+ * some new previews are opened.
+ */
+ public void clearPreviewHistory() {
+ this.iCurrentHistoryIdx = -1;
+ this.alCurrentHistory.clear();
+ this.alFullHistory.clear();
+ }
+
+ /**
+ * This method is a launcher for the real worker method ('createPreview()')
+ * that does all the job.
+ *
+ * The purpose of having this method is to manage history. This method is to
+ * be called every time when a "new" preview is requested. This will add a new
+ * link to the CurrentHistory stack.
+ *
+ * Clicks on "Back" and "Forward" buttons will only need to advance the
+ * counter of the current position in the CurrentHistory. Therefore, these
+ * will directly call 'createPreview()'.
+ */
+ public void preview(String action) {
+ // *** History Update ***
+ // if this is not the "newest" page in current history, remove all newer
+ // ones
+ // (that is if the user went "back" and opened some new link from on of the
+ // older pages)
+ while (alCurrentHistory.size() > iCurrentHistoryIdx + 1) {
+ alCurrentHistory.remove(alCurrentHistory.size() - 1);
+ }
+
+ boolean bPreviewNotTheSameAsTheLastOne = true;
+ if (alCurrentHistory.size() > 0) {
+ // will add new page to the history only if it's not the same as the last
+ // one!
+ if (action.equals(alCurrentHistory.get(alCurrentHistory.size() - 1))) {
+ bPreviewNotTheSameAsTheLastOne = false;
+ }
+
+ // this is not the first page in the history, enable "Back" button (if
+ // only this isn't the same page as was the first one);
+ // (this, however, is the last page in the history now - so disable
+ // "Forward" button)
+ bBack.setEnabled(bPreviewNotTheSameAsTheLastOne
+ || alCurrentHistory.size() > 1);
+ bForward.setEnabled(false);
+ } else if (alCurrentHistory.size() == 0) {
+ // this is the first preview after application has loaded or since the
+ // preview history was cleared - disable both Back and Forward buttons
+ bBack.setEnabled(false);
+ bForward.setEnabled(false);
+ }
+
+ // add current preview URI to the history
+ if (bPreviewNotTheSameAsTheLastOne) {
+ iCurrentHistoryIdx++;
+ alCurrentHistory.add(action);
+ }
+
+ // *** Launch Preview ***
+ createPreview(action);
+ }
+
+ private void createPreview(String action) {
+ // JUST FOR TESTING THE CURRENT_HISTORY OPERATION
+ // javax.swing.JOptionPane.showMessageDialog(null, "History idx: " +
+ // this.iCurrentHistoryIdx + "\n" + alCurrentHistory.toString());
+
+ // show that loading is in progress
+ this.setTitle("Loading preview...");
+ this.lSpinnerIcon.setIcon(this.iconSpinner);
+
+ // disable all action buttons while loading is in progress
+ bOpenInMyExp.setEnabled(false);
+ bDownload.setEnabled(false);
+ bOpenInTaverna.setEnabled(false);
+ bImportIntoTaverna.setEnabled(false);
+ bAddRemoveFavourite.setEnabled(false);
+ bAddComment.setEnabled(false);
+ bUpload.setEnabled(false);
+ bEditMetadata.setEnabled(false);
+
+ // Make call to myExperiment API in a different thread
+ // (then use SwingUtilities.invokeLater to update the UI when ready).
+ final String strAction = action;
+ final EventListener self = this;
+
+ new Thread("Load myExperiment resource preview content") {
+ @Override
+ public void run() {
+ logger.debug("Starting to fetch the preview content data");
+
+ try {
+ // *** Fetch Data and Create Preview Content ***
+ rpcContent = pluginMainComponent.getPreviewFactory().createPreview(strAction, self);
+
+ // as all the details about the previewed resource are now known, can
+ // store this into full preview history
+ // (before that make sure that if the this item was viewed before,
+ // it's removed and re-added at the "top" of the list)
+ // (also make sure that the history size doesn't exceed the pre-set
+ // value)
+ alFullHistory.remove(rpcContent.getResource());
+ alFullHistory.add(rpcContent.getResource());
+ if (alFullHistory.size() > PREVIEW_HISTORY_LENGTH)
+ alFullHistory.remove(0);
+ pluginMainComponent.getHistoryBrowser().refreshHistoryBox(HistoryBrowserTabContentPanel.PREVIEWED_ITEMS_HISTORY);
+
+ // *** Update the Preview Dialog Box when everything is ready ***
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // 'stop' loading action in the status bar and window title
+ setTitle(Resource.getResourceTypeName(rpcContent.getResourceType())
+ + ": " + rpcContent.getResourceTitle());
+ lSpinnerIcon.setIcon(iconSpinnerStopped);
+
+ // update the state of action buttons in the button bar
+ updateButtonBarState(rpcContent);
+
+ // wrap received content into a ScrollPane
+ spContentScroller = new JScrollPane(rpcContent.getContent());
+ spContentScroller.setBorder(BorderFactory.createEmptyBorder());
+ spContentScroller.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ // remove everything from the preview and re-add all components
+ // (NB! Removing only CENTER component didn't work properly)
+ jpMain.removeAll();
+ jpMain.add(redrawStatusBar(), BorderLayout.NORTH);
+ jpMain.add(spContentScroller, BorderLayout.CENTER);
+ validate();
+ repaint();
+ }
+ });
+ } catch (Exception ex) {
+ logger.error("Exception on attempt to login to myExperiment:\n", ex);
+ }
+ }
+ }.start();
+
+ // show the dialog box
+ this.setVisible(true);
+ }
+
+ private void initialiseUI() {
+ // create the STATUS BAR of the preview window
+ createButtonsForStatusBar();
+
+ // put everything together
+ jpMain = new JPanel();
+ jpMain.setOpaque(true);
+ jpMain.setLayout(new BorderLayout());
+ jpMain.add(redrawStatusBar(), BorderLayout.NORTH);
+
+ // add all content into the main dialog
+ this.getContentPane().add(jpMain);
+
+ }
+
+ private void createButtonsForStatusBar() {
+ // navigation buttons => far left of status bar
+ bBack = new JButton(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("back_icon")));
+ bBack.setToolTipText("Back");
+ bBack.addActionListener(this);
+ bBack.setEnabled(false);
+
+ bForward = new JButton(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("forward_icon")));
+ bForward.setToolTipText("Forward");
+ bForward.addActionListener(this);
+ bForward.setEnabled(false);
+
+ // refresh buttons => far right of status bar
+ bRefresh = new JButton(new ImageIcon(MyExperimentPerspective.getLocalResourceURL("refresh_icon")));
+ bRefresh.setToolTipText("Refresh");
+ bRefresh.addActionListener(this);
+
+ lSpinnerIcon = new JLabel(this.iconSpinner);
+
+ // ACTION BUTTONS
+ // 'open in myExperiment' button is the only one that is always available,
+ // still will be set available during loading of the preview for consistency of the UI
+
+ // myExperiment "webby" functions
+ bOpenInMyExp = new JButton(iconOpenInMyExp);
+ bOpenInMyExp.setEnabled(false);
+ bOpenInMyExp.addActionListener(this);
+
+ bAddRemoveFavourite = new JButton(iconAddFavourite);
+ bAddRemoveFavourite.setEnabled(false);
+ bAddRemoveFavourite.addActionListener(this);
+
+ bAddComment = new JButton(iconAddComment);
+ bAddComment.setEnabled(false);
+ bAddComment.addActionListener(this);
+
+ bEditMetadata = new JButton("Update", WorkbenchIcons.editIcon);
+ bEditMetadata.setEnabled(false);
+ bEditMetadata.addActionListener(this);
+
+ bUpload = new JButton("Upload", WorkbenchIcons.upArrowIcon);
+ bUpload.setEnabled(false);
+ bUpload.addActionListener(this);
+
+ // functions more specific to taverna
+ bOpenInTaverna = new JButton(WorkbenchIcons.openIcon);
+ bOpenInTaverna.setEnabled(false);
+ bOpenInTaverna.addActionListener(this);
+
+ bImportIntoTaverna = new JButton();
+ bImportIntoTaverna.setEnabled(false);
+ bImportIntoTaverna.addActionListener(this);
+
+ bDownload = new JButton(WorkbenchIcons.saveIcon);
+ bDownload.setEnabled(false);
+ bDownload.addActionListener(this);
+ }
+
+ private JPanel redrawStatusBar() {
+ // far left of button bar
+ JPanel jpNavigationButtons = new JPanel();
+ jpNavigationButtons.add(bBack);
+ jpNavigationButtons.add(bForward);
+
+ // far right of button bar
+ JPanel jpRefreshButtons = new JPanel();
+ jpRefreshButtons.add(bRefresh);
+ jpRefreshButtons.add(lSpinnerIcon);
+
+ // myExperiment buttons: second left of the button bar
+ JPanel jpMyExperimentButtons = new JPanel();
+ jpMyExperimentButtons.add(bOpenInMyExp);
+ jpMyExperimentButtons.add(bAddRemoveFavourite);
+ jpMyExperimentButtons.add(bAddComment);
+ jpMyExperimentButtons.add(bEditMetadata);
+ jpMyExperimentButtons.add(bUpload);
+
+ // taverna buttons: second right of the button bar
+ JPanel jpTavernaButtons = new JPanel();
+ jpTavernaButtons.add(bOpenInTaverna);
+ jpTavernaButtons.add(bImportIntoTaverna);
+ jpTavernaButtons.add(bDownload);
+
+ // put all action buttons into a button bar
+ JPanel jpStatusBar = new JPanel();
+ jpStatusBar.setLayout(new GridBagLayout());
+ int spaceBetweenSections = 40;
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(0, spaceBetweenSections, 0, spaceBetweenSections / 2);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 0;
+ c.gridy = 0;
+ jpStatusBar.add(jpNavigationButtons, c);
+
+ c.gridx++;
+ c.insets = new Insets(0, spaceBetweenSections / 2, 0, spaceBetweenSections / 2);
+ jpStatusBar.add(jpMyExperimentButtons, c);
+
+ c.gridx++;
+ jpStatusBar.add(jpTavernaButtons, c);
+
+ c.gridx++;
+ c.insets = new Insets(0, spaceBetweenSections / 2, 0, spaceBetweenSections);
+ jpStatusBar.add(jpRefreshButtons, c);
+
+ return jpStatusBar;
+
+ // // put all action buttons into a button bar
+ // JPanel jpActionButtons = new JPanel();
+ // jpActionButtons.setLayout(new GridBagLayout());
+ // GridBagConstraints c = new GridBagConstraints();
+ // double spacing = ResourcePreviewBrowser.PREFERRED_WIDTH * 0.1;
+ // c.insets = new Insets(0, (int) spacing, 0, (int) spacing / 2);
+ // c.gridx = 0;
+ // c.gridy = 0;
+ // jpActionButtons.add(jpMyExperimentButtons, c);
+ //
+ // c.gridx++;
+ // c.insets = new Insets(0, (int) spacing / 2, 0, (int) spacing);
+ // jpActionButtons.add(jpTavernaButtons, c);
+ //
+ // jpStatusBar = new JPanel();
+ // jpStatusBar.setLayout(new BorderLayout());
+ // jpStatusBar.add(jpNavigationButtons, BorderLayout.WEST);
+ // jpStatusBar.add(jpActionButtons, BorderLayout.CENTER);
+ // jpStatusBar.add(jpRefreshButtons, BorderLayout.EAST);
+ }
+
+ private void updateButtonBarState(ResourcePreviewContent content) {
+ // get the visible type name of the resource
+ Resource r = this.rpcContent.getResource();
+ String strResourceType = Resource.getResourceTypeName(r.getItemType()).toLowerCase();
+
+ // "Open in myExperiment" is always available for every item type
+ this.bOpenInMyExp.setEnabled(true);
+ this.bOpenInMyExp.setToolTipText("Open this " + strResourceType
+ + " in myExperiment");
+
+ // "edit metadata" to myExperiment is only available for logged in
+ // users who are the owners of the workflow
+ String strTooltip = "It is currently not possible to edit the metadata for this workflow";
+ boolean bUpdateMetaAvailable = false;
+
+ if (myExperimentClient.isLoggedIn()
+ && (myExperimentClient.getCurrentUser().equals(r.getUploader()))
+ && (r.getItemTypeName().equals("Workflow"))) {
+ strTooltip = "Update the metadata of this workflow.";
+ bUpdateMetaAvailable = true;
+ } else {
+ strTooltip = "Only the owners of workflows can change the metadata of workflows.";
+ }
+ this.bEditMetadata.setToolTipText(strTooltip);
+ this.bEditMetadata.setEnabled(bUpdateMetaAvailable);
+
+ // "upload new version" to myExperiment is only available for logged in
+ // users who are the owners of the workflow
+ strTooltip = "It is currently not possible to upload a new version of this workflow.";
+ boolean bUploadAvailable = false;
+
+ if (myExperimentClient.isLoggedIn()
+ && (myExperimentClient.getCurrentUser().equals(r.getUploader()))
+ && (r.getItemTypeName().equals("Workflow"))) {
+ strTooltip = "Upload a new version of this workflow.";
+ bUploadAvailable = true;
+ } else {
+ strTooltip = "Only the owners of workflows can upload new versions of workflows.";
+ }
+ this.bUpload.setToolTipText(strTooltip);
+ this.bUpload.setEnabled(bUploadAvailable);
+
+ // "Download" - only for selected types and based on current user's
+ // permissions (these conditions are checked within the action)
+ this.bDownload.setAction(pluginMainComponent.new DownloadResourceAction(r, false));
+
+ // "Open in Taverna" - only for Taverna workflows and when download is
+ // allowed for current user (these checks are carried out inside the action)
+ this.bOpenInTaverna.setAction(pluginMainComponent.new LoadResourceInTavernaAction(r, true));
+
+ // "Import into Taverna" - only for Taverna workflows and when download is
+ // allowed for current user (these checks are carried out inside the action)
+ // the import button
+ this.bImportIntoTaverna.setAction(pluginMainComponent.new ImportIntoTavernaAction(r));
+
+ // "Add to Favourites" - for all types, but only for logged in users
+ strTooltip = "It is currently not possible to add " + strResourceType
+ + "s to favourites";
+ boolean bFavouritingAvailable = false;
+ if (r.isFavouritable()) {
+ if (myExperimentClient.isLoggedIn()) {
+ if (r.isFavouritedBy(myExperimentClient.getCurrentUser())) {
+ strTooltip = "Remove this " + strResourceType
+ + " from your favourites";
+ this.bAddRemoveFavourite.setIcon(iconDeleteFavourite);
+ } else {
+ strTooltip = "Add this " + strResourceType + " to your favourites";
+ this.bAddRemoveFavourite.setIcon(iconAddFavourite);
+ }
+ bFavouritingAvailable = true;
+ } else {
+ // TODO should be changed to display login box first, then favouriting option
+ strTooltip = "Only logged in users can add items to favourites";
+ }
+ }
+ this.bAddRemoveFavourite.setToolTipText(strTooltip);
+ this.bAddRemoveFavourite.setEnabled(bFavouritingAvailable);
+
+ // "Add Comment" - for all types besides users and only for logged in users
+ strTooltip = "It is currently not possible to comment on "
+ + strResourceType + "s";
+ boolean bCommentingAvailable = false;
+ if (r.isCommentableOn()) {
+ if (myExperimentClient.isLoggedIn()) {
+ strTooltip = "Add a comment on this " + strResourceType;
+ bCommentingAvailable = true;
+ } else {
+ // TODO should be changed to display login box first, then commenting option
+ strTooltip = "Only logged in users can make comments";
+ }
+ }
+ this.bAddComment.setToolTipText(strTooltip);
+ this.bAddComment.setEnabled(bCommentingAvailable);
+ }
+
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ String strAction = e.getDescription().toString();
+
+ if (strAction.startsWith("preview:")) {
+ this.preview(strAction);
+ } else {
+ try {
+ Desktop.getDesktop().browse(new URI(strAction));
+ } catch (Exception ex) {
+ logger.error("Failed while trying to open the URL in a standard browser; URL was: "
+ + strAction + "\nException was: " + ex);
+ }
+ }
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bBack)) {
+ // "Back" button clicked
+
+ // update position in the history
+ iCurrentHistoryIdx--;
+
+ // enable or disable "back"/"forward" buttons as appropriate
+ bBack.setEnabled(iCurrentHistoryIdx > 0);
+ bForward.setEnabled(iCurrentHistoryIdx < alCurrentHistory.size() - 1);
+
+ // open requested preview from the history
+ this.createPreview(alCurrentHistory.get(iCurrentHistoryIdx));
+ } else if (e.getSource().equals(this.bForward)) {
+ // "Forward" button clicked
+
+ // update position in the history
+ iCurrentHistoryIdx++;
+
+ // enable or disable "back"/"forward" buttons as appropriate
+ bBack.setEnabled(iCurrentHistoryIdx > 0);
+ bForward.setEnabled(iCurrentHistoryIdx < alCurrentHistory.size() - 1);
+
+ // open requested preview from the history
+ this.createPreview(alCurrentHistory.get(iCurrentHistoryIdx));
+ } else if (e.getSource().equals(this.bRefresh)) {
+ // "Refresh" button clicked
+
+ // simply reload the same preview
+ this.createPreview(alCurrentHistory.get(iCurrentHistoryIdx));
+ } else if (e.getSource().equals(this.bOpenInMyExp)) {
+ // "Open in myExperiment" button clicked
+ try {
+ Desktop.getDesktop().browse(new URI(this.rpcContent.getResourceURL()));
+ } catch (Exception ex) {
+ logger.error("Failed while trying to open the URL in a standard browser; URL was: "
+ + this.rpcContent.getResourceURL() + "\nException was: " + ex);
+ }
+ } else if (e.getSource().equals(this.bUpload)) {
+ /* ************************************************************************* */
+ Resource resource = this.rpcContent.getResource();
+ if (resource.getItemTypeName().equals("Workflow")) {
+ UploadWorkflowDialog uploadWorkflowDialog = new UploadWorkflowDialog(this, true, resource, fileManager);
+
+ if (uploadWorkflowDialog.launchUploadDialogAndPostIfRequired()) {
+ // "true" has been returned so update the resource
+ this.actionPerformed(new ActionEvent(this.bRefresh, 0, ""));
+ }
+ }
+ } else if (e.getSource().equals(this.bEditMetadata)) {
+ Resource resource = this.rpcContent.getResource();
+ if (resource.getItemTypeName().equals("Workflow")) {
+ UploadWorkflowDialog uploadWorkflowDialog = new UploadWorkflowDialog(this, false, resource, fileManager);
+
+ if (uploadWorkflowDialog.launchUploadDialogAndPostIfRequired()) {
+ // "true" has been returned so update the resource
+ this.actionPerformed(new ActionEvent(this.bRefresh, 0, ""));
+ }
+ }
+ /* ************************************************************************* */
+ } else if (e.getSource().equals(this.bAddComment)) {
+ // "Add Comment" button was clicked
+ String strComment = null;
+ AddCommentDialog commentDialog = new AddCommentDialog(this, this.rpcContent.getResource(), pluginMainComponent, myExperimentClient, logger);
+ if ((strComment = commentDialog.launchAddCommentDialogAndPostCommentIfRequired()) != null) {
+ // comment was added because return value is not null;
+ // a good option now would be to reload only the comments tab, but
+ // for now we refresh the whole of the preview
+ this.actionPerformed(new ActionEvent(this.bRefresh, 0, ""));
+
+ // update history of the items that were commented on, making sure that:
+ // - there's only one occurrence of this item in the history;
+ // - if this item was in the history before, it is moved to the 'top' now;
+ // - predefined history size is not exceeded
+ this.pluginMainComponent.getHistoryBrowser().getCommentedOnItemsHistoryList().remove(this.rpcContent.getResource());
+ this.pluginMainComponent.getHistoryBrowser().getCommentedOnItemsHistoryList().add(this.rpcContent.getResource());
+ if (this.pluginMainComponent.getHistoryBrowser().getCommentedOnItemsHistoryList().size() > HistoryBrowserTabContentPanel.COMMENTED_ON_ITEMS_HISTORY) {
+ this.pluginMainComponent.getHistoryBrowser().getCommentedOnItemsHistoryList().remove(0);
+ }
+
+ // now update the history of the items that were commented on in 'History' tab
+ if (this.pluginMainComponent.getHistoryBrowser() != null) {
+ this.pluginMainComponent.getHistoryBrowser().refreshHistoryBox(HistoryBrowserTabContentPanel.COMMENTED_ON_ITEMS_HISTORY);
+ }
+ }
+ } else if (e.getSource().equals(this.bAddRemoveFavourite)) {
+ boolean bItemIsFavourited = this.rpcContent.getResource().isFavouritedBy(this.myExperimentClient.getCurrentUser());
+
+ AddRemoveFavouriteDialog favouriteDialog = new AddRemoveFavouriteDialog(this, !bItemIsFavourited, this.rpcContent.getResource(), pluginMainComponent, myExperimentClient, logger);
+ int iFavouritingStatus = favouriteDialog.launchAddRemoveFavouriteDialogAndPerformNecessaryActionIfRequired();
+
+ // if the operation wasn't cancelled, update status of the
+ // "add/remove favourite" button and the list of favourites in the user profile
+ if (iFavouritingStatus != AddRemoveFavouriteDialog.OPERATION_CANCELLED) {
+ this.updateButtonBarState(this.rpcContent);
+ this.pluginMainComponent.getMyStuffTab().getSidebar().repopulateFavouritesBox();
+ this.pluginMainComponent.getMyStuffTab().getSidebar().revalidate();
+ }
+ } else if (e.getSource() instanceof JClickableLabel) {
+ // clicked somewhere on a JClickableLabel; if that's a 'preview' request - launch preview
+ if (e.getActionCommand().startsWith("preview:")) {
+ this.preview(e.getActionCommand());
+ } else if (e.getActionCommand().startsWith("tag:")) {
+ // pass this event onto the Tag Browser tab
+ this.pluginMainComponent.getTagBrowserTab().actionPerformed(e);
+ this.pluginMainComponent.getMainTabs().setSelectedComponent(this.pluginMainComponent.getTagBrowserTab());
+ } else {
+ // show the link otherwise
+ try {
+ Desktop.getDesktop().browse(new URI(e.getActionCommand()));
+ } catch (Exception ex) {
+ logger.error("Failed while trying to open the URL in a standard browser; URL was: "
+ + e.getActionCommand() + "\nException was: " + ex);
+ }
+ }
+ } else if (e.getSource() instanceof TagCloudPanel
+ && e.getActionCommand().startsWith("tag:")) {
+ // close the window and pass this event onto the Tag Browser tab
+ this.setVisible(false);
+ this.pluginMainComponent.getTagBrowserTab().actionPerformed(e);
+ this.pluginMainComponent.getMainTabs().setSelectedComponent(this.pluginMainComponent.getTagBrowserTab());
+ }
+ }
+
+ // *** Callbacks for ComponentListener interface ***
+
+ public void componentShown(ComponentEvent e) {
+ // every time the preview browser window is shown, it will start loading a preview
+ // - this state is set in the preview() method; (so this won't have to be done here)
+
+ // remove everything from the preview and re-add only the status bar
+ // (this is done so that newly opened preview window won't show the old
+ // preview)
+ jpMain.removeAll();
+ jpMain.add(redrawStatusBar(), BorderLayout.NORTH);
+ repaint();
+
+ // set the size of the dialog box (NB! Size needs to be set before the position!)
+ this.setSize(ResourcePreviewBrowser.PREFERRED_WIDTH, ResourcePreviewBrowser.PREFERRED_HEIGHT);
+ this.setMinimumSize(new Dimension(ResourcePreviewBrowser.PREFERRED_WIDTH, ResourcePreviewBrowser.PREFERRED_HEIGHT));
+
+ // make sure that the dialog box appears centered horizontally relatively to
+ // the main component; also, pad by 30px vertically from the top of the main component
+ int iMainComponentCenterX = (int) Math.round(this.pluginMainComponent.getLocationOnScreen().getX()
+ + (this.pluginMainComponent.getWidth() / 2));
+ int iPosX = iMainComponentCenterX - (this.getWidth() / 2);
+ int iPosY = ((int) this.pluginMainComponent.getLocationOnScreen().getY()) + 30;
+ this.setLocation(iPosX, iPosY);
+
+ myExperimentClient.storeHistoryAndSettings();
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ myExperimentClient.storeHistoryAndSettings();
+ }
+
+ public void componentResized(ComponentEvent e) {
+ // do nothing
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ // do nothing
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewContent.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewContent.java
new file mode 100644
index 0000000..85b47c0
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewContent.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import javax.swing.JComponent;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+
+/**
+ * Helper class to hold all data about the generated preview.
+ *
+ * @author Sergejs Aleksejevs
+ *
+ */
+public class ResourcePreviewContent
+{
+ private Resource resource;
+ private JComponent jcContent;
+
+ public ResourcePreviewContent()
+ {
+ // empty constructor
+ }
+
+ public ResourcePreviewContent(Resource resource, JComponent content)
+ {
+ this.resource = resource;
+ this.jcContent = content;
+ }
+
+ public Resource getResource()
+ {
+ return(this.resource);
+ }
+
+ public int getResourceType()
+ {
+ return(this.resource.getItemType());
+ }
+
+ public String getResourceTitle()
+ {
+ return(this.resource.getTitle());
+ }
+
+ public String getResourceURL()
+ {
+ return(this.resource.getResource());
+ }
+
+ public String getResourceURI()
+ {
+ return(this.resource.getURI());
+ }
+
+ public JComponent getContent()
+ {
+ return(this.jcContent);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java
new file mode 100644
index 0000000..128cfd3
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java
@@ -0,0 +1,1359 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.JTextPane;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Comment;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.File;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Group;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Pack;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.PackItem;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.User;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class ResourcePreviewFactory {
+ // CONSTANTS
+ private static final int PREFERRED_LOWER_TABBED_PANE_HEIGHT = 250; // used for
+ // all
+ // tabbed
+ // views
+ // inside
+ // preview
+ // for
+ // every
+ // resource
+ // type
+
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // icons which are used in several places in the preview factory
+ private final ImageIcon iconWorkflow;
+ private final ImageIcon iconFile;
+ private final ImageIcon iconPack;
+ private final ImageIcon iconUser;
+ private final ImageIcon iconGroup;
+
+ public ResourcePreviewFactory(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // set up the icons
+ iconWorkflow = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.WORKFLOW));
+ iconFile = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.FILE));
+ iconPack = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.PACK));
+ iconUser = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.USER));
+ iconGroup = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.GROUP));
+ }
+
+ // main worker method - generates the content to be shown in the preview;
+ // responsible for parsing the preview action request, fetching data and
+ // generating all the content (via helpers)
+ public ResourcePreviewContent createPreview(String action, EventListener eventHandler) {
+ JPanel panelToPopulate = new JPanel();
+
+ // === PREPROCESSING ===
+
+ // return error message if the action string isn't actually a request for
+ // preview
+ if (!action.startsWith("preview:")) {
+ this.logger.error("Bad preview request: \"" + action + "\"");
+ panelToPopulate.add(new JLabel("An error has occurred."));
+ Resource r = new Resource();
+ r.setItemType(Resource.UNEXPECTED_TYPE);
+ r.setTitle("Bad preview request");
+ r.setURI(action);
+ return (new ResourcePreviewContent(r, panelToPopulate));
+ }
+
+ // parse the action string - we are now sure that it starts with a
+ // 'preview:'
+ action = action.substring(action.indexOf(":") + 1); // remove "preview:"
+ int iType = Integer.parseInt(action.substring(0, action.indexOf(":"))); // get
+ // type
+ action = action.substring(action.indexOf(":") + 1); // remove type
+ String strURI = action; // get URI
+
+ // === FETCHING RESOURCE DATA ===
+ Document doc = null;
+ try {
+ // the resource type is known at this point, hence can use specialist
+ // method
+ // that only fetches required metadata for (individual for each resource
+ // type)
+ doc = this.myExperimentClient.getResource(iType, strURI, Resource.REQUEST_FULL_PREVIEW);
+ } catch (Exception e) {
+ logger.error("Error while fetching resource data from myExperiment to generate a preview.\nResource type: "
+ + Resource.getResourceTypeName(iType)
+ + "\nResource URI: "
+ + strURI
+ + "\nException: " + e);
+ }
+
+ // === GENERATING PREVIEW ===
+ Resource resource = null;
+ switch (iType) {
+ case Resource.WORKFLOW:
+ Workflow w = Workflow.buildFromXML(doc, this.logger);
+ resource = w;
+ this.generateWorkflowPreviewContent(w, panelToPopulate, eventHandler);
+ break;
+
+ case Resource.FILE:
+ File f = File.buildFromXML(doc, this.logger);
+ resource = f;
+ this.generateFilePreviewContent(f, panelToPopulate, eventHandler);
+ break;
+
+ case Resource.PACK:
+ Pack p = Pack.buildFromXML(doc, this.myExperimentClient, this.logger);
+ resource = p;
+ this.generatePackPreviewContent(p, panelToPopulate, eventHandler);
+ break;
+
+ case Resource.USER:
+ User u = User.buildFromXML(doc, logger);
+ resource = u;
+ this.generateUserPreviewContent(u, panelToPopulate, eventHandler);
+ break;
+
+ case Resource.GROUP:
+ Group g = Group.buildFromXML(doc, logger);
+ resource = g;
+ this.generateGroupPreviewContent(g, panelToPopulate, eventHandler);
+ break;
+
+ default:
+ // unexpected resource type - can't generate preview
+ this.logger.error("Failed generating preview. Reason: unknown resource type - \""
+ + Resource.getResourceTypeName(iType) + "\"");
+ panelToPopulate.add(new JLabel("Cannot generate preview for unknown resource types."));
+ Resource r = new Resource();
+ r.setItemType(iType);
+ r.setTitle("Error: unknown resource type");
+ r.setURI(strURI);
+ return (new ResourcePreviewContent(r, panelToPopulate));
+ }
+
+ // format output data
+ return (new ResourcePreviewContent(resource, panelToPopulate));
+ }
+
+ private void generateWorkflowPreviewContent(Workflow w, JPanel panelToPopulate, EventListener eventHandler) {
+ if (w != null) {
+ try {
+ StringBuffer content = new StringBuffer();
+ content.append("<div class='outer'>");
+ content.append("<div class='workflow'>");
+
+ content.append("<br>");
+
+ content.append("<p class='title'>");
+ content.append("Workflow Entry: <a href='preview:" + Resource.WORKFLOW
+ + ":" + w.getURI() + "'>" + w.getTitle() + "</a> (version "
+ + w.getVersion() + ")");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<p class='info'>");
+ content.append("<b>Type:</b> " + w.getVisibleType() + "<br><br>");
+ content.append("<b>Uploader:</b> <a href='preview:" + Resource.USER
+ + ":" + w.getUploader().getURI() + "'>" + w.getUploader().getName()
+ + "</a><br>");
+ content.append("<b>Created at: </b> " + w.getCreatedAt() + "<br>");
+ content.append("<b>License: </b> <a href='"
+ + w.getLicense().getLink()
+ + "'>"
+ + w.getLicense().getText()
+ + "</a>"
+ + " <img src='"
+ + MyExperimentPerspective.getLocalResourceURL("external_link_small_icon")
+ + "' />");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<a href='" + w.getPreview() + "'>");
+ content.append("<img class='preview' src='" + w.getThumbnailBig()
+ + "'></img>");
+ content.append("</a>");
+
+ content.append("<br>");
+ content.append("<br>");
+
+ if (!w.getDescription().equals("")) {
+ content.append("<p class='desc'>");
+ content.append("<br>");
+ content.append(Util.stripHTML(w.getDescription()));
+ content.append("<br>");
+ content.append("</p>");
+ } else {
+ content.append("<span class='none_text'>No description</span>");
+ }
+
+ content.append("<br>");
+ content.append("</div>");
+ content.append("</div>");
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ // === Now render user's items as Swing components ===
+ // .. TABS for components, tags, comments, credits, attributions ..
+ JScrollPane spComponentsTab = createWorkflowComponentPreviewTab(w);
+ JScrollPane spTagsTab = createTagPreviewTab(w.getTags());
+ JScrollPane spCommentsTab = createCommentsPreviewTab(w.getComments());
+ JScrollPane spCreditsTab = createCreditsPreviewTab(w.getCredits());
+ JScrollPane spAttributionsTab = createAttributionsPreviewTab(w.getAttributions());
+
+ // .. ASSEMBLE ALL TABS together
+ JTabbedPane tpTabbedView = new JTabbedPane();
+ tpTabbedView.add("Components", spComponentsTab);
+ tpTabbedView.add("Tags (" + w.getTags().size() + ")", spTagsTab);
+ tpTabbedView.add("Comments (" + w.getComments().size() + ")", spCommentsTab);
+ tpTabbedView.addTab("Credits (" + w.getCredits().size() + ")", spCreditsTab);
+ tpTabbedView.addTab("Attributions (" + w.getAttributions().size() + ")", spAttributionsTab);
+
+ // PUT EVERYTHING TOGETHER
+ JTextPane tpWorkflowPreview = new JTextPane();
+ tpWorkflowPreview.setEditable(false);
+ tpWorkflowPreview.setEditorKit(kit);
+ tpWorkflowPreview.setDocument(doc);
+ tpWorkflowPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+ JPanel jpFullWorkflowPreview = wrapTextPaneAndTabbedViewIntoFullPreview(tpWorkflowPreview, tpTabbedView);
+
+ // POPULATE THE GIVEN PANEL
+ panelToPopulate.setLayout(new BorderLayout());
+ panelToPopulate.add(jpFullWorkflowPreview, BorderLayout.CENTER);
+
+ // this.statusLabel.setText("Workflow information found. Last fetched: "
+ // + new Date().toString());
+
+ // this.clearButton.setEnabled(true);
+ // this.refreshButton.setEnabled(true);
+ // this.loadButton.setEnabled(true);
+ // this.importButton.setEnabled(true);
+ } catch (Exception e) {
+ logger.error("Failed to populate Workflow Preview pane", e);
+ }
+ } else {
+ // statusLabel.setText("Could not find information for workflow ID: " +
+ // currentWorkflowId);
+ // clearContentTextPane();
+ // disableButtons();
+ }
+ }
+
+ private void generateFilePreviewContent(File f, JPanel panelToPopulate, EventListener eventHandler) {
+ if (f != null) {
+ try {
+ StringBuffer content = new StringBuffer();
+ content.append("<div class='outer'>");
+ content.append("<div class='file'>");
+
+ content.append("<br>");
+
+ content.append("<p class='title'>");
+ content.append("File: <a href='preview:" + Resource.FILE + ":"
+ + f.getURI() + "'>" + f.getTitle() + "</a>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<p class='info'>");
+ content.append("<b>Type:</b> " + f.getVisibleType() + "<br>");
+ content.append("<b>Filename:</b> " + f.getFilename() + "<br><br>");
+ content.append("<b>Uploader:</b> <a href='preview:" + Resource.USER
+ + ":" + f.getUploader().getURI() + "'>" + f.getUploader().getName()
+ + "</a><br>");
+ content.append("<b>Created at: </b> " + f.getCreatedAt() + "<br>");
+ content.append("<b>Last updated at: </b> " + f.getUpdatedAt() + "<br>");
+ content.append("<b>License: </b> <a href='"
+ + f.getLicense().getLink()
+ + "'>"
+ + f.getLicense().getText()
+ + "</a>"
+ + " <img src='"
+ + MyExperimentPerspective.getLocalResourceURL("external_link_small_icon")
+ + "' />");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ if (!f.getDescription().equals("")) {
+ content.append("<p class='desc'>");
+ content.append("<br>");
+ content.append(Util.stripHTML(f.getDescription()));
+ content.append("<br>");
+ content.append("</p>");
+ } else {
+ content.append("<span class='none_text'>No description</span>");
+ }
+
+ content.append("<br>");
+ content.append("</div>");
+ content.append("</div>");
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ // === Now render group's items as Swing components ===
+ // TABS FOR file's tags, credits, etc
+ JScrollPane spTagsTab = createTagPreviewTab(f.getTags());
+ JScrollPane spCommentsTab = createCommentsPreviewTab(f.getComments());
+ JScrollPane spCreditsTab = createCreditsPreviewTab(f.getCredits());
+ JScrollPane spAttributionsTab = createAttributionsPreviewTab(f.getAttributions());
+
+ // ASSEMBLE tabs into tabbed view
+ JTabbedPane tpTabbedView = new JTabbedPane();
+ tpTabbedView.add("Tags (" + f.getTags().size() + ")", spTagsTab);
+ tpTabbedView.add("Comments (" + f.getComments().size() + ")", spCommentsTab);
+ tpTabbedView.add("Credits (" + f.getCredits().size() + ")", spCreditsTab);
+ tpTabbedView.add("Attributions (" + f.getAttributions().size() + ")", spAttributionsTab);
+
+ // PUT EVERYTHING TOGETHER
+ JTextPane tpFilePreview = new JTextPane();
+ tpFilePreview.setEditable(false);
+ tpFilePreview.setEditorKit(kit);
+ tpFilePreview.setDocument(doc);
+ tpFilePreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+ JPanel jpFullFilePreview = new JPanel();
+ jpFullFilePreview.setBackground(Color.WHITE); // white background for
+ // the whole file preview
+ // panel
+ jpFullFilePreview.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 0;
+ c.weighty = 0; // will not change size when the window is resized
+ jpFullFilePreview.add(tpFilePreview, c);
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 1;
+ c.weighty = 1; // will grow in size when the window is resized..
+ c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+ // vertically
+ c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+ // bottom
+ jpFullFilePreview.add(tpTabbedView, c);
+
+ // POPULATE THE GIVEN PANEL
+ panelToPopulate.setLayout(new BorderLayout());
+ panelToPopulate.add(jpFullFilePreview, BorderLayout.CENTER);
+
+ // this.statusLabel.setText("File information found. Last fetched: " +
+ // new Date().toString());
+
+ // this.clearButton.setEnabled(true);
+ // this.refreshButton.setEnabled(true);
+ // this.loadButton.setEnabled(true);
+ // this.importButton.setEnabled(true);
+ } catch (Exception e) {
+ logger.error("Failed to populate File Preview pane", e);
+ }
+ } else {
+ // statusLabel.setText("Could not find information for file ID: " +
+ // currentFileId);
+ // clearContentTextPane();
+ // disableButtons();
+ }
+ }
+
+ private void generatePackPreviewContent(Pack p, JPanel panelToPopulate, EventListener eventHandler) {
+ if (p != null) {
+ try {
+ // === Render pack details in HTML format ===
+ StringBuffer content = new StringBuffer();
+ content.append("<div class='outer'>");
+ content.append("<div class='pack'>");
+
+ content.append("<br>");
+
+ content.append("<p class='title'>");
+ content.append("Pack: <a href='preview:" + Resource.PACK + ":"
+ + p.getURI() + "'>" + p.getTitle() + "</a>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<p class='info'>");
+ content.append("<b>Creator:</b> <a href='preview:" + Resource.USER
+ + ":" + p.getCreator().getURI() + "'>" + p.getCreator().getName()
+ + "</a><br>");
+ content.append("<b>Created at: </b> " + p.getCreatedAt() + "<br>");
+ content.append("<b>Last updated at: </b> " + p.getUpdatedAt() + "<br>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ if (!p.getDescription().equals("")) {
+ content.append("<p class='desc'>");
+ content.append("<br>");
+ content.append(Util.stripHTML(p.getDescription()));
+ content.append("<br>");
+ content.append("<br>");
+ content.append("</p>");
+ } else {
+ content.append("<span class='none_text'>No description</span>");
+ }
+
+ content.append("<br>");
+ content.append("</div>");
+ content.append("</div>");
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ // === Now render group's items as Swing components ===
+ // TABS FOR pack items, tags, etc
+ JScrollPane spPackItemsTab = createPackItemPreviewTab(p);
+ JScrollPane spTagsTab = createTagPreviewTab(p.getTags());
+ JScrollPane spCommentsTab = createCommentsPreviewTab(p.getComments());
+
+ // ASSEMBLE tabs into tabbed view
+ JTabbedPane tpTabbedView = new JTabbedPane();
+ tpTabbedView.addTab("Pack Items (" + p.getItemCount() + ")", spPackItemsTab);
+ tpTabbedView.add("Tags (" + p.getTags().size() + ")", spTagsTab);
+ tpTabbedView.add("Comments (" + p.getComments().size() + ")", spCommentsTab);
+
+ // PUT EVERYTHING TOGETHER
+ JTextPane tpPackPreview = new JTextPane();
+ tpPackPreview.setEditable(false);
+ tpPackPreview.setEditorKit(kit);
+ tpPackPreview.setDocument(doc);
+ tpPackPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+ JPanel jpFullPackPreview = new JPanel();
+ jpFullPackPreview.setBackground(Color.WHITE); // white background for
+ // the whole pack preview
+ // panel
+ jpFullPackPreview.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 0;
+ c.weighty = 0; // will not change size when the window is resized
+ jpFullPackPreview.add(tpPackPreview, c);
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 1;
+ c.weighty = 1; // will grow in size when the window is resized..
+ c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+ // vertically
+ c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+ // bottom
+ jpFullPackPreview.add(tpTabbedView, c);
+
+ // POPULATE THE GIVEN PANEL
+ panelToPopulate.setLayout(new BorderLayout());
+ panelToPopulate.add(jpFullPackPreview, BorderLayout.CENTER);
+
+ // this.statusLabel.setText("Pack information found. Last fetched: " +
+ // new Date().toString());
+
+ // this.clearButton.setEnabled(true);
+ // this.refreshButton.setEnabled(true);
+ // this.loadButton.setEnabled(true);
+ // this.importButton.setEnabled(true);
+ } catch (Exception e) {
+ logger.error("Failed to populate Pack Preview pane", e);
+ }
+ } else {
+ // statusLabel.setText("Could not find information for pack ID: " +
+ // currentPackId);
+ // clearContentTextPane();
+ // disableButtons();
+ }
+ }
+
+ private void generateUserPreviewContent(User u, JPanel panelToPopulate, EventListener eventHandler) {
+ if (u != null) {
+ try {
+ // === Render user details in HTML format ===
+ StringBuffer content = new StringBuffer();
+ content.append("<div class='outer'>");
+ content.append("<div class='user'>");
+
+ content.append("<br>");
+
+ content.append("<p class='name'>");
+ content.append("User: <a href=preview:" + Resource.USER + ":"
+ + u.getURI() + "'>" + u.getName() + "</a>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<p class='info'>");
+ String strLocation;
+ if (u.getCity().length() == 0 && u.getCountry().length() == 0)
+ strLocation = "<span class='none_text'>Not specified</span>";
+ else
+ strLocation = u.getCity()
+ + (u.getCity().length() == 0 || u.getCountry().length() == 0 ? "" : ", ")
+ + u.getCountry();
+ content.append("<b>Location:</b> " + strLocation + "<br>");
+ content.append("<b>Joined at: </b> " + u.getCreatedAt() + "<br>");
+ content.append("<b>Last seen at: </b> " + u.getUpdatedAt() + "<br>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<a href='" + u.getAvatarResource() + "'>");
+ content.append("<img class='avatar' src='" + u.getAvatarResource()
+ + "'></img>");
+ content.append("</a>");
+
+ content.append("<br>");
+ content.append("<br>");
+
+ if (!u.getDescription().equals("")) {
+ // HACK: the way JAVA renders html causes styling not to be inherited;
+ // hence need to
+ // remove any nested <p> or <div> tags to get a proper layout
+ content.append("<p class='desc'>"
+ + Util.stripHTML(u.getDescription()) + "<br><br></p>");
+ } else {
+ content.append("<span class='none_text'>No description</span>");
+ }
+
+ content.append("<p class='contact_details_header'>Contact Details</p>");
+ content.append("<p class='contact_details'>");
+ content.append("<b>Email: </b>"
+ + (u.getEmail().length() == 0 ? "<span class='none_text'>Not specified</span>" : u.getEmail())
+ + "<br>");
+ content.append("<b>Website: </b>"
+ + (u.getWebsite().length() == 0 ? "<span class='none_text'>Not specified</span>" : u.getWebsite()));
+ content.append("</p>");
+
+ content.append("</div>");
+ content.append("</div>");
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ // === Now render user's items as Swing components ===
+ // .. WORKFLOWS ..
+ JPanel jpWorkflowsTabContent = new JPanel();
+ jpWorkflowsTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ jpWorkflowsTabContent.setLayout(new BoxLayout(jpWorkflowsTabContent, BoxLayout.Y_AXIS));
+
+ // iterate through all workflows and add all to the panel
+ Iterator<HashMap<String, String>> iWorkflows = u.getWorkflows().iterator();
+ while (iWorkflows.hasNext()) {
+ HashMap<String, String> hmCurWF = iWorkflows.next();
+ jpWorkflowsTabContent.add(new JClickableLabel(hmCurWF.get("name"), "preview:"
+ + Resource.WORKFLOW + ":" + hmCurWF.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconWorkflow));
+ }
+
+ // .. FILES ..
+ JPanel jpFilesTabContent = new JPanel();
+ jpFilesTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ jpFilesTabContent.setLayout(new BoxLayout(jpFilesTabContent, BoxLayout.Y_AXIS));
+
+ // iterate through all files and add all to the panel
+ Iterator<HashMap<String, String>> iFiles = u.getFiles().iterator();
+ while (iFiles.hasNext()) {
+ HashMap<String, String> hmCurFile = iFiles.next();
+ jpFilesTabContent.add(new JClickableLabel(hmCurFile.get("name"), "preview:"
+ + Resource.FILE + ":" + hmCurFile.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconFile));
+ }
+
+ // .. PACKS ..
+ JPanel jpPacksTabContent = new JPanel();
+ jpPacksTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ jpPacksTabContent.setLayout(new BoxLayout(jpPacksTabContent, BoxLayout.Y_AXIS));
+
+ // iterate through all packs and add all to the panel
+ Iterator<HashMap<String, String>> iPacks = u.getPacks().iterator();
+ while (iPacks.hasNext()) {
+ HashMap<String, String> hmCurPack = iPacks.next();
+ jpPacksTabContent.add(new JClickableLabel(hmCurPack.get("name"), "preview:"
+ + Resource.PACK + ":" + hmCurPack.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconPack));
+ }
+
+ // .. FRIENDS ..
+ JPanel jpFriendsTabContent = new JPanel();
+ jpFriendsTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ jpFriendsTabContent.setLayout(new BoxLayout(jpFriendsTabContent, BoxLayout.Y_AXIS));
+
+ // iterate through all friends and add all to the panel
+ Iterator<HashMap<String, String>> iFriends = u.getFriends().iterator();
+ while (iFriends.hasNext()) {
+ HashMap<String, String> hmCurFriend = iFriends.next();
+ jpFriendsTabContent.add(new JClickableLabel(hmCurFriend.get("name"), "preview:"
+ + Resource.USER + ":" + hmCurFriend.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconUser));
+ }
+
+ // .. GROUPS ..
+ JPanel jpGroupsTabContent = new JPanel();
+ jpGroupsTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ jpGroupsTabContent.setLayout(new BoxLayout(jpGroupsTabContent, BoxLayout.Y_AXIS));
+
+ // iterate through all groups and add all to the panel
+ Iterator<HashMap<String, String>> iGroups = u.getGroups().iterator();
+ while (iGroups.hasNext()) {
+ HashMap<String, String> hmCurGroup = iGroups.next();
+ jpGroupsTabContent.add(new JClickableLabel(hmCurGroup.get("name"), "preview:"
+ + Resource.GROUP + ":" + hmCurGroup.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconGroup));
+ }
+
+ // .. WRAP EVERY TAB content into it's own scroll pane ..
+ Dimension dPreferredTabSize = new Dimension(ResourcePreviewBrowser.PREFERRED_WIDTH - 50, PREFERRED_LOWER_TABBED_PANE_HEIGHT);
+
+ JScrollPane spWorkflowsTab = new JScrollPane(jpWorkflowsTabContent);
+ spWorkflowsTab.setBorder(BorderFactory.createEmptyBorder());
+ spWorkflowsTab.setPreferredSize(dPreferredTabSize);
+ spWorkflowsTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ JScrollPane spFilesTab = new JScrollPane(jpFilesTabContent);
+ spFilesTab.setBorder(BorderFactory.createEmptyBorder());
+ spFilesTab.setPreferredSize(dPreferredTabSize);
+ spFilesTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ JScrollPane spPacksTab = new JScrollPane(jpPacksTabContent);
+ spPacksTab.setBorder(BorderFactory.createEmptyBorder());
+ spPacksTab.setPreferredSize(dPreferredTabSize);
+ spPacksTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ JScrollPane spFriendsTab = new JScrollPane(jpFriendsTabContent);
+ spFriendsTab.setBorder(BorderFactory.createEmptyBorder());
+ spFriendsTab.setPreferredSize(dPreferredTabSize);
+ spFriendsTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ JScrollPane spGroupsTab = new JScrollPane(jpGroupsTabContent);
+ spGroupsTab.setBorder(BorderFactory.createEmptyBorder());
+ spGroupsTab.setPreferredSize(dPreferredTabSize);
+ spGroupsTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ // .. ASSEMBLE ALL TABS together
+ JTabbedPane tpTabbedItems = new JTabbedPane();
+ tpTabbedItems.addTab("Workflows (" + u.getWorkflows().size() + ")", spWorkflowsTab);
+ tpTabbedItems.addTab("Files (" + u.getFiles().size() + ")", spFilesTab);
+ tpTabbedItems.addTab("Packs (" + u.getPacks().size() + ")", spPacksTab);
+ tpTabbedItems.addTab("Friends (" + u.getFriends().size() + ")", spFriendsTab);
+ tpTabbedItems.addTab("Groups (" + u.getGroups().size() + ")", spGroupsTab);
+
+ // === PUT EVERYTHING TOGETHER ===
+ JTextPane tpUserPreview = new JTextPane();
+ tpUserPreview.setEditable(false);
+ tpUserPreview.setEditorKit(kit);
+ tpUserPreview.setDocument(doc);
+ tpUserPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+ JPanel jpFullUserPreview = new JPanel();
+ jpFullUserPreview.setBackground(Color.WHITE); // white background for
+ // the whole user preview
+ // panel
+ jpFullUserPreview.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 0;
+ c.weighty = 0; // will not change size when the window is resized
+ jpFullUserPreview.add(tpUserPreview, c);
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 1;
+ c.weighty = 1; // will grow in size when the window is resized..
+ c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+ // vertically
+ c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+ // bottom
+ jpFullUserPreview.add(tpTabbedItems, c);
+
+ // POPULATE THE GIVEN PANEL
+ panelToPopulate.setLayout(new BorderLayout());
+ panelToPopulate.add(jpFullUserPreview, BorderLayout.CENTER);
+
+ // this.statusLabel.setText("User information found. Last fetched: " +
+ // new Date().toString());
+
+ // this.clearButton.setEnabled(true);
+ // this.refreshButton.setEnabled(true);
+ // this.loadButton.setEnabled(true);
+ // this.importButton.setEnabled(true);
+ } catch (Exception e) {
+ logger.error("Failed to populate Workflow Preview pane", e);
+ }
+ } else {
+ // statusLabel.setText("Could not find information for workflow ID: " +
+ // currentWorkflowId);
+ // clearContentTextPane();
+ // disableButtons();
+ }
+ }
+
+ private void generateGroupPreviewContent(Group g, JPanel panelToPopulate, EventListener eventHandler) {
+ if (g != null) {
+ try {
+ // === Render group details in HTML format ===
+ StringBuffer content = new StringBuffer();
+ content.append("<div class='outer'>");
+ content.append("<div class='group'>");
+
+ content.append("<br>");
+
+ content.append("<p class='title'>");
+ content.append("Group: <a href='preview:" + Resource.GROUP + ":"
+ + g.getURI() + "'>" + g.getTitle() + "</a>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ content.append("<p class='info'>");
+ content.append("<b>Administrator:</b> <a href='preview:"
+ + Resource.USER + ":" + g.getAdmin().getURI() + "'>"
+ + g.getAdmin().getName() + "</a><br>");
+ content.append("<b>Created at: </b> " + g.getCreatedAt() + "<br>");
+ content.append("</p>");
+
+ content.append("<br>");
+
+ if (!g.getDescription().equals("")) {
+ content.append("<p class='desc'>");
+ content.append("<br>");
+ content.append(Util.stripHTML(g.getDescription()));
+ content.append("<br>");
+ content.append("</p>");
+ } else {
+ content.append("<span class='none_text'>No description</span>");
+ }
+
+ content.append("<br>");
+ content.append("</div>");
+ content.append("</div>");
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ // === Now render group's items as Swing components ===
+
+ // .. MEMBERS ..
+ JPanel jpMembersTabContent = createStandardTabContentPanel();
+
+ // iterate through all shared items and add all to the panel
+ Iterator<User> iMembers = g.getMembers().iterator();
+ while (iMembers.hasNext()) {
+ User uCurMember = iMembers.next();
+ jpMembersTabContent.add(new JClickableLabel(uCurMember.getName(), "preview:"
+ + uCurMember.getItemType() + ":" + uCurMember.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(uCurMember.getItemType()))));
+ }
+
+ // wrap into a standard scroll pane
+ JScrollPane spMembersTabContent = wrapPreviewTabContentIntoScrollPane(jpMembersTabContent);
+
+ // .. SHARED ITEMS ..
+ JPanel jpSharedItemsTabContent = createStandardTabContentPanel();
+
+ // iterate through all shared items and add all to the panel
+ Iterator<Resource> iSharedItems = g.getSharedItems().iterator();
+ while (iSharedItems.hasNext()) {
+ Resource rCurItem = iSharedItems.next();
+ jpSharedItemsTabContent.add(new JClickableLabel(rCurItem.getTitle(), "preview:"
+ + rCurItem.getItemType() + ":" + rCurItem.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(rCurItem.getItemType()))));
+ }
+
+ // wrap into a standard scroll pane
+ JScrollPane spSharedItemsTabContent = wrapPreviewTabContentIntoScrollPane(jpSharedItemsTabContent);
+
+ // .. TAGS, COMMENTS ..
+ JScrollPane spTagsTabContent = createTagPreviewTab(g.getTags());
+ JScrollPane spCommentsTab = createCommentsPreviewTab(g.getComments());
+
+ // ASSEMBLE tabs together
+ JTabbedPane tpTabbedItems = new JTabbedPane();
+ tpTabbedItems.addTab("Members (" + g.getMemberCount() + ")", spMembersTabContent);
+ tpTabbedItems.addTab("Shared Items (" + g.getSharedItemCount() + ")", spSharedItemsTabContent);
+ tpTabbedItems.addTab("Tags (" + g.getTags().size() + ")", spTagsTabContent);
+ tpTabbedItems.addTab("Comments (" + g.getComments().size() + ")", spCommentsTab);
+
+ // PUT EVERYTHING TOGETHER
+ JTextPane tpGroupPreview = new JTextPane();
+ tpGroupPreview.setEditable(false);
+ tpGroupPreview.setEditorKit(kit);
+ tpGroupPreview.setDocument(doc);
+ tpGroupPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+ JPanel jpFullGroupPreview = new JPanel();
+ jpFullGroupPreview.setBackground(Color.WHITE); // white background for
+ // the whole group
+ // preview panel
+ jpFullGroupPreview.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 0;
+ c.weighty = 0; // will not change size when the window is resized
+ jpFullGroupPreview.add(tpGroupPreview, c);
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 1;
+ c.weighty = 1; // will grow in size when the window is resized..
+ c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+ // vertically
+ c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+ // bottom
+ jpFullGroupPreview.add(tpTabbedItems, c);
+
+ // POPULATE THE GIVEN PANEL
+ panelToPopulate.setLayout(new BorderLayout());
+ panelToPopulate.add(jpFullGroupPreview, BorderLayout.CENTER);
+
+ // this.statusLabel.setText("Group information found. Last fetched: " +
+ // new Date().toString());
+
+ // this.clearButton.setEnabled(true);
+ // this.refreshButton.setEnabled(true);
+ // this.loadButton.setEnabled(true);
+ // this.importButton.setEnabled(true);
+ } catch (Exception e) {
+ logger.error("Failed to populate Group Preview pane", e);
+ }
+ } else {
+ // statusLabel.setText("Could not find information for group ID: " +
+ // currentGroupId);
+ // clearContentTextPane();
+ // disableButtons();
+ }
+ }
+
+ // *** Helper methods follow that generate particular reusable pieces of the
+ // previews ***
+
+ private JScrollPane createWorkflowComponentPreviewTab(Workflow w) {
+ final JPanel jpWorkflowComponentsTabContent = createStandardTabContentPanel();
+
+ if (!w.isTavernaWorkflow()) {
+ // can only display components for Taverna 1 workflows at the moment
+ JLabel lNotSupported = new JLabel("<html>This is a "
+ + w.getVisibleType()
+ + " workflow;<br>myExperiment can only display Taverna workflow components at the moment.</html>");
+ lNotSupported.setFont(lNotSupported.getFont().deriveFont(Font.ITALIC));
+ lNotSupported.setForeground(Color.GRAY);
+ jpWorkflowComponentsTabContent.add(lNotSupported);
+ } else if (!w.isDownloadAllowed()) {
+ // can display components for workflow of this type, but current user is
+ // not
+ // allowed to download this workflow - and, hence, to view its components
+ JLabel lNotAuthorized = new JLabel("You are not authorised to download this workflow, "
+ + "and hence component preview is not available.");
+ lNotAuthorized.setFont(lNotAuthorized.getFont().deriveFont(Font.ITALIC));
+ lNotAuthorized.setForeground(Color.GRAY);
+ jpWorkflowComponentsTabContent.add(lNotAuthorized);
+ } else {
+ // can display the components
+
+ // storage for table column names
+ Vector<String> vColumnNames = new Vector<String>();
+
+ // ** inputs **
+ vColumnNames.clear();
+ vColumnNames.addAll(Arrays.asList(new String[] { "Name", "Description" }));
+
+ Vector<Vector<String>> vInputsData = new Vector<Vector<String>>();
+ ArrayList<HashMap<String, String>> inputs = w.getComponents().get("inputs");
+ if (inputs != null) {
+ for (HashMap<String, String> curInput : inputs) {
+ Vector<String> vCurData = new Vector<String>();
+ vCurData.add(curInput.get("name"));
+ vCurData.add(curInput.get("description"));
+
+ vInputsData.add(vCurData);
+ }
+ }
+
+ JTable jtInputs = new JTable(vInputsData, vColumnNames);
+ jtInputs.getColumnModel().getColumn(0).setPreferredWidth(100);
+ jtInputs.getTableHeader().setFont(jtInputs.getTableHeader().getFont().deriveFont(Font.BOLD));
+ JPanel jpInputs = new JPanel();
+ jpInputs.setLayout(new BorderLayout());
+ jpInputs.add(jtInputs.getTableHeader(), BorderLayout.NORTH);
+ jpInputs.add(jtInputs, BorderLayout.CENTER);
+
+ JPanel jpInputsWithTitle = new JPanel();
+ jpInputsWithTitle.setBorder(BorderFactory.createEtchedBorder());
+ jpInputsWithTitle.setLayout(new BorderLayout());
+ jpInputsWithTitle.add(new ShadedLabel("Workflow input ports ("
+ + vInputsData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+ if (vInputsData.size() > 0) {
+ jpInputsWithTitle.add(jpInputs, BorderLayout.CENTER);
+ }
+
+ // ** processors **
+ vColumnNames.clear();
+ vColumnNames.addAll(Arrays.asList(new String[] { "Name", "Type", "Description" }));
+
+ Vector<Vector<String>> vProcessorsData = new Vector<Vector<String>>();
+ ArrayList<HashMap<String, String>> processors = w.getComponents().get("processors");
+ if (processors != null) {
+ for (HashMap<String, String> curProcessor : processors) {
+ Vector<String> vCurData = new Vector<String>();
+ vCurData.add(curProcessor.get("name"));
+ vCurData.add(curProcessor.get("type"));
+ vCurData.add(curProcessor.get("description"));
+
+ vProcessorsData.add(vCurData);
+ }
+ }
+
+ JTable jtProcessors = new JTable(vProcessorsData, vColumnNames);
+ jtProcessors.getTableHeader().setFont(jtProcessors.getTableHeader().getFont().deriveFont(Font.BOLD));
+ JPanel jpProcessors = new JPanel();
+ jpProcessors.setLayout(new BorderLayout());
+ jpProcessors.add(jtProcessors.getTableHeader(), BorderLayout.NORTH);
+ jpProcessors.add(jtProcessors, BorderLayout.CENTER);
+
+ JPanel jpProcessorsWithTitle = new JPanel();
+ jpProcessorsWithTitle.setBorder(BorderFactory.createEtchedBorder());
+ jpProcessorsWithTitle.setLayout(new BorderLayout());
+ jpProcessorsWithTitle.add(new ShadedLabel("Services ("
+ + vProcessorsData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+ if (vProcessorsData.size() > 0) {
+ jpProcessorsWithTitle.add(jpProcessors, BorderLayout.CENTER);
+ }
+
+ // ** links **
+ vColumnNames.clear();
+ vColumnNames.addAll(Arrays.asList(new String[] { "Source", "Sink" }));
+
+ Vector<Vector<String>> vLinksData = new Vector<Vector<String>>();
+ ArrayList<HashMap<String, String>> links = w.getComponents().get("links");
+ if (links != null) {
+ for (HashMap<String, String> curLink : links) {
+ Vector<String> vCurData = new Vector<String>();
+ vCurData.add(curLink.get("source"));
+ vCurData.add(curLink.get("sink"));
+
+ vLinksData.add(vCurData);
+ }
+ }
+
+ JTable jtLinks = new JTable(vLinksData, vColumnNames);
+ jtLinks.getColumnModel().getColumn(0).setPreferredWidth(100);
+ jtLinks.getTableHeader().setFont(jtLinks.getTableHeader().getFont().deriveFont(Font.BOLD));
+ JPanel jpLinks = new JPanel();
+ jpLinks.setLayout(new BorderLayout());
+ jpLinks.add(jtLinks.getTableHeader(), BorderLayout.NORTH);
+ jpLinks.add(jtLinks, BorderLayout.CENTER);
+
+ JPanel jpLinksWithTitle = new JPanel();
+ jpLinksWithTitle.setBorder(BorderFactory.createEtchedBorder());
+ jpLinksWithTitle.setLayout(new BorderLayout());
+ jpLinksWithTitle.add(new ShadedLabel("Links (" + vLinksData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+ if (vLinksData.size() > 0) {
+ jpLinksWithTitle.add(jpLinks, BorderLayout.CENTER);
+ }
+
+ // ** outputs **
+ vColumnNames.clear();
+ vColumnNames.addAll(Arrays.asList(new String[] { "Name", "Description" }));
+
+ Vector<Vector<String>> vOutputsData = new Vector<Vector<String>>();
+ ArrayList<HashMap<String, String>> outputs = w.getComponents().get("outputs");
+ if (outputs != null) {
+ for (HashMap<String, String> curOutput : outputs) {
+ Vector<String> vCurData = new Vector<String>();
+ vCurData.add(curOutput.get("name"));
+ vCurData.add(curOutput.get("description"));
+
+ vOutputsData.add(vCurData);
+ }
+ }
+
+ JTable jtOutputs = new JTable(vOutputsData, vColumnNames);
+ jtOutputs.getColumnModel().getColumn(0).setPreferredWidth(100);
+ jtOutputs.getTableHeader().setFont(jtOutputs.getTableHeader().getFont().deriveFont(Font.BOLD));
+ JPanel jpOutputs = new JPanel();
+ jpOutputs.setLayout(new BorderLayout());
+ jpOutputs.add(jtOutputs.getTableHeader(), BorderLayout.NORTH);
+ jpOutputs.add(jtOutputs, BorderLayout.CENTER);
+
+ JPanel jpOutputsWithTitle = new JPanel();
+ jpOutputsWithTitle.setBorder(BorderFactory.createEtchedBorder());
+ jpOutputsWithTitle.setLayout(new BorderLayout());
+ jpOutputsWithTitle.add(new ShadedLabel("Workflow output ports ("
+ + vOutputsData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+ if (vOutputsData.size() > 0) {
+ jpOutputsWithTitle.add(jpOutputs, BorderLayout.CENTER);
+ }
+
+ // PUT EVERYTHING TOGETHER
+ jpWorkflowComponentsTabContent.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = 0;
+ c.gridy = GridBagConstraints.RELATIVE;
+ c.weightx = 1.0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.NORTH;
+ jpWorkflowComponentsTabContent.add(jpInputsWithTitle, c);
+
+ c.insets = new Insets(10, 0, 0, 0);
+ jpWorkflowComponentsTabContent.add(jpProcessorsWithTitle, c);
+
+ jpWorkflowComponentsTabContent.add(jpLinksWithTitle, c);
+
+ c.weighty = 1.0; // ONLY FOR THE LAST ELEMENT
+ jpWorkflowComponentsTabContent.add(jpOutputsWithTitle, c);
+ }
+
+ return (wrapPreviewTabContentIntoScrollPane(jpWorkflowComponentsTabContent));
+ }
+
+ private JScrollPane createPackItemPreviewTab(Pack p) {
+ JPanel jpPackItemsTabContent = createStandardTabContentPanel();
+ GridBagConstraints c = new GridBagConstraints();
+ jpPackItemsTabContent.setLayout(new GridBagLayout());
+ c.anchor = GridBagConstraints.NORTHWEST;
+
+ // iterate through all internal and external items and add all to the panel
+ if (p.getItems().size() > 0) {
+ int iCnt = 0;
+ boolean bNoCommentForPrevItem = false;
+
+ for (PackItem piCurItem : p.getItems()) {
+ c.gridx = 0;
+ c.gridy = 3 * iCnt;
+ c.weightx = 1.0;
+ c.insets = (bNoCommentForPrevItem ? new Insets(7, 0, 0, 0) : new Insets(0, 0, 0, 0));
+ c.fill = GridBagConstraints.NONE;
+ // item data is stored differently whether the item is internal or
+ // external
+ if (piCurItem.isInternalItem()) {
+ jpPackItemsTabContent.add(new JClickableLabel(piCurItem.getItem().getTitle(), "preview:"
+ + piCurItem.getItem().getItemType()
+ + ":"
+ + piCurItem.getItem().getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(piCurItem.getItem().getItemType()))), c);
+ } else {
+ jpPackItemsTabContent.add(new JClickableLabel(piCurItem.getTitle(), piCurItem.getLink(), // link should open up directly in the web
+ // browser
+ pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(piCurItem.getItemType())), SwingConstants.LEFT, piCurItem.getTitle()
+ + " (link: " + piCurItem.getLink() + ")"), c);
+ }
+
+ // prepare comment before populating the metadata
+ String strComment = Util.stripAllHTML(piCurItem.getComment());
+ bNoCommentForPrevItem = (strComment == null || strComment.length() == 0);
+
+ // add metadata to the item ..
+ // .. who and when added the item ..
+ JPanel jpWhoAddedTheItem = new JPanel();
+ jpWhoAddedTheItem.setLayout(new GridBagLayout());
+ GridBagConstraints c1 = new GridBagConstraints();
+ c1.anchor = GridBagConstraints.NORTHWEST;
+ jpWhoAddedTheItem.setBorder(BorderFactory.createEmptyBorder());
+ jpWhoAddedTheItem.add(new JLabel("Added by "), c1);
+ jpWhoAddedTheItem.add(new JClickableLabel(piCurItem.getUserWhoAddedTheItem().getName(), "preview:"
+ + Resource.USER + ":" + piCurItem.getUserWhoAddedTheItem().getURI(), pluginMainComponent.getPreviewBrowser()), c1);
+
+ String strAddedOnDate = MyExperimentClient.formatDate(piCurItem.getCreatedAt());
+ c1.weightx = 1.0;
+ jpWhoAddedTheItem.add(new JLabel(" [" + strAddedOnDate + "]"), c1);
+
+ c.gridx = 0;
+ c.gridy = 3 * iCnt + 1;
+ c.insets = new Insets(0, 25, 0, 0);
+ c.weightx = 1.0;
+ if (bNoCommentForPrevItem && (iCnt + 1 == p.getItems().size()))
+ c.weighty = 1.0;
+ jpPackItemsTabContent.add(jpWhoAddedTheItem, c);
+
+ // .. and the comment
+ if (!bNoCommentForPrevItem) {
+ c.gridx = 0;
+ c.gridy = 3 * iCnt + 2;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.insets = new Insets(0, 25, 7, 25);
+ c.weightx = 1.0;
+ if (iCnt + 1 == p.getItems().size())
+ c.weighty = 1.0; // only if this is the comment for the last item,
+ // shift all items to the top of the panel
+
+ DialogTextArea taCommentText = new DialogTextArea("Comment: "
+ + strComment);
+ taCommentText.setOpaque(false);
+ taCommentText.setEditable(false);
+ taCommentText.setLineWrap(true);
+ taCommentText.setWrapStyleWord(true);
+ jpPackItemsTabContent.add(taCommentText, c);
+ }
+
+ // update the item counter
+ iCnt++;
+ }
+ } else {
+ c.weighty = 1.0;
+ c.weightx = 1.0;
+ jpPackItemsTabContent.add(Util.generateNoneTextLabel("None"), c);
+ }
+
+ return (wrapPreviewTabContentIntoScrollPane(jpPackItemsTabContent));
+ }
+
+ private JScrollPane createTagPreviewTab(List<Tag> lTags) {
+ TagCloudPanel jpTagTabContent = new TagCloudPanel("Resource tag cloud", TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW, pluginMainComponent.getPreviewBrowser(), pluginMainComponent, myExperimentClient, logger);
+ jpTagTabContent.getTagCloudData().clear();
+ jpTagTabContent.getTagCloudData().addAll(lTags);
+ jpTagTabContent.refresh();
+
+ // tag cloud panel itself already has a scroll pane in it; hence the outer
+ // scroll pane
+ // is only used for consistency across the user interface (contents of all
+ // tabs in the
+ // preview window are scroll panes); the preferred size of the tag cloud
+ // panel should
+ // still be adjusted accordingly to the preferred size of the outer scroll
+ // pane
+ JScrollPane spTagTabContent = wrapPreviewTabContentIntoScrollPane(jpTagTabContent);
+ spTagTabContent.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ spTagTabContent.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+
+ jpTagTabContent.setPreferredSize(spTagTabContent.getPreferredSize());
+
+ return (spTagTabContent);
+ }
+
+ private JScrollPane createCommentsPreviewTab(List<Comment> comments) {
+ final List<Comment> lComments = comments;
+ final JPanel jpCommentsTabContent = createStandardTabContentPanel();
+
+ if (lComments.size() > 0) {
+ final GridBagConstraints c = new GridBagConstraints();
+ jpCommentsTabContent.setLayout(new GridBagLayout());
+ c.anchor = GridBagConstraints.NORTHWEST;
+
+ // a placeholder for comments while they are loading
+ JLabel lLoading = new JLabel("Loading comments...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.LEFT);
+ lLoading.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 10));
+ c.weightx = 1.0;
+ c.weighty = 1.0;
+ jpCommentsTabContent.add(lLoading, c);
+
+ new Thread("Load comments for preview") {
+ @Override
+ public void run() {
+ myExperimentClient.updateCommentListWithExtraData(lComments);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // remove 'loading...' placeholder
+ jpCommentsTabContent.removeAll();
+
+ int iCnt = 0;
+ for (Comment comment : lComments) {
+ c.gridx = 0;
+ c.gridy = 2 * iCnt;
+ c.weightx = 0;
+ c.weighty = 0;
+ c.gridwidth = 1;
+ JClickableLabel lCommentAuthor = new JClickableLabel(comment.getUser().getName(), "preview:"
+ + comment.getUser().getItemType()
+ + ":"
+ + comment.getUser().getURI(), pluginMainComponent.getPreviewBrowser(), iconUser);
+ jpCommentsTabContent.add(lCommentAuthor, c);
+
+ c.gridx = 1;
+ c.gridy = 2 * iCnt;
+ c.weightx = 1.0;
+ String strCommentDate = MyExperimentClient.formatDate(comment.getCreatedAt());
+ JLabel lCommentDate = new JLabel(" - [" + strCommentDate + "]");
+ lCommentDate.setBorder(lCommentAuthor.getBorder());
+ jpCommentsTabContent.add(lCommentDate, c);
+
+ c.gridx = 0;
+ c.gridy = 2 * iCnt + 1;
+ c.weightx = 1.0;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ if (iCnt + 1 == lComments.size())
+ c.weighty = 1.0;
+
+ DialogTextArea taCommentText = new DialogTextArea(Util.stripAllHTML(comment.getComment()));
+ taCommentText.setBorder(BorderFactory.createEmptyBorder(0, 25, 10, 25));
+ taCommentText.setOpaque(false);
+ taCommentText.setEditable(false);
+ taCommentText.setLineWrap(true);
+ taCommentText.setWrapStyleWord(true);
+ jpCommentsTabContent.add(taCommentText, c);
+
+ iCnt++;
+ }
+
+ jpCommentsTabContent.validate();
+ jpCommentsTabContent.repaint();
+
+ // this will ensure that even if there are many comments,
+ // the comment panel is still shown starting at the top comment
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jpCommentsTabContent.scrollRectToVisible(new Rectangle());
+ }
+ });
+ }
+ });
+ }
+ }.start();
+ } else {
+ jpCommentsTabContent.add(Util.generateNoneTextLabel("None"));
+ }
+
+ JScrollPane spCommentsTabContent = wrapPreviewTabContentIntoScrollPane(jpCommentsTabContent);
+ spCommentsTabContent.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ return (spCommentsTabContent);
+ }
+
+ private JScrollPane createCreditsPreviewTab(List<Resource> lCreditedUsersOrGroups) {
+ JPanel jpCreditsTabContent = createStandardTabContentPanel();
+
+ if (lCreditedUsersOrGroups.size() > 0) {
+ for (Resource r : lCreditedUsersOrGroups) {
+ jpCreditsTabContent.add(new JClickableLabel(r.getTitle(), "preview:"
+ + r.getItemType() + ":" + r.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(r.getItemType()))));
+ }
+ } else {
+ jpCreditsTabContent.add(Util.generateNoneTextLabel("None"));
+ }
+
+ return (wrapPreviewTabContentIntoScrollPane(jpCreditsTabContent));
+ }
+
+ private JScrollPane createAttributionsPreviewTab(List<Resource> lAttributions) {
+ JPanel jpAttributionsTabContent = createStandardTabContentPanel();
+
+ if (lAttributions.size() > 0) {
+ for (Resource r : lAttributions) {
+ jpAttributionsTabContent.add(new JClickableLabel(r.getTitle(), "preview:"
+ + r.getItemType() + ":" + r.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(r.getItemType()))));
+ }
+ } else {
+ jpAttributionsTabContent.add(Util.generateNoneTextLabel("None"));
+ }
+
+ return (wrapPreviewTabContentIntoScrollPane(jpAttributionsTabContent));
+ }
+
+ /**
+ * A standard starting point for all preview window tabs.
+ */
+ private JPanel createStandardTabContentPanel() {
+ JPanel jpTabContentPanel = new JPanel();
+ jpTabContentPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+ jpTabContentPanel.setLayout(new BoxLayout(jpTabContentPanel, BoxLayout.Y_AXIS));
+
+ return (jpTabContentPanel);
+ }
+
+ private JScrollPane wrapPreviewTabContentIntoScrollPane(JPanel jpTabContentPanel) {
+ // WRAPS TAB CONTENT into it's own SCROLL PANE ..
+ Dimension dPreferredTabSize = new Dimension(ResourcePreviewBrowser.PREFERRED_WIDTH - 50, PREFERRED_LOWER_TABBED_PANE_HEIGHT);
+
+ JScrollPane spTabContent = new JScrollPane(jpTabContentPanel);
+ spTabContent.setBorder(BorderFactory.createEmptyBorder());
+ spTabContent.setPreferredSize(dPreferredTabSize);
+ spTabContent.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ return (spTabContent);
+ }
+
+ private JPanel wrapTextPaneAndTabbedViewIntoFullPreview(JTextPane tpHTMLPreview, JTabbedPane tpTabbedView) {
+ // WRAPS HTML JTextPane PREVIEW AND A JTabbedPane WITH DETAILS INTO A SINGLE
+ // PREVIEW PANEL
+ JPanel jpFullPreview = new JPanel();
+ jpFullPreview.setBackground(Color.WHITE); // white background for the whole
+ // preview panel
+ jpFullPreview.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 0;
+ c.weighty = 0; // will not change size when the window is resized
+ jpFullPreview.add(tpHTMLPreview, c);
+
+ c.gridx = GridBagConstraints.REMAINDER;
+ c.gridy = 1;
+ c.weighty = 1; // will grow in size when the window is resized..
+ c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+ // vertically
+ c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top & bottom
+ jpFullPreview.add(tpTabbedView, c);
+
+ return (jpFullPreview);
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java
new file mode 100644
index 0000000..b2fd9f5
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java
@@ -0,0 +1,317 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Dimension;
+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.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.SpinnerNumberModel;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira
+ */
+public class SearchOptionsPanel extends JPanel implements ItemListener, KeyListener {
+ // CONSTANTS
+ protected static final int SEARCH_RESULT_LIMIT_MIN = 1;
+ protected static final int SEARCH_RESULT_LIMIT_INIT = 20;
+ protected static final int SEARCH_RESULT_LIMIT_MAX = 100;
+
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+ private final ActionListener clickHandler;
+
+ // COMPONENTS
+ protected JButton bSearch;
+ private JTextField tfSearchQuery;
+ private JCheckBox cbSearchAllTypes;
+ private JCheckBox cbWorkflows;
+ private JCheckBox cbFiles;
+ private JCheckBox cbPacks;
+ private JCheckBox cbUsers;
+ private JCheckBox cbGroups;
+ protected JSpinner jsResultLimit;
+ protected JTextField tfResultLimitTextField;
+
+ // Data
+ ArrayList<JCheckBox> alDataTypeCheckboxes;
+
+ public SearchOptionsPanel(ActionListener actionListener, MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ this.clickHandler = actionListener;
+
+ this.initialiseUI();
+
+ // this will hold the collection of all checkboxes that correspond to data types (will be used in item event handling)
+ alDataTypeCheckboxes = new ArrayList<JCheckBox>(Arrays.asList(new JCheckBox[] { cbWorkflows, cbFiles, cbPacks, cbUsers, cbGroups }));
+ }
+
+ private void initialiseUI() {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Search Settings "), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
+
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+ this.add(new JLabel("Query"), c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1.0;
+ tfSearchQuery = new JTextField();
+ tfSearchQuery.setToolTipText("<html>Tips for creating search queries:<br>1) Use wildcards to make more "
+ + "flexible queries. Asterisk (<b>*</b>) matches any zero or more<br> characters (e.g. "
+ + "<b><i>Seq*</i></b> would match <b><i>Sequence</i></b>), question mark (<b>?</b>) matches any single<br>"
+ + " character (e.g. <b><i>Tave?na</i></b> would match <b><i>Taverna</i></b>).<br>"
+ + "2) Enclose the <b><i>\"search query\"</i></b> in double quotes to make exact phrase matching, otherwise<br>"
+ + " items that contain any (or all) words in the <b><i>search query</i></b> will be found.</html>");
+ tfSearchQuery.addKeyListener(this);
+ this.add(tfSearchQuery, c);
+
+ c.gridx = 4;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0;
+ c.insets = new Insets(0, 5, 0, 0);
+ bSearch = new JButton("Search", WorkbenchIcons.searchIcon);
+ bSearch.addActionListener(this.clickHandler);
+ bSearch.addKeyListener(this);
+ this.add(bSearch, c);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ c.insets = new Insets(10, 0, 3, 0);
+ this.add(new JLabel("Search for..."), c);
+
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridwidth = 2;
+ c.insets = new Insets(0, 0, 0, 0);
+ cbSearchAllTypes = new JCheckBox("all resource types", true);
+ cbSearchAllTypes.addItemListener(this);
+ cbSearchAllTypes.addKeyListener(this);
+ this.add(cbSearchAllTypes, c);
+
+ c.gridx = 0;
+ c.gridy = 4;
+ c.gridwidth = 1;
+ c.ipady = 0;
+ cbWorkflows = new JCheckBox("workflows", true);
+ cbWorkflows.addItemListener(this);
+ cbWorkflows.addKeyListener(this);
+ this.add(cbWorkflows, c);
+
+ c.gridx = 0;
+ c.gridy = 5;
+ cbFiles = new JCheckBox("files", true);
+ cbFiles.addItemListener(this);
+ cbFiles.addKeyListener(this);
+ this.add(cbFiles, c);
+
+ c.gridx = 0;
+ c.gridy = 6;
+ cbPacks = new JCheckBox("packs", true);
+ cbPacks.addItemListener(this);
+ cbPacks.addKeyListener(this);
+ this.add(cbPacks, c);
+
+ c.gridx = 1;
+ c.gridy = 4;
+ cbUsers = new JCheckBox("users", true);
+ cbUsers.addItemListener(this);
+ cbUsers.addKeyListener(this);
+ this.add(cbUsers, c);
+
+ c.gridx = 1;
+ c.gridy = 5;
+ cbGroups = new JCheckBox("groups", true);
+ cbGroups.addItemListener(this);
+ cbGroups.addKeyListener(this);
+ this.add(cbGroups, c);
+
+ c.gridx = 3;
+ c.gridy = 2;
+ c.insets = new Insets(10, 25, 3, 0);
+ JLabel jlResultLimit = new JLabel("Result limit");
+ this.add(jlResultLimit, c);
+
+ c.gridx = 3;
+ c.gridy = 3;
+ c.insets = new Insets(0, 25, 0, 0);
+ jsResultLimit = new JSpinner(new SpinnerNumberModel(SEARCH_RESULT_LIMIT_INIT, SEARCH_RESULT_LIMIT_MIN, SEARCH_RESULT_LIMIT_MAX, 1));
+ jsResultLimit.setPreferredSize(new Dimension(jlResultLimit.getPreferredSize().width, jsResultLimit.getPreferredSize().height));
+ this.add(jsResultLimit, c);
+
+ // adding KeyListener to JSpinner directly doesn't make sense; need to attach KeyListener to the text field that is associated with spinner
+ tfResultLimitTextField = ((JSpinner.DefaultEditor) this.jsResultLimit.getEditor()).getTextField();
+ tfResultLimitTextField.addKeyListener(this);
+ }
+
+ public String getSearchQuery() {
+ return (this.tfSearchQuery.getText());
+ }
+
+ public void setSearchQuery(String strSearchQuery) {
+ this.tfSearchQuery.setText(strSearchQuery);
+ }
+
+ public void focusSearchQueryField() {
+ this.tfSearchQuery.selectAll();
+ this.tfSearchQuery.requestFocusInWindow();
+ }
+
+ public void setSearchAllResourceTypes(boolean bSearchAllTypes) {
+ this.cbSearchAllTypes.setSelected(bSearchAllTypes);
+ }
+
+ public boolean getSearchWorkflows() {
+ return (this.cbWorkflows.isSelected());
+ }
+
+ public void setSearchWorkflows(boolean bSearchWorkflows) {
+ this.cbWorkflows.setSelected(bSearchWorkflows);
+ }
+
+ public boolean getSearchFiles() {
+ return (this.cbFiles.isSelected());
+ }
+
+ public void setSearchFiles(boolean bSearchFiles) {
+ this.cbFiles.setSelected(bSearchFiles);
+ }
+
+ public boolean getSearchPacks() {
+ return (this.cbPacks.isSelected());
+ }
+
+ public void setSearchPacks(boolean bSearchPacks) {
+ this.cbPacks.setSelected(bSearchPacks);
+ }
+
+ public boolean getSearchUsers() {
+ return (this.cbUsers.isSelected());
+ }
+
+ public void setSearchUsers(boolean bSearchUsers) {
+ this.cbUsers.setSelected(bSearchUsers);
+ }
+
+ public boolean getSearchGroups() {
+ return (this.cbGroups.isSelected());
+ }
+
+ public void setSearchGroups(boolean bSearchGroups) {
+ this.cbGroups.setSelected(bSearchGroups);
+ }
+
+ public int getResultCountLimit() {
+ // JSpinner handles value validation itself, so there is no need to
+ // make our own validation too
+ return (Integer.parseInt(this.jsResultLimit.getValue().toString()));
+ }
+
+ public void setResultCountLimit(int iLimit) {
+ this.jsResultLimit.setValue(iLimit);
+ }
+
+ // this monitors all checkbox clicks and selects / deselects other checkboxes which are relevant
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getItemSelectable().equals(this.cbSearchAllTypes)) {
+ // "all resource types" clicked - need to select / deselect all data type checkboxes accordingly
+ for (JCheckBox cb : this.alDataTypeCheckboxes) {
+ cb.removeItemListener(this);
+ cb.setSelected(this.cbSearchAllTypes.isSelected());
+ cb.addItemListener(this);
+ }
+
+ // also, enable / disable the search button
+ this.bSearch.setEnabled(this.cbSearchAllTypes.isSelected());
+ } else if (this.alDataTypeCheckboxes.contains(e.getItemSelectable())) {
+ // one of the checkboxes for data types was clicked (e.g. workflows, files, etc);
+ // need to calculate how many of those are currently selected
+ int iSelectedCnt = 0;
+ for (JCheckBox cb : this.alDataTypeCheckboxes) {
+ if (cb.isSelected())
+ iSelectedCnt++;
+ }
+
+ // if all are selected, tick "search all types" checkbox; uncheck otherwise
+ this.cbSearchAllTypes.removeItemListener(this);
+ this.cbSearchAllTypes.setSelected(iSelectedCnt == this.alDataTypeCheckboxes.size());
+ this.cbSearchAllTypes.addItemListener(this);
+
+ // enable search button if at least one data type is selected; disable otherwise
+ this.bSearch.setEnabled(iSelectedCnt > 0);
+ }
+ }
+
+ public void keyPressed(KeyEvent e) {
+ // ENTER pressed - start search by simulating "search" button click
+ // (only do this if the "search" button was active at that moment)
+ if (e.getKeyCode() == KeyEvent.VK_ENTER
+ && this.bSearch.isEnabled()
+ && (Arrays.asList(new JComponent[] { this.tfSearchQuery, this.bSearch, this.cbSearchAllTypes, this.tfResultLimitTextField }).contains(e.getSource()) || this.alDataTypeCheckboxes.contains(e.getSource()))) {
+ this.clickHandler.actionPerformed(new ActionEvent(this.bSearch, 0, ""));
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ // do nothing
+ }
+
+ public void keyTyped(KeyEvent e) {
+ // do nothing
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java
new file mode 100644
index 0000000..dbc3b36
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResultsPanel extends JPanel
+{
+ // CONSTANTS
+ public static final String NO_SEARCHES_STATUS = "No searches have been done so far";
+
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ // COMPONENTS
+ private JLabel lStatusLabel;
+ public JButton bRefresh;
+ public JButton bClear;
+ private JPanel jpResultsBody;
+ private ActionListener alClickHandler;
+
+ // result data store
+ boolean bNoSearchesMadeYet;
+ String strCurrentSearchTerm;
+ HashMap<Integer, ArrayList<Resource>> hmSearchResults;
+
+
+ public SearchResultsPanel(ActionListener buttonClickHandler, MainComponent component, MyExperimentClient client, Logger logger)
+ {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ this.alClickHandler = buttonClickHandler;
+
+ // initialise the result data collection to an empty one, just in case
+ // someone calls refresh() before setting the real result data
+ bNoSearchesMadeYet = true;
+ hmSearchResults = new HashMap<Integer, ArrayList<Resource>>();
+
+ this.initialiseUI();
+ }
+
+
+
+ private void initialiseUI()
+ {
+ // label to hold the status of search (e.g. result query + count, etc)
+ this.lStatusLabel = new JLabel(NO_SEARCHES_STATUS);
+ this.lStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); // a bit of padding on the left
+
+ // control buttons for the search results panel
+ JPanel jpButtonsPanel = new JPanel();
+ jpButtonsPanel.setLayout(new BoxLayout(jpButtonsPanel, BoxLayout.LINE_AXIS));
+ this.bClear = new JButton("Clear", WorkbenchIcons.deleteIcon);
+ this.bClear.addActionListener(this.alClickHandler);
+ this.bClear.setToolTipText("Click this button to clear the search results");
+ this.bClear.setEnabled(false);
+ jpButtonsPanel.add(this.bClear);
+ this.bRefresh = new JButton("Refresh", WorkbenchIcons.refreshIcon);
+ this.bRefresh.addActionListener(this.alClickHandler);
+ this.bRefresh.setToolTipText("Click this button to refresh the search results");
+ this.bRefresh.setEnabled(false);
+ jpButtonsPanel.add(this.bRefresh);
+
+ // status panel containing the status label and control buttons
+ JPanel jpStatusPanel = new JPanel(new BorderLayout());
+ jpStatusPanel.setBorder(BorderFactory.createEtchedBorder());
+ jpStatusPanel.add(this.lStatusLabel, BorderLayout.CENTER);
+ jpStatusPanel.add(jpButtonsPanel, BorderLayout.EAST);
+
+ // create empty panel for the main search result content
+ this.jpResultsBody = new JPanel();
+ jpResultsBody.setLayout(new BorderLayout());
+
+ // PUT EVERYTHING TOGETHER
+ this.setLayout(new BorderLayout());
+ this.add(jpStatusPanel, BorderLayout.NORTH);
+ this.add(this.jpResultsBody, BorderLayout.CENTER);
+
+ // tabbed results view is missing from this method - this is
+ // because if no results will be found in a particular category,
+ // that tab will not be displayed (hence the results view will
+ // need to be generated every time dynamically)
+ }
+
+
+ public void setCurrentSearchTerm(String strSearchTerm)
+ {
+ this.strCurrentSearchTerm = strSearchTerm;
+ }
+
+ public String getCurrentSearchTerm()
+ {
+ return (this.strCurrentSearchTerm);
+ }
+
+
+ public void setSearchResultsData(HashMap<Integer, ArrayList<Resource>> hmResults)
+ {
+ this.bNoSearchesMadeYet = false;
+ this.hmSearchResults = hmResults;
+ }
+
+
+ public void setStatus(String status)
+ {
+ this.lStatusLabel.setText(status);
+ }
+
+ public void refresh()
+ {
+ // remove all items from the main results content panel
+ this.jpResultsBody.removeAll();
+
+ if (!this.bNoSearchesMadeYet) // this will be true if the result collection was never assigned to this result panel
+ {
+ ArrayList<Integer> alResourceTypes = new ArrayList<Integer>(this.hmSearchResults.keySet());
+ Collections.sort(alResourceTypes); // this will ensure that the tabs are always in the correct order
+ if(alResourceTypes.isEmpty()) {
+ this.jpResultsBody.add(new JLabel("No items to display"), BorderLayout.NORTH);
+ this.repaint();
+ }
+ else {
+ JTabbedPane tpResults = new JTabbedPane();
+
+ for(int iType : alResourceTypes)
+ {
+ // HACK: for now all item types are suitably turned into plural by simply appending "s"
+ String strTabLabel = Resource.getResourceTypeName(iType) + "s (" + this.hmSearchResults.get(iType).size() + ")";
+
+ ResourceListPanel jpTabContents = new ResourceListPanel(pluginMainComponent, myExperimentClient, logger);
+ jpTabContents.setListItems(this.hmSearchResults.get(iType));
+
+ JScrollPane spTabContents = new JScrollPane(jpTabContents);
+ spTabContents.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+ tpResults.add(strTabLabel, spTabContents);
+ }
+
+ this.jpResultsBody.add(tpResults, BorderLayout.CENTER);
+ this.bClear.setEnabled(true);
+ this.bRefresh.setEnabled(true);
+ }
+ }
+
+ this.revalidate();
+ }
+
+
+ public void clear()
+ {
+ // remove all items from the main results content panel
+ this.jpResultsBody.removeAll();
+ this.repaint();
+ this.revalidate();
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchTabContentPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchTabContentPanel.java
new file mode 100644
index 0000000..b583491
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchTabContentPanel.java
@@ -0,0 +1,450 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Base64;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine.QuerySearchInstance;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchTabContentPanel extends JPanel implements ActionListener {
+ // CONSTANTS
+ private final static int SEARCH_HISTORY_LENGTH = 50;
+ private final static int SEARCH_FAVOURITES_LENGTH = 30;
+ protected final static String SEARCH_FROM_FAVOURITES = "searchFromFavourites";
+ protected final static String SEARCH_FROM_HISTORY = "searchFromHistory";
+ protected final static String ADD_FAVOURITE_SEARCH_INSTANCE = "addFavouriteSearchInstance";
+ protected final static String REMOVE_FAVOURITE_SEARCH_INSTANCE = "removeFavouriteSearchInstance";
+
+ private final MainComponent pluginMainComponent;
+ private final MyExperimentClient myExperimentClient;
+ private final Logger logger;
+
+ // COMPONENTS
+ private JSplitPane spMainSplitPane;
+ private SearchOptionsPanel jpSearchOptions;
+ private JPanel jpFavouriteSearches;
+ private JPanel jpSearchHistory;
+ private SearchResultsPanel jpSearchResults;
+ private final ImageIcon iconFavourite = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("favourite_icon"));
+ private final ImageIcon iconRemove = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("destroy_icon"));
+
+ // Data storage
+ private QuerySearchInstance siPreviousSearch;
+ private LinkedList<QuerySearchInstance> llFavouriteSearches;
+ private LinkedList<QuerySearchInstance> llSearchHistory;
+
+ // Search components
+ private final SearchEngine searchEngine; // The search engine for executing keyword query searches
+ private final Vector<Long> vCurrentSearchThreadID; // This will keep ID of the current search thread (there will only be one such thread)
+
+ public SearchTabContentPanel(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // initialise the favourite searches
+ String strFavouriteSearches = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_FAVOURITE_SEARCHES);
+ if (strFavouriteSearches != null) {
+ Object oFavouriteSearches = Base64.decodeToObject(strFavouriteSearches);
+ this.llFavouriteSearches = (LinkedList<QuerySearchInstance>) oFavouriteSearches;
+ } else {
+ this.llFavouriteSearches = new LinkedList<QuerySearchInstance>();
+ }
+
+ // initialise the search history
+ String strSearchHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_SEARCH_HISTORY);
+ if (strSearchHistory != null) {
+ Object oSearchHistory = Base64.decodeToObject(strSearchHistory);
+ this.llSearchHistory = (LinkedList<QuerySearchInstance>) oSearchHistory;
+ } else {
+ this.llSearchHistory = new LinkedList<QuerySearchInstance>();
+ }
+
+ this.initialiseUI();
+ this.updateFavouriteSearches();
+ this.updateSearchHistory();
+
+ // initialise the search engine
+ vCurrentSearchThreadID = new Vector<Long>(1);
+ vCurrentSearchThreadID.add(null); // this is just a placeholder, so that it's possible to update this value instead of adding new ones later
+ this.searchEngine = new SearchEngine(vCurrentSearchThreadID, false, jpSearchResults, pluginMainComponent, myExperimentClient, logger);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // THIS MIGHT NOT BE NEEDED AS THE SEARCH OPTIONS BOX NOW
+ // SETS THE MINIMUM SIZE OF THE SIDEBAR PROPERLY
+ spMainSplitPane.setDividerLocation(390);
+ spMainSplitPane.setOneTouchExpandable(true);
+ spMainSplitPane.setDoubleBuffered(true);
+ }
+ });
+ }
+
+ private void initialiseUI() {
+ // create search options panel
+ jpSearchOptions = new SearchOptionsPanel(this, this.pluginMainComponent, this.myExperimentClient, this.logger);
+ jpSearchOptions.setMaximumSize(new Dimension(1024, 0)); // HACK: this is to make sure that search options box won't be stretched
+
+ // create favourite searches panel
+ jpFavouriteSearches = new JPanel();
+ jpFavouriteSearches.setMaximumSize(new Dimension(1024, 0)); // HACK: this is to make sure that favourite searches box won't be stretched
+ jpFavouriteSearches.setLayout(new GridBagLayout());
+ jpFavouriteSearches.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Favourite Searches "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+
+ // create search history panel
+ jpSearchHistory = new JPanel();
+ jpSearchHistory.setMaximumSize(new Dimension(1024, 0)); // HACK: this is to make sure that search history box won't be stretched
+ jpSearchHistory.setLayout(new GridBagLayout());
+ jpSearchHistory.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Search History "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+
+ // create the search sidebar
+ JPanel jpSearchSidebar = new JPanel();
+ jpSearchSidebar.setLayout(new GridBagLayout());
+
+ GridBagConstraints gbConstraints = new GridBagConstraints();
+ gbConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gbConstraints.fill = GridBagConstraints.BOTH;
+ gbConstraints.weightx = 1;
+ gbConstraints.gridx = 0;
+
+ gbConstraints.gridy = 0;
+ jpSearchSidebar.add(jpSearchOptions, gbConstraints);
+
+ gbConstraints.gridy = 1;
+ jpSearchSidebar.add(jpFavouriteSearches, gbConstraints);
+
+ gbConstraints.gridy = 2;
+ jpSearchSidebar.add(jpSearchHistory, gbConstraints);
+
+ JPanel jpSidebarContainer = new JPanel();
+ jpSidebarContainer.setLayout(new BorderLayout());
+ jpSidebarContainer.add(jpSearchSidebar, BorderLayout.NORTH);
+
+ // wrap sidebar in a scroll pane
+ JScrollPane spSearchSidebar = new JScrollPane(jpSidebarContainer);
+ spSearchSidebar.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+ spSearchSidebar.setMinimumSize(new Dimension(jpSidebarContainer.getMinimumSize().width + 20, 0));
+
+ // create panel for search results
+ this.jpSearchResults = new SearchResultsPanel(this, pluginMainComponent, myExperimentClient, logger);
+
+ spMainSplitPane = new JSplitPane();
+ spMainSplitPane.setLeftComponent(spSearchSidebar);
+ spMainSplitPane.setRightComponent(jpSearchResults);
+
+ // PUT EVERYTHING TOGETHER
+ this.setLayout(new BorderLayout());
+ this.add(spMainSplitPane);
+ }
+
+ private void addToSearchListQueue(LinkedList<QuerySearchInstance> searchInstanceList, QuerySearchInstance searchInstanceToAdd, int queueSize) {
+ // check if such entry is already in the list
+ int iDuplicateIdx = searchInstanceList.indexOf(searchInstanceToAdd);
+
+ // only do the following if the new search instance list OR current instance is not the same
+ // as the last one in the list
+ if (searchInstanceList.size() == 0
+ || iDuplicateIdx != searchInstanceList.size() - 1) {
+ // if the current item is already in the list, remove it (then re-add at the end of the list)
+ if (iDuplicateIdx >= 0)
+ searchInstanceList.remove(iDuplicateIdx);
+
+ // we want to keep the history size constant, therefore when it reaches a certain
+ // size, oldest element needs to be removed
+ if (searchInstanceList.size() >= queueSize)
+ searchInstanceList.remove();
+
+ // in either case, add the new element to the tail of the search history
+ searchInstanceList.offer(searchInstanceToAdd);
+ }
+ }
+
+ private void addToFavouriteSearches(QuerySearchInstance searchInstance) {
+ this.addToSearchListQueue(this.llFavouriteSearches, searchInstance, SEARCH_FAVOURITES_LENGTH);
+ Collections.sort(this.llFavouriteSearches);
+ }
+
+ // the method to update search history listing
+ protected void updateFavouriteSearches() {
+ this.jpFavouriteSearches.removeAll();
+
+ if (this.llFavouriteSearches.size() == 0) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weightx = 1.0;
+ c.anchor = GridBagConstraints.WEST;
+ this.jpFavouriteSearches.add(Util.generateNoneTextLabel("No favourite searches"), c);
+ } else {
+ for (int i = this.llFavouriteSearches.size() - 1; i >= 0; i--) {
+ addEntryToSearchListingPanel(this.llFavouriteSearches, i, SEARCH_FROM_FAVOURITES, this.jpFavouriteSearches, this.iconRemove, REMOVE_FAVOURITE_SEARCH_INSTANCE, "<html>Click to remove from your local favourite searches.<br>"
+ + "(This will not affect your myExperiment profile settings.)</html>");
+ }
+ }
+
+ this.jpFavouriteSearches.repaint();
+ this.jpFavouriteSearches.revalidate();
+ }
+
+ private void addToSearchHistory(QuerySearchInstance searchInstance) {
+ this.addToSearchListQueue(this.llSearchHistory, searchInstance, SEARCH_HISTORY_LENGTH);
+ }
+
+ // the method to update search history listing
+ protected void updateSearchHistory() {
+ this.jpSearchHistory.removeAll();
+
+ if (this.llSearchHistory.size() == 0) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weightx = 1.0;
+ c.anchor = GridBagConstraints.WEST;
+ this.jpSearchHistory.add(Util.generateNoneTextLabel(SearchResultsPanel.NO_SEARCHES_STATUS), c);
+ } else {
+ for (int i = this.llSearchHistory.size() - 1; i >= 0; i--) {
+ addEntryToSearchListingPanel(this.llSearchHistory, i, SEARCH_FROM_HISTORY, this.jpSearchHistory, this.iconFavourite, ADD_FAVOURITE_SEARCH_INSTANCE, "<html>Click to add to your local favourite"
+ + " searches - these will be available every time you use Taverna.<br>(This will not affect your"
+ + " myExperiment profile settings.)</html>");
+ }
+ }
+
+ this.jpSearchHistory.repaint();
+ this.jpSearchHistory.revalidate();
+
+ // also update search history in History tab
+ if (this.pluginMainComponent.getHistoryBrowser() != null) {
+ this.pluginMainComponent.getHistoryBrowser().refreshSearchHistory();
+ }
+ }
+
+ private void addEntryToSearchListingPanel(List<QuerySearchInstance> searchInstanceList, int iIndex, String searchAction, JPanel panelToPopulate, ImageIcon entryIcon, String iconAction, String iconActionTooltip) {
+ // labels with search query and search settings
+ JClickableLabel jclCurrentEntryLabel = new JClickableLabel(searchInstanceList.get(iIndex).getSearchQuery(), searchAction
+ + ":" + iIndex, this, WorkbenchIcons.findIcon, SwingUtilities.LEFT, searchInstanceList.get(iIndex).toString());
+ JLabel jlCurrentEntrySettings = new JLabel(searchInstanceList.get(iIndex).detailsAsString());
+ jlCurrentEntrySettings.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+
+ // grouping search details and search settings together
+ JPanel jpCurentEntryDetails = new JPanel();
+ jpCurentEntryDetails.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.anchor = GridBagConstraints.WEST;
+ jpCurentEntryDetails.add(jclCurrentEntryLabel, c);
+ c.weightx = 1.0;
+ jpCurentEntryDetails.add(jlCurrentEntrySettings, c);
+
+ // creating a "button" to add current item to favourites
+ JClickableLabel jclFavourite = new JClickableLabel("", iconAction + ":"
+ + iIndex, this, entryIcon, SwingUtilities.LEFT, iconActionTooltip);
+ jclFavourite.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+
+ // putting all pieces of current item together
+ JPanel jpCurrentEntry = new JPanel();
+ jpCurrentEntry.setLayout(new GridBagLayout());
+
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 1.0;
+ jpCurrentEntry.add(jpCurentEntryDetails, c);
+
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 0;
+ jpCurrentEntry.add(jclFavourite, c);
+
+ // adding current item to the history list
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1.0;
+ c.gridx = 0;
+ c.gridy = GridBagConstraints.RELATIVE;
+ panelToPopulate.add(jpCurrentEntry, c);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.jpSearchOptions.bSearch)) {
+ // "Search" button was clicked
+
+ // if no search query is specified, display error message
+ if (jpSearchOptions.getSearchQuery().length() == 0) {
+ javax.swing.JOptionPane.showMessageDialog(null, "Search query is empty. Please specify your search query and try again.", "Error", JOptionPane.WARNING_MESSAGE);
+ jpSearchOptions.focusSearchQueryField();
+ } else {
+ // will ensure that if the value in the search result limit editor
+ // is invalid, it is processed properly
+ try {
+ this.jpSearchOptions.jsResultLimit.commitEdit();
+ } catch (ParseException ex) {
+ JOptionPane.showMessageDialog(null, "Invalid search result limit value. This should be an\n"
+ + "integer in the range of "
+ + SearchOptionsPanel.SEARCH_RESULT_LIMIT_MIN
+ + ".."
+ + SearchOptionsPanel.SEARCH_RESULT_LIMIT_MAX, "MyExperiment Plugin - Error", JOptionPane.WARNING_MESSAGE);
+ this.jpSearchOptions.tfResultLimitTextField.selectAll();
+ this.jpSearchOptions.tfResultLimitTextField.requestFocusInWindow();
+ return;
+ }
+
+ // all fine, settings present - store the settings..
+ siPreviousSearch = new SearchEngine.QuerySearchInstance(jpSearchOptions.getSearchQuery(), jpSearchOptions.getResultCountLimit(), jpSearchOptions.getSearchWorkflows(), jpSearchOptions.getSearchFiles(), jpSearchOptions.getSearchPacks(), jpSearchOptions.getSearchUsers(), jpSearchOptions.getSearchGroups());
+
+ // .. and execute the query
+ this.jpSearchOptions.focusSearchQueryField();
+ this.runSearch();
+ }
+ } else if (e.getSource() instanceof JClickableLabel) {
+ if (e.getActionCommand().startsWith(SEARCH_FROM_HISTORY)
+ || e.getActionCommand().startsWith(SEARCH_FROM_FAVOURITES)) {
+ // the part of the action command that is following the prefix is the ID in the search history / favourites storage;
+ // this search instance is removed from history and will be re-added at the top of it when search is launched
+ int iEntryID = Integer.parseInt(e.getActionCommand().substring(e.getActionCommand().indexOf(":") + 1));
+ final QuerySearchInstance si = (e.getActionCommand().startsWith(SEARCH_FROM_HISTORY) ? this.llSearchHistory.remove(iEntryID) : this.llFavouriteSearches.get(iEntryID)); // in case of favourites, no need to remove the entry
+
+ // re-set search options in the settings box and re-run the search
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jpSearchOptions.setSearchQuery(si.getSearchQuery());
+ jpSearchOptions.setSearchAllResourceTypes(false); // reset the 'all resource types'
+ jpSearchOptions.setSearchWorkflows(si.getSearchWorkflows());
+ jpSearchOptions.setSearchFiles(si.getSearchFiles());
+ jpSearchOptions.setSearchPacks(si.getSearchPacks());
+ jpSearchOptions.setSearchUsers(si.getSearchUsers());
+ jpSearchOptions.setSearchGroups(si.getSearchGroups());
+ jpSearchOptions.setResultCountLimit(si.getResultCountLimit());
+
+ // set this as the 'latest' search
+ siPreviousSearch = si;
+
+ // run the search (and update the search history)
+ runSearch();
+ }
+ });
+ } else if (e.getActionCommand().startsWith(ADD_FAVOURITE_SEARCH_INSTANCE)) {
+ // get the ID of the entry in the history listing first; then fetch the instance itself
+ int iHistID = Integer.parseInt(e.getActionCommand().substring(e.getActionCommand().indexOf(":") + 1));
+ final QuerySearchInstance si = this.llSearchHistory.get(iHistID);
+
+ // add item to favourites and re-draw the panel
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ addToFavouriteSearches(si);
+ updateFavouriteSearches();
+ }
+ });
+ } else if (e.getActionCommand().startsWith(REMOVE_FAVOURITE_SEARCH_INSTANCE)) {
+ // get the ID of the entry in the favourite searches listing first; then remove the instance with that ID from the list
+ int iFavouriteID = Integer.parseInt(e.getActionCommand().substring(e.getActionCommand().indexOf(":") + 1));
+ this.llFavouriteSearches.remove(iFavouriteID);
+
+ // item removed from favourites - re-draw the panel now
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateFavouriteSearches();
+ }
+ });
+ }
+ } else if (e.getSource().equals(this.jpSearchResults.bRefresh)) {
+ // "Refresh" button clicked; disable clearing results and re-run previous search
+ this.jpSearchResults.bClear.setEnabled(false);
+ this.rerunLastSearch();
+ } else if (e.getSource().equals(this.jpSearchResults.bClear)) {
+ // "Clear" button clicked - clear last search and disable re-running it
+ siPreviousSearch = null;
+ vCurrentSearchThreadID.set(0, null);
+ this.jpSearchResults.clear();
+ this.jpSearchResults.setStatus(SearchResultsPanel.NO_SEARCHES_STATUS);
+ this.jpSearchResults.bClear.setEnabled(false);
+ this.jpSearchResults.bRefresh.setEnabled(false);
+ }
+ }
+
+ // search history will get updated by default
+ private void runSearch() {
+ runSearch(true);
+ }
+
+ // makes preparations and runs the current search
+ // (has an option not to update the search history)
+ private void runSearch(boolean bUpdateHistory) {
+ if (bUpdateHistory) {
+ // add current search to search history ..
+ this.addToSearchHistory(siPreviousSearch);
+
+ // .. update the search history box ..
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateSearchHistory();
+ }
+ });
+ }
+
+ // ..and run the query
+ this.searchEngine.searchAndPopulateResults(siPreviousSearch);
+ }
+
+ // re-executes the search for the most recent query
+ // (if searches have already been done before or were not cleared)
+ public void rerunLastSearch() {
+ if (this.siPreviousSearch != null) {
+ runSearch();
+ }
+ }
+
+ public SearchResultsPanel getSearchResultPanel() {
+ return (this.jpSearchResults);
+ }
+
+ public LinkedList<QuerySearchInstance> getSearchFavouritesList() {
+ return (this.llFavouriteSearches);
+ }
+
+ public LinkedList<QuerySearchInstance> getSearchHistory() {
+ return (this.llSearchHistory);
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/StyledHTMLEditorKit.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/StyledHTMLEditorKit.java
new file mode 100644
index 0000000..2a2302a
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/StyledHTMLEditorKit.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+public class StyledHTMLEditorKit extends HTMLEditorKit {
+
+ private final StyleSheet styleSheet;
+
+ public StyledHTMLEditorKit(StyleSheet styleSheet) {
+ this.styleSheet = styleSheet;
+ }
+
+ @Override
+ public StyleSheet getStyleSheet() {
+ return styleSheet;
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagBrowserTabContentPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagBrowserTabContentPanel.java
new file mode 100644
index 0000000..eca9ab0
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagBrowserTabContentPanel.java
@@ -0,0 +1,226 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Vector;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.SwingUtilities;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Base64;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+
+/*
+ * @author Jiten Bhagat
+ */
+public class TagBrowserTabContentPanel extends JPanel implements ActionListener {
+ // CONSTANTS
+ private static final double TAG_CLOUD_BALANCE = 0.35;
+ private static final double TAG_SIDEBAR_BALANCE = 0.4;
+ private static final int TAG_SEARCH_HISTORY_LENGTH = 50;
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ // COMPONENTS
+ private JSplitPane spMainSplitPane;
+ private JSplitPane spTagCloudSplitPane;
+ private TagCloudPanel jpMyTags;
+ private TagCloudPanel jpAllTags;
+ private SearchResultsPanel jpTagSearchResults;
+ private JButton bLoginToSeeMyTags;
+ private JPanel jpLoginToSeeMyTags;
+
+ // STORAGE
+ // last tag for which the search has been made
+ private String strCurrentTagCommand;
+ private ArrayList<Tag> lTagSearchHistory;
+
+ // Search components
+ private SearchEngine searchEngine; // The search engine for executing keyword
+ // query searches
+ private Vector<Long> vCurrentSearchThreadID; // This will keep ID of the
+
+ // current search thread (there
+ // will only be one such thread)
+
+ @SuppressWarnings("unchecked")
+ public TagBrowserTabContentPanel(MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set main variables to ensure access to myExperiment, logger and the
+ // parent component
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ // no tag searches have been done yet
+ this.strCurrentTagCommand = null;
+
+ // initialise the tag search history
+ String strTagSearchHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_TAG_SEARCH_HISTORY);
+ if (strTagSearchHistory != null) {
+ Object oTagSearchHistory = Base64.decodeToObject(strTagSearchHistory);
+ this.lTagSearchHistory = (ArrayList<Tag>) oTagSearchHistory;
+ } else {
+ this.lTagSearchHistory = new ArrayList<Tag>();
+ }
+
+ this.initialiseUI();
+
+ // initialise the search engine
+ vCurrentSearchThreadID = new Vector<Long>(1);
+ vCurrentSearchThreadID.add(null); // this is just a placeholder, so that
+ // it's possible to update this value
+ // instead of adding new ones later
+ this.searchEngine = new SearchEngine(vCurrentSearchThreadID, true, jpTagSearchResults, pluginMainComponent, myExperimentClient, logger);
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ spTagCloudSplitPane.setDividerLocation(TAG_CLOUD_BALANCE);
+ spMainSplitPane.setDividerLocation(TAG_SIDEBAR_BALANCE);
+ spMainSplitPane.setOneTouchExpandable(true);
+ spMainSplitPane.setDoubleBuffered(true);
+ }
+ });
+ }
+
+ private void initialiseUI() {
+ // This panel will be used when the user is not logged in - i.e. when can't
+ // show "My Tags";
+ // log-in button will be shown instead
+ this.bLoginToSeeMyTags = new JButton("Login to see your tags", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("login_icon")));
+ this.bLoginToSeeMyTags.addActionListener(this);
+
+ this.jpLoginToSeeMyTags = new JPanel();
+ this.jpLoginToSeeMyTags.setLayout(new GridBagLayout());
+ this.jpLoginToSeeMyTags.add(this.bLoginToSeeMyTags);
+
+ // Create the tag clouds
+ this.jpMyTags = new TagCloudPanel("My Tags", TagCloudPanel.TAGCLOUD_TYPE_USER, this, pluginMainComponent, myExperimentClient, logger);
+ this.jpAllTags = new TagCloudPanel("All Tags", TagCloudPanel.TAGCLOUD_TYPE_GENERAL, this, pluginMainComponent, myExperimentClient, logger);
+
+ // add the two tag clouds to the left-hand side sidebar
+ this.spTagCloudSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ this.spTagCloudSplitPane.setTopComponent(this.myExperimentClient.isLoggedIn() ? jpMyTags : jpLoginToSeeMyTags);
+ this.spTagCloudSplitPane.setBottomComponent(jpAllTags);
+
+ // create panel for tag search results
+ this.jpTagSearchResults = new SearchResultsPanel(this, pluginMainComponent, myExperimentClient, logger);
+
+ this.spMainSplitPane = new JSplitPane();
+ this.spMainSplitPane.setLeftComponent(spTagCloudSplitPane);
+ this.spMainSplitPane.setRightComponent(this.jpTagSearchResults);
+
+ this.setLayout(new BorderLayout());
+ this.add(this.spMainSplitPane, BorderLayout.CENTER);
+ }
+
+ // this helper is called when the user logs in / out to swap the
+ // view accordingly; 'refresh' on "My Tags" panel should be called
+ // immediately after changing the view
+ public void setMyTagsShown(boolean bShow) {
+ if (bShow) {
+ this.spTagCloudSplitPane.setTopComponent(this.jpMyTags);
+ } else {
+ this.spTagCloudSplitPane.setTopComponent(this.jpLoginToSeeMyTags);
+ }
+
+ // in either case apply element balance again
+ this.spTagCloudSplitPane.setDividerLocation(TAG_CLOUD_BALANCE);
+ }
+
+ public void refresh() {
+ if (this.myExperimentClient.isLoggedIn()) {
+ // "My Tags" are only accessible when the user has logged in
+ this.jpMyTags.refresh();
+ }
+ this.jpAllTags.refresh();
+ }
+
+ // re-executes the search for the most recent tag
+ // (if tag searches have already been done before)
+ public void rerunLastTagSearch() {
+ if (this.strCurrentTagCommand != null) {
+ this.actionPerformed(new ActionEvent(this.jpAllTags, 0, this.strCurrentTagCommand));
+ }
+ }
+
+ public TagCloudPanel getMyTagPanel() {
+ return (this.jpMyTags);
+ }
+
+ public TagCloudPanel getAllTagPanel() {
+ return (this.jpAllTags);
+ }
+
+ public SearchResultsPanel getTagSearchResultPanel() {
+ return (this.jpTagSearchResults);
+ }
+
+ public ArrayList<Tag> getTagSearchHistory() {
+ return (this.lTagSearchHistory);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.jpTagSearchResults.bRefresh)) {
+ // disable clearing results and re-run last tag search
+ this.getTagSearchResultPanel().bClear.setEnabled(false);
+ this.rerunLastTagSearch();
+ } else if (e.getSource().equals(this.jpTagSearchResults.bClear)) {
+ // clear last search and disable re-running it
+ this.strCurrentTagCommand = null;
+ vCurrentSearchThreadID.set(0, null);
+ this.getTagSearchResultPanel().clear();
+ this.getTagSearchResultPanel().setStatus(SearchResultsPanel.NO_SEARCHES_STATUS);
+ this.getTagSearchResultPanel().bClear.setEnabled(false);
+ this.getTagSearchResultPanel().bRefresh.setEnabled(false);
+ } else if (e.getSource() instanceof JClickableLabel
+ || e.getSource() instanceof TagCloudPanel) {
+ // one of the tags was clicked as a hyperlink in any TagCloudPanel
+ // (in Resource Preview Browser or in Tag Browser tab) or as a
+ // JClickableLabel
+ if (e.getActionCommand().startsWith("tag:")) {
+ // record the tag search command and run the search
+ this.strCurrentTagCommand = e.getActionCommand();
+ this.searchEngine.searchAndPopulateResults(strCurrentTagCommand);
+
+ // update tag search history making sure that:
+ // - there's only one occurrence of this tag in the history;
+ // - if this tag was in the history before, it is moved to the 'top'
+ // now;
+ // - predefined history size is not exceeded
+ Tag t = Tag.instantiateTagFromActionCommand(strCurrentTagCommand);
+ this.lTagSearchHistory.remove(t);
+ this.lTagSearchHistory.add(t);
+ if (this.lTagSearchHistory.size() > TAG_SEARCH_HISTORY_LENGTH)
+ this.lTagSearchHistory.remove(0);
+
+ // now update the tag search history panel in 'History' tab
+ if (this.pluginMainComponent.getHistoryBrowser() != null) {
+ this.pluginMainComponent.getHistoryBrowser().refreshTagSearchHistory();
+ }
+ }
+ } else if (e.getSource().equals(this.bLoginToSeeMyTags)) {
+ // set the return "link"
+ this.pluginMainComponent.getMyStuffTab().cTabContentComponentToSwitchToAfterLogin = this;
+
+ // switch to login tab
+ this.pluginMainComponent.getMainTabs().setSelectedComponent(this.pluginMainComponent.getMyStuffTab());
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagCloudPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagCloudPanel.java
new file mode 100644
index 0000000..b03fd8f
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagCloudPanel.java
@@ -0,0 +1,342 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTextPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.TagCloud;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+public class TagCloudPanel extends JPanel implements ChangeListener, ItemListener, ActionListener, HyperlinkListener {
+ // CONSTANTS
+ private static final int TAGCLOUD_MAX_FONTSIZE = 36;
+ private static final int TAGCLOUD_MIN_FONTSIZE = 12;
+ private static final int TAGCLOUD_DEFAULT_MAX_SIZE = 350;
+ private static final int TAGCLOUD_DEFAULT_DISPLAY_SIZE = 50;
+
+ public static final int TAGCLOUD_TYPE_GENERAL = 0;
+ public static final int TAGCLOUD_TYPE_USER = 1;
+ public static final int TAGCLOUD_TYPE_RESOURCE_PREVIEW = 2;
+
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ // COMPONENTS
+ private String strTitle;
+ private int iType;
+ private ShadedLabel lCloudTitle;
+ private JSlider jsCloudSizeSlider;
+ private JCheckBox cbShowAllTags;
+ private JButton bRefresh;
+ private JTextPane tpTagCloudBody;
+
+ private ActionListener clickHandler;
+ private TagCloud tcData = new TagCloud();
+ private boolean bUserTagCloudSliderValueNeverSet = true;
+
+ public TagCloudPanel(String title, int iTagCloudType, ActionListener clickHandler, MainComponent component, MyExperimentClient client, Logger logger) {
+ super();
+
+ // set parameters and the main variables to ensure access to myExperiment,
+ // logger and the parent component
+ this.strTitle = title;
+ this.iType = iTagCloudType;
+ this.clickHandler = clickHandler;
+ this.pluginMainComponent = component;
+ this.myExperimentClient = client;
+ this.logger = logger;
+
+ initialiseUI();
+ }
+
+ private void initialiseUI() {
+ // set the title of the tag cloud
+ lCloudTitle = new ShadedLabel(this.strTitle, ShadedLabel.BLUE);
+
+ // create the tag cloud controls panel
+ // (all controls will be created anyway, but if that's a resource
+ // preview tag cloud, make sure that these controls are not displayed)
+ JPanel jpCloudControlsPanel = new JPanel();
+ jpCloudControlsPanel.setLayout(new BoxLayout(jpCloudControlsPanel, BoxLayout.LINE_AXIS));
+ this.jsCloudSizeSlider = new JSlider(1, TAGCLOUD_DEFAULT_MAX_SIZE, TAGCLOUD_DEFAULT_DISPLAY_SIZE);
+ this.cbShowAllTags = new JCheckBox("All tags", false);
+ this.bRefresh = new JButton("Refresh", WorkbenchIcons.refreshIcon);
+
+ if (this.iType != TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW) {
+ this.jsCloudSizeSlider.addChangeListener(this);
+ this.jsCloudSizeSlider.setToolTipText("Drag the slider to select how big the tag cloud should be, or check the \"All tags\" box to get the full tag cloud.");
+ jpCloudControlsPanel.add(this.jsCloudSizeSlider);
+
+ this.cbShowAllTags.addItemListener(this);
+ jpCloudControlsPanel.add(this.cbShowAllTags);
+
+ this.bRefresh.addActionListener(this);
+ this.bRefresh.setToolTipText("Click this button to refresh the Tag Cloud");
+ jpCloudControlsPanel.add(this.bRefresh);
+ }
+
+ // tag cloud header panel which which contains controls
+ JPanel jpCloudHeader = new JPanel(new BorderLayout());
+ jpCloudHeader.add(jpCloudControlsPanel, BorderLayout.CENTER);
+ jpCloudHeader.setBorder(BorderFactory.createEtchedBorder());
+
+ // body of the tag cloud with the actual tags
+ this.tpTagCloudBody = new JTextPane();
+ this.tpTagCloudBody.setBorder(BorderFactory.createEmptyBorder());
+ this.tpTagCloudBody.setEditable(false);
+ this.tpTagCloudBody.setContentType("text/html");
+ this.tpTagCloudBody.addHyperlinkListener(this);
+
+ JScrollPane spTagCloudBody = new JScrollPane(this.tpTagCloudBody, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ spTagCloudBody.setBorder(BorderFactory.createEmptyBorder());
+ spTagCloudBody.setOpaque(true);
+
+ // PUT EVERYTHING TOGETHER
+ JPanel jpTagCloudContentWithControls = new JPanel();
+ jpTagCloudContentWithControls.setLayout(new BorderLayout());
+ jpTagCloudContentWithControls.add(spTagCloudBody, BorderLayout.CENTER);
+ if (this.iType != TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW) {
+ jpTagCloudContentWithControls.add(jpCloudHeader, BorderLayout.NORTH);
+ }
+
+ this.setLayout(new BorderLayout());
+ if (this.iType != TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW) {
+ this.add(lCloudTitle, BorderLayout.NORTH);
+ }
+ this.add(jpTagCloudContentWithControls, BorderLayout.CENTER);
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2), BorderFactory.createEtchedBorder()));
+ }
+
+ public void refresh() {
+ this.lCloudTitle.setText(strTitle
+ + " <span style='color: gray;'>(Loading...)</span>");
+
+ // Make call to myExperiment API in a different thread
+ // (then use SwingUtilities.invokeLater to update the UI when ready).
+ new Thread("Get '" + this.strTitle + "' tag cloud data") {
+ public void run() {
+ logger.debug("Getting '" + strTitle + "' tag cloud data");
+
+ try {
+ int size = -1;
+ if (!cbShowAllTags.isSelected()) {
+ size = jsCloudSizeSlider.getValue();
+ }
+
+ // based on the type of the tag cloud, different data needs to be
+ // fetched
+ switch (iType) {
+ case TagCloudPanel.TAGCLOUD_TYPE_GENERAL:
+ tcData = myExperimentClient.getGeneralTagCloud(size);
+ break;
+
+ case TagCloudPanel.TAGCLOUD_TYPE_USER:
+ tcData = myExperimentClient.getUserTagCloud(myExperimentClient.getCurrentUser(), size);
+ break;
+
+ case TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW:
+ // fetch tag counts for tags that are already pre-set
+ myExperimentClient.convertTagListIntoTagCloudData(tcData.getTags());
+ break;
+
+ default:
+ // unknown type of tag cloud; show no data
+ tcData = new TagCloud();
+ break;
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ repopulate();
+ }
+ });
+ } catch (Exception ex) {
+ logger.error("Failed to get tag cloud data from myExperiment", ex);
+ }
+ }
+ }.start();
+ }
+
+ public void repopulate() {
+ logger.debug("Building '" + this.strTitle + "' tag cloud...");
+
+ try {
+ this.jsCloudSizeSlider.removeChangeListener(this);
+ if (this.iType == TAGCLOUD_TYPE_USER) {
+ jsCloudSizeSlider.setMinimum(1);
+ jsCloudSizeSlider.setMaximum(myExperimentClient.getCurrentUser().getTags().size());
+ if (bUserTagCloudSliderValueNeverSet) {
+ // this is the first load of the cloud, show all user tags
+ jsCloudSizeSlider.setValue(jsCloudSizeSlider.getMaximum());
+ bUserTagCloudSliderValueNeverSet = false;
+ } else {
+ // not the first load, test if the position of the slider is still
+ // within the scope
+ // (put that back to maximum if exceeds)
+ if (jsCloudSizeSlider.getValue() > jsCloudSizeSlider.getMaximum()
+ || this.cbShowAllTags.isSelected())
+ jsCloudSizeSlider.setValue(jsCloudSizeSlider.getMaximum());
+ }
+ } else {
+ // if "All tags" check box is ticked, set the slider max to the max no
+ // of tags
+ // (this will be the total number of tags available from myExperiment);
+ // if "All tags" was never checked before, max position of the slider
+ // will
+ // stay at predefined default value (because the total number of tags is
+ // not known yet)
+ if (this.cbShowAllTags.isSelected()) {
+ int size = this.tcData.getTags().size();
+ this.jsCloudSizeSlider.setMaximum(size);
+ this.jsCloudSizeSlider.setValue(size);
+ }
+ }
+ this.jsCloudSizeSlider.addChangeListener(this);
+
+ // For tag cloud font size calculations
+ int iMaxCount = this.tcData.getMaxTagCount();
+
+ StringBuffer content = new StringBuffer();
+
+ if (this.tcData.getTags().size() > 0) {
+ content.append("<div class='outer'>");
+ content.append("<br>");
+ content.append("<div class='tag_cloud'>");
+
+ for (Tag t : this.tcData.getTags()) {
+ // Normalise count and use it to obtain a font size value.
+ // Also chops off based on min and max.
+ int fontSize = (int) (((double) t.getCount() / ((double) iMaxCount / 3)) * TAGCLOUD_MAX_FONTSIZE);
+ if (fontSize < TAGCLOUD_MIN_FONTSIZE) {
+ fontSize = TAGCLOUD_MIN_FONTSIZE;
+ }
+ if (fontSize > TAGCLOUD_MAX_FONTSIZE) {
+ fontSize = TAGCLOUD_MAX_FONTSIZE;
+ }
+
+ content.append("<a style='font-size: " + fontSize + "pt;' href='tag:"
+ + t.getTagName() + "'>" + t.getTagName() + "</a>");
+ content.append(" ");
+ }
+
+ content.append("<br>");
+ content.append("</div>");
+ content.append("</div>");
+ } else {
+ content.append("<br>");
+ content.append("<span style='color: gray; font-weight: italic;'> No tags to display</span>");
+ }
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ this.tpTagCloudBody.setEditorKit(kit);
+ this.tpTagCloudBody.setDocument(doc);
+ } catch (Exception e) {
+ logger.error("Failed to populate tag cloud", e);
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ lCloudTitle.setText(strTitle
+ + " <span style='color: gray;'>(currently showing "
+ + tcData.getTags().size() + ")</span>");
+ revalidate();
+ }
+ });
+ }
+
+ /**
+ * Helper method to get hold of the tag cloud data. Needed to be able to set
+ * tag cloud data when using this for resource preview (when tag fetching is
+ * not required).
+ */
+ public TagCloud getTagCloudData() {
+ return this.tcData;
+ }
+
+ public void stateChanged(ChangeEvent e) {
+ if (e.getSource().equals(this.jsCloudSizeSlider)) {
+ // cloud size slider was dragged to a new place and the drag event
+ // has finished; refresh the tag cloud with the newly selected tag count
+ if (!this.jsCloudSizeSlider.getValueIsAdjusting()) {
+ this.cbShowAllTags.removeItemListener(this);
+ this.cbShowAllTags.setSelected(false);
+ this.cbShowAllTags.addItemListener(this);
+
+ this.refresh();
+ }
+ }
+ }
+
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getItemSelectable().equals(this.cbShowAllTags)) {
+ // "Show all" clicked - need to refresh with all tags being displayed
+ this.refresh();
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bRefresh)) {
+ // refresh button clicked on the cloud controls panel -
+ // simply refresh the cloud with the same parameters
+ this.refresh();
+ }
+ }
+
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ if (e.getSource().equals(this.tpTagCloudBody)
+ && e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ // one of the tags was clicked, but click processing is off-loaded to
+ // 'clickHandler'
+ this.clickHandler.actionPerformed(new ActionEvent(this, (this.getClass().getName() + e.getDescription()).hashCode(), e.getDescription()));
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TestJFrameForLocalLaunch.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TestJFrameForLocalLaunch.java
new file mode 100644
index 0000000..15dd3a4
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TestJFrameForLocalLaunch.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Dimension;
+import javax.swing.JFrame;
+
+public class TestJFrameForLocalLaunch {
+
+ /**
+ * This is a simple test class for launching myExperiment perspective
+ * from outside Taverna. At some point it will be not usable anymore,
+ * when proper integration of myExperiment plugin is made.
+ *
+ * @author Sergejs Aleksejevs
+ */
+ public static void main(String[] args)
+ {
+ JFrame frame = new JFrame("myExperiment Perspective Test");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ frame.setMinimumSize(new Dimension(1000, 700));
+ frame.setLocation(300, 150);
+ frame.getContentPane().add(new net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent(null, null));
+
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/UploadWorkflowDialog.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/UploadWorkflowDialog.java
new file mode 100644
index 0000000..ed47183
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/UploadWorkflowDialog.java
@@ -0,0 +1,849 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+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.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.License;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.ServerResponse;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAsAction;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Emmanuel Tagarira, Sergejs Aleksejevs
+ */
+public class UploadWorkflowDialog extends HelpEnabledDialog implements ActionListener,
+ CaretListener, ComponentListener, KeyListener, FocusListener {
+ // components for accessing application's main elements
+ private final MainComponent pluginMainComponent = MainComponent.MAIN_COMPONENT;
+ private final MyExperimentClient myExperimentClient = MainComponent.MY_EXPERIMENT_CLIENT;
+ private final Logger logger = MainComponent.LOGGER;
+
+ // COMPONENTS
+ private JTextArea taDescription;
+ private JTextField tfTitle;
+ private JButton bUpload;
+ private JButton bCancel;
+ private JLabel lStatusMessage;
+ private JComboBox jcbLicences;
+ private JComboBox jcbSharingPermissions;
+ private String licence;
+ private String sharing;
+
+ // STORAGE
+ private File localWorkflowFile; // the local workflow file to be uploaded
+ private Resource updateResource; // the workflow resource that is to be
+ // updated
+ private File uploadFile; // THE UPLOAD FILE
+
+ private String strDescription = null;
+ private String strTitle = null;
+ private boolean bUploadingSuccessful = false;
+
+ // misc.
+ private int gridYPositionForStatusLabel;
+ private JRadioButton rbSelectLocalFile;
+ private JRadioButton rbSelectOpenWorkflow;
+ private JButton bSelectFile;
+ private JComboBox jcbOpenWorkflows;
+ private final JLabel selectedFileLabel = new JLabel("no wokflow file selected");
+ private boolean uploadWorkflowFromLocalFile;
+ JFileChooser jfsSelectFile = new JFileChooser();
+
+ private boolean userRequestedWorkflowUpload;
+ private final FileManager fileManager;
+
+ public UploadWorkflowDialog(JFrame parent, boolean doUpload, FileManager fileManager) {
+ super(parent, "Upload workflow to myExperiment", true);
+ this.fileManager = fileManager;
+ initVarsAndUI(doUpload, null);
+ }
+
+ public UploadWorkflowDialog(JFrame parent, boolean doUpload, Resource resource,
+ FileManager fileManager) {
+ super(parent, (doUpload ? "Upload new workflow version" : "Update workflow information"),
+ true);
+ this.fileManager = fileManager;
+ initVarsAndUI(doUpload, resource);
+ }
+
+ private void initVarsAndUI(boolean doUpload, Resource resource) {
+ // set the resource for which the comment is being added
+ this.updateResource = resource;
+ this.userRequestedWorkflowUpload = doUpload;
+
+ // set options of the 'add comment' dialog box
+ this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ initialiseUI();
+ this.setMinimumSize(new Dimension(525, 375));
+ }
+
+ private JPanel createSelectSource() {
+ // create radio buttons
+ ButtonGroup radioButtons = new ButtonGroup();
+ rbSelectOpenWorkflow = new JRadioButton("Already Opened Workflow");
+ rbSelectOpenWorkflow.addFocusListener(this);
+ rbSelectLocalFile = new JRadioButton("Select Local File");
+ rbSelectLocalFile.addFocusListener(this);
+ radioButtons.add(rbSelectOpenWorkflow);
+ rbSelectOpenWorkflow.setSelected(true);
+ radioButtons.add(rbSelectLocalFile);
+
+ // create the source panel and add items
+ JPanel source = new JPanel(new GridBagLayout());
+ source.setBorder(BorderFactory.createTitledBorder("Workflow source"));
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.gridy = 0;
+ c.gridx = 0;
+ c.gridwidth = 1;
+ c.weightx = 1;
+ c.insets = new Insets(3, 0, 3, 0);
+ c.fill = GridBagConstraints.BOTH;
+
+ // add info label
+ // JLabel info = new
+ // JLabel("Upload a workflow you would like to upload:");
+ // source.add(info, c);
+
+ // add open workflow radio button
+ c.gridy++;
+ source.add(rbSelectOpenWorkflow, c);
+ c.gridx = 1;
+ c.gridwidth = 2;
+ createDropdown();
+ source.add(jcbOpenWorkflows, c);
+
+ // add local file radio button
+ c.gridwidth = 1;
+ c.gridy++;
+ c.gridx = 0;
+ source.add(rbSelectLocalFile, c);
+ c.gridx = 1;
+ source.add(selectedFileLabel, c);
+ bSelectFile = new JButton(WorkbenchIcons.openIcon);
+ bSelectFile.addActionListener(this);
+ bSelectFile.setToolTipText("Select the file you would like to upload to myExperiment");
+ c.gridx = 2;
+ source.add(bSelectFile, c);
+
+ return source;
+ }
+
+ private void createDropdown() {
+ List<DataflowSelection> openDataflows = new ArrayList<DataflowSelection>();
+
+ int currentlyOpenedIndex = 0;
+ boolean foundIndex = false;
+
+ for (Dataflow df : fileManager.getOpenDataflows()) {
+ Object source = fileManager.getDataflowSource(df);
+
+ String name = "";
+ boolean getLocalName = source instanceof InputStream;
+ if (source != null)
+ name = (getLocalName ? df.getLocalName() : source.toString());
+
+ if (df.equals(fileManager.getCurrentDataflow())) {
+ name = "<html><body>" + name + " - " + " <i>(current)</i></body></html>";
+ foundIndex = true;
+ }
+
+ openDataflows.add(new DataflowSelection(df, name));
+ if (!foundIndex)
+ currentlyOpenedIndex++;
+ }
+
+ jcbOpenWorkflows = new JComboBox(openDataflows.toArray());
+ jcbOpenWorkflows.setSelectedIndex(currentlyOpenedIndex);
+ }
+
+ private JPanel createMetadataPanel() {
+ Insets fieldInset = new Insets(0, 5, 4, 5);
+ Insets labelInset = new Insets(3, 5, 4, 5);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.weightx = 1;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ JPanel metaPanel = new JPanel(new GridBagLayout());
+ metaPanel.setBorder(BorderFactory.createTitledBorder("Workflow information"));
+
+ // title
+ JLabel lTitle = new JLabel("Workflow title:");
+ c.insets = labelInset;
+ c.gridy++;
+ metaPanel.add(lTitle, c);
+
+ this.tfTitle = new JTextField();
+ if (this.updateResource != null)
+ this.tfTitle.setText(this.updateResource.getTitle());
+ c.gridy++;
+ c.insets = fieldInset;
+ metaPanel.add(this.tfTitle, c);
+
+ // description
+ JLabel lDescription = new JLabel("Workflow description:");
+ c.gridy++;
+ c.insets = labelInset;
+ metaPanel.add(lDescription, c);
+
+ this.taDescription = new JTextArea(5, 35);
+ this.taDescription.setLineWrap(true);
+ this.taDescription.setWrapStyleWord(true);
+ if (this.updateResource != null)
+ this.taDescription.setText(this.updateResource.getDescription());
+
+ JScrollPane spDescription = new JScrollPane(this.taDescription);
+ c.gridy++;
+ c.insets = fieldInset;
+ metaPanel.add(spDescription, c);
+
+ // licences
+ String[] licenseText = new String[License.SUPPORTED_TYPES.length];
+ for (int x = 0; x < License.SUPPORTED_TYPES.length; x++)
+ licenseText[x] = License.getInstance(License.SUPPORTED_TYPES[x]).getText();
+
+ jcbLicences = new JComboBox(licenseText);
+ String defaultLicenseText = License.getInstance(License.DEFAULT_LICENSE)
+ .getText();
+ jcbLicences.setSelectedItem(defaultLicenseText);
+
+ if (this.updateResource != null) { // adding a new workflow
+ Workflow wf = (Workflow) this.updateResource;
+ String wfText = wf.getLicense().getText();
+ for (int x = 0; x < licenseText.length; x++)
+ if (wfText.equals(licenseText[x])) {
+ jcbLicences.setSelectedIndex(x);
+ break;
+ }
+ }
+
+ jcbLicences.addActionListener(this);
+ jcbLicences.setEditable(false);
+
+ JLabel lLicense = new JLabel("Please select the licence to apply:");
+ c.gridy++;
+ c.insets = labelInset;
+ metaPanel.add(lLicense, c);
+
+ c.gridy++;
+ c.insets = fieldInset;
+ metaPanel.add(jcbLicences, c);
+
+ // sharing - options: private / view / download
+ String[] permissions = { "This workflow is private.",
+ "Anyone can view, but noone can download.",
+ "Anyone can view, and anyone can download" };
+
+ this.jcbSharingPermissions = new JComboBox(permissions);
+
+ jcbSharingPermissions.addActionListener(this);
+ jcbSharingPermissions.setEditable(false);
+
+ JLabel jSharing = new JLabel("Please select your sharing permissions:");
+ c.gridy++;
+ c.insets = labelInset;
+ metaPanel.add(jSharing, c);
+
+ c.gridy++;
+ c.insets = fieldInset;
+ metaPanel.add(jcbSharingPermissions, c);
+
+ return metaPanel;
+ }
+
+ private void initialiseUI() {
+ // get content pane
+ Container contentPane = this.getContentPane();
+
+ Insets fieldInset = new Insets(3, 5, 3, 5);
+
+ // set up layout
+ contentPane.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ // ADD ALL COMPONENTS
+ // source for workflow to upload
+ if (userRequestedWorkflowUpload) {
+ c.insets = fieldInset;
+ contentPane.add(createSelectSource(), c);
+ c.gridy++;
+ }
+
+ // create metadata panel
+ contentPane.add(createMetadataPanel(), c);
+
+ // buttons
+ this.bUpload = new JButton(userRequestedWorkflowUpload ? "Upload Workflow"
+ : "Update Workflow");
+ this.bUpload.setDefaultCapable(true);
+ this.getRootPane().setDefaultButton(this.bUpload);
+ this.bUpload.addActionListener(this);
+ this.bUpload.addKeyListener(this);
+
+ c.gridy++;
+ c.anchor = GridBagConstraints.EAST;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.insets = new Insets(10, 5, 10, 5);
+ contentPane.add(bUpload, c);
+
+ this.bCancel = new JButton("Cancel");
+ this.bCancel.setPreferredSize(this.bUpload.getPreferredSize());
+ this.bCancel.addActionListener(this);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 0.5;
+ contentPane.add(bCancel, c);
+
+ this.pack();
+ this.addComponentListener(this);
+
+ gridYPositionForStatusLabel = c.gridy;
+ }
+
+ /**
+ * Opens up a modal dialog where the user can enter the comment text. Window
+ * is simply closed if 'Cancel' button is pressed; on pressing 'Post
+ * Comment' button the window will turn into 'waiting' state, post the
+ * comment and return the resulting XML document (which would contain the
+ * newly added comment) back to the caller.
+ *
+ * @return String value of the non-empty comment text to be sent to
+ * myExperiment or null if action was cancelled.
+ */
+ public boolean launchUploadDialogAndPostIfRequired() {
+ // makes the 'add comment' dialog visible, then waits until it is
+ // closed;
+ // control returns to this method when the dialog window is disposed
+ this.setVisible(true);
+ return (bUploadingSuccessful);
+ }
+
+ private File performSourceCheck() {
+ if (!rbSelectLocalFile.isSelected() && !rbSelectOpenWorkflow.isSelected()) { // it
+ // is
+ // logicall
+ // impossible
+ // to
+ // get
+ // this
+ // message,
+ // have
+ // it
+ // JUST
+ // IN
+ // CASE
+ JOptionPane.showConfirmDialog(this,
+ "You have not selected a source for you workflow.\n"
+ + "Please select a source and try again.", "Select Workflow Source",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+
+ if (rbSelectOpenWorkflow.isSelected()) { // user requested to use a flow
+ // currently open in t2
+ Dataflow dataflowToUpload = ((DataflowSelection) jcbOpenWorkflows.getSelectedItem())
+ .getDataflow();
+ SaveWorkflowAsAction saveAction = new SaveWorkflowAsAction(fileManager);
+
+ boolean skipPrompt = false;
+ if (fileManager.isDataflowChanged(dataflowToUpload)) { // if flow
+ // has
+ // changed, prompt
+ // user to save
+ JOptionPane.showConfirmDialog(this, "The workflow you are trying to upload has\n"
+ + "changed since the last time it was saved.\n\n"
+ + "Please save your file to proceed...", "Save Workflow",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE);
+ saveAction.saveDataflow(this, dataflowToUpload);
+ skipPrompt = true;
+ }
+
+ File dataflowFile = (File) fileManager.getDataflowSource(dataflowToUpload);
+ if (dataflowFile == null && !skipPrompt) {
+ JOptionPane.showConfirmDialog(this, "You cannot upload an empty workflow.\n"
+ + "Please select a different workflow before\n"
+ + "you attempt the upload again.", "Upload Error",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
+ return null;
+ } else
+ return dataflowFile;
+
+ } else { // user wants to use local file
+ if (localWorkflowFile == null) {
+ JOptionPane.showConfirmDialog(this, "You have not selected a file to upload.\n"
+ + "Please select a file and try again.", "Select Workflow Source",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+
+ return localWorkflowFile;
+ }
+ }
+
+ private void getMetadata() {
+ // get sharing permission
+ switch (this.jcbSharingPermissions.getSelectedIndex()) {
+ case 0:
+ this.sharing = "private";
+ break;
+ case 1:
+ this.sharing = "view";
+ break;
+ case 2:
+ this.sharing = "download";
+ break;
+ }
+
+ // get licence
+ this.licence = License.SUPPORTED_TYPES[this.jcbLicences.getSelectedIndex()];
+
+ // get title
+ this.strTitle = this.tfTitle.getText();
+ this.strTitle = this.strTitle.trim();
+
+ // get description
+ this.strDescription = this.taDescription.getText();
+ this.strDescription = this.strDescription.trim();
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bUpload)) { // * *** *** *** * UPLOAD
+ // BUTTON *
+ // *** *** *** *
+ // perform source check returns a file if attaining the source was
+ // successful
+ if (userRequestedWorkflowUpload) {
+ uploadFile = performSourceCheck();
+ if (uploadFile == null)
+ return;
+ }
+
+ // collect and put the metadata values in their respectable vars
+ getMetadata();
+
+ // if the description or the title are empty, prompt the user to
+ // confirm
+ // the upload
+ boolean proceedWithUpload = false;
+ if ((this.strDescription.length() == 0) && (this.strTitle.length() == 0)) {
+ String strInfo = "The workflow 'title' field and the 'description' field\n"
+ + "(or both) are empty. Any metadata found within the\n"
+ + "workflow will be used instead. Do you wish to proceed?";
+ int confirm = JOptionPane.showConfirmDialog(this, strInfo, "Empty fields",
+ JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
+ if (confirm == JOptionPane.YES_OPTION)
+ proceedWithUpload = true;
+ } else
+ proceedWithUpload = true;
+
+ if (proceedWithUpload) {
+ // the window will stay visible, but should turn into 'waiting'
+ // state
+ final JRootPane rootPane = this.getRootPane();
+ final Container contentPane = this.getContentPane();
+ contentPane.remove(this.bUpload);
+ contentPane.remove(this.bCancel);
+ if (this.lStatusMessage != null)
+ contentPane.remove(this.lStatusMessage);
+ this.taDescription.setEditable(false);
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = gridYPositionForStatusLabel;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.NONE;
+ c.insets = new Insets(10, 5, 10, 5);
+ lStatusMessage = new JLabel((updateResource == null ? "Uploading" : "Updating")
+ + " your workflow...", new ImageIcon(
+ MyExperimentPerspective.getLocalResourceURL("spinner")),
+ SwingConstants.CENTER);
+ contentPane.add(lStatusMessage, c);
+
+ // disable the (X) button (ideally, would need to remove it, but
+ // there's
+ // no way to do this)
+ this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+
+ // revalidate the window
+ this.pack();
+ this.validate();
+ this.repaint();
+
+ new Thread("Posting workflow") {
+ boolean formatRecognized = false;
+
+ @Override
+ public void run() {
+ String workflowFileContent = "";
+ if (userRequestedWorkflowUpload) {
+ if (uploadFile != null) {
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(
+ uploadFile));
+ String line;
+
+ String scuflSchemaDef = "xmlns:s=\"http://org.embl.ebi.escience/xscufl/0.1alpha\"";
+ String t2flowSchemaDef = "xmlns=\"http://taverna.sf.net/2008/xml/t2flow\"";
+
+ while ((line = reader.readLine()) != null) {
+ if (!formatRecognized
+ && (line.contains(scuflSchemaDef) || line
+ .contains(t2flowSchemaDef)))
+ formatRecognized = true;
+
+ workflowFileContent += line + "\n";
+ }
+ } catch (Exception e) {
+ lStatusMessage = new JLabel("Error occurred:" + e.getMessage(),
+ new ImageIcon(MyExperimentPerspective
+ .getLocalResourceURL("failure_icon")),
+ SwingConstants.LEFT);
+ logger.error(e.getCause() + "\n" + e.getMessage());
+ }
+ }
+ }
+
+ // *** POST THE WORKFLOW ***
+ final ServerResponse response;
+
+ if ((userRequestedWorkflowUpload && formatRecognized)
+ || !userRequestedWorkflowUpload) {
+ if (updateResource == null) // upload a new workflow
+ response = myExperimentClient.postWorkflow(workflowFileContent,
+ Util.stripAllHTML(strTitle),
+ Util.stripAllHTML(strDescription), licence, sharing);
+ else
+ // edit existing workflow
+ response = myExperimentClient.updateWorkflowVersionOrMetadata(
+ updateResource, workflowFileContent,
+ Util.stripAllHTML(strTitle),
+ Util.stripAllHTML(strDescription), licence, sharing);
+
+ bUploadingSuccessful = (response.getResponseCode() == HttpURLConnection.HTTP_OK);
+ } else {
+ bUploadingSuccessful = false;
+ response = null;
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // *** REACT TO POSTING RESULT ***
+ if (bUploadingSuccessful) {
+ // workflow uploaded successfully
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+
+ // disable all fields in dialog
+ tfTitle.setEnabled(false);
+ taDescription.setEnabled(false);
+ jcbLicences.setEnabled(false);
+ jcbSharingPermissions.setEnabled(false);
+ if (userRequestedWorkflowUpload) {
+ rbSelectOpenWorkflow.setEnabled(false);
+ rbSelectLocalFile.setEnabled(false);
+ selectedFileLabel.setEnabled(false);
+ bSelectFile.setEnabled(false);
+ jcbOpenWorkflows.setEnabled(false);
+ }
+ contentPane.remove(lStatusMessage);
+
+ c.insets = new Insets(10, 5, 5, 5);
+ lStatusMessage = new JLabel("Your workflow was successfully "
+ + (updateResource == null ? "uploaded." : "updated."),
+ new ImageIcon(MyExperimentPerspective
+ .getLocalResourceURL("success_icon")),
+ SwingConstants.LEFT);
+ contentPane.add(lStatusMessage, c);
+
+ bCancel.setText("OK");
+ bCancel.setDefaultCapable(true);
+ rootPane.setDefaultButton(bCancel);
+ c.insets = new Insets(5, 5, 10, 5);
+ c.gridy++;
+ contentPane.add(bCancel, c);
+
+ pack();
+ bCancel.requestFocusInWindow();
+
+ // update uploaded items history making sure
+ // that:
+ // - there's only one occurrence of this
+ // item in the history;
+ // - if this item was in the history before,
+ // it is moved to
+ // the 'top' now;
+ // - predefined history size is not exceeded
+ MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getUploadedItemsHistoryList().remove(updateResource);
+ MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getUploadedItemsHistoryList().add(updateResource);
+ if (MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getUploadedItemsHistoryList().size() > HistoryBrowserTabContentPanel.UPLOADED_ITEMS_HISTORY_LENGTH) {
+ MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getUploadedItemsHistoryList().remove(0);
+ }
+
+ // now update the uploaded items history
+ // panel in 'History'
+ // tab
+ if (MainComponent.MAIN_COMPONENT.getHistoryBrowser() != null) {
+ MainComponent.MAIN_COMPONENT
+ .getHistoryBrowser()
+ .refreshHistoryBox(
+ HistoryBrowserTabContentPanel.UPLOADED_ITEMS_HISTORY);
+ }
+
+ } else {
+ // posting wasn't successful, notify the
+ // user
+ // and provide an option to close window or
+ // start again
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ taDescription.setEditable(true);
+ contentPane.remove(lStatusMessage);
+
+ c.insets = new Insets(10, 5, 5, 5);
+
+ String msg;
+ if (!formatRecognized)
+ msg = "Error occured: Invalid Taverna workflow.";
+ else
+ msg = "An error occured while processing your request.";
+
+ lStatusMessage = new JLabel(msg, new ImageIcon(
+ MyExperimentPerspective
+ .getLocalResourceURL("failure_icon")),
+ SwingConstants.LEFT);
+ contentPane.add(lStatusMessage, c);
+
+ bUpload.setText("Try again");
+ bUpload.setToolTipText("Please review your workflow or myExperiment base URL");
+ c.anchor = GridBagConstraints.EAST;
+ c.insets = new Insets(5, 5, 10, 5);
+ c.gridwidth = 1;
+ c.weightx = 0.5;
+ c.gridx = 0;
+ c.gridy++;
+ contentPane.add(bUpload, c);
+ rootPane.setDefaultButton(bUpload);
+
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 1;
+ bCancel.setPreferredSize(bUpload.getPreferredSize());
+ contentPane.add(bCancel, c);
+
+ pack();
+ validate();
+ repaint();
+ }
+ }
+ });
+ }
+ }.start();
+ } // if proceedWithUpload
+ } else if (e.getSource().equals(this.bCancel)) { // * CANCEL BUTTON *
+ // cleanup the input fields if it wasn't posted successfully +
+ // simply
+ // close and destroy the window
+ if (!this.bUploadingSuccessful) {
+ this.strDescription = null;
+ this.tfTitle = null;
+ }
+ this.dispose();
+ } else if (e.getSource().equals(bSelectFile)) {// * SELECT FILE BUTTON *
+ // *
+ if (jfsSelectFile.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+ localWorkflowFile = jfsSelectFile.getSelectedFile();
+
+ if (localWorkflowFile != null) {
+ selectedFileLabel.setText(localWorkflowFile.getAbsolutePath());
+ selectedFileLabel.setEnabled(true);
+ }
+ pack();
+ }
+ }
+ }
+
+ public void keyPressed(KeyEvent e) {
+ // if TAB was pressed in the text field (title), need to move keyboard
+ // focus
+ if (e.getSource().equals(this.tfTitle) || e.getSource().equals(this.taDescription)) {
+ if (e.getKeyCode() == KeyEvent.VK_TAB) {
+ if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) == KeyEvent.SHIFT_DOWN_MASK) {
+ // SHIFT + TAB move focus backwards
+ ((Component) e.getSource()).transferFocusBackward();
+ } else {
+ // TAB moves focus forward
+ ((Component) e.getSource()).transferFocus();
+ }
+ e.consume();
+ }
+ }
+ }
+
+ public void focusGained(FocusEvent e) {
+ if (e.getSource().equals(rbSelectLocalFile)) {
+ uploadWorkflowFromLocalFile = true;
+ bSelectFile.setEnabled(uploadWorkflowFromLocalFile);
+ jcbOpenWorkflows.setEnabled(!uploadWorkflowFromLocalFile);
+
+ if (localWorkflowFile != null) {
+ selectedFileLabel.setEnabled(uploadWorkflowFromLocalFile);
+ selectedFileLabel.setText(localWorkflowFile.getAbsolutePath());
+ pack();
+ } else
+ selectedFileLabel.setEnabled(!uploadWorkflowFromLocalFile);
+
+ } else if (e.getSource().equals(rbSelectOpenWorkflow)) {
+ uploadWorkflowFromLocalFile = false;
+ selectedFileLabel.setEnabled(uploadWorkflowFromLocalFile);
+ bSelectFile.setEnabled(uploadWorkflowFromLocalFile);
+ jcbOpenWorkflows.setEnabled(!uploadWorkflowFromLocalFile);
+ }
+ }
+
+ public void componentShown(ComponentEvent e) {
+ // center this dialog box within the preview browser window
+ if (updateResource == null) // upload has been pressed from the MAIN
+ // perspective window
+ Util.centerComponentWithinAnother(this.pluginMainComponent, this);
+ else
+ // upload pressed from resource preview window
+ Util.centerComponentWithinAnother(this.pluginMainComponent.getPreviewBrowser(), this);
+ }
+
+ public void focusLost(FocusEvent e) {
+ // not in use
+ }
+
+ public void caretUpdate(CaretEvent e) {
+ // not in use
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ // not in use
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ // not in use
+ }
+
+ public void keyReleased(KeyEvent e) {
+ // not in use
+ }
+
+ public void keyTyped(KeyEvent e) {
+ // not in use
+ }
+
+ public void componentResized(ComponentEvent e) {
+ // not in use
+ }
+
+ private class DataflowSelection {
+ private final Dataflow dataflow;
+ private final String name;
+
+ public DataflowSelection(Dataflow dataflow, String name) {
+ this.dataflow = dataflow;
+ this.name = name;
+ }
+
+ public Dataflow getDataflow() {
+ return dataflow;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$InputStream.class b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$InputStream.class
new file mode 100644
index 0000000..a1a5f82
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$InputStream.class
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$OutputStream.class b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$OutputStream.class
new file mode 100644
index 0000000..e754656
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$OutputStream.class
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.class b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.class
new file mode 100644
index 0000000..84fcee9
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.class
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.java
new file mode 100644
index 0000000..00af810
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.java
@@ -0,0 +1,1813 @@
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+import net.sf.taverna.t2.activities.dataflow.views.DataflowActivityConfigView;
+
+import org.apache.log4j.Logger;
+
+/**
+ * <p>Encodes and decodes to and from Base64 notation.</p>
+ * <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
+ *
+ * <p>The <tt>options</tt> parameter, which appears in a few places, is used to pass
+ * several pieces of information to the encoder. In the "higher level" methods such as
+ * encodeBytes( bytes, options ) the options parameter can be used to indicate such
+ * things as first gzipping the bytes before encoding them, not inserting linefeeds
+ * (though that breaks strict Base64 compatibility), and encoding using the URL-safe
+ * and Ordered dialects.</p>
+ *
+ * <p>The constants defined in Base64 can be OR-ed together to combine options, so you
+ * might make a call like this:</p>
+ *
+ * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DONT_BREAK_LINES );</code>
+ *
+ * <p>to compress the data before encoding it and then making the output have no newline characters.</p>
+ *
+ *
+ * <p>
+ * Change Log:
+ * </p>
+ * <ul>
+ * <li>v2.2.2 - Fixed encodeFileToFile and decodeFileToFile to use the
+ * Base64.InputStream class to encode and decode on the fly which uses
+ * less memory than encoding/decoding an entire file into memory before writing.</li>
+ * <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug
+ * when using very small files (~< 40 bytes).</li>
+ * <li>v2.2 - Added some helper methods for encoding/decoding directly from
+ * one file to the next. Also added a main() method to support command line
+ * encoding/decoding from one file to the next. Also added these Base64 dialects:
+ * <ol>
+ * <li>The default is RFC3548 format.</li>
+ * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates
+ * URL and file name friendly format as described in Section 4 of RFC3548.
+ * http://www.faqs.org/rfcs/rfc3548.html</li>
+ * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates
+ * URL and file name friendly format that preserves lexical ordering as described
+ * in http://www.faqs.org/qa/rfcc-1940.html</li>
+ * </ol>
+ * Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a>
+ * for contributing the new Base64 dialects.
+ * </li>
+ *
+ * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
+ * some convenience methods for reading and writing to and from files.</li>
+ * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
+ * with other encodings (like EBCDIC).</li>
+ * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
+ * encoded data was a single byte.</li>
+ * <li>v2.0 - I got rid of methods that used booleans to set options.
+ * Now everything is more consolidated and cleaner. The code now detects
+ * when data that's being decoded is gzip-compressed and will decompress it
+ * automatically. Generally things are cleaner. You'll probably have to
+ * change some method calls that you were making to support the new
+ * options format (<tt>int</tt>s that you "OR" together).</li>
+ * <li>v1.5.1 - Fixed bug when decompressing and decoding to a
+ * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
+ * Added the ability to "suspend" encoding in the Output Stream so
+ * you can turn on and off the encoding if you need to embed base64
+ * data in an otherwise "normal" stream (like an XML file).</li>
+ * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
+ * This helps when using GZIP streams.
+ * Added the ability to GZip-compress objects before encoding them.</li>
+ * <li>v1.4 - Added helper methods to read/write files.</li>
+ * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
+ * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
+ * where last buffer being read, if not completely full, was not returned.</li>
+ * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
+ * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
+ * </ul>
+ *
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rob@iharder.net
+ * @version 2.2.2
+ */
+public class Base64
+{
+
+ private static Logger logger = Logger
+ .getLogger(Base64.class);
+
+ /* ******** P U B L I C F I E L D S ******** */
+
+
+ /** No options specified. Value is zero. */
+ public final static int NO_OPTIONS = 0;
+
+ /** Specify encoding. */
+ public final static int ENCODE = 1;
+
+
+ /** Specify decoding. */
+ public final static int DECODE = 0;
+
+
+ /** Specify that data should be gzip-compressed. */
+ public final static int GZIP = 2;
+
+
+ /** Don't break lines when encoding (violates strict Base64 specification) */
+ public final static int DONT_BREAK_LINES = 8;
+
+ /**
+ * Encode using Base64-like encoding that is URL- and Filename-safe as described
+ * in Section 4 of RFC3548:
+ * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
+ * It is important to note that data encoded this way is <em>not</em> officially valid Base64,
+ * or at the very least should not be called Base64 without also specifying that is
+ * was encoded using the URL- and Filename-safe dialect.
+ */
+ public final static int URL_SAFE = 16;
+
+
+ /**
+ * Encode using the special "ordered" dialect of Base64 described here:
+ * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+ */
+ public final static int ORDERED = 32;
+
+
+/* ******** P R I V A T E F I E L D S ******** */
+
+
+ /** Maximum line length (76) of Base64 output. */
+ private final static int MAX_LINE_LENGTH = 76;
+
+
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte)'=';
+
+
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte)'\n';
+
+
+ /** Preferred encoding. */
+ private final static String PREFERRED_ENCODING = "UTF-8";
+
+
+ // I think I end up not using the BAD_ENCODING indicator.
+ //private final static byte BAD_ENCODING = -9; // Indicates error in encoding
+ private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
+ private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
+
+
+/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */
+
+ /** The 64 valid Base64 values. */
+ //private final static byte[] ALPHABET;
+ /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
+ private final static byte[] _STANDARD_ALPHABET =
+ {
+ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+ (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
+ };
+
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value
+ * or a negative number indicating some other meaning.
+ **/
+ private final static byte[] _STANDARD_DECODABET =
+ {
+ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
+ -5,-5, // Whitespace: Tab and Linefeed
+ -9,-9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
+ -9,-9,-9,-9,-9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9,-9,-9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
+ -9,-9,-9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9,-9,-9, // Decimal 62 - 64
+ 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
+ 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
+ -9,-9,-9,-9,-9,-9, // Decimal 91 - 96
+ 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
+ 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
+ -9,-9,-9,-9 // Decimal 123 - 126
+ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+
+/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */
+
+ /**
+ * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548:
+ * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
+ * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash."
+ */
+ private final static byte[] _URL_SAFE_ALPHABET =
+ {
+ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+ (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_'
+ };
+
+ /**
+ * Used in decoding URL- and Filename-safe dialects of Base64.
+ */
+ private final static byte[] _URL_SAFE_DECODABET =
+ {
+ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
+ -5,-5, // Whitespace: Tab and Linefeed
+ -9,-9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
+ -9,-9,-9,-9,-9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
+ -9, // Plus sign at decimal 43
+ -9, // Decimal 44
+ 62, // Minus sign at decimal 45
+ -9, // Decimal 46
+ -9, // Slash at decimal 47
+ 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
+ -9,-9,-9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9,-9,-9, // Decimal 62 - 64
+ 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
+ 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
+ -9,-9,-9,-9, // Decimal 91 - 94
+ 63, // Underscore at decimal 95
+ -9, // Decimal 96
+ 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
+ 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
+ -9,-9,-9,-9 // Decimal 123 - 126
+ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+
+
+/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */
+
+ /**
+ * I don't get the point of this technique, but it is described here:
+ * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+ */
+ private final static byte[] _ORDERED_ALPHABET =
+ {
+ (byte)'-',
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4',
+ (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9',
+ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+ (byte)'_',
+ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z'
+ };
+
+ /**
+ * Used in decoding the "ordered" dialect of Base64.
+ */
+ private final static byte[] _ORDERED_DECODABET =
+ {
+ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
+ -5,-5, // Whitespace: Tab and Linefeed
+ -9,-9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
+ -9,-9,-9,-9,-9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
+ -9, // Plus sign at decimal 43
+ -9, // Decimal 44
+ 0, // Minus sign at decimal 45
+ -9, // Decimal 46
+ -9, // Slash at decimal 47
+ 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine
+ -9,-9,-9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9,-9,-9, // Decimal 62 - 64
+ 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M'
+ 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z'
+ -9,-9,-9,-9, // Decimal 91 - 94
+ 37, // Underscore at decimal 95
+ -9, // Decimal 96
+ 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm'
+ 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z'
+ -9,-9,-9,-9 // Decimal 123 - 126
+ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+
+/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */
+
+
+ /**
+ * Returns one of the _SOMETHING_ALPHABET byte arrays depending on
+ * the options specified.
+ * It's possible, though silly, to specify ORDERED and URLSAFE
+ * in which case one of them will be picked, though there is
+ * no guarantee as to which one will be picked.
+ */
+ private final static byte[] getAlphabet( int options )
+ {
+ if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_ALPHABET;
+ else if( (options & ORDERED) == ORDERED ) return _ORDERED_ALPHABET;
+ else return _STANDARD_ALPHABET;
+
+ } // end getAlphabet
+
+
+ /**
+ * Returns one of the _SOMETHING_DECODABET byte arrays depending on
+ * the options specified.
+ * It's possible, though silly, to specify ORDERED and URL_SAFE
+ * in which case one of them will be picked, though there is
+ * no guarantee as to which one will be picked.
+ */
+ private final static byte[] getDecodabet( int options )
+ {
+ if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_DECODABET;
+ else if( (options & ORDERED) == ORDERED ) return _ORDERED_DECODABET;
+ else return _STANDARD_DECODABET;
+
+ } // end getAlphabet
+
+
+
+ /** Defeats instantiation. */
+ private Base64(){}
+
+
+ /**
+ * Encodes or decodes two files from the command line;
+ * <strong>feel free to delete this method (in fact you probably should)
+ * if you're embedding this code into a larger program.</strong>
+ */
+ public final static void main( String[] args )
+ {
+ if( args.length < 3 ){
+ usage("Not enough arguments.");
+ } // end if: args.length < 3
+ else {
+ String flag = args[0];
+ String infile = args[1];
+ String outfile = args[2];
+ if( flag.equals( "-e" ) ){
+ Base64.encodeFileToFile( infile, outfile );
+ } // end if: encode
+ else if( flag.equals( "-d" ) ) {
+ Base64.decodeFileToFile( infile, outfile );
+ } // end else if: decode
+ else {
+ usage( "Unknown flag: " + flag );
+ } // end else
+ } // end else
+ } // end main
+
+ /**
+ * Prints command line usage.
+ *
+ * @param msg A message to include with usage info.
+ */
+ private final static void usage( String msg )
+ {
+ logger.error( msg );
+ logger.error( "Usage: java Base64 -e|-d inputfile outputfile" );
+ } // end usage
+
+
+/* ******** E N C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Encodes up to the first three bytes of array <var>threeBytes</var>
+ * and returns a four-byte array in Base64 notation.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ * The array <var>threeBytes</var> needs only be as big as
+ * <var>numSigBytes</var>.
+ * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
+ *
+ * @param b4 A reusable byte array to reduce array instantiation
+ * @param threeBytes the array to convert
+ * @param numSigBytes the number of significant bytes in your array
+ * @return four byte array in Base64 notation.
+ * @since 1.5.1
+ */
+ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options )
+ {
+ encode3to4( threeBytes, 0, numSigBytes, b4, 0, options );
+ return b4;
+ } // end encode3to4
+
+
+ /**
+ * <p>Encodes up to three bytes of the array <var>source</var>
+ * and writes the resulting four Base64 bytes to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accomodate <var>srcOffset</var> + 3 for
+ * the <var>source</var> array or <var>destOffset</var> + 4 for
+ * the <var>destination</var> array.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.</p>
+ * <p>This is the lowest level of the encoding methods with
+ * all possible parameters.</p>
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @return the <var>destination</var> array
+ * @since 1.3
+ */
+ private static byte[] encode3to4(
+ byte[] source, int srcOffset, int numSigBytes,
+ byte[] destination, int destOffset, int options )
+ {
+ byte[] ALPHABET = getAlphabet( options );
+
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index ALPHABET
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
+ | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
+ | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
+
+ switch( numSigBytes )
+ {
+ case 3:
+ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
+ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+ destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
+ destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
+ return destination;
+
+ case 2:
+ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
+ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+ destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
+ destination[ destOffset + 3 ] = EQUALS_SIGN;
+ return destination;
+
+ case 1:
+ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
+ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+ destination[ destOffset + 2 ] = EQUALS_SIGN;
+ destination[ destOffset + 3 ] = EQUALS_SIGN;
+ return destination;
+
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+
+
+ /**
+ * Serializes an object and returns the Base64-encoded
+ * version of that serialized object. If the object
+ * cannot be serialized or there is another error,
+ * the method will return <tt>null</tt>.
+ * The object is not GZip-compressed before being encoded.
+ *
+ * @param serializableObject The object to encode
+ * @return The Base64-encoded object
+ * @since 1.4
+ */
+ public static String encodeObject( java.io.Serializable serializableObject )
+ {
+ return encodeObject( serializableObject, NO_OPTIONS );
+ } // end encodeObject
+
+
+
+ /**
+ * Serializes an object and returns the Base64-encoded
+ * version of that serialized object. If the object
+ * cannot be serialized or there is another error,
+ * the method will return <tt>null</tt>.
+ * <p>
+ * Valid options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+ *
+ * @param serializableObject The object to encode
+ * @param options Specified options
+ * @return The Base64-encoded object
+ * @see Base64#GZIP
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeObject( java.io.Serializable serializableObject, int options )
+ {
+ // Streams
+ java.io.ByteArrayOutputStream baos = null;
+ java.io.OutputStream b64os = null;
+ java.io.ObjectOutputStream oos = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+
+ // Isolate options
+ int gzip = (options & GZIP);
+ int dontBreakLines = (options & DONT_BREAK_LINES);
+
+ try
+ {
+ // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new Base64.OutputStream( baos, ENCODE | options );
+
+ // GZip?
+ if( gzip == GZIP )
+ {
+ gzos = new java.util.zip.GZIPOutputStream( b64os );
+ oos = new java.io.ObjectOutputStream( gzos );
+ } // end if: gzip
+ else
+ oos = new java.io.ObjectOutputStream( b64os );
+
+ oos.writeObject( serializableObject );
+ } // end try
+ catch( java.io.IOException e )
+ {
+ logger.error("Could not Base64 encode object");
+ return null;
+ } // end catch
+ finally
+ {
+ try{ oos.close(); } catch( Exception e ){logger.error("", e);}
+ } // end finally
+
+ // Return value according to relevant encoding.
+ try
+ {
+ return new String( baos.toByteArray(), PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue)
+ {
+ return new String( baos.toByteArray() );
+ } // end catch
+
+ } // end encode
+
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Does not GZip-compress data.
+ *
+ * @param source The data to convert
+ * @since 1.4
+ */
+ public static String encodeBytes( byte[] source )
+ {
+ return encodeBytes( source, 0, source.length, NO_OPTIONS );
+ } // end encodeBytes
+
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * <p>
+ * Valid options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+ *
+ *
+ * @param source The data to convert
+ * @param options Specified options
+ * @see Base64#GZIP
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes( byte[] source, int options )
+ {
+ return encodeBytes( source, 0, source.length, options );
+ } // end encodeBytes
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Does not GZip-compress data.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @since 1.4
+ */
+ public static String encodeBytes( byte[] source, int off, int len )
+ {
+ return encodeBytes( source, off, len, NO_OPTIONS );
+ } // end encodeBytes
+
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * <p>
+ * Valid options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+ *
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param options Specified options
+ * @param options alphabet type is pulled from this (standard, url-safe, ordered)
+ * @see Base64#GZIP
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes( byte[] source, int off, int len, int options )
+ {
+ // Isolate options
+ int dontBreakLines = ( options & DONT_BREAK_LINES );
+ int gzip = ( options & GZIP );
+
+ // Compress?
+ if( gzip == GZIP )
+ {
+ java.io.ByteArrayOutputStream baos = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+ Base64.OutputStream b64os = null;
+
+
+ try
+ {
+ // GZip -> Base64 -> ByteArray
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new Base64.OutputStream( baos, ENCODE | options );
+ gzos = new java.util.zip.GZIPOutputStream( b64os );
+
+ gzos.write( source, off, len );
+ gzos.close();
+ } // end try
+ catch( java.io.IOException e )
+ {
+ logger.error("Could not encode bytes", e);
+ return null;
+ } // end catch
+ finally
+ {
+ try{ gzos.close(); } catch( Exception e ){logger.error("", e);}
+ try{ b64os.close(); } catch( Exception e ){logger.error("", e);}
+ try{ baos.close(); } catch( Exception e ){logger.error("", e);}
+ } // end finally
+
+ // Return value according to relevant encoding.
+ try
+ {
+ return new String( baos.toByteArray(), PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue)
+ {
+ return new String( baos.toByteArray() );
+ } // end catch
+ } // end if: compress
+
+ // Else, don't compress. Better not to use streams at all then.
+ else
+ {
+ // Convert option to boolean in way that code likes it.
+ boolean breakLines = dontBreakLines == 0;
+
+ int len43 = len * 4 / 3;
+ byte[] outBuff = new byte[ ( len43 ) // Main 4:3
+ + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
+ + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for( ; d < len2; d+=3, e+=4 )
+ {
+ encode3to4( source, d+off, 3, outBuff, e, options );
+
+ lineLength += 4;
+ if( breakLines && lineLength == MAX_LINE_LENGTH )
+ {
+ outBuff[e+4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // en dfor: each piece of array
+
+ if( d < len )
+ {
+ encode3to4( source, d+off, len - d, outBuff, e, options );
+ e += 4;
+ } // end if: some padding needed
+
+
+ // Return value according to relevant encoding.
+ try
+ {
+ return new String( outBuff, 0, e, PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue)
+ {
+ return new String( outBuff, 0, e );
+ } // end catch
+
+ } // end else: don't compress
+
+ } // end encodeBytes
+
+
+
+
+
+/* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Decodes four bytes from array <var>source</var>
+ * and writes the resulting bytes (up to three of them)
+ * to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accomodate <var>srcOffset</var> + 4 for
+ * the <var>source</var> array or <var>destOffset</var> + 3 for
+ * the <var>destination</var> array.
+ * This method returns the actual number of bytes that
+ * were converted from the Base64 encoding.
+ * <p>This is the lowest level of the decoding methods with
+ * all possible parameters.</p>
+ *
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param options alphabet type is pulled from this (standard, url-safe, ordered)
+ * @return the number of decoded bytes converted
+ * @since 1.3
+ */
+ private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset, int options )
+ {
+ byte[] DECODABET = getDecodabet( options );
+
+ // Example: Dk==
+ if( source[ srcOffset + 2] == EQUALS_SIGN )
+ {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
+ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
+ | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
+
+ destination[ destOffset ] = (byte)( outBuff >>> 16 );
+ return 1;
+ }
+
+ // Example: DkL=
+ else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
+ {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
+ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
+ | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+ | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
+
+ destination[ destOffset ] = (byte)( outBuff >>> 16 );
+ destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
+ return 2;
+ }
+
+ // Example: DkLE
+ else
+ {
+ try{
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+ // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
+ | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+ | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
+ | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
+
+
+ destination[ destOffset ] = (byte)( outBuff >> 16 );
+ destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
+ destination[ destOffset + 2 ] = (byte)( outBuff );
+
+ return 3;
+ }catch( Exception e){
+ logger.error(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) , e);
+ logger.error(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ), e);
+ logger.error(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ), e);
+ logger.error(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ), e);
+ return -1;
+ } // end catch
+ }
+ } // end decodeToBytes
+
+
+
+
+ /**
+ * Very low-level access to decoding ASCII characters in
+ * the form of a byte array. Does not support automatically
+ * gunzipping or any other "fancy" features.
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @return decoded data
+ * @since 1.3
+ */
+ public static byte[] decode( byte[] source, int off, int len, int options )
+ {
+ byte[] DECODABET = getDecodabet( options );
+
+ int len34 = len * 3 / 4;
+ byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
+ int outBuffPosn = 0;
+
+ byte[] b4 = new byte[4];
+ int b4Posn = 0;
+ int i = 0;
+ byte sbiCrop = 0;
+ byte sbiDecode = 0;
+ for( i = off; i < off+len; i++ )
+ {
+ sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
+ sbiDecode = DECODABET[ sbiCrop ];
+
+ if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
+ {
+ if( sbiDecode >= EQUALS_SIGN_ENC )
+ {
+ b4[ b4Posn++ ] = sbiCrop;
+ if( b4Posn > 3 )
+ {
+ outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options );
+ b4Posn = 0;
+
+ // If that was the equals sign, break out of 'for' loop
+ if( sbiCrop == EQUALS_SIGN )
+ break;
+ } // end if: quartet built
+
+ } // end if: equals sign or better
+
+ } // end if: white space, equals sign or better
+ else
+ {
+ logger.error( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
+ return null;
+ } // end else:
+ } // each input character
+
+ byte[] out = new byte[ outBuffPosn ];
+ System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
+ return out;
+ } // end decode
+
+
+
+
+ /**
+ * Decodes data from Base64 notation, automatically
+ * detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ * @return the decoded data
+ * @since 1.4
+ */
+ public static byte[] decode( String s )
+ {
+ return decode( s, NO_OPTIONS );
+ }
+
+
+ /**
+ * Decodes data from Base64 notation, automatically
+ * detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ * @param options encode options such as URL_SAFE
+ * @return the decoded data
+ * @since 1.4
+ */
+ public static byte[] decode( String s, int options )
+ {
+ byte[] bytes;
+ try
+ {
+ bytes = s.getBytes( PREFERRED_ENCODING );
+ } // end try
+ catch( java.io.UnsupportedEncodingException uee )
+ {
+ bytes = s.getBytes();
+ } // end catch
+ //</change>
+
+ // Decode
+ bytes = decode( bytes, 0, bytes.length, options );
+
+
+ // Check to see if it's gzip-compressed
+ // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+ if( bytes != null && bytes.length >= 4 )
+ {
+
+ int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
+ if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head )
+ {
+ java.io.ByteArrayInputStream bais = null;
+ java.util.zip.GZIPInputStream gzis = null;
+ java.io.ByteArrayOutputStream baos = null;
+ byte[] buffer = new byte[2048];
+ int length = 0;
+
+ try
+ {
+ baos = new java.io.ByteArrayOutputStream();
+ bais = new java.io.ByteArrayInputStream( bytes );
+ gzis = new java.util.zip.GZIPInputStream( bais );
+
+ while( ( length = gzis.read( buffer ) ) >= 0 )
+ {
+ baos.write(buffer,0,length);
+ } // end while: reading input
+
+ // No error? Get new bytes.
+ bytes = baos.toByteArray();
+
+ } // end try
+ catch( java.io.IOException e )
+ {
+ // Just return originally-decoded bytes
+ } // end catch
+ finally
+ {
+ try{ baos.close(); } catch( Exception e ){logger.error("", e);}
+ try{ gzis.close(); } catch( Exception e ){logger.error("", e);}
+ try{ bais.close(); } catch( Exception e ){logger.error("", e);}
+ } // end finally
+
+ } // end if: gzipped
+ } // end if: bytes.length >= 2
+
+ return bytes;
+ } // end decode
+
+
+
+
+ /**
+ * Attempts to decode Base64 data and deserialize a Java
+ * Object within. Returns <tt>null</tt> if there was an error.
+ *
+ * @param encodedObject The Base64 data to decode
+ * @return The decoded and deserialized object
+ * @since 1.5
+ */
+ public static Object decodeToObject( String encodedObject )
+ {
+ // Decode and gunzip if necessary
+ byte[] objBytes = decode( encodedObject );
+
+ java.io.ByteArrayInputStream bais = null;
+ java.io.ObjectInputStream ois = null;
+ Object obj = null;
+
+ try
+ {
+ bais = new java.io.ByteArrayInputStream( objBytes );
+ ois = new java.io.ObjectInputStream( bais );
+
+ obj = ois.readObject();
+ } // end try
+ catch( java.io.IOException e )
+ {
+ logger.error("Could not decode Base64 object", e);
+ obj = null;
+ } // end catch
+ catch( java.lang.ClassNotFoundException e )
+ {
+ logger.error("Could not decode Base64 object", e);
+ obj = null;
+ } // end catch
+ finally
+ {
+ try{ bais.close(); } catch( Exception e ){logger.error("", e);}
+ try{ ois.close(); } catch( Exception e ){logger.error("", e);}
+ } // end finally
+
+ return obj;
+ } // end decodeObject
+
+
+
+ /**
+ * Convenience method for encoding data to a file.
+ *
+ * @param dataToEncode byte array of data to encode in base64 form
+ * @param filename Filename for saving encoded data
+ * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
+ *
+ * @since 2.1
+ */
+ public static boolean encodeToFile( byte[] dataToEncode, String filename )
+ {
+ boolean success = false;
+ Base64.OutputStream bos = null;
+ try
+ {
+ bos = new Base64.OutputStream(
+ new java.io.FileOutputStream( filename ), Base64.ENCODE );
+ bos.write( dataToEncode );
+ success = true;
+ } // end try
+ catch( java.io.IOException e )
+ {
+
+ success = false;
+ } // end catch: IOException
+ finally
+ {
+ try{ bos.close(); } catch( Exception e ){logger.error("", e);}
+ } // end finally
+
+ return success;
+ } // end encodeToFile
+
+
+ /**
+ * Convenience method for decoding data to a file.
+ *
+ * @param dataToDecode Base64-encoded data as a string
+ * @param filename Filename for saving decoded data
+ * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
+ *
+ * @since 2.1
+ */
+ public static boolean decodeToFile( String dataToDecode, String filename )
+ {
+ boolean success = false;
+ Base64.OutputStream bos = null;
+ try
+ {
+ bos = new Base64.OutputStream(
+ new java.io.FileOutputStream( filename ), Base64.DECODE );
+ bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
+ success = true;
+ } // end try
+ catch( java.io.IOException e )
+ {
+ success = false;
+ } // end catch: IOException
+ finally
+ {
+ try{ bos.close(); } catch( Exception e ){logger.error("", e);}
+ } // end finally
+
+ return success;
+ } // end decodeToFile
+
+
+
+
+ /**
+ * Convenience method for reading a base64-encoded
+ * file and decoding it.
+ *
+ * @param filename Filename for reading encoded data
+ * @return decoded byte array or null if unsuccessful
+ *
+ * @since 2.1
+ */
+ public static byte[] decodeFromFile( String filename )
+ {
+ byte[] decodedData = null;
+ Base64.InputStream bis = null;
+ try
+ {
+ // Set up some useful variables
+ java.io.File file = new java.io.File( filename );
+ byte[] buffer = null;
+ int length = 0;
+ int numBytes = 0;
+
+ // Check for size of file
+ if( file.length() > Integer.MAX_VALUE )
+ {
+ logger.error( "File is too big for this convenience method (" + file.length() + " bytes)." );
+ return null;
+ } // end if: file too big for int index
+ buffer = new byte[ (int)file.length() ];
+
+ // Open a stream
+ bis = new Base64.InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream( file ) ), Base64.DECODE );
+
+ // Read until done
+ while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
+ length += numBytes;
+
+ // Save in a variable to return
+ decodedData = new byte[ length ];
+ System.arraycopy( buffer, 0, decodedData, 0, length );
+
+ } // end try
+ catch( java.io.IOException e )
+ {
+ logger.error( "Error decoding from file " + filename, e);
+ } // end catch: IOException
+ finally
+ {
+ try{ bis.close(); } catch( Exception e) {logger.error("", e);}
+ } // end finally
+
+ return decodedData;
+ } // end decodeFromFile
+
+
+
+ /**
+ * Convenience method for reading a binary file
+ * and base64-encoding it.
+ *
+ * @param filename Filename for reading binary data
+ * @return base64-encoded string or null if unsuccessful
+ *
+ * @since 2.1
+ */
+ public static String encodeFromFile( String filename )
+ {
+ String encodedData = null;
+ Base64.InputStream bis = null;
+ try
+ {
+ // Set up some useful variables
+ java.io.File file = new java.io.File( filename );
+ byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4),40) ]; // Need max() for math on small files (v2.2.1)
+ int length = 0;
+ int numBytes = 0;
+
+ // Open a stream
+ bis = new Base64.InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream( file ) ), Base64.ENCODE );
+
+ // Read until done
+ while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
+ length += numBytes;
+
+ // Save in a variable to return
+ encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
+
+ } // end try
+ catch( java.io.IOException e )
+ {
+ logger.error( "Error encoding from file " + filename, e);
+ } // end catch: IOException
+ finally
+ {
+ try{ bis.close(); } catch( Exception e) {logger.error("", e);}
+ } // end finally
+
+ return encodedData;
+ } // end encodeFromFile
+
+
+
+
+ /**
+ * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>.
+ *
+ * @param infile Input file
+ * @param outfile Output file
+ * @return true if the operation is successful
+ * @since 2.2
+ */
+ public static boolean encodeFileToFile( String infile, String outfile )
+ {
+ boolean success = false;
+ java.io.InputStream in = null;
+ java.io.OutputStream out = null;
+ try{
+ in = new Base64.InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream( infile ) ),
+ Base64.ENCODE );
+ out = new java.io.BufferedOutputStream( new java.io.FileOutputStream( outfile ) );
+ byte[] buffer = new byte[65536]; // 64K
+ int read = -1;
+ while( ( read = in.read(buffer) ) >= 0 ){
+ out.write( buffer,0,read );
+ } // end while: through file
+ success = true;
+ } catch( java.io.IOException exc ){
+ logger.error("Could not encode Base64 file", exc);
+ } finally{
+ try{ in.close(); } catch( Exception exc ){logger.error("", exc);}
+ try{ out.close(); } catch( Exception exc ){logger.error("", exc);}
+ } // end finally
+
+ return success;
+ } // end encodeFileToFile
+
+
+
+ /**
+ * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>.
+ *
+ * @param infile Input file
+ * @param outfile Output file
+ * @return true if the operation is successful
+ * @since 2.2
+ */
+ public static boolean decodeFileToFile( String infile, String outfile )
+ {
+ boolean success = false;
+ java.io.InputStream in = null;
+ java.io.OutputStream out = null;
+ try{
+ in = new Base64.InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream( infile ) ),
+ Base64.DECODE );
+ out = new java.io.BufferedOutputStream( new java.io.FileOutputStream( outfile ) );
+ byte[] buffer = new byte[65536]; // 64K
+ int read = -1;
+ while( ( read = in.read(buffer) ) >= 0 ){
+ out.write( buffer,0,read );
+ } // end while: through file
+ success = true;
+ } catch( java.io.IOException exc ){
+ logger.error("Could not decode Base64 file", exc);
+ } finally{
+ try{ in.close(); } catch( Exception exc ){logger.error("", exc);}
+ try{ out.close(); } catch( Exception exc ){logger.error("", exc);}
+ } // end finally
+
+ return success;
+ } // end decodeFileToFile
+
+
+ /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
+
+
+
+ /**
+ * A {@link Base64.InputStream} will read data from another
+ * <tt>java.io.InputStream</tt>, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class InputStream extends java.io.FilterInputStream
+ {
+ private boolean encode; // Encoding or decoding
+ private int position; // Current position in the buffer
+ private byte[] buffer; // Small buffer holding converted data
+ private int bufferLength; // Length of buffer (3 or 4)
+ private int numSigBytes; // Number of meaningful bytes in the buffer
+ private int lineLength;
+ private boolean breakLines; // Break lines at less than 80 characters
+ private int options; // Record options used to create the stream.
+ private byte[] alphabet; // Local copies to avoid extra method calls
+ private byte[] decodabet; // Local copies to avoid extra method calls
+
+
+ /**
+ * Constructs a {@link Base64.InputStream} in DECODE mode.
+ *
+ * @param in the <tt>java.io.InputStream</tt> from which to read data.
+ * @since 1.3
+ */
+ public InputStream( java.io.InputStream in )
+ {
+ this( in, DECODE );
+ } // end constructor
+
+
+ /**
+ * Constructs a {@link Base64.InputStream} in
+ * either ENCODE or DECODE mode.
+ * <p>
+ * Valid options:<pre>
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * (only meaningful when encoding)
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
+ *
+ *
+ * @param in the <tt>java.io.InputStream</tt> from which to read data.
+ * @param options Specified options
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public InputStream( java.io.InputStream in, int options )
+ {
+ super( in );
+ this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+ this.encode = (options & ENCODE) == ENCODE;
+ this.bufferLength = encode ? 4 : 3;
+ this.buffer = new byte[ bufferLength ];
+ this.position = -1;
+ this.lineLength = 0;
+ this.options = options; // Record for later, mostly to determine which alphabet to use
+ this.alphabet = getAlphabet(options);
+ this.decodabet = getDecodabet(options);
+ } // end constructor
+
+ /**
+ * Reads enough of the input stream to convert
+ * to/from Base64 and returns the next byte.
+ *
+ * @return next byte
+ * @since 1.3
+ */
+ public int read() throws java.io.IOException
+ {
+ // Do we need to get data?
+ if( position < 0 )
+ {
+ if( encode )
+ {
+ byte[] b3 = new byte[3];
+ int numBinaryBytes = 0;
+ for( int i = 0; i < 3; i++ )
+ {
+ try
+ {
+ int b = in.read();
+
+ // If end of stream, b is -1.
+ if( b >= 0 )
+ {
+ b3[i] = (byte)b;
+ numBinaryBytes++;
+ } // end if: not end of stream
+
+ } // end try: read
+ catch( java.io.IOException e )
+ {
+ // Only a problem if we got no data at all.
+ if( i == 0 )
+ throw e;
+
+ } // end catch
+ } // end for: each needed input byte
+
+ if( numBinaryBytes > 0 )
+ {
+ encode3to4( b3, 0, numBinaryBytes, buffer, 0, options );
+ position = 0;
+ numSigBytes = 4;
+ } // end if: got data
+ else
+ {
+ return -1;
+ } // end else
+ } // end if: encoding
+
+ // Else decoding
+ else
+ {
+ byte[] b4 = new byte[4];
+ int i = 0;
+ for( i = 0; i < 4; i++ )
+ {
+ // Read four "meaningful" bytes:
+ int b = 0;
+ do{ b = in.read(); }
+ while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC );
+
+ if( b < 0 )
+ break; // Reads a -1 if end of stream
+
+ b4[i] = (byte)b;
+ } // end for: each needed input byte
+
+ if( i == 4 )
+ {
+ numSigBytes = decode4to3( b4, 0, buffer, 0, options );
+ position = 0;
+ } // end if: got four characters
+ else if( i == 0 ){
+ return -1;
+ } // end else if: also padded correctly
+ else
+ {
+ // Must have broken out from above.
+ throw new java.io.IOException( "Improperly padded Base64 input." );
+ } // end
+
+ } // end else: decode
+ } // end else: get data
+
+ // Got data?
+ if( position >= 0 )
+ {
+ // End of relevant data?
+ if( /*!encode &&*/ position >= numSigBytes )
+ return -1;
+
+ if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
+ {
+ lineLength = 0;
+ return '\n';
+ } // end if
+ else
+ {
+ lineLength++; // This isn't important when decoding
+ // but throwing an extra "if" seems
+ // just as wasteful.
+
+ int b = buffer[ position++ ];
+
+ if( position >= bufferLength )
+ position = -1;
+
+ return b & 0xFF; // This is how you "cast" a byte that's
+ // intended to be unsigned.
+ } // end else
+ } // end if: position >= 0
+
+ // Else error
+ else
+ {
+ // When JDK1.4 is more accepted, use an assertion here.
+ throw new java.io.IOException( "Error in Base64 code reading stream." );
+ } // end else
+ } // end read
+
+
+ /**
+ * Calls {@link #read()} repeatedly until the end of stream
+ * is reached or <var>len</var> bytes are read.
+ * Returns number of bytes read into array or -1 if
+ * end of stream is encountered.
+ *
+ * @param dest array to hold values
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ * @return bytes read into array or -1 if end of stream is encountered.
+ * @since 1.3
+ */
+ public int read( byte[] dest, int off, int len ) throws java.io.IOException
+ {
+ int i;
+ int b;
+ for( i = 0; i < len; i++ )
+ {
+ b = read();
+
+ //if( b < 0 && i == 0 )
+ // return -1;
+
+ if( b >= 0 )
+ dest[off + i] = (byte)b;
+ else if( i == 0 )
+ return -1;
+ else
+ break; // Out of 'for' loop
+ } // end for: each byte read
+ return i;
+ } // end read
+
+ } // end inner class InputStream
+
+
+
+
+
+
+ /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
+
+
+
+ /**
+ * A {@link Base64.OutputStream} will write data to another
+ * <tt>java.io.OutputStream</tt>, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class OutputStream extends java.io.FilterOutputStream
+ {
+ private boolean encode;
+ private int position;
+ private byte[] buffer;
+ private int bufferLength;
+ private int lineLength;
+ private boolean breakLines;
+ private byte[] b4; // Scratch used in a few places
+ private boolean suspendEncoding;
+ private int options; // Record for later
+ private byte[] alphabet; // Local copies to avoid extra method calls
+ private byte[] decodabet; // Local copies to avoid extra method calls
+
+ /**
+ * Constructs a {@link Base64.OutputStream} in ENCODE mode.
+ *
+ * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+ * @since 1.3
+ */
+ public OutputStream( java.io.OutputStream out )
+ {
+ this( out, ENCODE );
+ } // end constructor
+
+
+ /**
+ * Constructs a {@link Base64.OutputStream} in
+ * either ENCODE or DECODE mode.
+ * <p>
+ * Valid options:<pre>
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * (only meaningful when encoding)
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
+ *
+ * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+ * @param options Specified options.
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DONT_BREAK_LINES
+ * @since 1.3
+ */
+ public OutputStream( java.io.OutputStream out, int options )
+ {
+ super( out );
+ this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+ this.encode = (options & ENCODE) == ENCODE;
+ this.bufferLength = encode ? 3 : 4;
+ this.buffer = new byte[ bufferLength ];
+ this.position = 0;
+ this.lineLength = 0;
+ this.suspendEncoding = false;
+ this.b4 = new byte[4];
+ this.options = options;
+ this.alphabet = getAlphabet(options);
+ this.decodabet = getDecodabet(options);
+ } // end constructor
+
+
+ /**
+ * Writes the byte to the output stream after
+ * converting to/from Base64 notation.
+ * When encoding, bytes are buffered three
+ * at a time before the output stream actually
+ * gets a write() call.
+ * When decoding, bytes are buffered four
+ * at a time.
+ *
+ * @param theByte the byte to write
+ * @since 1.3
+ */
+ public void write(int theByte) throws java.io.IOException
+ {
+ // Encoding suspended?
+ if( suspendEncoding )
+ {
+ super.out.write( theByte );
+ return;
+ } // end if: supsended
+
+ // Encode?
+ if( encode )
+ {
+ buffer[ position++ ] = (byte)theByte;
+ if( position >= bufferLength ) // Enough to encode.
+ {
+ out.write( encode3to4( b4, buffer, bufferLength, options ) );
+
+ lineLength += 4;
+ if( breakLines && lineLength >= MAX_LINE_LENGTH )
+ {
+ out.write( NEW_LINE );
+ lineLength = 0;
+ } // end if: end of line
+
+ position = 0;
+ } // end if: enough to output
+ } // end if: encoding
+
+ // Else, Decoding
+ else
+ {
+ // Meaningful Base64 character?
+ if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC )
+ {
+ buffer[ position++ ] = (byte)theByte;
+ if( position >= bufferLength ) // Enough to output.
+ {
+ int len = Base64.decode4to3( buffer, 0, b4, 0, options );
+ out.write( b4, 0, len );
+ //out.write( Base64.decode4to3( buffer ) );
+ position = 0;
+ } // end if: enough to output
+ } // end if: meaningful base64 character
+ else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC )
+ {
+ throw new java.io.IOException( "Invalid character in Base64 data." );
+ } // end else: not white space either
+ } // end else: decoding
+ } // end write
+
+
+
+ /**
+ * Calls {@link #write(int)} repeatedly until <var>len</var>
+ * bytes are written.
+ *
+ * @param theBytes array from which to read bytes
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ * @since 1.3
+ */
+ public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
+ {
+ // Encoding suspended?
+ if( suspendEncoding )
+ {
+ super.out.write( theBytes, off, len );
+ return;
+ } // end if: supsended
+
+ for( int i = 0; i < len; i++ )
+ {
+ write( theBytes[ off + i ] );
+ } // end for: each byte written
+
+ } // end write
+
+
+
+ /**
+ * Method added by PHIL. [Thanks, PHIL. -Rob]
+ * This pads the buffer without closing the stream.
+ */
+ public void flushBase64() throws java.io.IOException
+ {
+ if( position > 0 )
+ {
+ if( encode )
+ {
+ out.write( encode3to4( b4, buffer, position, options ) );
+ position = 0;
+ } // end if: encoding
+ else
+ {
+ throw new java.io.IOException( "Base64 input not properly padded." );
+ } // end else: decoding
+ } // end if: buffer partially full
+
+ } // end flush
+
+
+ /**
+ * Flushes and closes (I think, in the superclass) the stream.
+ *
+ * @since 1.3
+ */
+ public void close() throws java.io.IOException
+ {
+ // 1. Ensure that pending characters are written
+ flushBase64();
+
+ // 2. Actually close the stream
+ // Base class both flushes and closes.
+ super.close();
+
+ buffer = null;
+ out = null;
+ } // end close
+
+
+
+ /**
+ * Suspends encoding of the stream.
+ * May be helpful if you need to embed a piece of
+ * base640-encoded data in a stream.
+ *
+ * @since 1.5.1
+ */
+ public void suspendEncoding() throws java.io.IOException
+ {
+ flushBase64();
+ this.suspendEncoding = true;
+ } // end suspendEncoding
+
+
+ /**
+ * Resumes encoding of the stream.
+ * May be helpful if you need to embed a piece of
+ * base640-encoded data in a stream.
+ *
+ * @since 1.5.1
+ */
+ public void resumeEncoding()
+ {
+ this.suspendEncoding = false;
+ } // end resumeEncoding
+
+
+
+ } // end inner class OutputStream
+
+
+} // end class Base64
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Comment.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Comment.java
new file mode 100644
index 0000000..c5d9101
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Comment.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Comment extends Resource {
+ private User user;
+ private String comment;
+ private int typeOfCommentedResource;
+ private String uriOfCommetedResource;
+
+ public Comment() {
+ super();
+ this.setItemType(Resource.COMMENT);
+ }
+
+ public User getUser() {
+ return (this.user);
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public String getComment() {
+ return (this.comment);
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ public int getTypeOfCommentedResource() {
+ return (this.typeOfCommentedResource);
+ }
+
+ public void setTypeOfCommentedResource(int typeOfCommentedResource) {
+ this.typeOfCommentedResource = typeOfCommentedResource;
+ }
+
+ public String getURIOfCommentedResource() {
+ return (this.uriOfCommetedResource);
+ }
+
+ public void setURIOfCommentedResource(String uriOfCommetedResource) {
+ this.uriOfCommetedResource = uriOfCommetedResource;
+ }
+
+ /**
+ * A helper method to return a set of API elements that are needed to satisfy
+ * request of a particular type - e.g. creating a listing of resources or
+ * populating full preview, etc.
+ *
+ * @param iRequestType
+ * A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType) {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case
+ switch (iRequestType) {
+ case Resource.REQUEST_DEFAULT_FROM_API:
+ strElements += ""; // no change needed - defaults will be used
+ }
+
+ return (strElements);
+ }
+
+ //class method to build a comment instance from XML
+ public static Comment buildFromXML(Document doc, Logger logger) {
+ // if no XML was supplied, return null to indicate an error
+ if (doc == null)
+ return (null);
+
+ Comment c = new Comment();
+
+ try {
+ Element root = doc.getRootElement();
+
+ c.setResource(root.getAttributeValue("resource"));
+ c.setURI(root.getAttributeValue("uri"));
+
+ c.setTitle(root.getChildText("comment"));
+ c.setComment(root.getChildText("comment"));
+
+ Element commentedResourceElement = root.getChild("subject");
+ if (commentedResourceElement != null) {
+ c.setTypeOfCommentedResource(Resource.getResourceTypeFromVisibleName(commentedResourceElement.getName()));
+ c.setURIOfCommentedResource(commentedResourceElement.getAttributeValue("uri"));
+ }
+
+ Element userElement = root.getChild("author");
+ c.setUser(Util.instantiatePrimitiveUserFromElement(userElement));
+
+ String createdAt = root.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ c.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ logger.debug("Found information for comment with ID: " + c.getID()
+ + ", URI: " + c.getURI());
+ } catch (Exception e) {
+ logger.error("Failed midway through creating comment object from XML", e);
+ }
+
+ // return created comment instance
+ return (c);
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/File.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/File.java
new file mode 100644
index 0000000..83853ee
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/File.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class File extends Resource {
+ private int accessType;
+
+ private User uploader;
+ private License license;
+ private String filename;
+ private String visibleType;
+ private String contentType;
+ private List<Tag> tags;
+ private List<Comment> comments;
+ private List<Resource> credits;
+ private List<Resource> attributions;
+
+ public File() {
+ super();
+ this.setItemType(Resource.FILE);
+ }
+
+ public int getAccessType() {
+ return this.accessType;
+ }
+
+ public void setAccessType(int accessType) {
+ this.accessType = accessType;
+ }
+
+ public List<Tag> getTags() {
+ return tags;
+ }
+
+ public User getUploader() {
+ return uploader;
+ }
+
+ public void setUploader(User uploader) {
+ this.uploader = uploader;
+ }
+
+ public License getLicense() {
+ return license;
+ }
+
+ public void setLicense(License license) {
+ this.license = license;
+ }
+
+ public String getFilename() {
+ return this.filename;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public String getVisibleType() {
+ return this.visibleType;
+ }
+
+ public void setVisibleType(String visibleType) {
+ this.visibleType = visibleType;
+ }
+
+ public List<Comment> getComments() {
+ return this.comments;
+ }
+
+ public List<Resource> getCredits() {
+ return this.credits;
+ }
+
+ public List<Resource> getAttributions() {
+ return this.attributions;
+ }
+
+ /**
+ * A helper method to return a set of API elements that are needed to satisfy
+ * request of a particular type - e.g. creating a listing of resources or
+ * populating full preview, etc.
+ *
+ * @param iRequestType
+ * A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType) {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case
+ switch (iRequestType) {
+ case Resource.REQUEST_FULL_PREVIEW:
+ strElements += "filename,content-type,created-at,updated-at,"
+ + "license-type,tags,comments,credits,attributions,";
+ case Resource.REQUEST_FULL_LISTING:
+ strElements += "uploader,type,";
+ case Resource.REQUEST_SHORT_LISTING:
+ strElements += "id,title,description,privileges";
+ }
+
+ return (strElements);
+ }
+
+ public static File buildFromXML(Document doc, Logger logger) {
+ // if no XML document was supplied, return NULL
+ if (doc == null)
+ return (null);
+
+ // call main method which parses XML document starting from root element
+ return (File.buildFromXML(doc.getRootElement(), logger));
+ }
+
+ //class method to build a file instance from XML
+ public static File buildFromXML(Element docRootElement, Logger logger) {
+ // return null to indicate an error if XML document contains no root element
+ if (docRootElement == null)
+ return (null);
+
+ File f = new File();
+
+ try {
+ // Access type
+ f.setAccessType(Util.getAccessTypeFromXMLElement(docRootElement.getChild("privileges")));
+
+ // URI
+ f.setURI(docRootElement.getAttributeValue("uri"));
+
+ // Resource URI
+ f.setResource(docRootElement.getAttributeValue("resource"));
+
+ // Id
+ String id = docRootElement.getChildText("id");
+ if (id == null || id.equals("")) {
+ id = "API Error - No file ID supplied";
+ logger.error("Error while parsing file XML data - no ID provided for file with title: \""
+ + docRootElement.getChildText("title") + "\"");
+ }
+ f.setID(id);
+
+ // Filename
+ f.setFilename(docRootElement.getChildText("filename"));
+
+ // Title
+ f.setTitle(docRootElement.getChildText("title"));
+
+ // Description
+ f.setDescription(docRootElement.getChildText("description"));
+
+ // Uploader
+ Element uploaderElement = docRootElement.getChild("uploader");
+ f.setUploader(Util.instantiatePrimitiveUserFromElement(uploaderElement));
+
+ // Created at
+ String createdAt = docRootElement.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ f.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ // Updated at
+ String updatedAt = docRootElement.getChildText("updated-at");
+ if (updatedAt != null && !updatedAt.equals("")) {
+ f.setUpdatedAt(MyExperimentClient.parseDate(updatedAt));
+ }
+
+ // License
+ f.setLicense(License.getInstance(docRootElement.getChildText("license-type")));
+
+ // Type and Content-Type
+ f.setVisibleType(docRootElement.getChildText("type"));
+ f.setContentType(docRootElement.getChildText("content-type"));
+
+ // Tags
+ f.tags = new ArrayList<Tag>();
+ f.getTags().addAll(Util.retrieveTags(docRootElement));
+
+ // Comments
+ f.comments = new ArrayList<Comment>();
+ f.getComments().addAll(Util.retrieveComments(docRootElement, f));
+
+ // Credits
+ f.credits = new ArrayList<Resource>();
+ f.getCredits().addAll(Util.retrieveCredits(docRootElement));
+
+ // Attributions
+ f.attributions = new ArrayList<Resource>();
+ f.getAttributions().addAll(Util.retrieveAttributions(docRootElement));
+
+ logger.debug("Found information for file with ID: " + f.getID()
+ + ", Title: " + f.getTitle());
+ } catch (Exception e) {
+ logger.error("Failed midway through creating file object from XML", e);
+ }
+
+ // return created file instance
+ return (f);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Group.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Group.java
new file mode 100644
index 0000000..fa4ac87
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Group.java
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.User;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Group extends Resource
+{
+ private User admin;
+
+ private List<Tag> tags;
+ private List<Comment> comments;
+ private List<User> members;
+ private List<Resource> sharedItems;
+
+
+ public Group()
+ {
+ super();
+ this.setItemType(Resource.GROUP);
+ }
+
+ public User getAdmin() {
+ return admin;
+ }
+
+ public void setAdmin(User admin) {
+ this.admin = admin;
+ }
+
+ public List<Tag> getTags() {
+ return this.tags;
+ }
+
+ public List<Comment> getComments()
+ {
+ return this.comments;
+ }
+
+ public int getSharedItemCount()
+ {
+ return this.sharedItems.size();
+ }
+
+ public int getMemberCount()
+ {
+ return this.members.size();
+ }
+
+ public List<Resource> getSharedItems()
+ {
+ return this.sharedItems;
+ }
+
+ public List<User> getMembers()
+ {
+ return this.members;
+ }
+
+
+ /**
+ * A helper method to return a set of API elements that are
+ * needed to satisfy request of a particular type - e.g. creating
+ * a listing of resources or populating full preview, etc.
+ *
+ * @param iRequestType A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType)
+ {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case
+ switch (iRequestType) {
+ case Resource.REQUEST_FULL_PREVIEW:
+ strElements += "created-at,updated-at,members,shared-items,tags,comments,";
+ case Resource.REQUEST_FULL_LISTING:
+ strElements += "owner,";
+ case Resource.REQUEST_SHORT_LISTING:
+ strElements += "id,title,description";
+ }
+
+ return (strElements);
+ }
+
+
+ public static Group buildFromXML(Document doc, Logger logger)
+ {
+ // if no XML document was supplied, return NULL
+ if(doc == null) return(null);
+
+ // call main method which parses XML document starting from root element
+ return (Group.buildFromXML(doc.getRootElement(), logger));
+ }
+
+
+ //class method to build a group instance from XML
+ @SuppressWarnings("unchecked")
+ public static Group buildFromXML(Element docRootElement, Logger logger)
+ {
+ // return null to indicate an error if XML document contains no root element
+ if(docRootElement == null) return(null);
+
+ Group g = new Group();
+
+ try {
+ // URI
+ g.setURI(docRootElement.getAttributeValue("uri"));
+
+ // Resource URI
+ g.setResource(docRootElement.getAttributeValue("resource"));
+
+ // Id
+ String id = docRootElement.getChildText("id");
+ if (id == null || id.equals("")) {
+ id = "API Error - No group ID supplied";
+ logger.error("Error while parsing group XML data - no ID provided for group with title: \"" + docRootElement.getChildText("title") + "\"");
+ }
+ g.setID(Integer.parseInt(id));
+
+ // Title
+ g.setTitle(docRootElement.getChildText("title"));
+
+ // Description
+ g.setDescription(docRootElement.getChildText("description"));
+
+ // Owner
+ Element ownerElement = docRootElement.getChild("owner");
+ g.setAdmin(Util.instantiatePrimitiveUserFromElement(ownerElement));
+
+ // Created at
+ String createdAt = docRootElement.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ g.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ // Updated at
+ String updatedAt = docRootElement.getChildText("updated-at");
+ if (updatedAt != null && !updatedAt.equals("")) {
+ g.setUpdatedAt(MyExperimentClient.parseDate(updatedAt));
+ }
+
+
+ // Tags
+ g.tags = new ArrayList<Tag>();
+ g.getTags().addAll(Util.retrieveTags(docRootElement));
+
+ // Comments
+ g.comments = new ArrayList<Comment>();
+ g.getComments().addAll(Util.retrieveComments(docRootElement, g));
+
+ // Members
+ g.members = new ArrayList<User>();
+
+ Element membersElement = docRootElement.getChild("members");
+ if (membersElement != null) {
+ List<Element> memberNodes = membersElement.getChildren();
+ for (Element e : memberNodes) {
+ g.getMembers().add(Util.instantiatePrimitiveUserFromElement(e));
+ }
+ }
+ // sort the items after all items have been added
+ Collections.sort(g.getMembers());
+
+
+ // Shared Items
+ g.sharedItems = new ArrayList<Resource>();
+
+ Element sharedItemsElement = docRootElement.getChild("shared-items");
+ if (sharedItemsElement != null) {
+ List<Element> itemsNodes = sharedItemsElement.getChildren();
+ for (Element e : itemsNodes) {
+ g.getSharedItems().add(Util.instantiatePrimitiveResourceFromElement(e));
+ }
+ }
+ // sort the items after all items have been added
+ Collections.sort(g.getSharedItems());
+
+
+ logger.debug("Found information for group with ID: " + g.getID() + ", Title: " + g.getTitle());
+ }
+ catch (Exception e) {
+ logger.error("Failed midway through creating group object from XML", e);
+ }
+
+ // return created group instance
+ return(g);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/License.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/License.java
new file mode 100644
index 0000000..25b44ae
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/License.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+import java.io.Serializable;
+
+/*
+ * @author Jiten Bhagat, Emmanuel Tagarira
+ */
+public class License implements Serializable {
+ private String type;
+
+ private String text;
+
+ private String link;
+
+ public static String[] SUPPORTED_TYPES = { "by-nd", "by", "by-sa", "by-nc-nd", "by-nc", "by-nc-sa" };
+ public static String DEFAULT_LICENSE = "by-sa";
+
+ private License() {
+
+ }
+
+ private License(String type, String text, String link) {
+ this.type = type;
+ this.text = text;
+ this.link = link;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public static License getInstance(String type) {
+ if (type == null)
+ return null;
+
+ if (type.equalsIgnoreCase("by-nd")) {
+ return new License(type, "Creative Commons Attribution-NoDerivs 3.0 License", "http://creativecommons.org/licenses/by-nd/3.0/");
+ } else if (type.equalsIgnoreCase("by")) {
+ return new License(type, "Creative Commons Attribution 3.0 License", "http://creativecommons.org/licenses/by/3.0/");
+ } else if (type.equalsIgnoreCase("by-sa")) {
+ return new License(type, "Creative Commons Attribution-Share Alike 3.0 License", "http://creativecommons.org/licenses/by-sa/3.0/");
+ } else if (type.equalsIgnoreCase("by-nc-nd")) {
+ return new License(type, "Creative Commons Attribution-Noncommercial-NoDerivs 3.0 License", "http://creativecommons.org/licenses/by-nc-nd/3.0/");
+ } else if (type.equalsIgnoreCase("by-nc")) {
+ return new License(type, "Creative Commons Attribution-Noncommercial 3.0 License", "http://creativecommons.org/licenses/by-nc/3.0/");
+ } else if (type.equalsIgnoreCase("by-nc-sa")) {
+ return new License(type, "Creative Commons Attribution-Noncommercial-Share Alike 3.0 License", "http://creativecommons.org/licenses/by-nc-sa/3.0/");
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/MyExperimentClient.class b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/MyExperimentClient.class
new file mode 100644
index 0000000..0f243ee
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/MyExperimentClient.class
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/MyExperimentClient.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/MyExperimentClient.java
new file mode 100644
index 0000000..6449e95
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/MyExperimentClient.java
@@ -0,0 +1,1218 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.raven.appconfig.ApplicationRuntime;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MyExperimentPerspective;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine.QuerySearchInstance;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+import org.xml.sax.InputSource;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira, Jiten Bhagat
+ */
+public class MyExperimentClient {
+ // CONSTANTS
+ public static final String DEFAULT_BASE_URL = "http://www.myexperiment.org";
+ public static final String PLUGIN_USER_AGENT = "Taverna2-myExperiment-plugin/"
+ + MyExperimentPerspective.PLUGIN_VERSION
+ + " Java/"
+ + System.getProperty("java.version");
+ private static final String INI_FILE_NAME = "myexperiment-plugin.ini";
+
+ private static final int EXAMPLE_WORKFLOWS_PACK_ID = 254;
+
+ public static final String INI_BASE_URL = "my_experiment_base_url";
+ public static final String INI_AUTO_LOGIN = "auto_login";
+ public static final String INI_FAVOURITE_SEARCHES = "favourite_searches";
+ public static final String INI_SEARCH_HISTORY = "search_history";
+ public static final String INI_TAG_SEARCH_HISTORY = "tag_search_history";
+ public static final String INI_PREVIEWED_ITEMS_HISTORY = "previewed_items_history";
+ public static final String INI_OPENED_ITEMS_HISTORY = "opened_items_history";
+ public static final String INI_UPLOADED_ITEMS_HISTORY = "uploaded_items_history";
+ public static final String INI_DOWNLOADED_ITEMS_HISTORY = "downloaded_items_history";
+ public static final String INI_COMMENTED_ITEMS_HISTORY = "commented_items_history";
+ public static final String INI_DEFAULT_LOGGED_IN_TAB = "default_tab_for_logged_in_users";
+ public static final String INI_DEFAULT_ANONYMOUS_TAB = "default_tab_for_anonymous_users";
+ public static final String INI_MY_STUFF_WORKFLOWS = "show_workflows_in_my_stuff";
+ public static final String INI_MY_STUFF_FILES = "show_files_in_my_stuff";
+ public static final String INI_MY_STUFF_PACKS = "show_packs_in_my_stuff";
+
+ private final String DO_PUT = "_DO_UPDATE_SIGNAL_";
+
+ public static boolean baseChangedSinceLastStart = false;
+
+ // old format
+ private static final DateFormat OLD_DATE_FORMATTER = new SimpleDateFormat(
+ "EEE MMM dd HH:mm:ss Z yyyy", Locale.ENGLISH);
+ private static final DateFormat OLD_SHORT_DATE_FORMATTER = new SimpleDateFormat(
+ "HH:mm 'on' dd/MM/yyyy", Locale.ENGLISH);
+
+ // universal date formatter
+ private static final DateFormat NEW_DATE_FORMATTER = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss Z");
+
+ // SETTINGS
+ private String BASE_URL; // myExperiment base URL to use
+ private java.io.File fIniFileDir; // a folder, where the INI file will be
+ // stored
+ private Properties iniSettings; // settings that are read/stored from/to INI
+ // file
+
+ // the logger
+ private Logger logger;
+
+ // authentication settings (and the current user)
+ private boolean LOGGED_IN = false;
+ private String AUTH_STRING = "";
+ private User current_user = null;
+
+ // default constructor
+ public MyExperimentClient() {
+ }
+
+ public MyExperimentClient(Logger logger) {
+ this();
+
+ this.logger = logger;
+
+ // === Load INI settings ===
+ // but loading settings from INI file, determine what folder is to be used
+ // for INI file
+ if (Util.isRunningInTaverna()) {
+ // running inside Taverna - use its folder to place the config file
+ this.fIniFileDir = new java.io.File(ApplicationRuntime.getInstance()
+ .getApplicationHomeDir(), "conf");
+ } else {
+ // running outside Taverna, place config file into the user's home
+ // directory
+ this.fIniFileDir = new java.io.File(System.getProperty("user.home"),
+ ".Taverna2-myExperiment Plugin");
+ }
+
+ // load preferences if the INI file exists
+ this.iniSettings = new Properties();
+ this.loadSettings();
+
+ // === Check if defaults should be applied to override not sensible settings
+ // from INI file ===
+ // verify that myExperiment BASE URL was read - use default otherwise
+ if (BASE_URL == null || BASE_URL.length() == 0)
+ BASE_URL = DEFAULT_BASE_URL;
+ this.iniSettings.put(INI_BASE_URL, BASE_URL); // store this to settings (if
+ // no changes were made - same as before, alternatively default URL)
+ }
+
+ // getter for the current status
+ public boolean isLoggedIn() {
+ return (LOGGED_IN);
+ }
+
+ public String getBaseURL() {
+ return this.BASE_URL;
+ }
+
+ public void setBaseURL(String baseURL) {
+ this.BASE_URL = baseURL;
+ }
+
+ // getter for the current user, if one is logged in to myExperiment
+ public User getCurrentUser() {
+ return (this.current_user);
+ }
+
+ // setter for the current user (the one that has logged in to myExperiment)
+ public void setCurrentUser(User user) {
+ this.current_user = user;
+ }
+
+ public Properties getSettings() {
+ return this.iniSettings;
+ }
+
+ // loads all plugin settings from the INI file
+ public synchronized void loadSettings() {
+ try {
+ // === READ SETTINGS ===
+ FileInputStream fIniInputStream = new FileInputStream(new java.io.File(
+ this.fIniFileDir, this.INI_FILE_NAME));
+ this.iniSettings.load(fIniInputStream);
+ fIniInputStream.close();
+
+ // set BASE_URL if from INI settings
+ this.BASE_URL = this.iniSettings.getProperty(INI_BASE_URL);
+
+ } catch (FileNotFoundException e) {
+ this.logger
+ .debug("myExperiment plugin INI file was not found, defaults will be used.");
+
+ } catch (IOException e) {
+ this.logger.error("Error on reading settings from INI file:\n" + e);
+ }
+ }
+
+ // writes all plugin settings to the INI file
+ private void storeSettings() {
+
+ // === STORE THE SETTINGS ===
+ try {
+ this.fIniFileDir.mkdirs();
+ FileOutputStream fIniOutputStream = new FileOutputStream(
+ new java.io.File(this.fIniFileDir, this.INI_FILE_NAME));
+ this.iniSettings.store(fIniOutputStream, "Test comment");
+ fIniOutputStream.close();
+ } catch (IOException e) {
+ this.logger.error("Error while trying to store settings to INI file:\n"
+ + e);
+ }
+
+ }
+
+ public void storeHistoryAndSettings() {
+ this.iniSettings.put(MyExperimentClient.INI_FAVOURITE_SEARCHES, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getSearchTab()
+ .getSearchFavouritesList()));
+ this.iniSettings.put(MyExperimentClient.INI_SEARCH_HISTORY, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getSearchTab()
+ .getSearchHistory()));
+ this.iniSettings.put(MyExperimentClient.INI_TAG_SEARCH_HISTORY, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getTagBrowserTab()
+ .getTagSearchHistory()));
+ this.iniSettings.put(MyExperimentClient.INI_PREVIEWED_ITEMS_HISTORY, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getPreviewBrowser()
+ .getPreviewHistory()));
+ this.iniSettings.put(MyExperimentClient.INI_DOWNLOADED_ITEMS_HISTORY,
+ Base64.encodeObject(MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getDownloadedItemsHistoryList()));
+ this.iniSettings.put(MyExperimentClient.INI_OPENED_ITEMS_HISTORY, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getOpenedItemsHistoryList()));
+ this.iniSettings.put(MyExperimentClient.INI_UPLOADED_ITEMS_HISTORY, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getUploadedItemsHistoryList()));
+ this.iniSettings.put(MyExperimentClient.INI_COMMENTED_ITEMS_HISTORY, Base64
+ .encodeObject(MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+ .getCommentedOnItemsHistoryList()));
+
+ storeSettings();
+ }
+
+ private UsernamePassword getUserPass(String urlString) {
+ try {
+ URI userpassUrl = URI.create(urlString);
+ final UsernamePassword userAndPass = CredentialManager.getInstance().getUsernameAndPasswordForService(userpassUrl, true, null);
+ return userAndPass;
+ } catch (CMException e) {
+ throw new RuntimeException("Error in Taverna Credential Manager", e);
+ }
+ }
+
+ public boolean doLogin() {
+
+ // check if the stored credentials are valid
+ ServerResponse response = null;
+ Document doc = null;
+ try {
+// CredentialManager.getInstance().getUsernameAndPasswordForService(new URI(this.BASE_URL), true, null);
+ response = this.doMyExperimentGET(this.BASE_URL + "/whoami.xml");
+ } catch (Exception e) {
+ this.logger
+ .error("Error while attempting to verify login credentials from INI file:\n"
+ + e);
+ }
+
+
+ if (response.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ try {
+ List<String> toDelete = CredentialManager.getInstance().getServiceURLsforAllUsernameAndPasswordPairs();
+ for (String uri : toDelete) {
+ if (uri.startsWith(BASE_URL)) {
+ CredentialManager.getInstance().deleteUsernameAndPasswordForService(uri);
+ }
+ }
+// CredentialManager.getInstance().resetAuthCache();
+ doc = null;
+ } catch (Exception e) {
+ logger.error(e);
+ }
+ } else {
+ doc = response.getResponseBody();
+ }
+
+ // verify outcomes
+ if (doc == null) {
+ // login credentials were invalid - revert to not logged in state and
+ // disable autologin function;
+ // stored credentials will be kept to allow the user to verify and edit
+ // them
+ // (login screen will be displayed as usual + an error message box will
+ // appear)
+ this.LOGGED_IN = false;
+ this.AUTH_STRING = "";
+ this.iniSettings.put(MyExperimentClient.INI_AUTO_LOGIN,
+ new Boolean(false).toString());
+
+ javax.swing.JOptionPane.showMessageDialog(null,
+ "Your myExperiment login details appear to be incorrect.\n"
+ + "Please check your details.");
+ return false;
+ } else {
+ UsernamePassword userPass = getUserPass(this.BASE_URL + "/whoami.xml");
+
+ if (userPass == null) {
+ return false;
+ }
+
+ // set the system to the "logged in" state from INI file properties
+ this.LOGGED_IN = true;
+ this.AUTH_STRING = Base64.encodeBytes((userPass.getUsername() + ":" + userPass.getPasswordAsString())
+ .getBytes());
+
+ // login credentials were verified successfully; load current user
+ String strCurrentUserURI = doc.getRootElement().getAttributeValue("uri");
+ try {
+ this.current_user = this.fetchCurrentUser(strCurrentUserURI);
+ this.logger
+ .debug("Logged in to myExperiment successfully with credentials that were loaded from INI file.");
+ return true;
+ } catch (Exception e) {
+ // this is highly unlikely because the login credentials were validated
+ // successfully just before this
+ this.logger.error("Couldn't fetch user data from myExperiment ("
+ + strCurrentUserURI
+ + ")", e);
+ return false;
+ }
+ }
+ }
+
+ // Simulates a "logout" action. Logging in and out in the plugin is only an
+ // abstraction created for user convenience; it is a purely virtual concept,
+ // because the
+ // myExperiment API is completely stateless - hence, logging out simply
+ // consists of "forgetting"
+ // the authentication details and updating the state.
+ public void doLogout() throws Exception {
+ LOGGED_IN = false;
+ AUTH_STRING = "";
+ }
+
+ /**
+ * Generic method to execute GET requests to myExperiment server.
+ *
+ * @param strURL
+ * The URL on myExperiment to issue GET request to.
+ * @return An object containing XML Document with server's response body and a
+ * response code. Response body XML document might be null if there
+ * was an error or the user wasn't authorised to perform a certain
+ * action. Response code will always be set.
+ * @throws Exception
+ */
+ public ServerResponse doMyExperimentGET(String strURL) throws Exception {
+ // open server connection using provided URL (with no modifications to it)
+ URL url = new URL(strURL);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+ if (LOGGED_IN) {
+ // if the user has "logged in", also add authentication details
+ conn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+ }
+
+ // check server's response
+ return (doMyExperimentReceiveServerResponse(conn, strURL, true));
+ }
+
+ /**
+ * Generic method to execute GET requests to myExperiment server.
+ *
+ * @param strURL
+ * The URL on myExperiment to POST to.
+ * @param strXMLDataBody
+ * Body of the XML data to be POSTed to strURL.
+ * @return An object containing XML Document with server's response body and a
+ * response code. Response body XML document might be null if there
+ * was an error or the user wasn't authorised to perform a certain
+ * action. Response code will always be set.
+ * @throws Exception
+ */
+ public ServerResponse doMyExperimentPOST(String strURL, String strXMLDataBody)
+ throws Exception {
+ // POSTing to myExperiment is only allowed for authorised users
+ if (!LOGGED_IN) return (null);
+
+ // open server connection using provided URL (with no modifications to it)
+ URL url = new URL(strURL);
+ HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+
+ // "tune" the connection
+ urlConn.setRequestMethod((strURL.contains(DO_PUT) ? "PUT" : "POST"));
+ strURL = strURL.replace(DO_PUT, "");
+ urlConn.setDoOutput(true);
+ urlConn.setRequestProperty("Content-Type", "application/xml");
+ urlConn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+ urlConn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+ // the last line wouldn't be executed if the user wasn't logged in (see
+ // above code), so safe to run
+
+ // prepare and PUT/POST XML data
+ String strPOSTContent = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
+ + strXMLDataBody;
+ OutputStreamWriter out = new OutputStreamWriter(urlConn.getOutputStream());
+ out.write(strPOSTContent);
+ out.close();
+
+ // check server's response
+ return (doMyExperimentReceiveServerResponse(urlConn, strURL, false));
+ }
+
+ /**
+ * Generic method to execute DELETE requests to myExperiment server. This is
+ * only to be called when a user is logged in.
+ *
+ * @param strURL
+ * The URL on myExperiment to direct DELETE request to.
+ * @return An object containing XML Document with server's response body and a
+ * response code. Response body XML document might be null if there
+ * was an error or the user wasn't authorised to perform a certain
+ * action. Response code will always be set.
+ * @throws Exception
+ */
+ public ServerResponse doMyExperimentDELETE(String strURL) throws Exception {
+ // open server connection using provided URL (with no modifications to it)
+ URL url = new URL(strURL);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+
+ // "tune" the connection
+ conn.setRequestMethod("DELETE");
+ conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+ conn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+
+ // check server's response
+ return (doMyExperimentReceiveServerResponse(conn, strURL, true));
+ }
+
+ /**
+ * A common method for retrieving myExperiment server's response for both GET
+ * and POST requests.
+ *
+ * @param conn
+ * Instance of the established URL connection to poll for server's
+ * response.
+ * @param strURL
+ * The URL on myExperiment with which the connection is established.
+ * @param bIsGetRequest
+ * Flag for identifying type of the request. True when the current
+ * connection executes GET request; false when it executes a POST
+ * request.
+ * @return An object containing XML Document with server's response body and a
+ * response code. Response body XML document might be null if there
+ * was an error or the user wasn't authorised to perform a certain
+ * action. Response code will always be set.
+ */
+ private ServerResponse doMyExperimentReceiveServerResponse(
+ HttpURLConnection conn, String strURL, boolean bIsGETRequest)
+ throws Exception {
+ int iResponseCode = conn.getResponseCode();
+
+ switch (iResponseCode) {
+ case HttpURLConnection.HTTP_OK:
+ // data retrieval was successful - parse the response XML and return it
+ // along with response code
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn
+ .getInputStream()));
+ Document doc = new SAXBuilder().build(new InputSource(reader));
+ reader.close();
+
+ return (new ServerResponse(iResponseCode, doc));
+
+ case HttpURLConnection.HTTP_BAD_REQUEST:
+ // this was a bad XML request - need full XML response to retrieve the
+ // error message from it;
+ // Java throws IOException if getInputStream() is used when non HTTP_OK
+ // response code was received -
+ // hence can use getErrorStream() straight away to fetch the error
+ // document
+ BufferedReader errorReader = new BufferedReader(new InputStreamReader(
+ conn.getErrorStream()));
+ Document errorDoc = new SAXBuilder()
+ .build(new InputSource(errorReader));
+ errorReader.close();
+
+ return (new ServerResponse(iResponseCode, errorDoc));
+
+ case HttpURLConnection.HTTP_UNAUTHORIZED:
+ // this content is not authorised for current user
+ return (new ServerResponse(iResponseCode, null));
+
+ default:
+ // unexpected response code - raise an exception
+ throw new IOException("Received unexpected HTTP response code ("
+ + conn.getResponseCode() + ") while "
+ + (bIsGETRequest ? "fetching data at " : "posting data to ")
+ + strURL);
+ }
+ }
+
+ // a method to fetch a user instance with full details (including avatar
+ // image)
+ public User fetchCurrentUser(String uri) {
+ // fetch user data
+ User user = null;
+ try {
+ Document doc = this.getResource(Resource.USER, uri,
+ Resource.REQUEST_FULL_PREVIEW);
+ user = User.buildFromXML(doc, logger);
+ } catch (Exception ex) {
+ logger.error("Failed to fetch user data from myExperiment (" + uri
+ + "); exception:\n" + ex);
+ }
+
+ // fetch the avatar
+ try {
+ if (user.getAvatarURI() == null) {
+ ImageIcon icon = new ImageIcon(user.getAvatarResource());
+ user.setAvatar(icon);
+ } else {
+ Document doc = this.doMyExperimentGET(user.getAvatarURI())
+ .getResponseBody();
+ user.setAvatar(doc);
+ }
+ } catch (Exception ex) {
+ logger.error("Failed to fetch user's avatar from myExperiment ("
+ + user.getAvatarURI() + "); exception:\n" + ex);
+ }
+
+ return (user);
+ }
+
+ /**
+ * Fetches resource data and returns an XML document containing it.
+ *
+ * @param iResourceType
+ * Type of the resource for which the XML data is to be fetched. This
+ * implies which elements are required to be selected.
+ * @param strURI
+ * URI of the resource in myExperiment API.
+ * @param iRequestType
+ * Determines the level of detail of data to be fetched from the API;
+ * constants for using in this field are defined in Resource class.
+ */
+ public Document getResource(int iResourceType, String strURI, int iRequestType)
+ throws Exception {
+ if (iRequestType == Resource.REQUEST_ALL_DATA) {
+ // it doesn't matter what kind of resource this is if all available data
+ // is requested anyway
+ strURI += "&all_elements=yes";
+ } else {
+ // only required metadata is to be fetched; this depends on the type of
+ // the resource
+ switch (iResourceType) {
+ case Resource.WORKFLOW:
+ strURI += "&elements="
+ + Workflow.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.FILE:
+ strURI += "&elements=" + File.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.PACK:
+ strURI += "&elements=" + Pack.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.PACK_INTERNAL_ITEM:
+ strURI += "&all_elements=yes"; // TODO determine which are required
+ // elements
+ break;
+ case Resource.PACK_EXTERNAL_ITEM:
+ strURI += "&all_elements=yes"; // TODO determine which are required
+ // elements
+ break;
+ case Resource.USER:
+ strURI += "&elements=" + User.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.GROUP:
+ strURI += "&elements=" + Group.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.TAG:
+ // this should set no elements, because default is desired at the
+ // moment -
+ // but even having "&elements=" with and empty string at the end will
+ // still
+ // retrieve default fields from the API
+ strURI += "&elements=" + Tag.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.COMMENT:
+ // this should set no elements, because default is desired at the
+ // moment - but even having "&elements=" with and empty string at the
+ // end will
+ // still retrieve default fields from the API
+ strURI += "&elements=" + Comment.getRequiredAPIElements(iRequestType);
+ break;
+ }
+ }
+
+ return (this.doMyExperimentGET(strURI).getResponseBody());
+ }
+
+ /**
+ * Fetches workflow data from myExperiment.
+ *
+ * @param strWorkflowURI
+ * URI of the workflow to be opened.
+ * @return Workflow instance containing only workflow data and content type.
+ */
+ public Workflow fetchWorkflowBinary(String strWorkflowURI) throws Exception {
+ // fetch workflows data
+ Document doc = this.getResource(Resource.WORKFLOW, strWorkflowURI,
+ Resource.REQUEST_WORKFLOW_CONTENT_ONLY);
+
+ // verify that the type of the workflow data is correct
+ Element root = doc.getRootElement();
+ Workflow w = new Workflow();
+ w.setVisibleType(root.getChildText("type"));
+ w.setContentType(root.getChildText("content-type"));
+
+ if (!w.isTavernaWorkflow()) { throw new Exception(
+ "Unsupported workflow type. Details:\nWorkflow type: "
+ + w.getVisibleType() + "\nMime type: " + w.getContentType()); }
+
+ // check that content encoding is correct
+ String strEncoding = root.getChild("content").getAttributeValue("encoding");
+ String strDataFormat = root.getChild("content").getAttributeValue("type");
+ if (!strEncoding.toLowerCase().equals("base64")
+ || !strDataFormat.toLowerCase().equals("binary")) { throw new Exception(
+ "Unsupported workflow data format. Details:\nContent encoding: "
+ + strEncoding + "\nFormat: " + strDataFormat); }
+
+ // all checks seem to be fine, decode workflow data
+ byte[] arrWorkflowData = Base64.decode(root.getChildText("content"));
+ w.setContent(arrWorkflowData);
+
+ return (w);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Workflow> getExampleWorkflows() {
+ List<Workflow> workflows = new ArrayList<Workflow>();
+
+ try {
+ String strExampleWorkflowsPackUrl = this.BASE_URL + "/pack.xml?id="
+ + EXAMPLE_WORKFLOWS_PACK_ID + "&elements=internal-pack-items";
+ Document doc = this.doMyExperimentGET(strExampleWorkflowsPackUrl)
+ .getResponseBody();
+
+ if (doc != null) {
+ List<Element> allInternalItems = doc.getRootElement().getChild(
+ "internal-pack-items").getChildren("workflow");
+ for (Element e : allInternalItems) {
+ String itemUri = e.getAttributeValue("uri");
+ Document itemDoc = this.doMyExperimentGET(itemUri).getResponseBody();
+ String workflowUri = itemDoc.getRootElement().getChild("item")
+ .getChild("workflow").getAttributeValue("uri");
+ Document docCurWorkflow = this.getResource(Resource.WORKFLOW,
+ workflowUri, Resource.REQUEST_FULL_LISTING);
+ workflows.add(Workflow.buildFromXML(docCurWorkflow, this.logger));
+ }
+ }
+ } catch (Exception e) {
+ this.logger.error("Failed to retrieve example workflows", e);
+ }
+
+ logger.debug(workflows.size()
+ + " example workflows retrieved from myExperiment");
+
+ return (workflows);
+ }
+
+ @SuppressWarnings("unchecked")
+ public TagCloud getGeneralTagCloud(int size) {
+ TagCloud tcCloud = new TagCloud();
+
+ try {
+ // assemble tag cloud URL and fetch the XML document
+ String strTagCloudURL = BASE_URL + "/tag-cloud.xml?num="
+ + (size > 0 ? ("" + size) : "all");
+ Document doc = this.doMyExperimentGET(strTagCloudURL).getResponseBody();
+
+ // process all tags and add them to the cloud
+ if (doc != null) {
+ List<Element> nodes = doc.getRootElement().getChildren("tag");
+ for (Element e : nodes) {
+ Tag t = new Tag();
+ t.setTitle(e.getText());
+ t.setTagName(e.getText());
+ t.setResource(e.getAttributeValue("resource"));
+ t.setURI(e.getAttributeValue("uri"));
+ t.setCount(Integer.parseInt(e.getAttributeValue("count")));
+
+ tcCloud.getTags().add(t);
+ }
+ }
+ } catch (Exception e) {
+ this.logger.error("ERROR: Failed to get tag cloud.\n", e);
+ }
+
+ logger.debug("Tag cloud retrieval successful; fetched "
+ + tcCloud.getTags().size() + " tags from myExperiment");
+ return (tcCloud);
+ }
+
+ public TagCloud getUserTagCloud(User user, int size) {
+ TagCloud tcCloud = new TagCloud();
+
+ // iterate through all tags that the user has applied;
+ // fetch the title and the number of times that this tag
+ // was applied across myExperiment (e.g. overall popularity)
+ try {
+ // update user tags first (this happens concurrently with the other
+ // threads
+ // during the load time, hence needs to be synchronised properly)
+ synchronized (user.getTags()) {
+ user.getTags().clear();
+ Document doc = this.getResource(Resource.USER, user.getURI(),
+ Resource.REQUEST_USER_APPLIED_TAGS_ONLY);
+ Iterator<Element> iNewUserTags = doc.getRootElement().getChild(
+ "tags-applied").getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iNewUserTags, user.getTags());
+ }
+
+ // fetch additional required data about the tags
+ Iterator<HashMap<String, String>> iTagsResourcesHashMaps = user.getTags()
+ .iterator();
+ while (iTagsResourcesHashMaps.hasNext()) {
+ // get the tag object uri in myExperiment API
+ String strCurTagURI = iTagsResourcesHashMaps.next().get("uri");
+
+ // fetch tag data from myExperiment (namely, number of times that this
+ // tag was applied)
+ Document doc = this.doMyExperimentGET(strCurTagURI).getResponseBody();
+ Element root = doc.getRootElement();
+
+ // create the tag
+ Tag t = new Tag();
+ t.setTagName(root.getChild("name").getText());
+ t.setCount(Integer.parseInt(root.getChild("count").getText()));
+
+ tcCloud.getTags().add(t);
+ }
+
+ // a little preprocessing before tag selection - if "size" is set to 0, -1
+ // or any negative number, assume the request is for ALL user tags
+ if (size <= 0) size = tcCloud.getTags().size();
+
+ // sort the collection by popularity..
+ Comparator<Tag> byPopularity = new Tag.ReversePopularityComparator();
+ Collections.sort(tcCloud.getTags(), byPopularity);
+
+ // ..take top "size" elements
+ int iSelectedTags = 0;
+ List<Tag> tagListOfRequiredSize = new ArrayList<Tag>();
+ Iterator<Tag> iTags = tcCloud.getTags().iterator();
+ while (iTags.hasNext() && iSelectedTags < size) {
+ tagListOfRequiredSize.add(iTags.next());
+ iSelectedTags++;
+ }
+
+ // purge the original tag collection; add only selected tags to it;
+ // then sort back in alphabetical order again
+ tcCloud.getTags().clear();
+ tcCloud.getTags().addAll(tagListOfRequiredSize);
+ Comparator<Tag> byAlphabet = new Tag.AlphanumericComparator();
+ Collections.sort(tcCloud.getTags(), byAlphabet);
+ } catch (Exception e) {
+ logger.error("Failed midway through fetching user tags for user ID = "
+ + user.getID() + "\n" + e);
+ }
+
+ return (tcCloud);
+ }
+
+ /**
+ * A helper to fetch workflows, files or packs of a specific user. This will
+ * only make *one* request to the API, therefore it's faster than getting all
+ * the items one by one.
+ *
+ * @param user
+ * User instance for which the items are to be fetched.
+ * @param iResourceType
+ * One of Resource.WORKFLOW, Resource.FILE, Resource.PACK
+ * @param iRequestType
+ * Type of the request - i.e. amount of data to fetch. One of
+ * Resource.REQUEST_SHORT_LISTING, Resource.REQUEST_FULL_LISTING,
+ * Resource.REQUEST_FULL_PREVIEW, Resource.REQUEST_ALL_DATA.
+ * @return An XML document containing data about all items in the amount that
+ * was specified.
+ */
+ public Document getUserContributions(User user, int iResourceType,
+ int iRequestType, int page) {
+ Document doc = null;
+ String strURL = BASE_URL;
+ String strElements = "&elements=";
+
+ try {
+ // determine query parameters
+ switch (iResourceType) {
+ case Resource.WORKFLOW:
+ strURL += "/workflows.xml?uploader=";
+ strElements += Workflow.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.FILE:
+ strURL += "/files.xml?uploader=";
+ strElements += File.getRequiredAPIElements(iRequestType);
+ break;
+ case Resource.PACK:
+ strURL += "/packs.xml?owner=";
+ strElements += Workflow.getRequiredAPIElements(iRequestType);
+ break;
+ }
+
+ if (page != 0) {
+ strElements += "&num=100&page=" + page;
+ }
+
+ // create final query URL and retrieve data
+ strURL += MyExperimentClient.urlEncodeQuery(user.getResource())
+ + strElements;
+ doc = this.doMyExperimentGET(strURL).getResponseBody();
+ } catch (Exception e) {
+ logger.error("ERROR: Failed to fetch user's contributions.");
+ }
+
+ return (doc);
+ }
+
+ /**
+ * Queries myExperiment API for all items that are tagged with particular
+ * type.
+ *
+ * @param strTag
+ * The tag to search for. This will be URL encoded before submitting
+ * the query.
+ * @return XML document containing search results.
+ */
+ public Document searchByTag(String strTag) {
+ Document doc = null;
+
+ try {
+ String strUrlEncodedTag = MyExperimentClient.urlEncodeQuery(strTag);
+ doc = this.doMyExperimentGET(
+ BASE_URL + "/tagged.xml?tag=" + strUrlEncodedTag
+ + Util.composeAPIQueryElements(null)).getResponseBody();
+ } catch (Exception e) {
+ logger
+ .error("ERROR: Failed to fetch tagged items from myExperiment. Query tag was '"
+ + strTag + "'\n" + e);
+ }
+
+ return (doc);
+ }
+
+ /**
+ * Converts a tag list into tag cloud data by fetching tag application count
+ * for each instance in the list.
+ *
+ * @param tags
+ * Tag list to work on.
+ */
+ public void convertTagListIntoTagCloudData(List<Tag> tags) {
+ try {
+ Document doc = null;
+
+ for (Tag t : tags) {
+ doc = this.getResource(Resource.TAG, t.getURI(),
+ Resource.REQUEST_ALL_DATA);
+ Element rootElement = doc.getRootElement();
+ t.setCount(Integer.parseInt(rootElement.getChild("count").getText()));
+ }
+ } catch (Exception e) {
+ logger
+ .error("Failed while getting tag application counts when turning tag list into tag cloud data", e);
+ }
+ }
+
+ /**
+ * Fetches the data about user's favourite items and updates the provided user
+ * instance with the latest data.
+ */
+ public void updateUserFavourites(User user) {
+ // fetch and update favourites data
+ try {
+ Document doc = this.getResource(Resource.USER, user.getURI(),
+ Resource.REQUEST_USER_FAVOURITES_ONLY);
+ List<Resource> newUserFavouritesList = Util.retrieveUserFavourites(doc
+ .getRootElement());
+
+ user.getFavourites().clear();
+ user.getFavourites().addAll(newUserFavouritesList);
+ } catch (Exception ex) {
+ logger
+ .error("Failed to fetch favourites data from myExperiment for a user (URI: "
+ + user.getURI() + "); exception:\n" + ex);
+ JOptionPane
+ .showMessageDialog(
+ null,
+ "Couldn't synchronise data about your favourite items with myExperiment.\n"
+ + "You might not be able to add / remove other items to your favourites and.\n"
+ + "Please refresh your profile data manually by clicking 'Refresh' button in 'My Stuff' tab.",
+ "myExperiment Plugin - Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * For each comment in the list fetches the user which made the comment, the
+ * date when it was made, etc.
+ */
+ public void updateCommentListWithExtraData(List<Comment> comments) {
+ try {
+ Document doc = null;
+
+ for (Comment c : comments) {
+ doc = this.getResource(Resource.COMMENT, c.getURI(),
+ Resource.REQUEST_ALL_DATA);
+ Element rootElement = doc.getRootElement();
+
+ Element userElement = rootElement.getChild("author");
+ User u = new User();
+ u.setTitle(userElement.getText());
+ u.setName(userElement.getText());
+ u.setResource(userElement.getAttributeValue("resource"));
+ u.setURI(userElement.getAttributeValue("uri"));
+ c.setUser(u);
+
+ String createdAt = rootElement.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ c.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Failed while updating comment list for preview", e);
+ }
+ }
+
+ public Document searchByQuery(QuerySearchInstance searchQuery) {
+ Document doc = null;
+ String strSearchURL = null;
+
+ try {
+ // this will URL encode the query so that it can be directly inserted into
+ // the search URL
+ String strUrlEncodedQuery = MyExperimentClient.urlEncodeQuery(searchQuery
+ .getSearchQuery());
+
+ // determine which types to include in the search URL
+ // (if none are added, any types will be searched for)
+ String strSearchFor = "";
+ if (searchQuery.getSearchWorkflows()) strSearchFor += "workflow";
+ if (searchQuery.getSearchFiles()) strSearchFor += ",file";
+ if (searchQuery.getSearchPacks()) strSearchFor += ",pack";
+ if (searchQuery.getSearchUsers()) strSearchFor += ",user";
+ if (searchQuery.getSearchGroups()) strSearchFor += ",group";
+ if (strSearchFor.length() != 0) {
+ // some types were added;
+ // remove leading comma (if it exists)
+ if (strSearchFor.startsWith(","))
+ strSearchFor = strSearchFor.replaceFirst(",", "");
+
+ // add parameter prefix
+ strSearchFor = "type=" + strSearchFor;
+ }
+
+ // assemble all search parameters together
+ // (we will definitely have the number of results)
+ String strParameters = strSearchFor;
+ if (strParameters.length() != 0) strParameters += "&";
+ strParameters += "num=" + searchQuery.getResultCountLimit();
+ strParameters += Util.composeAPIQueryElements(searchQuery);
+
+ // generate the search URL
+ strSearchURL = BASE_URL + "/search.xml?query=" + strUrlEncodedQuery + "&"
+ + strParameters;
+
+ // DEBUG
+ // javax.swing.JOptionPane.showMessageDialog(null, strSearchURL);
+
+ // execute the search on myExperiment
+ doc = this.doMyExperimentGET(strSearchURL).getResponseBody();
+ } catch (Exception e) {
+ logger
+ .error("ERROR: Failed to run search on myExperiment. Query URL was'"
+ + strSearchURL + "'\n" + e);
+ }
+
+ return (doc);
+ }
+
+ public ServerResponse postComment(Resource resource, String strComment) {
+ try {
+ String strCommentData = "<comment><subject resource=\""
+ + resource.getResource() + "\"/><comment>" + strComment
+ + "</comment></comment>";
+ ServerResponse response = this.doMyExperimentPOST(BASE_URL
+ + "/comment.xml", strCommentData);
+
+ if (response.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ // XML response should contain the new comment that was posted
+ Comment cNew = Comment.buildFromXML(response.getResponseBody(), logger);
+
+ // this resource should be commentable on as the comment was posted
+ resource.getComments().add(cNew);
+ }
+
+ // will return the whole response object so that the application could
+ // decide
+ // on the next steps
+ return (response);
+ } catch (Exception e) {
+ logger.error("Failed while trying to post a comment for "
+ + resource.getURI() + "\n" + e);
+ return (new ServerResponse(ServerResponse.LOCAL_FAILURE, null));
+ }
+ }
+
+ private String prepareWorkflowPostContent(String workflowContent,
+ String title, String description, String license, String sharing) {
+ String strWorkflowData = "<workflow>";
+
+ if (title.length() > 0) strWorkflowData += "<title>" + title + "</title>";
+
+ if (description.length() > 0)
+ strWorkflowData += "<description>" + description + "</description>";
+
+ if (license.length() > 0)
+ strWorkflowData += "<license-type>" + license + "</license-type>";
+
+ if (sharing.length() > 0) {
+ if (sharing.contains("private")) strWorkflowData += "<permissions />";
+ else {
+ strWorkflowData += "<permissions><permission>"
+ + "<category>public</category>";
+ if (sharing.contains("view") || sharing.contains("download"))
+ strWorkflowData += "<privilege type=\"view\" />";
+ if (sharing.contains("download"))
+ strWorkflowData += "<privilege type=\"download\" />";
+ strWorkflowData += "</permission></permissions>";
+ }
+ }
+
+ String encodedWorkflow = "";
+
+ // check the format of the workflow
+ String scuflSchemaDef = "xmlns:s=\"http://org.embl.ebi.escience/xscufl/0.1alpha\"";
+ String t2flowSchemaDef = "xmlns=\"http://taverna.sf.net/2008/xml/t2flow\"";
+
+ if (workflowContent.length() > 0) {
+ String contentType;
+ if (workflowContent.contains(scuflSchemaDef)) contentType = "application/vnd.taverna.scufl+xml";
+ else if (workflowContent.contains(t2flowSchemaDef)) contentType = "application/vnd.taverna.t2flow+xml";
+ else contentType = "";
+
+ encodedWorkflow += "<content-type>" + contentType + "</content-type>"
+ + "<content encoding=\"base64\" type=\"binary\">"
+ + Base64.encodeBytes(workflowContent.getBytes()) + "</content>";
+ strWorkflowData += encodedWorkflow;
+ }
+
+ strWorkflowData += "</workflow>";
+
+ return (strWorkflowData);
+ }
+
+ private void afterMyExperimentPost(ServerResponse response) {
+ // if (response.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ // // XML response should contain the new workflow that was posted
+ // Workflow newWorkflow = Workflow.buildFromXML(response.getResponseBody(),
+ // logger);
+ //
+ // System.out.println("* *** *** *** *" + response.getResponseBody()
+ // + "* *** *** *** *");
+ // }
+ }
+
+ public ServerResponse postWorkflow(String workflowContent, String title,
+ String description, String license, String sharing) {
+ try {
+ String strWorkflowData = prepareWorkflowPostContent(workflowContent,
+ title, description, license, sharing);
+
+ ServerResponse response = this.doMyExperimentPOST(BASE_URL
+ + "/workflow.xml", strWorkflowData);
+
+ afterMyExperimentPost(response);
+ // will return the whole response object so that the application could
+ // decide on the next steps
+ return (response);
+ } catch (Exception e) {
+ logger.error("Failed while trying to upload the workflow");
+ return (new ServerResponse(ServerResponse.LOCAL_FAILURE, null));
+ }
+ }
+
+ public ServerResponse updateWorkflowVersionOrMetadata(Resource resource,
+ String workflowContent, String title, String description, String license,
+ String sharing) {
+ try {
+ String strWorkflowData = prepareWorkflowPostContent(workflowContent,
+ title, description, license, sharing);
+
+ // if strWorkflowFileContent is empty; include version info for PUT (since
+ // workflow is being updated)
+ // a POST would require data, hence strWorkflowFileContent would not be
+ // empty
+ String doUpdateStatus = (workflowContent.length() == 0 ? DO_PUT : "");
+
+ ServerResponse response = this.doMyExperimentPOST(BASE_URL
+ + "/workflow.xml?id=" + resource.getID() + doUpdateStatus,
+ strWorkflowData);
+
+ afterMyExperimentPost(response);
+ // will return the whole response object so that the application could
+ // decide on the next steps
+ return (response);
+ } catch (Exception e) {
+ logger.error("Failed while trying to upload the workflow");
+ return (new ServerResponse(ServerResponse.LOCAL_FAILURE, null));
+ }
+ }
+
+ public ServerResponse addFavourite(Resource resource) {
+ try {
+ String strData = "<favourite><object resource=\""
+ + resource.getResource() + "\"/></favourite>";
+ ServerResponse response = this.doMyExperimentPOST(BASE_URL
+ + "/favourite.xml", strData);
+
+ // will return full server response
+ return (response);
+ } catch (Exception e) {
+ logger.error("Failed while trying to add an item (" + resource.getURI()
+ + ") to favourites4", e);
+ return (new ServerResponse(ServerResponse.LOCAL_FAILURE, null));
+ }
+ }
+
+ public ServerResponse deleteFavourite(Resource resource) {
+ try {
+ // deleting a favourite is a two-step process - first need to retrieve the
+ // the
+ // actual "favourite" object by current user's URL and favourited item's
+ // URL
+ String strGetFavouriteObjectURL = BASE_URL
+ + "/favourites.xml?user="
+ + MyExperimentClient.urlEncodeQuery(this.getCurrentUser()
+ .getResource()) + "&object="
+ + MyExperimentClient.urlEncodeQuery(resource.getResource());
+ ServerResponse response = this
+ .doMyExperimentGET(strGetFavouriteObjectURL);
+
+ // now retrieve this object's URI from server's response
+ Element root = response.getResponseBody().getRootElement();
+ String strFavouriteURI = root.getChild("favourite").getAttributeValue(
+ "uri");
+
+ // finally, delete the found object
+ response = this.doMyExperimentDELETE(strFavouriteURI);
+
+ // will return full server response
+ return (response);
+ } catch (Exception e) {
+ logger.error("Failed while trying to remove an item ("
+ + resource.getURI() + ") from favourites\n" + e);
+ return (new ServerResponse(ServerResponse.LOCAL_FAILURE, null));
+ }
+ }
+
+ public static Date parseDate(String date) {
+ Date result = null;
+ try {
+ result = OLD_DATE_FORMATTER.parse(date);
+ } catch (ParseException e) {
+ try {
+ result = OLD_SHORT_DATE_FORMATTER.parse(date);
+ }
+ catch (ParseException e1) {
+ try {
+ result = NEW_DATE_FORMATTER.parse(date);
+ } catch (ParseException e2) {
+ result = null;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static String formatDate(Date date) {
+ return NEW_DATE_FORMATTER.format(date);
+ }
+
+ /**
+ * Prepares the string to serve as a part of url query to the server.
+ *
+ * @param query
+ * The string that needs URL encoding.
+ * @return URL encoded string that can be inserted into the request URL.
+ */
+ private static String urlEncodeQuery(String query) {
+ String strRes = "";
+
+ try {
+ strRes = URLEncoder.encode(query, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // do nothing
+ }
+
+ return (strRes);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Pack.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Pack.java
new file mode 100644
index 0000000..d844fa6
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Pack.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.User;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Pack extends Resource
+{
+ private int accessType;
+
+ private User creator;
+ private List<Tag> tags;
+ private List<Comment> comments;
+ private ArrayList<PackItem> items;
+
+
+ public Pack()
+ {
+ super();
+ this.setItemType(Resource.PACK);
+ }
+
+ public int getAccessType()
+ {
+ return this.accessType;
+ }
+
+ public void setAccessType(int accessType)
+ {
+ this.accessType = accessType;
+ }
+
+ public User getCreator() {
+ return creator;
+ }
+
+ public void setCreator(User creator) {
+ this.creator = creator;
+ }
+
+ public List<Tag> getTags()
+ {
+ return (this.tags);
+ }
+
+ public List<Comment> getComments()
+ {
+ return this.comments;
+ }
+
+ public int getItemCount()
+ {
+ return this.items.size();
+ }
+
+ public ArrayList<PackItem> getItems()
+ {
+ return this.items;
+ }
+
+
+ /**
+ * A helper method to return a set of API elements that are
+ * needed to satisfy request of a particular type - e.g. creating
+ * a listing of resources or populating full preview, etc.
+ *
+ * @param iRequestType A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType)
+ {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case
+ switch (iRequestType) {
+ case Resource.REQUEST_FULL_PREVIEW:
+ strElements += "created-at,updated-at,internal-pack-items,external-pack-items,tags,comments,";
+ case Resource.REQUEST_FULL_LISTING:
+ strElements += "owner,";
+ case Resource.REQUEST_SHORT_LISTING:
+ strElements += "id,title,description,privileges";
+ }
+
+ return (strElements);
+ }
+
+
+ public static Pack buildFromXML(Document doc, MyExperimentClient client, Logger logger)
+ {
+ // if no XML document was supplied, return NULL
+ if(doc == null) return(null);
+
+ // call main method which parses XML document starting from root element
+ return (Pack.buildFromXML(doc.getRootElement(), client, logger));
+ }
+
+
+ // class method to build a pack instance from XML
+ @SuppressWarnings("unchecked")
+ public static Pack buildFromXML(Element docRootElement, MyExperimentClient client, Logger logger)
+ {
+ // return null to indicate an error if XML document contains no root element
+ if(docRootElement == null) return(null);
+
+ Pack p = new Pack();
+
+ try {
+ // Access type
+ p.setAccessType(Util.getAccessTypeFromXMLElement(docRootElement.getChild("privileges")));
+
+ // URI
+ p.setURI(docRootElement.getAttributeValue("uri"));
+
+ // Resource URI
+ p.setResource(docRootElement.getAttributeValue("resource"));
+
+ // Id
+ String id = docRootElement.getChildText("id");
+ if (id == null || id.equals("")) {
+ id = "API Error - No pack ID supplied";
+ logger.error("Error while parsing pack XML data - no ID provided for pack with title: \"" + docRootElement.getChildText("title") + "\"");
+ }
+ p.setID(Integer.parseInt(id));
+
+ // Title
+ p.setTitle(docRootElement.getChildText("title"));
+
+ // Description
+ p.setDescription(docRootElement.getChildText("description"));
+
+ // Owner
+ Element ownerElement = docRootElement.getChild("owner");
+ p.setCreator(Util.instantiatePrimitiveUserFromElement(ownerElement));
+
+ // Created at
+ String createdAt = docRootElement.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ p.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ // Updated at
+ String updatedAt = docRootElement.getChildText("updated-at");
+ if (updatedAt != null && !updatedAt.equals("")) {
+ p.setUpdatedAt(MyExperimentClient.parseDate(updatedAt));
+ }
+
+ // Tags
+ p.tags = new ArrayList<Tag>();
+ p.getTags().addAll(Util.retrieveTags(docRootElement));
+
+ // Comments
+ p.comments = new ArrayList<Comment>();
+ p.getComments().addAll(Util.retrieveComments(docRootElement, p));
+
+
+ // === All items will be stored together in one array ===
+ p.items = new ArrayList<PackItem>();
+ int iCount = 0;
+
+ // adding internal items first
+ Element itemsElement = docRootElement.getChild("internal-pack-items");
+ if (itemsElement != null) {
+ List<Element> itemsNodes = itemsElement.getChildren();
+ for (Element e : itemsNodes) {
+ Document docCurrentItem = client.getResource(Resource.PACK_INTERNAL_ITEM, e.getAttributeValue("uri"), Resource.REQUEST_DEFAULT_FROM_API);
+ PackItem piCurrentItem = PackItem.buildFromXML(docCurrentItem, logger);
+
+ p.getItems().add(piCurrentItem);
+ iCount++;
+ }
+ }
+
+ // now adding external items
+ itemsElement = docRootElement.getChild("external-pack-items");
+ if (itemsElement != null) {
+ List<Element> itemsNodes = itemsElement.getChildren();
+ for (Element e : itemsNodes) {
+ Document docCurrentItem = client.getResource(Resource.PACK_EXTERNAL_ITEM, e.getAttributeValue("uri"), Resource.REQUEST_DEFAULT_FROM_API);
+ PackItem piCurrentItem = PackItem.buildFromXML(docCurrentItem, logger);
+
+ p.getItems().add(piCurrentItem);
+ iCount++;
+ }
+ }
+
+ // sort the items after all of those have been added
+ Collections.sort(p.getItems());
+
+
+ logger.debug("Found information for pack with ID: " + p.getID() + ", Title: " + p.getTitle());
+ }
+ catch (Exception e) {
+ logger.error("Failed midway through creating pack object from XML", e);
+ }
+
+ // return created pack instance
+ return(p);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/PackItem.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/PackItem.java
new file mode 100644
index 0000000..d3f2773
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/PackItem.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.text.DateFormat;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class PackItem extends Resource {
+ private int id;
+ private User userWhoAddedThisItem;
+ private String strComment;
+
+ private boolean bInternalItem;
+
+ private Resource item; // for internal items
+
+ private String strLink; // for external items
+ private String strAlternateLink; // for external items
+
+ public PackItem() {
+ super();
+ this.setItemType(Resource.UNKNOWN); // set to unknown originally; will be changed as soon as the type is known
+ }
+
+ public int getID() {
+ return id;
+ }
+
+ public void setID(int id) {
+ this.id = id;
+ }
+
+ public void setID(String id) {
+ this.id = Integer.parseInt(id);
+ }
+
+ public boolean isInternalItem() {
+ return this.bInternalItem;
+ }
+
+ public void setInternalItem(boolean bIsInternalItem) {
+ this.bInternalItem = bIsInternalItem;
+ }
+
+ public User getUserWhoAddedTheItem() {
+ return this.userWhoAddedThisItem;
+ }
+
+ public void setUserWhoAddedTheItem(User userWhoAddedTheItem) {
+ this.userWhoAddedThisItem = userWhoAddedTheItem;
+ }
+
+ public String getComment() {
+ return this.strComment;
+ }
+
+ public void setComment(String strComment) {
+ this.strComment = strComment;
+ }
+
+ public Resource getItem() {
+ return this.item;
+ }
+
+ public void setItem(Resource item) {
+ this.item = item;
+ }
+
+ public String getLink() {
+ return this.strLink;
+ }
+
+ public void setLink(String strLink) {
+ this.strLink = strLink;
+ }
+
+ public String getAlternateLink() {
+ return this.strAlternateLink;
+ }
+
+ public void setAlternateLink(String strAlternateLink) {
+ this.strAlternateLink = strAlternateLink;
+ }
+
+ public static PackItem buildFromXML(Document doc, Logger logger) {
+ // if no XML was supplied, return null to indicate an error
+ if (doc == null)
+ return (null);
+
+ PackItem p = new PackItem();
+
+ try {
+ Element root = doc.getRootElement();
+
+ // URI
+ p.setURI(root.getAttributeValue("uri"));
+
+ // Resource URI
+ p.setResource(root.getAttributeValue("resource"));
+
+ // Id
+ String strID = root.getChildText("id");
+ if (strID == null || strID.equals("")) {
+ strID = "API Error - No pack item ID supplied";
+ logger.error("Error while parsing pack item XML data - no ID provided for pack item with uri: \""
+ + p.getURI() + "\"");
+ } else {
+ p.setID(strID);
+ }
+
+ // User who added the item to the pack
+ Element ownerElement = root.getChild("owner");
+ p.setUserWhoAddedTheItem(Util.instantiatePrimitiveUserFromElement(ownerElement));
+
+ // Date when the item was added to the pack
+ String createdAt = root.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ p.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ // Comment
+ Element commentElement = root.getChild("comment");
+ if (commentElement != null) {
+ p.setComment(commentElement.getText());
+ }
+
+ // === UP TO THIS POINT EXTERNAL AND INTERNAL ITEMS HAD THE SAME DATA ===
+ if (root.getName().equals("internal-pack-item")) {
+ // record that this is internal item
+ p.setInternalItem(true);
+
+ // add a link to a resource for internal items
+ Element itemElement = (Element) root.getChild("item").getChildren().get(0);
+ if (itemElement != null) {
+ p.setItem(Util.instantiatePrimitiveResourceFromElement(itemElement));
+ }
+
+ // now need to replicate title and item type attributes to the pack item object
+ // itself - this is required to allow proper sorting of the items
+ p.setItemType(p.getItem().getItemType());
+ p.setTitle(p.getItem().getTitle());
+ } else {
+ // record that this is external item
+ p.setInternalItem(false);
+
+ // add links to the external resource for external items
+ p.setItemType(Resource.PACK_EXTERNAL_ITEM);
+ p.setTitle(root.getChildText("title"));
+ p.setLink(root.getChildText("uri"));
+ p.setAlternateLink(root.getChildText("alternate-uri"));
+ }
+
+ logger.debug("Found information for pack item with URI: " + p.getURI());
+ } catch (Exception e) {
+ logger.error("Failed midway through creating pack item object from XML", e);
+ }
+
+ // return created pack item instance
+ return (p);
+
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Resource.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Resource.java
new file mode 100644
index 0000000..3c1a36c
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Resource.java
@@ -0,0 +1,683 @@
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.io.Serializable;
+import java.net.URI;
+import java.util.Date;
+import java.util.EventListener;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MyExperimentPerspective;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.ResourceListPanel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.StyledHTMLEditorKit;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Jiten Bhagat, Sergejs Aleksejevs
+ */
+public class Resource implements Comparable<Resource>, Serializable {
+ // CONSTANTS
+ // (integer resource types)
+ public static final int UNEXPECTED_TYPE = -1; // erroneous type
+ public static final int UNKNOWN = 0;
+ public static final int WORKFLOW = 10;
+ public static final int FILE = 11;
+ public static final int PACK = 12;
+ public static final int PACK_INTERNAL_ITEM = 14;
+ public static final int PACK_EXTERNAL_ITEM = 15;
+ public static final int USER = 20;
+ public static final int GROUP = 21;
+ public static final int TAG = 30;
+ public static final int COMMENT = 31;
+
+ // (string resource types)
+ public static final String WORKFLOW_VISIBLE_NAME = "Workflow";
+ public static final String FILE_VISIBLE_NAME = "File";
+ public static final String PACK_VISIBLE_NAME = "Pack";
+ public static final String USER_VISIBLE_NAME = "User";
+ public static final String GROUP_VISIBLE_NAME = "Group";
+ public static final String TAG_VISIBLE_NAME = "Tag";
+ public static final String COMMENT_VISIBLE_NAME = "Comment";
+ public static final String UNKWNOWN_VISIBLE_NAME = "Unknown";
+ public static final String UNEXPECTED_TYPE_VISIBLE_NAME = "ERROR: Unexpected unknown type!";
+
+ // (integer access types)
+ public static final int ACCESS_VIEWING = 1000;
+ public static final int ACCESS_DOWNLOADING = 1001;
+ public static final int ACCESS_EDITING = 1002;
+
+ // (categories for selecting required elements for every resource type for a particular purpose)
+ public static final int REQUEST_ALL_DATA = 5000; // essentially obtains all data that API provides
+ public static final int REQUEST_FULL_PREVIEW = 5005; // used to get all data for preview in a browser window
+ public static final int REQUEST_FULL_LISTING = 5010; // used for displaying results of searches by query / by tag
+ public static final int REQUEST_SHORT_LISTING = 5015; // used for displaying items in 'My Stuff' tab
+ public static final int REQUEST_USER_FAVOURITES_ONLY = 5050;
+ public static final int REQUEST_USER_APPLIED_TAGS_ONLY = 5051;
+ public static final int REQUEST_WORKFLOW_CONTENT_ONLY = 5055;
+ public static final int REQUEST_DEFAULT_FROM_API = 5100; // used when default fields that come from the API are acceptable
+
+ // instance variables
+ private int iID;
+ private String uri;
+ private String resource;
+ private String title;
+
+ private int itemType;
+
+ private Date createdAt;
+ private Date updatedAt;
+ private String description;
+
+ public Resource() {
+ // empty constructor
+ }
+
+ public int getID() {
+ return iID;
+ }
+
+ public void setID(int id) {
+ this.iID = id;
+ }
+
+ public void setID(String id) {
+ this.iID = Integer.parseInt(id);
+ }
+
+ public String getURI() {
+ return uri;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+
+ public int getItemType() {
+ return itemType;
+ }
+
+ public String getItemTypeName() {
+ return Resource.getResourceTypeName(itemType);
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setURI(String uri) {
+ this.uri = uri;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public void setItemType(int type) {
+ this.itemType = type;
+ }
+
+ public void setItemType(String type) {
+ this.itemType = Resource.getResourceTypeFromVisibleName(type);
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ @Override
+ public String toString() {
+ return ("(" + this.getItemTypeName() + ", " + this.getURI() + ","
+ + this.getTitle() + ")");
+ }
+
+ /**
+ * This method is needed to sort Resource instances.
+ */
+ public int compareTo(Resource other) {
+ int iTypesCompared = this.getItemType() - other.getItemType();
+
+ if (iTypesCompared == 0) {
+ // types are identical, compare by title
+ return (this.getTitle().compareTo(other.getTitle()));
+ } else {
+ // types are different - this is sufficient to order these two resources
+ // (NB! This presumes that type constants were set in a way that produces correct
+ // ordering of the types for sorting operations!)
+ return (iTypesCompared);
+ }
+ }
+
+ /**
+ * This makes sure that things like instanceOf() and remove() in List
+ * interface work properly - this way resources are treated to be the same if
+ * they store identical data, rather than they simply hold the same reference.
+ */
+ @Override
+ public boolean equals(Object other) {
+ // could only be equal to another Resource object, not anything else
+ if (!(other instanceof Resource))
+ return (false);
+
+ // 'other' object is a Resource; equality is based on the data stored
+ // in the current and 'other' Resource instances - the main data of the
+ // Resource: item type, URI in the API and resource URL on myExperiment
+ // (these fields will always be present in every Resource instance)
+ Resource otherRes = (Resource) other;
+ return (this.itemType == otherRes.itemType && this.uri.equals(otherRes.uri) && this.resource.equals(otherRes.resource));
+ }
+
+ /**
+ * Check if the current type of resource is supposed to have an uploader.
+ */
+ public boolean hasUploader() {
+ return (this.itemType == Resource.WORKFLOW || this.itemType == Resource.FILE);
+ }
+
+ /**
+ * Casts the resource to one of the specialist types to get the uploader.
+ */
+ public User getUploader() {
+ switch (this.itemType) {
+ case Resource.WORKFLOW:
+ return ((Workflow) this).getUploader();
+ case Resource.FILE:
+ return ((File) this).getUploader();
+ default:
+ return (null);
+ }
+ }
+
+ /**
+ * Check if the current type of resource is supposed to have a creator.
+ */
+ public boolean hasCreator() {
+ return (this.itemType == Resource.PACK);
+ }
+
+ /**
+ * Casts the resource to one of the specialist types to get the creator.
+ */
+ public User getCreator() {
+ switch (this.itemType) {
+ case Resource.PACK:
+ return ((Pack) this).getCreator();
+ default:
+ return (null);
+ }
+ }
+
+ /**
+ * Check if the current type of resource is supposed to have a administrator.
+ */
+ public boolean hasAdmin() {
+ return (this.itemType == Resource.GROUP);
+ }
+
+ /**
+ * Casts the resource to one of the specialist types to get the administrator.
+ */
+ public User getAdmin() {
+ switch (this.itemType) {
+ case Resource.GROUP:
+ return ((Group) this).getAdmin();
+ default:
+ return (null);
+ }
+ }
+
+ /**
+ * Determines whether the current type of resource can be favourited.
+ */
+ public boolean isFavouritable() {
+ switch (this.itemType) {
+ case Resource.WORKFLOW:
+ case Resource.FILE:
+ case Resource.PACK:
+ return (true);
+ default:
+ return (false);
+ }
+ }
+
+ /**
+ * Determines whether the current resource is favourited by specified user.
+ */
+ public boolean isFavouritedBy(User user) {
+ for (Resource r : user.getFavourites()) {
+ if (r.getURI().equals(this.getURI())) {
+ return (true);
+ }
+ }
+
+ return (false);
+ }
+
+ /**
+ * Determines whether the current type of resource can be commented on.
+ */
+ public boolean isCommentableOn() {
+ switch (this.itemType) {
+ case Resource.WORKFLOW:
+ case Resource.FILE:
+ case Resource.PACK:
+ case Resource.GROUP:
+ return (true);
+ default:
+ return (false);
+ }
+ }
+
+ /**
+ * Retrieves the collection of comments for the current resource.
+ */
+ public List<Comment> getComments() {
+ switch (this.itemType) {
+ case Resource.WORKFLOW:
+ return ((Workflow) this).getComments();
+ case Resource.FILE:
+ return ((File) this).getComments();
+ case Resource.PACK:
+ return ((Pack) this).getComments();
+ case Resource.GROUP:
+ return ((Group) this).getComments();
+ default:
+ return (null);
+ }
+ }
+
+ /**
+ * Determines whether the current type of resource can be downloaded in
+ * general.
+ */
+ public boolean isDownloadable() {
+ return (this.itemType == Resource.WORKFLOW
+ || this.itemType == Resource.FILE || this.itemType == Resource.PACK);
+ }
+
+ /**
+ * Determines whether the current resource instance can be downloaded by the
+ * current user.
+ */
+ public boolean isDownloadAllowed() {
+ int iAccessType = 0;
+
+ switch (this.itemType) {
+ case Resource.WORKFLOW:
+ iAccessType = ((Workflow) this).getAccessType();
+ break;
+ case Resource.FILE:
+ iAccessType = ((File) this).getAccessType();
+ break;
+ case Resource.PACK:
+ iAccessType = ((Pack) this).getAccessType();
+ break;
+ default:
+ iAccessType = 0;
+ }
+
+ return (iAccessType >= Resource.ACCESS_DOWNLOADING);
+ }
+
+ /**
+ * Only workflows (and files?) have visible types.
+ */
+ public boolean hasVisibleType() {
+ return (this.itemType == Resource.WORKFLOW || this.itemType == Resource.FILE);
+ }
+
+ public String getVisibleType() {
+ switch (this.itemType) {
+ case Resource.WORKFLOW:
+ return ((Workflow) this).getVisibleType();
+ case Resource.FILE:
+ return ((File) this).getVisibleType();
+ default:
+ return (null);
+ }
+ }
+
+ /**
+ * Will create a small preview panel for the instance of the resource that it
+ * holds. Shows reduced amount of information when "bCreateFullSizeView" is
+ * set to false.
+ *
+ * @return JPanel containing title, description and links to actions on this
+ * resource.
+ */
+ public JPanel createListViewPanel(boolean bCreateFullSizeView, MainComponent pluginMainComponent, EventListener eventHandler, Logger logger) {
+ try {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
+
+ JTextPane infoTextPane = new JTextPane();
+ infoTextPane.setBorder(BorderFactory.createEmptyBorder());
+ infoTextPane.setEditable(false);
+
+ StringBuffer content = new StringBuffer();
+ content.append("<div class='list_item_container'>");
+ content.append("<div class='list_item'>");
+
+ content.append("<p class='title'>");
+ content.append("<a href='preview:"
+ + this.getItemType()
+ + ":"
+ + this.getURI()
+ + "'>"
+ + this.getTitle()
+ + ((this.getItemType() == Resource.WORKFLOW) ? (" (version "
+ + ((Workflow) this).getVersion() + ")") : "") + "</a>");
+ content.append("</p>");
+
+ if (bCreateFullSizeView) {
+ // Uploader / Creator / Administrator
+ if (this.hasUploader()) {
+ content.append("<p class='uploader'>");
+ content.append("Uploader: <a href='preview:" + Resource.USER + ":"
+ + this.getUploader().getURI() + "'>"
+ + this.getUploader().getName() + "</a>");
+ content.append("</p>");
+ } else if (this.hasCreator()) {
+ content.append("<p class='uploader'>");
+ content.append("Creator: <a href='preview:" + Resource.USER + ":"
+ + this.getCreator().getURI() + "'>" + this.getCreator().getName()
+ + "</a>");
+ content.append("</p>");
+ } else if (this.hasAdmin()) {
+ content.append("<p class='uploader'>");
+ content.append("Administrator: <a href='preview:" + Resource.USER
+ + ":" + this.getAdmin().getURI() + "'>"
+ + this.getAdmin().getName() + "</a>");
+ content.append("</p>");
+ }
+
+ // Type
+ if (this.hasVisibleType()) {
+ content.append("<p class='uploader'>");
+ content.append("Type: " + this.getVisibleType());
+ content.append("</p>");
+ }
+ }
+
+ content.append("<div class='desc'>");
+ content.append("<table style='margin-top: 5px; margin-bottom: 5px;'>");
+ content.append("<tr>");
+
+ if (this.itemType == Resource.WORKFLOW || this.itemType == Resource.USER) {
+ boolean bManualResizeNeeded = false; // manual resize will be needed for user avatars (these don't have auto thumbnails)
+ URI previewURI = null;
+ if (this.itemType == Resource.WORKFLOW)
+ previewURI = ((Workflow) this).getThumbnail();
+ else if (this.itemType == Resource.USER
+ && ((User) this).getAvatarResource() != null) {
+ previewURI = new URI(((User) this).getAvatarResource());
+ bManualResizeNeeded = true;
+ }
+
+ // preview pictures are only shown for workflows
+ content.append("<td valign='top'>");
+ content.append("<a href='preview:" + this.itemType + ":"
+ + this.getURI() + "'>");
+
+ if (bCreateFullSizeView) {
+ if (!bManualResizeNeeded)
+ content.append("<img class='preview' src='" + previewURI
+ + "'></img>");
+ else {
+ String resizedImageURL = Util.getResizedImageIconTempFileURL(previewURI.toURL(), ResourceListPanel.THUMBNAIL_WIDTH_FOR_FULL_LIST_VIEW, ResourceListPanel.THUMBNAIL_HEIGHT_FOR_FULL_LIST_VIEW);
+ content.append("<img class='preview' src='" + resizedImageURL
+ + "'></img>");
+ }
+ } else {
+ String resizedImageURL = Util.getResizedImageIconTempFileURL(previewURI.toURL(), ResourceListPanel.THUMBNAIL_WIDTH_FOR_SHORT_LIST_VIEW, ResourceListPanel.THUMBNAIL_HEIGHT_FOR_SHORT_LIST_VIEW);
+ content.append("<img class='preview' src='" + resizedImageURL
+ + "'></img>");
+ }
+
+ content.append("</a>");
+ content.append("</td>");
+ }
+
+ content.append("<td>");
+ content.append(" ");
+ content.append("</td>");
+ content.append("<td valign='top' style='margin-bottom: 10px;'>");
+
+ if (this.getDescription() != null && this.getDescription().length() > 0) {
+ if (bCreateFullSizeView)
+ content.append(this.getDescription());
+ else {
+ String strTruncatedDescription = this.getDescription();
+ if (strTruncatedDescription.length() > ResourceListPanel.DESCRIPTION_TRUNCATE_LENGTH_FOR_SHORT_LIST_VIEW) {
+ strTruncatedDescription = strTruncatedDescription.substring(0, ResourceListPanel.DESCRIPTION_TRUNCATE_LENGTH_FOR_SHORT_LIST_VIEW);
+ strTruncatedDescription += " ...";
+ }
+ content.append(strTruncatedDescription);
+ }
+ } else {
+ content.append("<span class='none_text'>No description</span>");
+ }
+
+ content.append("</td>");
+ content.append("</tr>");
+ content.append("</table>");
+ content.append("</div>");
+
+ if (bCreateFullSizeView) {
+ content.append("<p style='text-align: left;'><b><a href='"
+ + this.getResource()
+ + "'>Open in myExperiment</a></b>"
+ + " <img style='border: 0px;' src='"
+ + MyExperimentPerspective.getLocalResourceURL("external_link_small_icon")
+ + "' /></p>");
+ }
+
+ content.append("</div>");
+ content.append("</div>");
+
+ HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+ HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+
+ doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+ infoTextPane.setEditorKit(kit);
+ infoTextPane.setDocument(doc);
+ infoTextPane.setContentType("text/html");
+ infoTextPane.addHyperlinkListener((HyperlinkListener) eventHandler);
+ infoTextPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 7, 0)); // little bit of padding below the HTML preview pane
+
+ mainPanel.add(infoTextPane, BorderLayout.CENTER);
+
+ if (bCreateFullSizeView) {
+ JPanel jpButtonsPanel = new JPanel();
+ jpButtonsPanel.setLayout(new BoxLayout(jpButtonsPanel, BoxLayout.LINE_AXIS));
+
+ // "Preview" button
+ JButton previewButton = new JButton();
+ previewButton.setAction(pluginMainComponent.new PreviewResourceAction(this.getItemType(), this.getURI()));
+ jpButtonsPanel.add(previewButton);
+
+ // "Download" button
+ if (this.isDownloadable()) {
+ // will have a link to the actual resource on myExperiment
+ // (tests of different conditions are made inside the action)
+ JButton downloadButton = new JButton();
+ downloadButton.setAction(pluginMainComponent.new DownloadResourceAction(this));
+ jpButtonsPanel.add(downloadButton);
+ }
+
+ // "Load" button is only to be displayed for workflows
+ if (this.getItemType() == Resource.WORKFLOW) {
+ // (various checks apply to see if this can be done - these are made inside the action)
+ JButton loadButton = new JButton();
+ loadButton.setAction(pluginMainComponent.new LoadResourceInTavernaAction(this));
+ jpButtonsPanel.add(loadButton);
+ }
+
+ // "Import" button is only to be displayed for workflows
+ if (this.getItemType() == Resource.WORKFLOW) {
+ // (various checks apply to see if this can be done - these are made inside the action)
+ JButton importButton = new JButton();
+ importButton.setAction(pluginMainComponent.new ImportIntoTavernaAction(this));
+ jpButtonsPanel.add(importButton);
+ }
+
+ // setting look and feel for buttons
+ mainPanel.add(jpButtonsPanel, BorderLayout.SOUTH);
+ jpButtonsPanel.setBackground(new Color(247, 247, 247)); // grey background (lighter than main background of the frame)
+ jpButtonsPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(new Color(235, 235, 235)), // subtle darker border around the button bar
+ BorderFactory.createEmptyBorder(3, 3, 3, 3) // a bit of padding around the buttons
+ ));
+ }
+
+ // setting border around one workflow entry
+ if (bCreateFullSizeView) {
+ mainPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5), BorderFactory.createLineBorder(Color.GRAY)));
+ } else {
+ mainPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY));
+ }
+
+ return (mainPanel);
+ } catch (Exception e) {
+ logger.error("Failed while creating " + this.getItemTypeName()
+ + " list view:\n" + e);
+ return (null);
+ }
+ }
+
+ /**
+ * Translates resource type codes into a textual representation.
+ *
+ * @param resourceTypeCode
+ * This code should be one of the resource type constants defined in
+ * Resource class.
+ * @return Textual translation of the resource type code.
+ */
+ public static String getResourceTypeName(int resourceTypeCode) {
+ switch (resourceTypeCode) {
+ case Resource.WORKFLOW:
+ return Resource.WORKFLOW_VISIBLE_NAME;
+ case Resource.FILE:
+ return Resource.FILE_VISIBLE_NAME;
+ case Resource.PACK:
+ return Resource.PACK_VISIBLE_NAME;
+ case Resource.USER:
+ return Resource.USER_VISIBLE_NAME;
+ case Resource.GROUP:
+ return Resource.GROUP_VISIBLE_NAME;
+ case Resource.TAG:
+ return Resource.TAG_VISIBLE_NAME;
+ case Resource.COMMENT:
+ return Resource.COMMENT_VISIBLE_NAME;
+ case Resource.UNKNOWN:
+ return Resource.UNKWNOWN_VISIBLE_NAME;
+ default:
+ return Resource.UNEXPECTED_TYPE_VISIBLE_NAME;
+ }
+ }
+
+ /**
+ * Translates resource visible name into the type codes.
+ */
+ public static int getResourceTypeFromVisibleName(String name) {
+ if (name.toLowerCase().equals(Resource.WORKFLOW_VISIBLE_NAME.toLowerCase()))
+ return (Resource.WORKFLOW);
+ else if (name.toLowerCase().equals(Resource.FILE_VISIBLE_NAME.toLowerCase()))
+ return (Resource.FILE);
+ else if (name.toLowerCase().equals(Resource.PACK_VISIBLE_NAME.toLowerCase()))
+ return (Resource.PACK);
+ else if (name.toLowerCase().equals(Resource.USER_VISIBLE_NAME.toLowerCase()))
+ return (Resource.USER);
+ else if (name.toLowerCase().equals(Resource.GROUP_VISIBLE_NAME.toLowerCase()))
+ return (Resource.GROUP);
+ else if (name.toLowerCase().equals(Resource.TAG_VISIBLE_NAME.toLowerCase()))
+ return (Resource.TAG);
+ else if (name.toLowerCase().equals(Resource.COMMENT_VISIBLE_NAME.toLowerCase()))
+ return (Resource.COMMENT);
+ else
+ return (Resource.UNKNOWN);
+ }
+
+ /**
+ * This method will act as dispatcher for local buildFromXML() methods for
+ * each of individual resource types. This way there's a generic way to turn
+ * XML content into a Resource instance.
+ */
+ public static Resource buildFromXML(Document resourceXMLDocument, MyExperimentClient client, Logger logger) {
+ Element root = resourceXMLDocument.getRootElement();
+ return (Resource.buildFromXML(root, client, logger));
+ }
+
+ /**
+ * This method will act as dispatcher for local buildFromXML() methods for
+ * each of individual resource types. This way there's a generic way to turn
+ * XML content into a Resource instance.
+ */
+ public static Resource buildFromXML(Element docRootElement, MyExperimentClient client, Logger logger) {
+ Resource res = null;
+ switch (Resource.getResourceTypeFromVisibleName(docRootElement.getName())) {
+ case Resource.WORKFLOW:
+ res = Workflow.buildFromXML(docRootElement, logger);
+ break;
+ case Resource.FILE:
+ res = File.buildFromXML(docRootElement, logger);
+ break;
+ case Resource.PACK:
+ res = Pack.buildFromXML(docRootElement, client, logger);
+ break;
+ case Resource.USER:
+ res = User.buildFromXML(docRootElement, logger);
+ break;
+ case Resource.GROUP:
+ res = Group.buildFromXML(docRootElement, logger);
+ break;
+ default:
+ // do nothing - will return 'null'
+ break;
+ }
+
+ return (res);
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/SearchEngine.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/SearchEngine.java
new file mode 100644
index 0000000..57b7a86
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/SearchEngine.java
@@ -0,0 +1,321 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.SearchResultsPanel;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchEngine {
+ // holds a reference to the instance of the search thread in the current context
+ // that should be active at the moment (will aid early termination of older searches
+ // when new ones are started)
+ private Vector<Long> vCurrentSearchThreadID;
+
+ private boolean bSearchByTag; // indicates what kind of search this is
+ private String strSearchQuery; // stores the actual query
+
+ private SearchResultsPanel jpResultsPanel;
+ private MainComponent pluginMainComponent;
+ private MyExperimentClient myExperimentClient;
+ private Logger logger;
+
+ /**
+ * Creates an instance of a search engine that is aware of the current context
+ * of the search.
+ */
+ public SearchEngine(Vector<Long> currentSearchThreadIDVector, boolean bSearchByTag, SearchResultsPanel resultsPanel, MainComponent pluginMainComponent, MyExperimentClient client, Logger logger) {
+ super();
+
+ this.vCurrentSearchThreadID = currentSearchThreadIDVector;
+ this.bSearchByTag = bSearchByTag;
+
+ this.jpResultsPanel = resultsPanel;
+ this.pluginMainComponent = pluginMainComponent;
+ this.myExperimentClient = client;
+ this.logger = logger;
+ }
+
+ public void searchAndPopulateResults(String tagQuery) {
+ this.strSearchQuery = tagQuery;
+
+ new Thread("Search by tag") {
+ public void run() {
+ // Record 'this' search thread and set it as the new "primary" one
+ // (this way it if a new search thread starts afterwards, it is possible to
+ // detect this and stop the 'older' search, because it is no longer relevant)
+ long lThisSearchThreadID = Thread.currentThread().getId();
+ vCurrentSearchThreadID.set(0, lThisSearchThreadID);
+
+ // set the status in the status bar
+ pluginMainComponent.getStatusBar().setStatus(pluginMainComponent.getTagBrowserTab().getClass().getName(), "Searching");
+
+ // get the search query (which is the tag name in this case)
+ strSearchQuery = Tag.instantiateTagFromActionCommand(strSearchQuery).getTagName();
+ jpResultsPanel.setCurrentSearchTerm(strSearchQuery);
+ jpResultsPanel.setStatus("Starting to search for a tag '"
+ + strSearchQuery + "'...");
+
+ // Execute the search query
+ Document doc = myExperimentClient.searchByTag(strSearchQuery);
+
+ if (doc == null) {
+ logger.error("Failed while fetching tagged items. See the error message above. Can't continue.");
+ javax.swing.JOptionPane.showMessageDialog(null, "An error occurred while searching for the results of your query. Please try again.", "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
+ } else {
+ // search was successful, get tagged items' collection
+ Element root = doc.getRootElement();
+ processSearchResults(lThisSearchThreadID, root.getChildren());
+ }
+ }
+ }.start();
+ }
+
+ public void searchAndPopulateResults(QuerySearchInstance querySearchDetails) {
+ final QuerySearchInstance searchQuery = querySearchDetails;
+ this.strSearchQuery = searchQuery.getSearchQuery();
+
+ new Thread("Search by query") {
+ public void run() {
+ // Record 'this' search thread and set it as the new "primary" one
+ // (this way it if a new search thread starts afterwards, it is possible to
+ // detect this and stop the 'older' search, because it is no longer relevant)
+ long lThisSearchThreadID = Thread.currentThread().getId();
+ vCurrentSearchThreadID.set(0, lThisSearchThreadID);
+
+ // set the status in the status bar
+ pluginMainComponent.getStatusBar().setStatus(pluginMainComponent.getSearchTab().getClass().getName(), "Searching");
+
+ // strip out the leading "search:"
+ strSearchQuery = strSearchQuery.replaceFirst("search:", "");
+ jpResultsPanel.setCurrentSearchTerm(strSearchQuery);
+ jpResultsPanel.setStatus("Starting to search for '" + strSearchQuery
+ + "'...");
+
+ // Execute the search query
+ Document doc = myExperimentClient.searchByQuery(searchQuery);
+
+ if (doc == null) {
+ logger.error("Failed while fetching search result XML document. See the error message above. Can't continue.");
+ javax.swing.JOptionPane.showMessageDialog(null, "An error occurred while searching for the results of your query. Please try again.", "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
+ } else {
+ // search was successful, get found items' collection
+ Element root = doc.getRootElement();
+ processSearchResults(lThisSearchThreadID, root.getChildren());
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Generic worker method to process search results from both search by tags
+ * and search by query.
+ *
+ * @param foundSearchItemElements
+ * List of Elements that comprise the search results.
+ */
+ private void processSearchResults(Long thisSearchThreadID, List<Element> foundSearchItemElements) {
+ // prepare variables
+ final Long lThisSearchThreadID = thisSearchThreadID;
+ final List<Element> lFoundSearchItemElements = foundSearchItemElements;
+ final HashMap<Integer, ArrayList<Resource>> hmClassifiedResults = new HashMap<Integer, ArrayList<Resource>>();
+
+ // iterate through all found items, fetch more data about each and classify
+ // the results by resource type
+ for (Element foundItem : foundSearchItemElements) {
+ // check if the current thread is still the active one (that is if a new search
+ // thread hasn't been started yet - if the new search has been started, the
+ // current one should terminate)
+ if (!lThisSearchThreadID.equals(vCurrentSearchThreadID.get(0)))
+ return;
+
+ // PROCESS THE NEXT ELEMENT
+ // data required for generating full listing preview was fetched along with the list of items -
+ // so now can build the item directly from the received data
+ Resource res = Resource.buildFromXML(foundItem, myExperimentClient, logger);
+
+ // check if this is a new type of resource - if so, create new ArrayList for these
+ if (!hmClassifiedResults.containsKey(res.getItemType())) {
+ hmClassifiedResults.put(res.getItemType(), new ArrayList<Resource>());
+ }
+
+ // add the resource into the result set
+ hmClassifiedResults.get(res.getItemType()).add(res);
+ }
+
+ // populate the results when everything's done
+ // (only make a final check to make sure that the "top" search thread is still the current one -
+ // otherwise, it's not necessary to make any rendering of unwanted results)
+ if (lThisSearchThreadID.equals(vCurrentSearchThreadID.get(0))) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jpResultsPanel.setStatus(lFoundSearchItemElements.size()
+ + " items found " + (bSearchByTag ? "with tag '" : "by query '")
+ + jpResultsPanel.getCurrentSearchTerm() + "'");
+ jpResultsPanel.setSearchResultsData(hmClassifiedResults);
+ jpResultsPanel.refresh();
+
+ // if this search thread is displaying results, then this was the last search thread - can display
+ // "ready" status in the status bar now
+ JComponent jcTabForWhichToSetStatus = (bSearchByTag ? pluginMainComponent.getTagBrowserTab() : pluginMainComponent.getSearchTab());
+ pluginMainComponent.getStatusBar().setStatus(jcTabForWhichToSetStatus.getClass().getName(), null);
+ }
+ });
+ }
+ }
+
+ /**
+ * Class to hold settings for a query search. This will then be used to re-run
+ * a search instance at a later time.
+ */
+ public static class QuerySearchInstance implements Comparable<QuerySearchInstance>, Serializable {
+ private String strSearchQuery;
+ private int iResultCountLimit;
+ private boolean bSearchWorkflows;
+ private boolean bSearchFiles;
+ private boolean bSearchPacks;
+ private boolean bSearchUsers;
+ private boolean bSearchGroups;
+
+ // constructor
+ public QuerySearchInstance(String searchQuery, int resultCountLimit, boolean searchWorkflows, boolean searchFiles, boolean searchPacks, boolean searchUsers, boolean searchGroups) {
+ this.strSearchQuery = searchQuery;
+ this.iResultCountLimit = resultCountLimit;
+ this.bSearchWorkflows = searchWorkflows;
+ this.bSearchFiles = searchFiles;
+ this.bSearchPacks = searchPacks;
+ this.bSearchUsers = searchUsers;
+ this.bSearchGroups = searchGroups;
+ }
+
+ // determines whether the two search instances are identical
+ public boolean equals(Object other) {
+ if (other instanceof QuerySearchInstance) {
+ QuerySearchInstance si = (QuerySearchInstance) other;
+
+ return (this.strSearchQuery.equals(si.getSearchQuery())
+ && this.iResultCountLimit == si.getResultCountLimit()
+ && this.bSearchWorkflows == si.getSearchWorkflows()
+ && this.bSearchFiles == si.getSearchFiles()
+ && this.bSearchPacks == si.getSearchPacks()
+ && this.bSearchUsers == si.getSearchUsers() && this.bSearchGroups == si.getSearchGroups());
+ } else
+ return (false);
+ }
+
+ public int compareTo(QuerySearchInstance other) {
+ if (this.equals(other))
+ return (0);
+ else {
+ // this will return results in the descending order - which is
+ // fine, because the way this collection will be rendered will
+ // eventually traverse it from the rear end first; so results
+ // will be shown alphabetically
+ return (-1 * this.toString().compareTo(other.toString()));
+ }
+ }
+
+ public String toString() {
+ return ("Search: '" + this.strSearchQuery + "' " + this.detailsAsString());
+ }
+
+ public String detailsAsString() {
+ String str = "";
+
+ // output types that were searched for
+ int iCnt = 0;
+ if (this.bSearchWorkflows) {
+ str += "workflows, ";
+ iCnt++;
+ }
+ if (this.bSearchFiles) {
+ str += "files, ";
+ iCnt++;
+ }
+ if (this.bSearchPacks) {
+ str += "packs, ";
+ iCnt++;
+ }
+ if (this.bSearchUsers) {
+ str += "users, ";
+ iCnt++;
+ }
+ if (this.bSearchGroups) {
+ str += "groups, ";
+ iCnt++;
+ }
+
+ // if that's all types, have just the word 'all'
+ if (iCnt == 5)
+ str = "all";
+ else
+ str = str.substring(0, str.length() - 2); // remove trailing ", "
+
+ // add the rest to the string representation of the search instance
+ str = "[" + str + "; limit: " + this.iResultCountLimit + "]";
+
+ return (str);
+ }
+
+ public String getSearchQuery() {
+ return (this.strSearchQuery);
+ }
+
+ public int getResultCountLimit() {
+ return (this.iResultCountLimit);
+ }
+
+ public boolean getSearchWorkflows() {
+ return (this.bSearchWorkflows);
+ }
+
+ public boolean getSearchFiles() {
+ return (this.bSearchFiles);
+ }
+
+ public boolean getSearchPacks() {
+ return (this.bSearchPacks);
+ }
+
+ public boolean getSearchUsers() {
+ return (this.bSearchUsers);
+ }
+
+ public boolean getSearchGroups() {
+ return (this.bSearchGroups);
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/ServerResponse.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/ServerResponse.java
new file mode 100644
index 0000000..d3d5d94
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/ServerResponse.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import org.jdom.Document;
+
+public class ServerResponse {
+ // CONSTANTS
+ public static int LOCAL_FAILURE = -1;
+
+ // STORAGE
+ private int iResponseCode;
+ private Document docResponseBody;
+
+ public ServerResponse() {
+ // do nothing - empty constructor
+ }
+
+ public ServerResponse(int responseCode, Document responseBody) {
+ super();
+
+ this.iResponseCode = responseCode;
+ this.docResponseBody = responseBody;
+ }
+
+ public int getResponseCode() {
+ return (this.iResponseCode);
+ }
+
+ public void setResponseCode(int responseCode) {
+ this.iResponseCode = responseCode;
+ }
+
+ public Document getResponseBody() {
+ return (this.docResponseBody);
+ }
+
+ public void setResponseBody(Document responseBody) {
+ this.docResponseBody = responseBody;
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Tag.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Tag.java
new file mode 100644
index 0000000..070dda1
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Tag.java
@@ -0,0 +1,125 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * @author Jiten Bhagat, Sergejs Aleksejevs
+ */
+public class Tag extends Resource implements Serializable {
+ private String tagName;
+ private int count;
+
+ public Tag() {
+ super();
+ this.setItemType(Resource.TAG);
+ }
+
+ public String getTagName() {
+ return tagName;
+ }
+
+ public void setTagName(String tagName) {
+ this.tagName = tagName;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public static class ReversePopularityComparator implements Comparator<Tag> {
+ public ReversePopularityComparator() {
+ super();
+ }
+
+ public int compare(Tag t1, Tag t2) {
+ if (t1.getCount() == t2.getCount()) {
+ // in case of the same popularity, compare by tag name
+ return (t1.getTagName().compareTo(t2.getTagName()));
+ } else {
+ // popularity isn't the same; arrange by popularity (more popular first)
+ return (t2.getCount() - t1.getCount());
+ }
+ }
+ }
+
+ public static class AlphanumericComparator implements Comparator<Tag> {
+ public AlphanumericComparator() {
+ super();
+ }
+
+ public int compare(Tag t1, Tag t2) {
+ return (t1.getTagName().compareTo(t2.getTagName()));
+ }
+ }
+
+ /**
+ * This makes sure that things like instanceOf() and remove() in List
+ * interface work properly - this way resources are treated to be the same if
+ * they store identical data, rather than they simply hold the same reference.
+ */
+ public boolean equals(Object other) {
+ // could only be equal to another Tag object, not anything else
+ if (!(other instanceof Tag))
+ return (false);
+
+ // 'other' object is a Tag; equality is based on the data stored
+ // in the current and 'other' Tag instances
+ Tag otherTag = (Tag) other;
+ return (this.count == otherTag.count && this.tagName.equals(otherTag.tagName));
+ }
+
+ public String toString() {
+ return ("Tag (" + this.tagName + ", " + this.count + ")");
+ }
+
+ /**
+ * A helper method to return a set of API elements that are needed to satisfy
+ * request of a particular type - e.g. creating a listing of resources or
+ * populating full preview, etc.
+ *
+ * @param iRequestType
+ * A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType) {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case
+ switch (iRequestType) {
+ case Resource.REQUEST_DEFAULT_FROM_API:
+ strElements += ""; // no change needed - defaults will be used
+ }
+
+ return (strElements);
+ }
+
+ /**
+ * Instantiates a Tag object from action command string that is used to
+ * trigger tag search events in the plugin. These action commands should look
+ * like "tag:<tag_name>".
+ *
+ * @param strActionCommand
+ * The action command to parse.
+ * @return A Tab object instance or null if action command was invalid.
+ */
+ public static Tag instantiateTagFromActionCommand(String strActionCommand) {
+ if (!strActionCommand.startsWith("tag:")) {
+ return (null);
+ } else {
+ // instantiate the Tag object, strip out the leading "tag:" and return result
+ Tag t = new Tag();
+ t.setTagName(strActionCommand.replaceFirst("tag:", ""));
+ return (t);
+ }
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/TagCloud.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/TagCloud.java
new file mode 100644
index 0000000..094d208
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/TagCloud.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Jiten Bhagat
+ */
+public class TagCloud {
+
+ private List<Tag> tags = new ArrayList<Tag>();
+
+ public List<Tag> getTags() {
+ return this.tags;
+ }
+
+ /**
+ * Removes all tags from the current tag cloud.
+ */
+ public void clear() {
+ this.tags.clear();
+ }
+
+ /**
+ * Adds all tags from the supplied collection into the tag cloud.
+ */
+ public void addAll(List<Tag> newTags) {
+ this.tags.addAll(newTags);
+ }
+
+ /**
+ * @return Count of the occurrences of the most popular tag in the cloud data.
+ */
+ public int getMaxTagCount() {
+ int iMax = 0;
+
+ for (Tag t : tags) {
+ if (t.getCount() > iMax) {
+ iMax = t.getCount();
+ }
+ }
+
+ return (iMax);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/User.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/User.java
new file mode 100644
index 0000000..719593d
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/User.java
@@ -0,0 +1,297 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.swing.ImageIcon;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Jiten Bhagat, Sergejs Aleksejevs
+ */
+public class User extends Resource {
+ private String name;
+ private String city;
+ private String country;
+ private String email;
+ private String website;
+
+ private ImageIcon avatar;
+ private String avatar_uri;
+ private String avatar_resource;
+
+ private ArrayList<HashMap<String, String>> workflows;
+ private ArrayList<HashMap<String, String>> files;
+ private ArrayList<HashMap<String, String>> packs;
+ private ArrayList<HashMap<String, String>> friends;
+ private ArrayList<HashMap<String, String>> groups;
+ private ArrayList<HashMap<String, String>> tags;
+ private ArrayList<Resource> favourites;
+
+ public User() {
+ super();
+ this.setItemType(Resource.USER);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ this.setTitle(name); // this will allow to use name/title interchangeably
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getWebsite() {
+ return website;
+ }
+
+ public void setWebsite(String website) {
+ this.website = website;
+ }
+
+ public String getAvatarURI() {
+ return avatar_uri;
+ }
+
+ public void setAvatarURI(String avatar_uri) {
+ this.avatar_uri = avatar_uri;
+ }
+
+ public ImageIcon getAvatar() {
+ return avatar;
+ }
+
+ // creates avatar from the XML of it
+ public void setAvatar(Document doc) {
+ Element root = doc.getRootElement();
+ String strAvatarData = root.getChild("data").getText();
+
+ this.avatar = new ImageIcon(Base64.decode(strAvatarData));
+ }
+
+ public void setAvatar(ImageIcon avatar) {
+ this.avatar = avatar;
+ }
+
+ public String getAvatarResource() {
+ return avatar_resource;
+ }
+
+ public void setAvatarResource(String avatar_resource) {
+ this.avatar_resource = avatar_resource;
+ }
+
+ public ArrayList<HashMap<String, String>> getWorkflows() {
+ return workflows;
+ }
+
+ public ArrayList<HashMap<String, String>> getFiles() {
+ return files;
+ }
+
+ public ArrayList<HashMap<String, String>> getPacks() {
+ return packs;
+ }
+
+ public ArrayList<HashMap<String, String>> getFriends() {
+ return friends;
+ }
+
+ public ArrayList<HashMap<String, String>> getGroups() {
+ return groups;
+ }
+
+ public ArrayList<Resource> getFavourites() {
+ return favourites;
+ }
+
+ public ArrayList<HashMap<String, String>> getTags() {
+ return this.tags;
+ }
+
+ /**
+ * A helper method to return a set of API elements that are needed to satisfy
+ * request of a particular type - e.g. creating a listing of resources or
+ * populating full preview, etc.
+ *
+ * @param iRequestType
+ * A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType) {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case;
+ //
+ // cases after first 'break' statement are separate ones and hence are treated
+ // individually
+ switch (iRequestType) {
+ case Resource.REQUEST_FULL_PREVIEW:
+ strElements += "created-at,updated-at,email,website,city,country,"
+ + "friends,groups,workflows,files,packs,favourited,tags-applied,";
+ case Resource.REQUEST_FULL_LISTING:
+ strElements += ""; // essentially the same as short listing
+ case Resource.REQUEST_SHORT_LISTING:
+ strElements += "id,name,description,avatar";
+ break;
+ case Resource.REQUEST_USER_FAVOURITES_ONLY:
+ strElements += "favourited";
+ break;
+ case Resource.REQUEST_USER_APPLIED_TAGS_ONLY:
+ strElements += "tags-applied";
+ break;
+ }
+
+ return (strElements);
+ }
+
+ public static User buildFromXML(Document doc, Logger logger) {
+ // if no XML document was supplied, return NULL
+ if (doc == null)
+ return (null);
+
+ // call main method which parses XML document starting from root element
+ return (User.buildFromXML(doc.getRootElement(), logger));
+ }
+
+ // class method to build a user instance from XML
+ @SuppressWarnings("unchecked")
+ public static User buildFromXML(Element docRootElement, Logger logger) {
+ // can't make any processing if root element is NULL
+ if (docRootElement == null)
+ return (null);
+
+ // create instance and parse the XML otherwise
+ User user = new User();
+
+ try {
+ // store all simple values
+ user.setURI(docRootElement.getAttributeValue("uri"));
+ user.setResource(docRootElement.getAttributeValue("resource"));
+ user.setID(docRootElement.getChildText("id"));
+ user.setName(docRootElement.getChildText("name"));
+ user.setTitle(user.getName()); // to allow generic handling of all resources - for users 'title' will replicate the 'name'
+ user.setDescription(docRootElement.getChild("description").getText());
+ user.setCity(docRootElement.getChildText("city"));
+ user.setCountry(docRootElement.getChildText("country"));
+ user.setEmail(docRootElement.getChildText("email"));
+ user.setWebsite(docRootElement.getChildText("website"));
+
+ // avatar URI in the API
+ Element avatarURIElement = docRootElement.getChild("avatar");
+ if (avatarURIElement != null) {
+ user.setAvatarURI(avatarURIElement.getAttributeValue("uri"));
+ }
+
+ // avatar resource on myExperiment
+ Element avatarElement = docRootElement.getChild("avatar");
+ if (avatarElement != null) {
+ user.setAvatarResource(avatarElement.getAttributeValue("resource"));
+ }
+
+ // Created at
+ String createdAt = docRootElement.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ user.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ // Updated at
+ String updatedAt = docRootElement.getChildText("updated-at");
+ if (updatedAt != null && !updatedAt.equals("")) {
+ user.setUpdatedAt(MyExperimentClient.parseDate(updatedAt));
+ }
+
+ // store workflows
+ user.workflows = new ArrayList<HashMap<String, String>>();
+ Element workflowsElement = docRootElement.getChild("workflows");
+ if (workflowsElement != null) {
+ Iterator<Element> iWorkflows = workflowsElement.getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iWorkflows, user.workflows);
+ }
+
+ // store files
+ user.files = new ArrayList<HashMap<String, String>>();
+ Element filesElement = docRootElement.getChild("files");
+ if (filesElement != null) {
+ Iterator<Element> iFiles = filesElement.getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iFiles, user.files);
+ }
+
+ // store packs
+ user.packs = new ArrayList<HashMap<String, String>>();
+ Element packsElement = docRootElement.getChild("packs");
+ if (packsElement != null) {
+ Iterator<Element> iPacks = packsElement.getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iPacks, user.packs);
+ }
+
+ // store friends
+ user.friends = new ArrayList<HashMap<String, String>>();
+ Element friendsElement = docRootElement.getChild("friends");
+ if (filesElement != null) {
+ Iterator<Element> iFriends = friendsElement.getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iFriends, user.friends);
+ }
+
+ // store groups
+ user.groups = new ArrayList<HashMap<String, String>>();
+ Element groupsElement = docRootElement.getChild("groups");
+ if (groupsElement != null) {
+ Iterator<Element> iGroups = groupsElement.getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iGroups, user.groups);
+ }
+
+ // store tags
+ user.tags = new ArrayList<HashMap<String, String>>();
+ Element tagsElement = docRootElement.getChild("tags-applied");
+ if (tagsElement != null) {
+ Iterator<Element> iTags = tagsElement.getChildren().iterator();
+ Util.getResourceCollectionFromXMLIterator(iTags, user.tags);
+ }
+
+ // store favourites
+ user.favourites = new ArrayList<Resource>();
+ user.favourites.addAll(Util.retrieveUserFavourites(docRootElement));
+
+ } catch (Exception e) {
+ logger.error("Failed midway through creating user object from XML", e);
+ }
+
+ return (user);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Util.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Util.java
new file mode 100644
index 0000000..08f16e5
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Util.java
@@ -0,0 +1,624 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.ui.perspectives.myexperiment.model;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.raven.appconfig.ApplicationRuntime;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.JClickableLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MyExperimentPerspective;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine.QuerySearchInstance;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Util {
+ private static Logger logger = Logger.getLogger(Util.class);
+
+ // ******** DATA ENCRYPTION ********
+
+ private static final String PBE_PASSWORD = System.getProperty("user.home");
+ private static final String PBE_SALT;
+
+ static {
+ String host_name = "";
+ try {
+ host_name = InetAddress.getLocalHost().toString();
+ } catch (UnknownHostException e) {
+ host_name = "unknown_localhost";
+ }
+ PBE_SALT = host_name;
+ }
+
+ /**
+ * The following section (encrypt(), decrypt() and doEncryption() methods) is
+ * used to store user passwords in an encrypted way within the settings file.
+ */
+ public static byte[] encrypt(String str) {
+ return (doEncryption(str, Cipher.ENCRYPT_MODE));
+ }
+
+ public static byte[] decrypt(String str) {
+ return (doEncryption(str, Cipher.DECRYPT_MODE));
+ }
+
+ private static byte[] doEncryption(String str, int mode) {
+ // password-based encryption uses 2 parameters for processing:
+ // a *password*, which is then hashed with a *salt* to generate
+ // a strong key - these 2 are defined as class constants
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
+ PBEKeySpec keySpec = new PBEKeySpec(Util.PBE_PASSWORD.toCharArray());
+ SecretKey key = keyFactory.generateSecret(keySpec);
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update(Util.PBE_SALT.getBytes());
+ byte[] digest = md.digest();
+ byte[] salt = new byte[8];
+ for (int i = 0; i < 8; ++i)
+ salt[i] = digest[i];
+ PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 20);
+
+ Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
+ cipher.init(mode, key, paramSpec);
+
+ byte[] encrypted = cipher.doFinal(str.getBytes());
+ return (encrypted);
+ } catch (Exception e) {
+ logger.error("Could not encrypt and store password");
+ logger.error(e.getCause() + "\n" + e.getMessage());
+ return (new byte[1]);
+ }
+
+ }
+
+ // ******** RESIZING OF IMAGES ********
+
+ /**
+ * The method will scale down the <b>sourceImageIcon</b> in a way that it will
+ * fit into the rectangle with dimensions <b>(iRequiredWidth,
+ * iRequiredHeight)</b> with the original aspect ratio being preserved.
+ *
+ * @param iRequiredWidth
+ * Maximum desired width of the resized image.
+ * @param iRequiredHeight
+ * Maximum desired height of the resized image.
+ */
+ public static ImageIcon getResizedImageIcon(ImageIcon sourceImageIcon, int iRequiredWidth, int iRequiredHeight) {
+ // *** calculate the desired width and height of the resized image ***
+ int iWidth = sourceImageIcon.getIconWidth();
+ int iHeight = sourceImageIcon.getIconHeight();
+
+ float fWidthResizeRatio = iWidth / (float) iRequiredWidth;
+ float fHeightResizeRatio = iHeight / (float) iRequiredHeight;
+
+ // the chosen resize ratio will be the greatest of the two -
+ // this way we will preserve aspect ratio of the sides of the
+ // original image
+ float fResizeRatio = Math.max(fWidthResizeRatio, fHeightResizeRatio);
+
+ // obtain the width and height of the new image
+ int iNewWidth = Math.round(iWidth / fResizeRatio);
+ int iNewHeight = Math.round(iHeight / fResizeRatio);
+
+ // *** make the actual resizing work ***
+ BufferedImage resizedImage = new BufferedImage(iNewWidth, iNewHeight, BufferedImage.TYPE_INT_RGB);
+ Graphics2D g2 = resizedImage.createGraphics();
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g2.drawImage(sourceImageIcon.getImage(), 0, 0, iNewWidth, iNewHeight, null);
+ g2.dispose();
+ return (new ImageIcon(resizedImage, ""));
+ }
+
+ /**
+ * Loads an image from a remote URL, scales it down to the required size and
+ * stores in a temporary file for the duration of the current session (e.g.
+ * the temporary file will be destroyed on the application termination).
+ *
+ * @param sourceImageURL
+ * URL to fetch the source image from.
+ * @param iRequiredWidth
+ * Maximum desired width of the resized image.
+ * @param iRequiredHeight
+ * Maximum desired height of the resized image.
+ *
+ * @return Local file URL which can be directly used to fetch the resized
+ * image.
+ */
+ public static String getResizedImageIconTempFileURL(URL sourceImageURL, int iRequiredWidth, int iRequiredHeight) {
+ java.io.File fDestinationTempFile = null;
+
+ try {
+ // all resized images will be store in temporary files - these will be deleted on program termination
+ fDestinationTempFile = java.io.File.createTempFile("resized_image", "jpg");
+ fDestinationTempFile.deleteOnExit();
+
+ // resize the image icon
+ ImageIcon sourceImageIcon = new ImageIcon(sourceImageURL);
+ ImageIcon resizedImageIcon = Util.getResizedImageIcon(sourceImageIcon, iRequiredWidth, iRequiredHeight);
+
+ Image img = resizedImageIcon.getImage();
+ BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
+ Graphics2D g2 = bi.createGraphics();
+ // Draw img into bi so we can write it to file.
+ g2.drawImage(img, 0, 0, null);
+ g2.dispose();
+ // Now bi contains the img.
+ // Note: img may have transparent pixels in it; if so, okay.
+ // If not and you can use TYPE_INT_RGB you will get better
+ // performance with it in the jvm.
+ ImageIO.write(bi, "jpg", fDestinationTempFile);
+ } catch (Exception e) {
+
+ }
+
+ return ("file:///" + fDestinationTempFile.getAbsolutePath());
+ }
+
+ // ******** DATA RETRIEVAL FROM XML DOCUMENT FRAGMENTS ********
+
+ /**
+ * Instantiates primitive Resource object from XML element. This is very
+ * lightweight and completely generic - it only sets resource's type, title
+ * and URL on myExperiment / URI in the API.
+ */
+ public static Resource instantiatePrimitiveResourceFromElement(Element e) {
+ if (e != null) {
+ Resource r = new Resource();
+ r.setItemType(e.getName());
+ r.setTitle(e.getText());
+ r.setURI(e.getAttributeValue("uri"));
+ r.setResource(e.getAttributeValue("resource"));
+ return (r);
+ } else {
+ return (null);
+ }
+ }
+
+ /**
+ * Instantiates primitive User object from XML element. This is much more
+ * lightweight than User.buildFromXML() where full details of the user can be
+ * obtained from XML.
+ */
+ public static User instantiatePrimitiveUserFromElement(Element e) {
+ if (e != null) {
+ User u = new User();
+ u.setName(e.getText());
+ u.setTitle(e.getText());
+ u.setURI(e.getAttributeValue("uri"));
+ u.setResource(e.getAttributeValue("resource"));
+ return (u);
+ } else {
+ return (null);
+ }
+ }
+
+ /**
+ * Instantiates primitive Tag object from XML element. Very lightweight
+ * method.
+ */
+ public static Tag instantiatePrimitiveTagFromElement(Element e) {
+ if (e != null) {
+ Tag t = new Tag();
+ t.setTitle(e.getText());
+ t.setTagName(e.getText());
+ t.setResource(e.getAttributeValue("resource"));
+ t.setURI(e.getAttributeValue("uri"));
+ return (t);
+ } else {
+ return (null);
+ }
+ }
+
+ /**
+ * Instantiates primitive Comment object from XML element. Very lightweight
+ * method - faster and requires less data than Comment.buildFromXML().
+ *
+ * Can only be used if known which resource is being commented.
+ */
+ public static Comment instantiatePrimitiveCommentFromElement(Element e, Resource r) {
+ if (e != null) {
+ Comment c = new Comment();
+ c.setTitle(e.getText());
+ c.setComment(e.getText());
+ c.setTypeOfCommentedResource(r.getItemType());
+ c.setURIOfCommentedResource(r.getURI());
+ c.setResource(e.getAttributeValue("resource"));
+ c.setURI(e.getAttributeValue("uri"));
+ return (c);
+ } else {
+ return (null);
+ }
+ }
+
+ /**
+ * Generic method that accepts the iterator over a collection of resources in
+ * XML format (obtained from myExperiment API) and an ArrayList to store the
+ * processed results in.
+ *
+ * The method will extract 3 attributes for every item in the collection: 1)
+ * name of the item; 2) URI of the item (to access this item via the API); 3)
+ * URI of the item (to access via WEB);
+ *
+ * @return Returns the number of processed elements in the collection.
+ */
+ public static int getResourceCollectionFromXMLIterator(Iterator<Element> iterator, ArrayList<HashMap<String, String>> collection) {
+ int iCount = 0;
+
+ while (iterator.hasNext()) {
+ // get XML element for the next element in the collection
+ Element element = iterator.next();
+
+ // store all details of current group into a hash map
+ HashMap<String, String> itemDetails = new HashMap<String, String>();
+ itemDetails.put("name", element.getText());
+ itemDetails.put("uri", element.getAttributeValue("uri"));
+ itemDetails.put("resource", element.getAttributeValue("resource"));
+
+ // add current item to the complete list of items
+ collection.add(itemDetails);
+ iCount++;
+ }
+
+ return (iCount);
+ }
+
+ /**
+ * Takes XML Element instance with privilege listing for an item and returns
+ * an integer value for that access type.
+ */
+ public static int getAccessTypeFromXMLElement(Element privileges) {
+ // if the item for which the privileges are processed got received,
+ // there definitely is viewing access to it
+ int iAccessType = Resource.ACCESS_VIEWING;
+
+ // pick the highest allowed access type
+ Iterator<Element> iPrivileges = privileges.getChildren("privilege").iterator();
+ while (iPrivileges.hasNext()) {
+ Element privilege = iPrivileges.next();
+ String strValue = privilege.getAttributeValue("type");
+
+ int iCurrentPrivilege = Resource.ACCESS_VIEWING;
+ if (strValue.equals("download"))
+ iCurrentPrivilege = Resource.ACCESS_DOWNLOADING;
+ else if (strValue.equals("edit"))
+ iCurrentPrivilege = Resource.ACCESS_EDITING;
+
+ if (iCurrentPrivilege > iAccessType)
+ iAccessType = iCurrentPrivilege;
+ }
+
+ return (iAccessType);
+ }
+
+ /**
+ * Returns contents of the "reason" field of the error message.
+ */
+ public static String retrieveReasonFromErrorXMLDocument(Document doc) {
+ if (doc != null) {
+ Element root = doc.getRootElement();
+ return (root.getChildText("reason"));
+ } else {
+ return ("unknown reason");
+ }
+ }
+
+ /**
+ * Parses an XML document containing resources favourited by a User. These
+ * resources are instantiated with the basic data available in the XML
+ * document and collected into a single List object.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Resource> retrieveUserFavourites(Element docRootElement) {
+ List<Resource> favouritedItemList = new ArrayList<Resource>();
+
+ Element favouritesElement = docRootElement.getChild("favourited");
+ if (favouritesElement != null) {
+ Iterator<Element> iFavourites = favouritesElement.getChildren().iterator();
+ while (iFavourites.hasNext()) {
+ favouritedItemList.add(Util.instantiatePrimitiveResourceFromElement(iFavourites.next()));
+ }
+ }
+
+ return (favouritedItemList);
+ }
+
+ /**
+ * Returns a list of credits - can be applied to any XML document which
+ * contains "credits" element.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Resource> retrieveCredits(Element docRootElement) {
+ List<Resource> credits = new ArrayList<Resource>();
+
+ Element creditsElement = docRootElement.getChild("credits");
+ if (creditsElement != null) {
+ List<Element> creditsNodes = creditsElement.getChildren();
+ for (Element e : creditsNodes) {
+ credits.add(Util.instantiatePrimitiveResourceFromElement(e));
+ }
+ }
+
+ return (credits);
+ }
+
+ /**
+ * Returns a list of attributions - can be applied to any XML document which
+ * contains "attributions" element.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Resource> retrieveAttributions(Element docRootElement) {
+ List<Resource> attributions = new ArrayList<Resource>();
+
+ Element attributionsElement = docRootElement.getChild("attributions");
+ if (attributionsElement != null) {
+ List<Element> attributionsNodes = attributionsElement.getChildren();
+ for (Element e : attributionsNodes) {
+ attributions.add(Util.instantiatePrimitiveResourceFromElement(e));
+ }
+ }
+
+ return (attributions);
+ }
+
+ /**
+ * Returns a list of tags - can be applied to any XML document which contains
+ * "tags" element.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Tag> retrieveTags(Element docRootElement) {
+ List<Tag> tags = new ArrayList<Tag>();
+
+ Element tagsElement = docRootElement.getChild("tags");
+ if (tagsElement != null) {
+ List<Element> tagsNodes = tagsElement.getChildren();
+ for (Element e : tagsNodes) {
+ tags.add(Util.instantiatePrimitiveTagFromElement(e));
+ }
+ }
+
+ return (tags);
+ }
+
+ /**
+ * Returns a list of comments - can be applied to any XML document which
+ * contains "comments" element. This should be called when the Resource object
+ * (for which the list of comments is being obtained) is known.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Comment> retrieveComments(Element docRootElement, Resource r) {
+ List<Comment> comments = new ArrayList<Comment>();
+
+ Element tagsElement = docRootElement.getChild("comments");
+ if (tagsElement != null) {
+ List<Element> tagsNodes = tagsElement.getChildren();
+ for (Element e : tagsNodes) {
+ comments.add(Util.instantiatePrimitiveCommentFromElement(e, r));
+ }
+ }
+
+ return (comments);
+ }
+
+ // ******** STRIPPING OUT HTML FROM STRINGS ********
+
+ /**
+ * Tiny helper to strip out HTML tags. Basic HTML tags like and <br>
+ * are left in place, because these can be rendered by JLabel. This helps to
+ * present HTML content inside JAVA easier.
+ */
+ public static String stripHTML(String source) {
+ // don't do anything if not string is provided
+ if (source == null)
+ return ("");
+
+ // need to preserve at least all line breaks
+ // (ending and starting paragraph also make a line break)
+ source = source.replaceAll("</p>[\r\n]*<p>", "<br>");
+ source = source.replaceAll("\\<br/?\\>", "[-=BR=-]");
+
+ // strip all HTML
+ source = source.replaceAll("\\<.*?\\>", "");
+
+ // put the line breaks back
+ source = source.replaceAll("\\[-=BR=-\\]", "<br><br>");
+
+ return (source);
+ }
+
+ /**
+ * Tiny helper to strip out all HTML tags. This will not leave any HTML tags
+ * at all (so that the content can be displayed in DialogTextArea - and the
+ * like - components. This helps to present HTML content inside JAVA easier.
+ */
+ public static String stripAllHTML(String source) {
+ // don't do anything if not string is provided
+ if (source == null)
+ return ("");
+
+ // need to preserve at least all line breaks
+ // (ending and starting paragraph also make a line break)
+ source = source.replaceAll("</p>[\r\n]*<p>", "<br>");
+ source = source.replaceAll("\\<br/?\\>", "\n\n");
+
+ // strip all HTML
+ source = source.replaceAll("\\<.*?\\>", ""); // any HTML tags
+ source = source.replaceAll("&\\w{1,4};", ""); // this is for things like " ", ">", etc
+
+ return (source);
+ }
+
+ // ******** WINDOW OPERATIONS ********
+
+ /**
+ * Makes sure that one component (for example, a window) is centered
+ * horizontally and vertically relatively to the other component.
+ *
+ * This method is applicable when 'dependentComponent' fully fits within the
+ * 'mainComponent'. Otherwise behaviour is unpredictable.
+ */
+ public static void centerComponentWithinAnother(Component mainComponent, Component dependentComponent) {
+ int iMainComponentCenterX = (int) Math.round(mainComponent.getLocationOnScreen().getX()
+ + (mainComponent.getWidth() / 2));
+ int iPosX = iMainComponentCenterX - (dependentComponent.getWidth() / 2);
+ int iMainComponentCenterY = (int) Math.round(mainComponent.getLocationOnScreen().getY()
+ + (mainComponent.getHeight() / 2));
+ int iPosY = iMainComponentCenterY - (dependentComponent.getHeight() / 2);
+
+ dependentComponent.setLocation(iPosX, iPosY);
+ }
+
+ // ******** VARIOUS HELPERS ********
+
+ /**
+ * Generates a JClickableLabel instance for a supplied Resource object with a
+ * specified click handler.
+ */
+ public static JClickableLabel generateClickableLabelFor(Resource r, ActionListener clickHandler) {
+ return (new JClickableLabel(r.getTitle(), "preview:" + r.getItemType()
+ + ":" + r.getURI(), clickHandler, new ImageIcon(MyExperimentPerspective.getLocalIconURL(r.getItemType())), SwingConstants.LEFT, r.getItemTypeName()
+ + ": " + r.getTitle()));
+ }
+
+ /**
+ * Generates a JLabel with a "none-text" style.
+ */
+ public static JLabel generateNoneTextLabel(String strLabel) {
+ JLabel lNoneText = new JLabel(strLabel);
+ lNoneText.setFont(lNoneText.getFont().deriveFont(Font.ITALIC));
+ lNoneText.setForeground(Color.GRAY);
+ lNoneText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ return (lNoneText);
+ }
+
+ /**
+ * A helper method to assemble part of the URL query for myExperiment API.
+ * This will produce a string in the form of
+ * "&elements=<element1>[,<element2>,...]
+ *
+ * @param queryInstance
+ * Instance of {@link QuerySearchInstance} that contains details on
+ * the current search by query or null value if search by tag is
+ * being made.
+ */
+ public static String composeAPIQueryElements(QuerySearchInstance queryInstance) {
+ // this will create one large comma-separated string of elements that
+ // are required for generating a FULL LISTING (which is used in search
+ // result presentation) for all various types
+ String strElements = "";
+ if (queryInstance == null || queryInstance.getSearchWorkflows()) {
+ strElements += Workflow.getRequiredAPIElements(Resource.REQUEST_FULL_LISTING)
+ + ",";
+ }
+ if (queryInstance == null || queryInstance.getSearchFiles()) {
+ strElements += File.getRequiredAPIElements(Resource.REQUEST_FULL_LISTING)
+ + ",";
+ }
+ if (queryInstance == null || queryInstance.getSearchPacks()) {
+ strElements += Pack.getRequiredAPIElements(Resource.REQUEST_FULL_LISTING)
+ + ",";
+ }
+ if (queryInstance == null || queryInstance.getSearchUsers()) {
+ strElements += User.getRequiredAPIElements(Resource.REQUEST_FULL_LISTING)
+ + ",";
+ }
+ if (queryInstance == null || queryInstance.getSearchGroups()) {
+ strElements += Group.getRequiredAPIElements(Resource.REQUEST_FULL_LISTING)
+ + ",";
+ }
+
+ // now need to make this list of elements to contain only unique tokens
+ TreeSet<String> elementSet = new TreeSet<String>(Arrays.asList(strElements.split(",")));
+ Iterator<String> elementSetIterator = elementSet.iterator();
+ strElements = "&elements=";
+ while (elementSetIterator.hasNext()) {
+ strElements += elementSetIterator.next();
+ if (elementSetIterator.hasNext())
+ strElements += ",";
+ }
+
+ return (strElements);
+ }
+
+ /**
+ * The parameter is the class name to be processed; class name is likely to be
+ * in the form <class_name>$<integer_value>, where the trailing part starting
+ * with the $ sign indicates the anonymous inner class within the base class.
+ * This will strip out that part of the class name to get the base class name.
+ */
+ public static String getBaseClassName(String strClassName) {
+ // strip out the class name part after the $ sign; return
+ // the original value if the dollar sign wasn't found
+ String strResult = strClassName;
+
+ int iDollarIdx = strResult.indexOf("$");
+ if (iDollarIdx != -1)
+ strResult = strResult.substring(0, iDollarIdx);
+
+ return (strResult);
+ }
+
+ /**
+ * Determines whether the plugin is running as a standalone JFrame or inside
+ * Taverna Workbench.
+ */
+ public static boolean isRunningInTaverna() {
+ try {
+ // ApplicationRuntime class is defined within Taverna API. If this is available,
+ // it should mean that the plugin runs within Taverna.
+ ApplicationRuntime.getInstance();
+ return true;
+ } catch (NoClassDefFoundError e) {
+ return false;
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Workflow.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Workflow.java
new file mode 100644
index 0000000..0b0e3d3
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Workflow.java
@@ -0,0 +1,420 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
+
+import java.net.URI;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+
+/**
+ * @author Jiten Bhagat, Sergejs Aleksejevs
+ */
+public class Workflow extends Resource {
+ // CONSTANTS
+ public static final String MIME_TYPE_TAVERNA_1 = "application/vnd.taverna.scufl+xml";
+ public static final String MIME_TYPE_TAVERNA_2 = "application/vnd.taverna.t2flow+xml";
+
+ private int accessType;
+
+ private int version;
+ private User uploader;
+ private URI preview;
+ private URI thumbnail;
+ private URI thumbnailBig;
+ private URI svg;
+ private License license;
+
+ private String visibleType;
+ private String contentType;
+ private URI contentUri;
+ byte[] content;
+
+ private List<Tag> tags;
+ private List<Comment> comments;
+ private List<Resource> credits;
+ private List<Resource> attributions;
+ private HashMap<String, ArrayList<HashMap<String, String>>> components;
+
+ public Workflow() {
+ super();
+ this.setItemType(Resource.WORKFLOW);
+ }
+
+ public int getAccessType() {
+ return this.accessType;
+ }
+
+ public void setAccessType(int accessType) {
+ this.accessType = accessType;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public User getUploader() {
+ return uploader;
+ }
+
+ public void setUploader(User uploader) {
+ this.uploader = uploader;
+ }
+
+ public URI getPreview() {
+ return preview;
+ }
+
+ public void setPreview(URI preview) {
+ this.preview = preview;
+ }
+
+ public URI getThumbnail() {
+ return thumbnail;
+ }
+
+ public void setThumbnail(URI thumbnail) {
+ this.thumbnail = thumbnail;
+ }
+
+ public URI getSvg() {
+ return svg;
+ }
+
+ public void setSvg(URI svg) {
+ this.svg = svg;
+ }
+
+ public License getLicense() {
+ return license;
+ }
+
+ public void setLicense(License license) {
+ this.license = license;
+ }
+
+ public URI getContentUri() {
+ return contentUri;
+ }
+
+ public void setContentUri(URI contentUri) {
+ this.contentUri = contentUri;
+ }
+
+ public String getVisibleType() {
+ return this.visibleType;
+ }
+
+ public void setVisibleType(String visibleType) {
+ this.visibleType = visibleType;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public byte[] getContent() {
+ return this.content;
+ }
+
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
+
+ public List<Tag> getTags() {
+ return tags;
+ }
+
+ public List<Comment> getComments() {
+ return comments;
+ }
+
+ public List<Resource> getCredits() {
+ return credits;
+ }
+
+ public List<Resource> getAttributions() {
+ return this.attributions;
+ }
+
+ public HashMap<String, ArrayList<HashMap<String, String>>> getComponents() {
+ return this.components;
+ }
+
+ public URI getThumbnailBig() {
+ return thumbnailBig;
+ }
+
+ public void setThumbnailBig(URI thumbnailBig) {
+ this.thumbnailBig = thumbnailBig;
+ }
+
+ /**
+ * Determines whether the current instance of the workflow is a Taverna 1 or
+ * Taverna 2 workflow
+ */
+ public boolean isTavernaWorkflow() {
+ return (contentType.equals(Workflow.MIME_TYPE_TAVERNA_1) || contentType.equals(Workflow.MIME_TYPE_TAVERNA_2));
+ }
+
+ public boolean isTaverna1Workflow() {
+ return (contentType.equals(Workflow.MIME_TYPE_TAVERNA_1));
+ }
+
+ public boolean isTaverna2Workflow() {
+ return (contentType.equals(Workflow.MIME_TYPE_TAVERNA_2));
+ }
+
+ /**
+ * A helper method to return a set of API elements that are needed to satisfy
+ * request of a particular type - e.g. creating a listing of resources or
+ * populating full preview, etc.
+ *
+ * @param iRequestType
+ * A constant value from Resource class.
+ * @return Comma-separated string containing values of required API elements.
+ */
+ public static String getRequiredAPIElements(int iRequestType) {
+ String strElements = "";
+
+ // cases higher up in the list are supersets of those that come below -
+ // hence no "break" statements are required, because 'falling through' the
+ // switch statement is the desired behaviour in this case
+ //
+ // cases that follow after the first 'break' statement are to be treated
+ // separately - these require individual processing and have nothing to do
+ // with joining different elements for various listings / previews
+ switch (iRequestType) {
+ case Resource.REQUEST_FULL_PREVIEW:
+ strElements += "created-at,updated-at,preview,thumbnail-big,svg,license-type,content-uri,"
+ + "tags,comments,ratings,credits,attributions,components,";
+ case Resource.REQUEST_FULL_LISTING:
+ strElements += "uploader,type,";
+ case Resource.REQUEST_SHORT_LISTING:
+ strElements += "id,title,thumbnail,description,privileges,content-type";
+ break;
+ case Resource.REQUEST_WORKFLOW_CONTENT_ONLY:
+ strElements += "type,content-type,content";
+ break;
+ }
+
+ return (strElements);
+ }
+
+ public static Workflow buildFromXML(Document doc, Logger logger) {
+ // if no XML document was supplied, return NULL
+ if (doc == null)
+ return (null);
+
+ // call main method which parses XML document starting from root element
+ return (Workflow.buildFromXML(doc.getRootElement(), logger));
+ }
+
+ // class method to build a workflow instance from XML
+ @SuppressWarnings("unchecked")
+ public static Workflow buildFromXML(Element docRootElement, Logger logger) {
+ // return null to indicate an error if XML document contains no root element
+ if (docRootElement == null)
+ return (null);
+
+ Workflow w = new Workflow();
+
+ try {
+ // Access type
+ w.setAccessType(Util.getAccessTypeFromXMLElement(docRootElement.getChild("privileges")));
+
+ // URI
+ w.setURI(docRootElement.getAttributeValue("uri"));
+
+ // Resource URI
+ w.setResource(docRootElement.getAttributeValue("resource"));
+
+ // Version
+ String version = docRootElement.getAttributeValue("version");
+ if (version != null && !version.equals("")) {
+ w.setVersion(Integer.parseInt(version));
+ }
+
+ // Id
+ String id = docRootElement.getChildText("id");
+ if (id == null || id.equals("")) {
+ id = "API Error - No workflow ID supplied";
+ logger.error("Error while parsing workflow XML data - no ID provided for workflow with title: \""
+ + docRootElement.getChildText("title") + "\"");
+ }
+ w.setID(id);
+
+ // Title
+ w.setTitle(docRootElement.getChildText("title"));
+
+ // Description
+ w.setDescription(docRootElement.getChildText("description"));
+
+ // Uploader
+ Element uploaderElement = docRootElement.getChild("uploader");
+ w.setUploader(Util.instantiatePrimitiveUserFromElement(uploaderElement));
+
+ // Created at
+ String createdAt = docRootElement.getChildText("created-at");
+ if (createdAt != null && !createdAt.equals("")) {
+ w.setCreatedAt(MyExperimentClient.parseDate(createdAt));
+ }
+
+ // Updated at
+ String updatedAt = docRootElement.getChildText("updated-at");
+ if (updatedAt != null && !updatedAt.equals("")) {
+ w.setUpdatedAt(MyExperimentClient.parseDate(updatedAt));
+ }
+
+ // Preview
+ String preview = docRootElement.getChildText("preview");
+ if (preview != null && !preview.equals("")) {
+ w.setPreview(new URI(preview));
+ }
+
+ // Thumbnail
+ String thumbnail = docRootElement.getChildText("thumbnail");
+ if (thumbnail != null && !thumbnail.equals("")) {
+ w.setThumbnail(new URI(thumbnail));
+ }
+
+ // Thumbnail (big)
+ String thumbnailBig = docRootElement.getChildText("thumbnail-big");
+ if (thumbnailBig != null && !thumbnailBig.equals("")) {
+ w.setThumbnailBig(new URI(thumbnailBig));
+ }
+
+ // SVG
+ String svg = docRootElement.getChildText("svg");
+ if (svg != null && !svg.equals("")) {
+ w.setSvg(new URI(svg));
+ }
+
+ // License
+ w.setLicense(License.getInstance(docRootElement.getChildText("license-type")));
+
+ // Content URI
+ String contentUri = docRootElement.getChildText("content-uri");
+ if (contentUri != null && !contentUri.equals("")) {
+ w.setContentUri(new URI(contentUri));
+ }
+
+ // Type and Content-Type
+ w.setVisibleType(docRootElement.getChildText("type"));
+ w.setContentType(docRootElement.getChildText("content-type"));
+
+ // Tags
+ w.tags = new ArrayList<Tag>();
+ w.tags.addAll(Util.retrieveTags(docRootElement));
+
+ // Comments
+ w.comments = new ArrayList<Comment>();
+ w.getComments().addAll(Util.retrieveComments(docRootElement, w));
+
+ // Credits
+ w.credits = new ArrayList<Resource>();
+ w.getCredits().addAll(Util.retrieveCredits(docRootElement));
+
+ // Attributions
+ w.attributions = new ArrayList<Resource>();
+ w.getAttributions().addAll(Util.retrieveAttributions(docRootElement));
+
+ // Components
+ w.components = new HashMap<String, ArrayList<HashMap<String, String>>>();
+
+ Element componentsElement = docRootElement.getChild("components");
+ if (componentsElement != null) {
+ // ** inputs **
+ Element sourcesElement = componentsElement.getChild("sources");
+ if (sourcesElement != null) {
+ ArrayList<HashMap<String, String>> inputs = new ArrayList<HashMap<String, String>>();
+ List<Element> sourcesNodes = sourcesElement.getChildren();
+ for (Element e : sourcesNodes) {
+ HashMap<String, String> curInput = new HashMap<String, String>();
+ curInput.put("name", e.getChildText("name"));
+ curInput.put("description", e.getChildText("description"));
+ inputs.add(curInput);
+ }
+
+ // put all inputs that were found into the overall component collection
+ w.getComponents().put("inputs", inputs);
+ }
+
+ // ** outputs **
+ Element outputsElement = componentsElement.getChild("sinks");
+ if (outputsElement != null) {
+ ArrayList<HashMap<String, String>> sinks = new ArrayList<HashMap<String, String>>();
+ List<Element> outputsNodes = outputsElement.getChildren();
+ for (Element e : outputsNodes) {
+ HashMap<String, String> curOutput = new HashMap<String, String>();
+ curOutput.put("name", e.getChildText("name"));
+ curOutput.put("description", e.getChildText("description"));
+ sinks.add(curOutput);
+ }
+
+ // put all outputs that were found into the overall component collection
+ w.getComponents().put("outputs", sinks);
+ }
+
+ // ** processors **
+ Element processorsElement = componentsElement.getChild("processors");
+ if (processorsElement != null) {
+ ArrayList<HashMap<String, String>> processors = new ArrayList<HashMap<String, String>>();
+ List<Element> processorsNodes = processorsElement.getChildren();
+ for (Element e : processorsNodes) {
+ HashMap<String, String> curProcessor = new HashMap<String, String>();
+ curProcessor.put("name", e.getChildText("name"));
+ curProcessor.put("type", e.getChildText("type"));
+ curProcessor.put("description", e.getChildText("description"));
+ processors.add(curProcessor);
+ }
+
+ // put all processors that were found into the overall component collection
+ w.getComponents().put("processors", processors);
+ }
+
+ // ** links **
+ Element linksElement = componentsElement.getChild("links");
+ if (linksElement != null) {
+ ArrayList<HashMap<String, String>> links = new ArrayList<HashMap<String, String>>();
+ List<Element> linksNodes = linksElement.getChildren();
+ for (Element e : linksNodes) {
+ HashMap<String, String> curLink = new HashMap<String, String>();
+ String strSource = e.getChild("source").getChildText("node")
+ + (e.getChild("source").getChildText("port") == null ? "" : (":" + e.getChild("source").getChildText("port")));
+ curLink.put("source", strSource);
+ String strSink = e.getChild("sink").getChildText("node")
+ + (e.getChild("sink").getChildText("port") == null ? "" : (":" + e.getChild("sink").getChildText("port")));
+ curLink.put("sink", strSink);
+ links.add(curLink);
+ }
+
+ // put all links that were found into the overall component collection
+ w.getComponents().put("links", links);
+ }
+ }
+
+ logger.debug("Found information for worklow with ID: " + w.getID()
+ + ", Title: " + w.getTitle());
+ } catch (Exception e) {
+ logger.error("Failed midway through creating workflow object from XML", e);
+ }
+
+ // return created workflow instance
+ return (w);
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfiguration.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfiguration.java
new file mode 100644
index 0000000..6ae333b
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfiguration.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.myexperiment.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+//import org.apache.log4j.Logger;
+import uk.org.taverna.configuration.Configurable;
+
+/**
+ * @author Emmanuel Tagarira, Alan Williams
+ */
+public class MyExperimentConfiguration extends AbstractConfigurable {
+ private static class Singleton {
+ private static MyExperimentConfiguration instance = new MyExperimentConfiguration();
+ }
+
+ //private static Logger logger = Logger.getLogger(MyExperimentConfiguration.class);
+
+ private Map<String, String> defaultPropertyMap;
+
+ public String getCategory() {
+ return "general";
+ }
+
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<String, String>();
+ }
+ return defaultPropertyMap;
+ }
+
+ public String getDisplayName() {
+ return "myExperiment";
+ }
+
+ public String getFilePrefix() {
+ return "myExperiment";
+ }
+
+ public String getUUID() {
+ return "d25867g1-6078-22ee-bf27-1911311d0b77";
+ }
+
+ public static Configurable getInstance() {
+ return Singleton.instance;
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfigurationPanel.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfigurationPanel.java
new file mode 100644
index 0000000..656dfc1
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfigurationPanel.java
@@ -0,0 +1,305 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.myexperiment.config;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+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 javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.workbench.helper.Helper;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+// import org.apache.log4j.Logger;
+
+/**
+ * @author Emmanuel Tagarira, Sergejs Aleksejevs, Alan Williams
+ */
+@SuppressWarnings("serial")
+public class MyExperimentConfigurationPanel extends JPanel implements ActionListener {
+ // CONSTANTS
+
+ // components for accessing application's main elements
+ private final MainComponent pluginMainComponent = MainComponent.MAIN_COMPONENT;
+ private final MyExperimentClient myExperimentClient = MainComponent.MY_EXPERIMENT_CLIENT;
+ //private final Logger logger = MainComponent.LOGGER;
+
+ // COMPONENTS
+ private JTextField tfMyExperimentURL;
+ private JComboBox cbDefaultLoggedInTab;
+ private JComboBox cbDefaultNotLoggedInTab;
+ private JCheckBox cbMyStuffWorkflows;
+ private JCheckBox cbMyStuffFiles;
+ private JCheckBox cbMyStuffPacks;
+ private JButton bApply;
+ private JButton bReset;
+ private JButton bHelp;
+
+ // DATA STORAGE
+ private final Component[] pluginTabComponents;
+ private final ArrayList<String> alPluginTabComponentNames;
+
+ public MyExperimentConfigurationPanel() {
+ super();
+
+ // prepare plugin tab names to display in the UI afterwards
+ this.alPluginTabComponentNames = new ArrayList<String>();
+ this.pluginTabComponents = pluginMainComponent.getMainTabs().getComponents();
+ for (int i = 0; i < this.pluginTabComponents.length; i++)
+ alPluginTabComponentNames.add(pluginMainComponent.getMainTabs().getTitleAt(i));
+
+ this.initialiseUI();
+ this.initialiseData();
+ }
+
+ private void initialiseUI() {
+ // this constraints instance will be shared among all components in the window
+ GridBagConstraints c = new GridBagConstraints();
+
+ // create the myExperiment API address box
+ JPanel jpApiLocation = new JPanel();
+ jpApiLocation.setLayout(new GridBagLayout());
+
+ Insets insLabel = new Insets(0, 0, 0, 10);
+ Insets insParam = new Insets(0, 3, 5, 3);
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea("Configure the myExperiment integration functionality");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 1.0;
+ c.weighty = 0.0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ jpApiLocation.add(descriptionText, c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.weightx = 1.0;
+ //c.insets = insLabel;
+ c.anchor = GridBagConstraints.WEST;
+ c.insets = new Insets(10, 0, 0, 10);
+ jpApiLocation.add(new JLabel("Base URL of myExperiment instance to connect to"), c);
+
+ c.gridy = 2;
+ //c.insets = insParam;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.insets = new Insets(0, 0, 0, 0);
+ this.tfMyExperimentURL = new JTextField();
+ this.tfMyExperimentURL.setToolTipText("<html>Here you can specify the base URL of the myExperiment "
+ + "instance that you wish to connect to.<br>This allows the plugin to connect not only to the "
+ + "<b>main myExperiment website</b> (default value:<br><b>http://www.myexperiment.org</b>) but "
+ + "also to any other myExperiment instance that might<br>exist elsewhere.<br><br>It is recommended "
+ + "that you only change this setting if you are certain in your actions.</html>");
+ jpApiLocation.add(this.tfMyExperimentURL, c);
+
+ // create startup tab choice box
+ JPanel jpStartupTabChoice = new JPanel();
+ jpStartupTabChoice.setLayout(new GridBagLayout());
+
+ c.gridy = 0;
+ c.insets = insLabel;
+ jpStartupTabChoice.add(new JLabel("Default startup tab for anonymous user"), c);
+
+ c.gridx = 1;
+ c.insets = insParam;
+ this.cbDefaultNotLoggedInTab = new JComboBox(this.alPluginTabComponentNames.toArray());
+ this.cbDefaultNotLoggedInTab.setToolTipText("<html>This tab will be automatically opened at plugin start up time if you are <b>not</b> logged id to myExperiment.</html>");
+ jpStartupTabChoice.add(this.cbDefaultNotLoggedInTab, c);
+
+ c.gridy = 1;
+ c.gridx = 0;
+ c.insets = insLabel;
+ jpStartupTabChoice.add(new JLabel("Default startup tab after successful auto-login"), c);
+
+ c.gridx = 1;
+ c.insets = insParam;
+ this.cbDefaultLoggedInTab = new JComboBox(this.alPluginTabComponentNames.toArray());
+ this.cbDefaultLoggedInTab.setToolTipText("<html>This tab will be automatically opened at plugin start up time if you have chosen to use <b>auto logging in</b> to myExperiment.</html>");
+ jpStartupTabChoice.add(this.cbDefaultLoggedInTab, c);
+
+ // create 'my stuff' tab preference box
+ JPanel jpMyStuffPrefs = new JPanel();
+ jpMyStuffPrefs.setLayout(new GridBagLayout());
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.insets = insLabel;
+ jpMyStuffPrefs.add(new JLabel("Sections to show in this tab:"), c);
+
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 1.0;
+ c.insets = new Insets(insParam.top, 100, insParam.bottom / 3, insParam.right);
+ this.cbMyStuffWorkflows = new JCheckBox("My Workflows");
+ jpMyStuffPrefs.add(this.cbMyStuffWorkflows, c);
+
+ c.gridy = 1;
+ this.cbMyStuffFiles = new JCheckBox("My Files");
+ jpMyStuffPrefs.add(this.cbMyStuffFiles, c);
+
+ c.gridy = 2;
+ this.cbMyStuffPacks = new JCheckBox("My Packs");
+ jpMyStuffPrefs.add(this.cbMyStuffPacks, c);
+
+ // create button panel
+ this.bApply = new JButton("Apply");
+ this.bApply.addActionListener(this);
+
+ this.bReset = new JButton("Reset");
+ this.bReset.addActionListener(this);
+
+ this.bHelp = new JButton("Help");
+ this.bHelp.addActionListener(this);
+
+ JPanel jpButtons = new JPanel();
+ jpButtons.add(bHelp, c);
+ jpButtons.add(bReset, c);
+ jpButtons.add(bApply, c);
+
+ // PUT EVERYTHING TOGETHER
+ JPanel jpEverything = new JPanel();
+ GridBagLayout jpEverythingLayout = new GridBagLayout();
+ jpEverything.setLayout(jpEverythingLayout);
+
+ GridBagConstraints gbConstraints = new GridBagConstraints();
+ gbConstraints.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.NORTHWEST;
+
+ gbConstraints.weightx = 1;
+ gbConstraints.gridx = 0;
+
+ gbConstraints.gridy = 0;
+ jpEverything.add(jpApiLocation, gbConstraints);
+
+ gbConstraints.gridy++;
+ jpEverything.add(jpStartupTabChoice, gbConstraints);
+
+ gbConstraints.gridy++;
+ jpEverything.add(jpMyStuffPrefs, gbConstraints);
+
+ gbConstraints.gridy++;
+ c.insets = new Insets(10, 0, 0, 0);
+ jpEverything.add(jpButtons, gbConstraints);
+
+ BorderLayout layout = new BorderLayout();
+ this.setLayout(layout);
+ this.add(jpEverything, BorderLayout.NORTH);
+
+ if (MyExperimentClient.baseChangedSinceLastStart) {
+ JPanel jpInfo = new JPanel();
+ jpInfo.setLayout(new BoxLayout(jpInfo, BoxLayout.Y_AXIS));
+ String info = "<html>Your myExperiment base url has been modified since Taverna was started;<br>"
+ + "this change will not take effect until you restart Taverna.</html>";
+ jpInfo.add(new JLabel(info, WorkbenchIcons.leafIcon, SwingConstants.LEFT));
+ this.add(jpInfo, BorderLayout.SOUTH);
+ }
+ }
+
+ private void initialiseData() {
+ // myExperiment Base URL
+ this.tfMyExperimentURL.setText(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_BASE_URL));
+
+ // default tabs
+ this.cbDefaultNotLoggedInTab.setSelectedIndex(Integer.parseInt(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB)));
+ this.cbDefaultLoggedInTab.setSelectedIndex(Integer.parseInt(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB)));
+
+ // components of "My Stuff" tab
+ this.cbMyStuffWorkflows.setSelected(Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_WORKFLOWS)));
+ this.cbMyStuffFiles.setSelected(Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_FILES)));
+ this.cbMyStuffPacks.setSelected(Boolean.parseBoolean(myExperimentClient.getSettings().getProperty(MyExperimentClient.INI_MY_STUFF_PACKS)));
+ }
+
+ // *** Callback for ActionListener interface ***
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource().equals(this.bApply)) {
+ // check if myExperiment address is present
+ String strNewMyExperimentURL = this.tfMyExperimentURL.getText().trim();
+ if (strNewMyExperimentURL.length() == 0) {
+ javax.swing.JOptionPane.showMessageDialog(null, "Please specify a base URL of myExperiment instance that you wish to connect to", "Error", JOptionPane.WARNING_MESSAGE);
+ this.tfMyExperimentURL.requestFocusInWindow();
+ return;
+ }
+
+ // check if at least one of the checkboxes (for sections in 'My Stuff' tab) is selected
+ if (!(this.cbMyStuffWorkflows.isSelected()
+ || this.cbMyStuffFiles.isSelected() || this.cbMyStuffPacks.isSelected())) {
+ javax.swing.JOptionPane.showMessageDialog(null, "Please choose at least one section to display in 'My Stuff' tab", "Error", JOptionPane.WARNING_MESSAGE);
+ this.cbMyStuffWorkflows.requestFocusInWindow();
+ return;
+ }
+
+ // all values should be present - store these into Properties object
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_BASE_URL, strNewMyExperimentURL);
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_DEFAULT_ANONYMOUS_TAB, new Integer(cbDefaultNotLoggedInTab.getSelectedIndex()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_DEFAULT_LOGGED_IN_TAB, new Integer(cbDefaultLoggedInTab.getSelectedIndex()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_WORKFLOWS, new Boolean(cbMyStuffWorkflows.isSelected()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_FILES, new Boolean(cbMyStuffFiles.isSelected()).toString());
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_MY_STUFF_PACKS, new Boolean(cbMyStuffPacks.isSelected()).toString());
+
+ // NB! changed myExperiment location will not take action until the next application restart
+ if (MyExperimentClient.baseChangedSinceLastStart
+ || !strNewMyExperimentURL.equals(myExperimentClient.getBaseURL())) {
+ // turn off auto-login
+ myExperimentClient.getSettings().put(MyExperimentClient.INI_AUTO_LOGIN, new Boolean(false).toString());
+
+ javax.swing.JOptionPane.showMessageDialog(null, "You have selected a new Base URL for myExperiment.\n"
+ + "Your new setting has been saved, but will not take\n"
+ + "effect until you restart Taverna.\n\n"
+ + "The auto-login feature has been disabled for you to\n"
+ + "check the login details at the next launch.", "myExperiment Plugin - Info", JOptionPane.INFORMATION_MESSAGE);
+
+ MyExperimentClient.baseChangedSinceLastStart = true;
+ }
+
+ myExperimentClient.storeHistoryAndSettings();
+ // pluginMainComponent = new MainComponent();
+ } else if (e.getSource().equals(this.bHelp)) {
+ Helper.showHelp(this);
+ } else if (e.getSource().equals(this.bReset)) {
+ initialiseData();
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfigurationUIFactory.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfigurationUIFactory.java
new file mode 100644
index 0000000..3026edc
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/MyExperimentConfigurationUIFactory.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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.myexperiment.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+/**
+ * @author Emmanuel Tagarira, Alan Williams
+ */
+public class MyExperimentConfigurationUIFactory implements ConfigurationUIFactory {
+
+ private EditManager editManager;
+private FileManager fileManager;
+
+public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ public JPanel getConfigurationPanel() {
+ if (MainComponent.MAIN_COMPONENT == null)
+ MainComponent.MAIN_COMPONENT = new MainComponent(editManager, fileManager);
+ return new MyExperimentConfigurationPanel();
+ }
+
+ public Configurable getConfigurable() {
+ return MyExperimentConfiguration.getInstance();
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/TestJFrameForPreferencesLocalLaunch.java b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/TestJFrameForPreferencesLocalLaunch.java
new file mode 100644
index 0000000..2cdc111
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/java/net/sf/taverna/t2/workbench/myexperiment/config/TestJFrameForPreferencesLocalLaunch.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.myexperiment.config;
+
+import java.awt.Dimension;
+
+import javax.swing.JFrame;
+
+/**
+ * This is a class to get a visual on what the preferences will look like when
+ * integrated into the main taverna preferences.
+ *
+ * @author Emmanuel Tagarira
+ */
+public class TestJFrameForPreferencesLocalLaunch {
+
+ public static void main(String[] args) {
+ JFrame frame = new JFrame("myExperiment Preferences Test");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ frame.setMinimumSize(new Dimension(500, 300));
+ frame.setLocation(300, 150);
+ frame.getContentPane().add(new MyExperimentConfigurationPanel());
+
+ frame.pack();
+ frame.setVisible(true);
+ }
+}
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..97e6981
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponentShutdownHook
\ No newline at end of file
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..1391621
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.myexperiment.config.MyExperimentConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
new file mode 100644
index 0000000..62fd99e
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.myexperiment.MyExperimentPerspective
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..9a7aa26
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..e9b6cee
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/spring/perspective-myexperiment-context-osgi.xml b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/spring/perspective-myexperiment-context-osgi.xml
new file mode 100644
index 0000000..0296415
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/spring/perspective-myexperiment-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="MyExperimentConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <service ref="MainComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <service ref="MyExperimentPerspective" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" />
+
+ <service ref="MainComponentShutdownHook" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/spring/perspective-myexperiment-context.xml b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/spring/perspective-myexperiment-context.xml
new file mode 100644
index 0000000..bfd031f
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/META-INF/spring/perspective-myexperiment-context.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="MyExperimentConfigurationUIFactory" class="net.sf.taverna.t2.workbench.myexperiment.config.MyExperimentConfigurationUIFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+
+ <bean id="MainComponentFactory" class="net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponentFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+
+ <bean id="MyExperimentPerspective" class="net.sf.taverna.t2.ui.perspectives.myexperiment.MyExperimentPerspective" />
+
+ <bean id="MainComponentShutdownHook" class="net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponentShutdownHook" />
+
+</beans>
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/ajax-loader-still.gif b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/ajax-loader-still.gif
new file mode 100644
index 0000000..b100470
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/ajax-loader-still.gif
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/ajax-loader.gif b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/ajax-loader.gif
new file mode 100644
index 0000000..078b55f
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/ajax-loader.gif
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_left.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_left.png
new file mode 100644
index 0000000..2bed329
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_left.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_refresh.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_refresh.png
new file mode 100644
index 0000000..0de2656
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_refresh.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_right.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_right.png
new file mode 100644
index 0000000..2cf15f1
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/arrow_right.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/comment_add.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/comment_add.png
new file mode 100644
index 0000000..75e78de
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/comment_add.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/comment_delete.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/comment_delete.png
new file mode 100644
index 0000000..643fdbe
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/comment_delete.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/cross.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/cross.png
new file mode 100644
index 0000000..1514d51
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/cross.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/denied.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/denied.png
new file mode 100644
index 0000000..c37bd06
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/denied.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/dummy-workflow.t2flow b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/dummy-workflow.t2flow
new file mode 100644
index 0000000..120c29d
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/dummy-workflow.t2flow
@@ -0,0 +1,157 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow">
+ <dataflow id="ec0991ba-275c-49ed-b1d6-38534180fb7c" role="top">
+ <name>simple_workflow_with_input</name>
+ <inputPorts>
+ <port>
+ <name>input</name>
+ <depth>0</depth>
+ <granularDepth>0</granularDepth>
+ </port>
+ </inputPorts>
+ <outputPorts>
+ <port>
+ <name>output</name>
+ </port>
+ </outputPorts>
+ <processors>
+ <processor>
+ <name>Concat_XXX</name>
+ <inputPorts>
+ <port>
+ <name>input</name>
+ <depth>0</depth>
+ </port>
+ </inputPorts>
+ <outputPorts>
+ <port>
+ <name>output</name>
+ <depth>0</depth>
+ <granularDepth>0</granularDepth>
+ </port>
+ </outputPorts>
+ <annotations />
+ <activities>
+ <activity>
+ <class>
+ net.sf.taverna.t2.activities.beanshell.BeanshellActivity
+ </class>
+ <inputMap>
+ <map from="input" to="input" />
+ </inputMap>
+ <outputMap>
+ <map from="output" to="output" />
+ </outputMap>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean
+ xmlns="">
+ <script>String output = input + "XXX";</script>
+ <dependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>input</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>0</granularDepth>
+ <name>output</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+ </net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean>
+ </configBean>
+ </activity>
+ </activities>
+ <dispatchStack>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize
+ </class>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig
+ xmlns="">
+ <maxJobs>1</maxJobs>
+ </net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig>
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry
+ </class>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig
+ xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+ </net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig>
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ </dispatchStack>
+ <iterationStrategyStack>
+ <iteration>
+ <strategy>
+ <port name="input" depth="0" />
+ </strategy>
+ </iteration>
+ </iterationStrategyStack>
+ </processor>
+ </processors>
+ <conditions />
+ <datalinks>
+ <datalink>
+ <sink type="processor">
+ <processor>Concat_XXX</processor>
+ <port>input</port>
+ </sink>
+ <source type="dataflow">
+ <port>input</port>
+ </source>
+ </datalink>
+ <datalink>
+ <sink type="dataflow">
+ <port>output</port>
+ </sink>
+ <source type="processor">
+ <processor>Concat_XXX</processor>
+ <port>output</port>
+ </source>
+ </datalink>
+ </datalinks>
+ </dataflow>
+</workflow>
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/external_link_listing_small.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/external_link_listing_small.png
new file mode 100644
index 0000000..a22b1f7
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/external_link_listing_small.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/favourite_add.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/favourite_add.png
new file mode 100644
index 0000000..e94a7b0
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/favourite_add.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/favourite_delete.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/favourite_delete.png
new file mode 100644
index 0000000..7d7c4f5
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/favourite_delete.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/file.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/file.png
new file mode 100644
index 0000000..336ea5d
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/file.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/group.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/group.png
new file mode 100644
index 0000000..7fb4e1f
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/group.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/login.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/login.png
new file mode 100644
index 0000000..41676a0
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/login.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/logout.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/logout.png
new file mode 100644
index 0000000..2541d2b
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/logout.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexp_icon.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexp_icon.png
new file mode 100644
index 0000000..6cc44a8
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexp_icon.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexp_icon16x16.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexp_icon16x16.png
new file mode 100644
index 0000000..1732b94
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexp_icon16x16.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexperiment-perspective.xml b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexperiment-perspective.xml
new file mode 100644
index 0000000..3f9a5e2
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/myexperiment-perspective.xml
@@ -0,0 +1,17 @@
+<basepane>
+ <child>
+ <znode classname="net.sf.taverna.zaria.ZRavenComponent">
+ <namedcomponent scroll="false">
+ <name>myExperiment Perspective</name>
+ </namedcomponent>
+ </znode>
+ </child>
+ <namedcomponents>
+ <namedcomponent>
+ <groupid>net.sf.taverna.t2.ui-exts</groupid>
+ <artifact>perspective-myexperiment</artifact>
+ <classname>net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponentFactory</classname>
+ <name>myExperiment Perspective</name>
+ </namedcomponent>
+ </namedcomponents>
+</basepane>
\ No newline at end of file
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/open_in_myExperiment.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/open_in_myExperiment.png
new file mode 100644
index 0000000..c530a98
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/open_in_myExperiment.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/pack.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/pack.png
new file mode 100644
index 0000000..3f1fc42
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/pack.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/remote_resource.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/remote_resource.png
new file mode 100644
index 0000000..b8edc12
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/remote_resource.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/star.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/star.png
new file mode 100644
index 0000000..b88c857
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/star.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/styles.css b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/styles.css
new file mode 100644
index 0000000..4097415
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/styles.css
@@ -0,0 +1,380 @@
+body {
+ font-family: arial, helvetica, clean, sans-serif;
+ margin: 0;
+ padding: 0;
+ background: #FFFFFF;
+}
+
+div.outer {
+ padding-top: 0px;
+ padding-bottom: 0px;
+ padding-left: 10px;
+ padding-right: 10px;
+ background: #FFFFFF;
+}
+
+a {
+ color: #000099;
+ text-decoration: none;
+ overflow: auto;
+}
+
+div.list_item_container {
+ padding-left: 10px;
+ padding-right: 10px;
+ background: #FFFFFF;
+}
+
+div.list_item {
+ margin-top: 10px;
+}
+
+.list_item .title {
+ text-align: left;
+ line-height: 1.0;
+ font-size: 16pt;
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding: 0;
+}
+
+.list_item .uploader {
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding: 0;
+}
+
+.list_item .title a {
+ color: #990000;
+}
+
+.list_item .desc {
+ width: 400px;
+ font-size: 12pt;
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-left: 15px;
+ padding-right: 5px;
+ margin-top: 6px;
+ margin-bottom: 0;
+ line-height: 1.4;
+}
+
+div.tag_cloud {
+ text-align: center;
+ line-height: 1.6;
+}
+
+div.tag_cloud a {
+ color: #990000;
+}
+
+div.workflow {
+ text-align: center;
+ margin: 6px;
+ border-width: 1px solid #333333;
+}
+
+.workflow .info {
+ text-align: center;
+ line-height: 1.6;
+ color: #333333;
+}
+
+.workflow .title {
+ text-align: center;
+ line-height: 1.0;
+ color: #333333;
+ font-size: 18pt;
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top; 0;
+ padding: 0;
+}
+
+.workflow img.preview {
+ padding: 5px;
+}
+
+.workflow .desc {
+ line-height: 1.4;
+ /*background-color: #EEEEEE;*/
+ text-align: left;
+ width: 400px;
+ font-size: 12pt;
+ padding-left: 12px;
+ padding-right: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.workflow .desc p {
+ padding: 0;
+}
+
+.workflow .tags {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+.workflow .tags a {
+ color: #990000;
+}
+
+.workflow .credits {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+
+div.file {
+ text-align: center;
+ margin: 6px;
+ border-width: 1px solid #333333;
+}
+
+.file .info {
+ text-align: center;
+ line-height: 1.6;
+ color: #333333;
+}
+
+.file .title {
+ text-align: center;
+ line-height: 1.0;
+ color: #333333;
+ font-size: 18pt;
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding: 0;
+}
+
+.file img.preview {
+ padding: 5px;
+}
+
+.file .desc {
+ line-height: 1.4;
+ /*background-color: #EEEEEE;*/
+ text-align: left;
+ width: 400px;
+ font-size: 12pt;
+ padding-left: 12px;
+ padding-right: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.file .desc p {
+ padding: 0;
+}
+
+.file .tags {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+.file .tags a {
+ color: #990000;
+}
+
+.file .credits {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+
+div.pack {
+ text-align: center;
+ margin: 6px;
+ border-width: 1px solid #333333;
+}
+
+.pack .info {
+ text-align: center;
+ line-height: 1.6;
+ color: #333333;
+}
+
+.pack .title {
+ text-align: center;
+ line-height: 1.0;
+ color: #333333;
+ font-size: 18pt;
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding: 0;
+}
+
+.pack img.preview {
+ padding: 5px;
+}
+
+.pack .desc {
+ line-height: 1.4;
+ text-align: left;
+ width: 400px;
+ font-size: 12pt;
+ padding-left: 12px;
+ padding-right: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.pack .desc p {
+ padding: 0;
+}
+
+.pack .tags {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+.pack .tags a {
+ color: #990000;
+}
+
+.group .credits {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+
+div.group {
+ text-align: center;
+ margin: 6px;
+ border-width: 1px solid #333333;
+}
+
+.group .info {
+ text-align: center;
+ line-height: 1.6;
+ color: #333333;
+}
+
+.group .title {
+ text-align: center;
+ line-height: 1.0;
+ color: #333333;
+ font-size: 18pt;
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding: 0;
+}
+
+.group img.preview {
+ padding: 5px;
+}
+
+.group .desc {
+ line-height: 1.4;
+ text-align: left;
+ width: 400px;
+ font-size: 12pt;
+ padding-left: 12px;
+ padding-right: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.group .desc p {
+ padding: 0;
+}
+
+.group .tags {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+.group .tags a {
+ color: #990000;
+}
+
+.group .credits {
+ text-align: center;
+ width: 400px;
+ line-height: 1.6;
+}
+
+
+div.user {
+ text-align: center;
+ margin: 6px;
+ border-width: 1px solid #333333;
+}
+
+.user .info {
+ text-align: center;
+ line-height: 1.6;
+ color: #333333;
+}
+
+.user .name {
+ text-align: center;
+ line-height: 1.0;
+ color: #333333;
+ font-size: 18pt;
+ font-weight: bold;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding: 0;
+}
+
+.user img.avatar {
+ padding: 5px;
+}
+
+.user .desc {
+ line-height: 1.4;
+ text-align: left;
+ width: 400px;
+ font-size: 12pt;
+ padding-left: 12px;
+ padding-right: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.user .desc p {
+ padding: 0;
+ width: 400px;
+}
+
+.user .contact_details_header {
+ font-size: 12px;
+ font-weight: bold;
+ text-align: center;
+ margin-top: 12px;
+ margin-bottom: 6px;
+}
+
+.user .contact_details {
+ text-align: left;
+ margin-left: 100px;
+ line-height: 1.5;
+}
+
+.user .items_header {
+ font-size: 12px;
+ font-weight: bold;
+ text-align: center;
+ margin-top: 22px;
+ margin-bottom: 6px;
+}
+
+.none_text {
+ color: #666666;
+ font-style: italic;
+}
\ No newline at end of file
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/tag_blue.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/tag_blue.png
new file mode 100644
index 0000000..9757fc6
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/tag_blue.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/tick.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/tick.png
new file mode 100644
index 0000000..a9925a0
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/tick.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/transparent_icon.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/transparent_icon.png
new file mode 100644
index 0000000..39ede24
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/transparent_icon.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/user.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/user.png
new file mode 100644
index 0000000..79f35cc
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/user.png
Binary files differ
diff --git a/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/workflow.png b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/workflow.png
new file mode 100644
index 0000000..0be81b3
--- /dev/null
+++ b/taverna-workbench-perspective-myexperiment/src/main/resources/net/sf/taverna/t2/ui/perspectives/myexperiment/workflow.png
Binary files differ
diff --git a/taverna-workbench-perspective-results/pom.xml b/taverna-workbench-perspective-results/pom.xml
new file mode 100644
index 0000000..f4efdd9
--- /dev/null
+++ b/taverna-workbench-perspective-results/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>perspective-results</artifactId>
+ <packaging>bundle</packaging>
+ <name>Results Perspective</name>
+ <description>Results component for viewing progress of a workflow run,
+ intermediate and final results, as well as previous workflow runs.</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>monitor-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>results-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-run-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
+
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/ResultsPerspective.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/ResultsPerspective.java
new file mode 100644
index 0000000..005da54
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/ResultsPerspective.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.resultsPerspectiveIcon;
+
+import java.io.File;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * @author David Withers
+ */
+public class ResultsPerspective implements PerspectiveSPI, EventHandler {
+ private static final String RUN_STORE_DIRECTORY = "workflow-runs";
+
+ private ResultsPerspectiveComponent resultsPerspectiveComponent;
+ @SuppressWarnings("unused")
+ private RunMonitor runMonitor;
+ private RunService runService;
+ private SelectionManager selectionManager;
+ private ColourManager colourManager;
+ private ActivityIconManager activityIconManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private RendererRegistry rendererRegistry;
+ private List<SaveAllResultsSPI> saveAllResultsSPIs;
+ private List<SaveIndividualResultSPI> saveIndividualResultSPIs;
+ private ApplicationConfiguration applicationConfiguration;
+
+ @Override
+ public String getID() {
+ return ResultsPerspective.class.getName();
+ }
+
+ @Override
+ public JComponent getPanel() {
+ if (resultsPerspectiveComponent == null) {
+ File runStore = new File(
+ applicationConfiguration.getApplicationHomeDir(),
+ RUN_STORE_DIRECTORY);
+ runStore.mkdirs();
+ resultsPerspectiveComponent = new ResultsPerspectiveComponent(
+ runService, selectionManager, colourManager,
+ activityIconManager, workbenchConfiguration,
+ rendererRegistry, saveAllResultsSPIs,
+ saveIndividualResultSPIs, runStore);
+ runMonitor = new RunMonitor(runService, selectionManager,
+ resultsPerspectiveComponent);
+ }
+ return resultsPerspectiveComponent;
+ }
+
+// public void loadWorkflowRuns(File runStoreDirectory) {
+// if (runStoreDirectory.exists()) {
+// for (File runFile : runStoreDirectory.listFiles(new RunFileFilter())) {
+// try {
+// runService.open(runFile);
+// } catch (IOException e) {
+// }
+// }
+// }
+// }
+
+ @Override
+ public ImageIcon getButtonIcon() {
+ return resultsPerspectiveIcon;
+ }
+
+ @Override
+ public String getText() {
+ return "Results";
+ }
+
+ @Override
+ public int positionHint() {
+ return 30;
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ if (resultsPerspectiveComponent != null)
+ resultsPerspectiveComponent.handleEvent(event);
+ }
+
+ public void setRunService(RunService runService) {
+ this.runService = runService;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setWorkbenchConfiguration(
+ WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setRendererRegistry(RendererRegistry rendererRegistry) {
+ this.rendererRegistry = rendererRegistry;
+ }
+
+ public void setSaveAllResultsSPIs(List<SaveAllResultsSPI> saveAllResultsSPIs) {
+ this.saveAllResultsSPIs = saveAllResultsSPIs;
+ }
+
+ public void setSaveIndividualResultSPIs(
+ List<SaveIndividualResultSPI> saveIndividualResultSPIs) {
+ this.saveIndividualResultSPIs = saveIndividualResultSPIs;
+ }
+
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/ResultsPerspectiveComponent.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/ResultsPerspectiveComponent.java
new file mode 100644
index 0000000..55b8acb
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/ResultsPerspectiveComponent.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Font.BOLD;
+import static java.lang.Math.round;
+import static javax.swing.JSplitPane.VERTICAL_SPLIT;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Font;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.tabselector.Tab;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.monitor.graph.MonitorGraphComponent;
+import net.sf.taverna.t2.workbench.views.monitor.progressreport.TableMonitorComponent;
+import net.sf.taverna.t2.workbench.views.results.ResultsComponent;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+import org.apache.log4j.Logger;
+import org.osgi.service.event.Event;
+
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ResultsPerspectiveComponent extends JPanel implements Updatable {
+ private static final Logger logger = Logger.getLogger(ResultsPerspectiveComponent.class);
+ private static final String NO_RUNS_MESSAGE = "No workflow runs";
+ private static final String RUNS_SELECTED = "RUNS_SELECTED";
+ private static final String NO_RUNS_SELECTED = "NO_RUNS_SELECTED";
+
+ private final RunService runService;
+ private final SelectionManager selectionManager;
+ @SuppressWarnings("unused")
+ private final ColourManager colourManager;
+ @SuppressWarnings("unused")
+ private final ActivityIconManager activityIconManager;
+ @SuppressWarnings("unused")
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ private List<Updatable> updatables = new ArrayList<>();
+ private CardLayout cardLayout;
+ private SelectionManagerObserver selectionManagerObserver;
+ private MonitorGraphComponent monitorGraphComponent;
+ private TableMonitorComponent tableMonitorComponent;
+ private ResultsComponent resultsComponent;
+ private RunSelectorComponent runSelectorComponent;
+
+ public ResultsPerspectiveComponent(RunService runService, SelectionManager selectionManager,
+ ColourManager colourManager, ActivityIconManager activityIconManager,
+ WorkbenchConfiguration workbenchConfiguration, RendererRegistry rendererRegistry,
+ List<SaveAllResultsSPI> saveAllResultsSPIs,
+ List<SaveIndividualResultSPI> saveIndividualResultSPIs, File runStore) {
+ this.runService = runService;
+ this.selectionManager = selectionManager;
+ this.colourManager = colourManager;
+ this.activityIconManager = activityIconManager;
+ this.workbenchConfiguration = workbenchConfiguration;
+
+ cardLayout = new CardLayout();
+ setLayout(cardLayout);
+
+ JLabel noRunsMessage = new JLabel(NO_RUNS_MESSAGE, JLabel.CENTER);
+ Font font = noRunsMessage.getFont();
+ if (font != null) {
+ font = font.deriveFont(round(font.getSize() * 1.5))
+ .deriveFont(BOLD);
+ noRunsMessage.setFont(font);
+ }
+ JPanel noRunsPanel = new JPanel(new BorderLayout());
+ noRunsPanel.add(noRunsMessage, CENTER);
+ add(noRunsPanel, NO_RUNS_SELECTED);
+
+ monitorGraphComponent = new MonitorGraphComponent(runService,
+ colourManager, workbenchConfiguration, selectionManager);
+ tableMonitorComponent = new TableMonitorComponent(runService,
+ selectionManager, activityIconManager);
+
+ resultsComponent = new ResultsComponent(runService, selectionManager,
+ rendererRegistry, saveAllResultsSPIs, saveIndividualResultSPIs);
+
+ updatables.add(monitorGraphComponent);
+ updatables.add(tableMonitorComponent);
+ updatables.add(resultsComponent);
+
+ JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.add("Graph", monitorGraphComponent);
+ tabbedPane.add("Progress report", tableMonitorComponent);
+
+ JSplitPane splitPane = new JSplitPane(VERTICAL_SPLIT);
+ splitPane.setBorder(null);
+ splitPane.setLeftComponent(tabbedPane);
+ splitPane.setRightComponent(resultsComponent);
+ splitPane.setDividerLocation(200);
+
+ runSelectorComponent = new RunSelectorComponent(runService,
+ selectionManager, runStore);
+
+ JPanel runsPanel = new JPanel(new BorderLayout());
+ runsPanel.add(runSelectorComponent, NORTH);
+ runsPanel.add(splitPane, CENTER);
+ add(runsPanel, RUNS_SELECTED);
+
+ selectionManagerObserver = new SelectionManagerObserver();
+ selectionManager.addObserver(selectionManagerObserver);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ selectionManager.removeObserver(selectionManagerObserver);
+ }
+
+ @Override
+ public void update() {
+ for (Updatable updatable : updatables)
+ updatable.update();
+ }
+
+ private class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (!(message instanceof WorkflowRunSelectionEvent)) return;
+ String workflowRun = ((WorkflowRunSelectionEvent) message)
+ .getSelectedWorkflowRun();
+ if (workflowRun == null) {
+ cardLayout.show(ResultsPerspectiveComponent.this,
+ NO_RUNS_SELECTED);
+ return;
+ }
+
+ cardLayout.show(ResultsPerspectiveComponent.this, RUNS_SELECTED);
+ runSelectorComponent.selectObject(workflowRun);
+ try {
+ monitorGraphComponent.setWorkflowRun(workflowRun);
+ tableMonitorComponent.setWorkflowRun(workflowRun);
+ } catch (InvalidRunIdException e) {
+ logger.warn(
+ "Failed to create monitor components for workflow run "
+ + workflowRun, e);
+ }
+ }
+ }
+
+ public void handleEvent(Event event) {
+ String workflowRun = event.getProperty("RUN_ID").toString();
+ switch (event.getTopic()) {
+ case RunService.RUN_CLOSED:
+ case RunService.RUN_DELETED:
+ runSelectorComponent.removeObject(workflowRun);
+ monitorGraphComponent.removeWorkflowRun(workflowRun);
+ tableMonitorComponent.removeWorkflowRun(workflowRun);
+ resultsComponent.removeWorkflowRun(workflowRun);
+ if (selectionManager.getSelectedWorkflowRun().equals(workflowRun)) {
+ List<String> runs = runService.getRuns();
+ if (runs.isEmpty())
+ selectionManager.setSelectedWorkflowRun(null);
+ else
+ selectionManager.setSelectedWorkflowRun(runs.get(0));
+ }
+ break;
+ case RunService.RUN_CREATED:
+ case RunService.RUN_OPENED:
+ selectionManager.setSelectedWorkflowRun(workflowRun);
+ break;
+ case RunService.RUN_STOPPED:
+ case RunService.RUN_PAUSED:
+ case RunService.RUN_STARTED:
+ case RunService.RUN_RESUMED:
+ Tab<String> tab = runSelectorComponent.getTab(workflowRun);
+ if (tab instanceof RunTab) {
+ RunTab runTab = (RunTab) tab;
+ runTab.updateTabIcon();
+ }
+ break;
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunMonitor.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunMonitor.java
new file mode 100644
index 0000000..dab7745
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunMonitor.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.platform.report.State;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * @author David Withers
+ */
+public class RunMonitor {
+ private static final Logger logger = Logger.getLogger(RunMonitor.class);
+ private static final long monitorRate = 300;
+
+ private RunService runService;
+ private SelectionManager selectionManager;
+ private final Updatable updatable;
+
+ private Timer updateTimer = new Timer("RunMonitor update timer", true);
+ private UpdateTask updateTask;
+ private String workflowRun;
+ private Set<String> finishedRuns = new HashSet<>();
+ private SelectionManagerObserver selectionManagerObserver = new SelectionManagerObserver();
+
+ public RunMonitor(RunService runService, SelectionManager selectionManager, Updatable updatable) {
+ this.runService = runService;
+ this.selectionManager = selectionManager;
+ this.updatable = updatable;
+ selectionManager.addObserver(selectionManagerObserver);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ selectionManager.removeObserver(selectionManagerObserver);
+ }
+
+ public void start() {
+ synchronized (this) {
+ if (updateTask != null)
+ updateTask.cancel();
+ updateTask = new UpdateTask();
+ try {
+ updateTimer.schedule(updateTask, monitorRate, monitorRate);
+ } catch (IllegalStateException ex) {
+ // task already cancelled
+ }
+ }
+ }
+
+ public void stop() {
+ synchronized (this) {
+ if (updateTask != null) {
+ updateTask.cancel();
+ updateTask = null;
+ }
+ }
+ }
+
+ private void setWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ this.workflowRun = workflowRun;
+ if (workflowRun == null || isFinished(workflowRun))
+ stop();
+ else
+ start();
+ }
+
+ /**
+ * Returns true if a workflow run has been recorded by the monitor as finished.
+ * <p>
+ * If the workflow run has not been recorded as finished false is returned and the state of the
+ * workflow is checked and recorded for future calls of this method.
+ *
+ * @param workflowRun
+ * the ID of the run to check
+ * @return true if a workflow run has been recorded by the monitor as finished
+ * @throws InvalidRunIdException
+ * if the runId is invalid
+ */
+ private boolean isFinished(String workflowRun) throws InvalidRunIdException {
+ boolean finished = false;
+ if (finishedRuns.contains(workflowRun))
+ finished = true;
+ else {
+ State state = runService.getState(workflowRun);
+ if (state == State.COMPLETED || state == State.CANCELLED
+ || state == State.FAILED)
+ finishedRuns.add(workflowRun);
+ }
+ return finished;
+ }
+
+ private class UpdateTask extends TimerTask {
+ @Override
+ public void run() {
+ try {
+ updatable.update();
+ if (isFinished(workflowRun)) {
+ updatable.update();
+ stop();
+ }
+ } catch (InvalidRunIdException e) {
+ logger.warn("workflow run could not be queried", e);
+ stop();
+ }
+ }
+ }
+
+ private class SelectionManagerObserver extends SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowRunSelectionEvent) {
+ WorkflowRunSelectionEvent event = (WorkflowRunSelectionEvent) message;
+ try {
+ setWorkflowRun(event.getSelectedWorkflowRun());
+ } catch (InvalidRunIdException e) {
+ logger.warn("Selected workflow run ID is invalid", e);
+ }
+ } else if (message instanceof PerspectiveSelectionEvent) {
+ PerspectiveSelectionEvent event = (PerspectiveSelectionEvent) message;
+ PerspectiveSPI selectedPerspective = event
+ .getSelectedPerspective();
+ if ("net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective"
+ .equals(selectedPerspective.getID())) {
+ try {
+ if (workflowRun != null && !isFinished(workflowRun))
+ start();
+ } catch (InvalidRunIdException e) {
+ logger.warn("Selected workflow run ID is invalid", e);
+ }
+ } else
+ stop();
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunSelectorComponent.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunSelectorComponent.java
new file mode 100644
index 0000000..39ca15a
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunSelectorComponent.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import java.io.File;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.tabselector.Tab;
+import net.sf.taverna.t2.lang.ui.tabselector.TabSelectorComponent;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * Component for managing selection of workflow runs.
+ *
+ * @author David Withers
+ */
+public class RunSelectorComponent extends TabSelectorComponent<String> {
+ private static final long serialVersionUID = 3679972772159328891L;
+
+ private final RunService runService;
+ private final SelectionManager selectionManager;
+ private final File runStore;
+
+ public RunSelectorComponent(RunService runSevice,
+ SelectionManager selectionManager, File runStore) {
+ this.runService = runSevice;
+ this.selectionManager = selectionManager;
+ this.runStore = runStore;
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ private class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowRunSelectionEvent) {
+ WorkflowRunSelectionEvent event = (WorkflowRunSelectionEvent) message;
+ String workflowRun = event.getSelectedWorkflowRun();
+ if (workflowRun != null)
+ selectObject(workflowRun);
+ }
+ }
+ }
+
+ @Override
+ protected Tab<String> createTab(String runID) {
+ return new RunTab(runID, selectionManager, runService, runStore);
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunTab.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunTab.java
new file mode 100644
index 0000000..305e342
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/RunTab.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.pauseIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tickIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.lang.ui.tabselector.Tab;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.platform.execution.api.InvalidExecutionIdException;
+import uk.org.taverna.platform.report.State;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+import uk.org.taverna.platform.run.api.RunStateException;
+
+/**
+ * Tab for selecting the current workflow run.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RunTab extends Tab<String> {
+ private static Logger logger = Logger.getLogger(RunTab.class);
+
+ private final SelectionManager selectionManager;
+ private final RunService runService;
+ private final File runStore;
+
+ public RunTab(final String runID, final SelectionManager selectionManager,
+ final RunService runService, File runStore) {
+ super(getRunName(runService, runID), runID);
+ this.selectionManager = selectionManager;
+ this.runService = runService;
+ this.runStore = runStore;
+ updateTabIcon();
+ }
+
+ private static String getRunName(RunService runService, String runID) {
+ try {
+ return runService.getRunName(runID);
+ } catch (InvalidRunIdException e) {
+ return "Invalid Run";
+ }
+ }
+
+ @Override
+ protected void clickTabAction() {
+ selectionManager.setSelectedWorkflowRun(selection);
+ }
+
+ @Override
+ protected void closeTabAction() {
+ try {
+ State state = runService.getState(selection);
+ if (state == State.RUNNING || state == State.PAUSED) {
+ if (showConfirmDialog(
+ null,
+ "Closing the tab will cancel the workflow run. Do you want to continue?",
+ "Workflow is still running", YES_NO_OPTION) == JOptionPane.NO_OPTION)
+ return;
+
+ try {
+ runService.cancel(selection);
+ } catch (RunStateException | InvalidExecutionIdException e) {
+ // workflow may have finished by now
+ }
+ }
+ File file = new File(runStore, getName() + ".wfRun");
+ try {
+ if (!file.exists())
+ runService.save(selection, file);
+ } catch (IOException e) {
+ logger.warn("Failed to save workflow run to " + file, e);
+ }
+ runService.close(selection);
+ } catch (InvalidRunIdException | InvalidExecutionIdException e) {
+ // TODO Have to cope with this - execution ID could be invalid but still need to close the tab
+ logger.error("problem with invalid id", e);
+ }
+ }
+
+ public void updateTabIcon() {
+ try {
+ switch (runService.getState(selection)) {
+ case RUNNING:
+ setIcon(workingIcon);
+ break;
+ case COMPLETED:
+ setIcon(tickIcon);
+ break;
+ case PAUSED:
+ setIcon(pauseIcon);
+ break;
+ case CANCELLED:
+ case FAILED:
+ setIcon(tickIcon);
+ default:
+ break;
+ }
+ } catch (InvalidRunIdException e) {
+ logger.warn(e);
+ }
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRun.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRun.java
new file mode 100644
index 0000000..3b888cf
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRun.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+/**
+ * @author David Withers
+ */
+public class WorkflowRun {
+ private String id, name;
+
+ public WorkflowRun(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRunListCellRenderer.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRunListCellRenderer.java
new file mode 100644
index 0000000..eb1dac8
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRunListCellRenderer.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import java.awt.Component;
+import java.text.SimpleDateFormat;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+
+import uk.org.taverna.platform.report.WorkflowReport;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * @author David Withers
+ */
+public class WorkflowRunListCellRenderer extends JLabel implements ListCellRenderer<String> {
+ private static final long serialVersionUID = -4954451814016664554L;
+ private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ private final RunService runService;
+
+ public WorkflowRunListCellRenderer(RunService runService) {
+ this.runService = runService;
+ }
+
+ @Override
+ public Component getListCellRendererComponent(JList<? extends String> list, String value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ try {
+ WorkflowReport workflowReport = runService.getWorkflowReport(value);
+ setText(workflowReport.getSubject().getName() + " "
+ + ISO_8601.format(workflowReport.getCreatedDate()));
+ } catch (InvalidRunIdException e) {
+ setText(value);
+ }
+ return this;
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRunListModel.java b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRunListModel.java
new file mode 100644
index 0000000..e534515
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/java/net/sf/taverna/t2/ui/perspectives/results/WorkflowRunListModel.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui.perspectives.results;
+
+import javax.swing.AbstractListModel;
+
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * @author David Withers
+ */
+public class WorkflowRunListModel extends AbstractListModel<String> {
+ private static final long serialVersionUID = 6899849120823569185L;
+
+ private final RunService runService;
+
+ public WorkflowRunListModel(RunService runService) {
+ this.runService = runService;
+ }
+
+ @Override
+ public int getSize() {
+ return runService.getRuns().size();
+ }
+
+ @Override
+ public String getElementAt(int index) {
+ return runService.getRuns().get(index);
+ }
+
+ /**
+ * @param runID
+ */
+ public void runAdded(String runID) {
+ int index = runService.getRuns().indexOf(runID);
+ if (index >= 0)
+ fireIntervalAdded(this, index, index);
+ }
+
+ /**
+ * @param runID
+ */
+ public void runRemoved(String runID) {
+ int index = runService.getRuns().indexOf(runID);
+ if (index >= 0)
+ fireIntervalRemoved(this, index, index);
+ }
+}
diff --git a/taverna-workbench-perspective-results/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI b/taverna-workbench-perspective-results/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
new file mode 100755
index 0000000..4d76417
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective
diff --git a/taverna-workbench-perspective-results/src/main/resources/META-INF/spring/perspective-results-context-osgi.xml b/taverna-workbench-perspective-results/src/main/resources/META-INF/spring/perspective-results-context-osgi.xml
new file mode 100644
index 0000000..69b3e2a
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/resources/META-INF/spring/perspective-results-context-osgi.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ResultsPerspective" auto-export="interfaces">
+ <service-properties value-type="java.lang.String[]">
+ <beans:entry key="event.topics"
+ value="uk/org/taverna/platform/run/RunService/*" />
+ </service-properties>
+ </service>
+
+ <reference id="runService" interface="uk.org.taverna.platform.run.api.RunService" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+ <reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <reference id="rendererRegistry" interface="net.sf.taverna.t2.renderers.RendererRegistry" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+ <list id="saveAllResultsSPIs" interface="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI" />
+ <list id="saveIndividualResultSPIs" interface="net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI" />
+
+</beans:beans>
diff --git a/taverna-workbench-perspective-results/src/main/resources/META-INF/spring/perspective-results-context.xml b/taverna-workbench-perspective-results/src/main/resources/META-INF/spring/perspective-results-context.xml
new file mode 100644
index 0000000..6777633
--- /dev/null
+++ b/taverna-workbench-perspective-results/src/main/resources/META-INF/spring/perspective-results-context.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ResultsPerspective" class="net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective">
+ <property name="runService" ref="runService" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="colourManager" ref="colourManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="workbenchConfiguration" ref="workbenchConfiguration" />
+ <property name="rendererRegistry" ref="rendererRegistry" />
+ <property name="saveAllResultsSPIs" ref="saveAllResultsSPIs" />
+ <property name="saveIndividualResultSPIs" ref="saveIndividualResultSPIs" />
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-plugin-manager/pom.xml b/taverna-workbench-plugin-manager/pom.xml
new file mode 100644
index 0000000..4e9f0c1
--- /dev/null
+++ b/taverna-workbench-plugin-manager/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>plugin-manager</artifactId>
+ <packaging>bundle</packaging>
+ <name>Taverna Workbench Plugin Manager</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-api</artifactId>
+ <version> ${taverna.commons.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>${osgi.core.version}</version>
+ </dependency>
+
+ <!-- <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-download-impl</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-impl</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency> -->
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java
new file mode 100644
index 0000000..f189f91
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AvailablePluginPanel extends PluginPanel {
+ private final PluginManager pluginManager;
+ private final PluginVersions pluginVersions;
+
+ public AvailablePluginPanel(PluginVersions pluginVersions,
+ PluginManager pluginManager) {
+ super(pluginVersions.getName(), pluginVersions.getOrganization(),
+ pluginVersions.getLatestVersion().getVersion(), pluginVersions
+ .getDescription());
+ this.pluginVersions = pluginVersions;
+ this.pluginManager = pluginManager;
+ }
+
+ @Override
+ public Action getPluginAction() {
+ return new PluginAction();
+ }
+
+ class PluginAction extends AbstractAction {
+ public PluginAction() {
+ putValue(NAME, "Install");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setEnabled(false);
+ putValue(NAME, "Installing");
+ try {
+ pluginManager.installPlugin(pluginVersions.getPluginSiteUrl(),
+ pluginVersions.getLatestVersion().getFile()).start();
+ } catch (PluginException ex) {
+ ex.printStackTrace();
+ }
+ putValue(NAME, "Installed");
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java
new file mode 100644
index 0000000..a08cf14
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class InstalledPluginPanel extends PluginPanel {
+ private final Plugin plugin;
+
+ public InstalledPluginPanel(Plugin plugin) {
+ super(plugin.getName(), plugin.getOrganization(), plugin.getVersion()
+ .toString(), plugin.getDescription());
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Action getPluginAction() {
+ return new PluginAction();
+ }
+
+ class PluginAction extends AbstractAction {
+ public PluginAction() {
+ putValue(NAME, "Uninstall");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setEnabled(false);
+ putValue(NAME, "Uninstalling");
+ try {
+ plugin.uninstall();
+ } catch (PluginException ex) {
+ ex.printStackTrace();
+ }
+ putValue(NAME, "Uninstalled");
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java
new file mode 100644
index 0000000..dfef2fe
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.EAST;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.lang.Math.max;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
+import static javax.swing.SwingConstants.CENTER;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.LayoutManager;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class PluginManagerPanel extends JPanel {
+ private static final Logger logger = Logger
+ .getLogger(PluginManagerPanel.class);
+
+ private PluginManager pluginManager;
+ private JLabel message = new JLabel("");
+
+ public PluginManagerPanel(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ public void initialize() {
+ removeAll();
+ setLayout(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = WEST;
+ gbc.gridy = 0;
+ gbc.insets.left = 5;
+ gbc.insets.right = 5;
+ gbc.insets.top = 5;
+
+ JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.addTab("Available", createAvailablePluginsPanel());
+ tabbedPane.addTab("Installed", createInstalledPluginsPanel());
+ tabbedPane.addTab("Updates", createUpdatePluginsPanel());
+
+ gbc.weightx = 1;
+ gbc.weighty = 1;
+ gbc.gridy = 1;
+ gbc.fill = BOTH;
+ add(tabbedPane, gbc);
+
+ gbc.anchor = EAST;
+ gbc.fill = NONE;
+ gbc.weightx = 0;
+ gbc.weighty = 0;
+ gbc.gridy = 2;
+ gbc.insets.bottom = 5;
+ add(message, gbc);
+ }
+
+ public void checkForUpdates() {
+ message.setText("Checking for updates");
+ try {
+ pluginManager.checkForUpdates();
+ } catch (PluginException e) {
+ logger.info("Error checking for plugin updates", e);
+ } finally {
+ message.setText("");
+ }
+ }
+
+ private static Component scrolled(Component view) {
+ JScrollPane scrollPane = new JScrollPane(view);
+ scrollPane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
+ scrollPane.setBorder(null);
+ return scrollPane;
+ }
+
+ private Component createAvailablePluginsPanel() {
+ try {
+ List<PluginVersions> availablePlugins = pluginManager
+ .getAvailablePlugins();
+ if (availablePlugins.size() == 0)
+ return new JLabel("No new plugins available", CENTER);
+
+ JPanel panel = new JPanel(new ListLayout());
+ for (PluginVersions plugin : availablePlugins)
+ panel.add(new AvailablePluginPanel(plugin, pluginManager));
+ return scrolled(panel);
+ } catch (PluginException e) {
+ logger.info("Error looking for new plugins", e);
+ return new JLabel("No new plugins available", CENTER);
+ }
+ }
+
+ private Component createInstalledPluginsPanel() {
+ try {
+ List<Plugin> installedPlugins = pluginManager.getInstalledPlugins();
+ if (installedPlugins.size() == 0)
+ return new JLabel("No installed plugins", CENTER);
+
+ JPanel panel = new JPanel(new ListLayout());
+ for (Plugin plugin : installedPlugins)
+ panel.add(new InstalledPluginPanel(plugin));
+ return scrolled(panel);
+ } catch (PluginException e) {
+ return new JLabel("No installed plugins", CENTER);
+ }
+ }
+
+ private Component createUpdatePluginsPanel() {
+ try {
+ List<PluginVersions> pluginUpdates = pluginManager
+ .getPluginUpdates();
+ if (pluginUpdates.size() == 0)
+ return new JLabel("All plugins are up to date", CENTER);
+
+ JPanel panel = new JPanel(new ListLayout());
+ for (PluginVersions plugin : pluginUpdates)
+ panel.add(new UpdatePluginPanel(plugin, pluginManager));
+ return scrolled(panel);
+ } catch (PluginException e) {
+ return new JLabel("All plugins are up to date", CENTER);
+ }
+ }
+
+ private final class ListLayout implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension preferredLayoutSize = new Dimension(0, 1);
+ for (Component component : parent.getComponents()) {
+ Dimension preferredSize = component.getPreferredSize();
+ preferredLayoutSize.width = max(preferredSize.width,
+ preferredLayoutSize.width);
+ preferredLayoutSize.height = preferredSize.height
+ + preferredLayoutSize.height - 1;
+ }
+ return preferredLayoutSize;
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ Dimension minimumLayoutSize = new Dimension(0, 1);
+ for (Component component : parent.getComponents()) {
+ Dimension minimumSize = component.getMinimumSize();
+ minimumLayoutSize.width = max(minimumSize.width,
+ minimumLayoutSize.width);
+ minimumLayoutSize.height = minimumSize.height
+ + minimumLayoutSize.height - 1;
+ }
+ return minimumLayoutSize;
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ int y = 0;
+ for (Component component : parent.getComponents()) {
+ component.setLocation(0, y);
+ component.setSize(parent.getSize().width,
+ component.getPreferredSize().height);
+ y += component.getHeight() - 1;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java
new file mode 100644
index 0000000..27822a6
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl;
+
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+
+import javax.swing.JDialog;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+/**
+ * @author David Withers
+ */
+public class PluginManagerView implements EventHandler {
+ private JDialog dialog;
+ private PluginManagerPanel pluginManagerPanel;
+ private PluginManager pluginManager;
+
+ public void showDialog() {
+ getDialog().setVisible(true);
+ getPluginManagerPanel().checkForUpdates();
+ }
+
+ private JDialog getDialog() {
+ if (dialog == null) {
+ dialog = new JDialog(getMainWindow(), "Plugin Manager");
+ dialog.add(getPluginManagerPanel());
+ dialog.setSize(700, 500);
+ dialog.setLocationRelativeTo(dialog.getOwner());
+ dialog.setVisible(true);
+ }
+ return dialog;
+ }
+
+ private PluginManagerPanel getPluginManagerPanel() {
+ if (pluginManagerPanel == null)
+ pluginManagerPanel = new PluginManagerPanel(pluginManager);
+ return pluginManagerPanel;
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ pluginManagerPanel.initialize();
+ pluginManagerPanel.revalidate();
+ }
+
+ public void setPluginManager(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java
new file mode 100644
index 0000000..c156630
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl;
+
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.NORTHWEST;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tavernaCogs64x64Icon;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicButtonUI;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class PluginPanel extends JPanel {
+ @SuppressWarnings("unused")
+ private static final int logoSize = 64;
+
+ private JLabel descriptionLabel;
+ private JLabel descriptionTitle;
+ private JButton actionButton;
+
+ public PluginPanel(String name, String organization, String version,
+ String description) {
+ setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = NORTHWEST;
+ gbc.insets.left = 10;
+ gbc.insets.right = 10;
+ gbc.insets.top = 10;
+ gbc.insets.bottom = 10;
+
+ gbc.gridx = 0;
+ gbc.weightx = 0;
+ gbc.gridheight = 4;
+ JLabel logo = new JLabel(tavernaCogs64x64Icon);
+ add(logo, gbc);
+
+ gbc.gridx = 2;
+ gbc.anchor = CENTER;
+ actionButton = new JButton(getPluginAction());
+ add(actionButton, gbc);
+
+ gbc.gridx = 1;
+ gbc.weightx = 1;
+ gbc.gridheight = 1;
+ gbc.insets.top = 7;
+ gbc.insets.bottom = 0;
+ gbc.anchor = NORTHWEST;
+ JLabel nameLabel = new JLabel(name);
+ nameLabel.setFont(getFont().deriveFont(BOLD));
+ add(nameLabel, gbc);
+
+ gbc.insets.top = 0;
+ add(new JLabel(organization), gbc);
+
+ add(new JLabel("Version " + version), gbc);
+
+ JButton information = new JButton(new InfoAction());
+ information.setFont(information.getFont().deriveFont(BOLD));
+ information.setUI(new BasicButtonUI());
+ information.setBorder(null);
+ add(information, gbc);
+
+ descriptionTitle = new JLabel("Description");
+ descriptionTitle.setFont(getFont().deriveFont(BOLD));
+ descriptionLabel = new JLabel("<html>" + description);
+
+ setBorder(new PluginBorder());
+ }
+
+ private void showInformation() {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = NORTHWEST;
+ gbc.insets.left = 10;
+ gbc.insets.right = 10;
+ gbc.insets.bottom = 10;
+ gbc.gridx = 0;
+ gbc.gridwidth = 3;
+
+ add(descriptionTitle, gbc);
+ add(descriptionLabel, gbc);
+ revalidate();
+ }
+
+ private void hideInformation() {
+ remove(descriptionTitle);
+ remove(descriptionLabel);
+ revalidate();
+ }
+
+ public abstract Action getPluginAction();
+
+ class InfoAction extends AbstractAction {
+ private boolean showInformation = true;
+
+ public InfoAction() {
+ putValue(NAME, "Show information");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (showInformation) {
+ showInformation();
+ putValue(NAME, "Hide information");
+ showInformation = false;
+ } else {
+ hideInformation();
+ putValue(NAME, "Show information");
+ showInformation = true;
+ }
+ }
+ }
+
+ class PluginBorder implements Border {
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(getBackground().darker());
+ g.drawLine(x, y, x + width, y);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 0, 0, 0);
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java
new file mode 100644
index 0000000..bcd427f
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class UpdatePluginPanel extends PluginPanel {
+ private final PluginVersions pluginVersions;
+ private final PluginManager pluginManager;
+
+ public UpdatePluginPanel(PluginVersions pluginVersions,
+ PluginManager pluginManager) {
+ super(pluginVersions.getName(), pluginVersions.getOrganization(),
+ pluginVersions.getLatestVersion().getVersion(), pluginVersions
+ .getDescription());
+ this.pluginVersions = pluginVersions;
+ this.pluginManager = pluginManager;
+ }
+
+ @Override
+ public Action getPluginAction() {
+ return new AbstractAction("Update") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setEnabled(false);
+ putValue(NAME, "Updating");
+ boolean succeeded = doUpdate();
+ putValue(NAME, succeeded ? "Updated" : "Failed to update");
+ }
+ };
+ }
+
+ private boolean doUpdate() {
+ try {
+ pluginManager.updatePlugin(pluginVersions);
+ return true;
+ } catch (PluginException e) {
+ // FIXME Log exception properly
+ e.printStackTrace();
+ return false;
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java
new file mode 100644
index 0000000..6d132de
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.plugin.impl.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.plugin.impl.PluginManagerView;
+
+public class PluginMenuAction extends AbstractMenuAction {
+ private static final URI ADVANCED_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+ private PluginManagerView pluginManagerView;
+
+ public PluginMenuAction() {
+ super(ADVANCED_MENU_URI, 1100);
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ protected Action createAction() {
+ return new AbstractAction("Plugin Manager") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ pluginManagerView.showDialog();
+ }
+ };
+ }
+
+ public void setPluginManagerView(PluginManagerView pluginManagerView) {
+ this.pluginManagerView = pluginManagerView;
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml
new file mode 100644
index 0000000..5e5b207
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="PluginMenuAction" auto-export="interfaces" />
+
+ <service ref="pluginManagerView" auto-export="interfaces">
+ <service-properties value-type="java.lang.String[]">
+ <beans:entry key="event.topics"
+ value="uk/org/taverna/commons/plugin/PluginManager/*" />
+ </service-properties>
+ </service>
+
+ <reference id="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml
new file mode 100644
index 0000000..2634fb3
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="PluginMenuAction"
+ class="net.sf.taverna.t2.workbench.plugin.impl.menu.PluginMenuAction">
+ <property name="pluginManagerView" ref="pluginManagerView" />
+ </bean>
+
+ <bean id="pluginManagerView" class="net.sf.taverna.t2.workbench.plugin.impl.PluginManagerView">
+ <property name="pluginManager" ref="pluginManager" />
+ </bean>
+</beans>
diff --git a/taverna-workbench-plugins-gui/pom.xml b/taverna-workbench-plugins-gui/pom.xml
new file mode 100644
index 0000000..0468c67
--- /dev/null
+++ b/taverna-workbench-plugins-gui/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>plugins-gui</artifactId>
+ <name>Raven plugin manager GUI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>0.1.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-api</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java
new file mode 100644
index 0000000..0ada996
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: AddPluginSiteFrame.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 8 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+@SuppressWarnings("serial")
+public class AddPluginSiteFrame extends HelpEnabledDialog {
+
+ private JPanel jContentPane = null;
+ private JButton okButton = null;
+ private JButton cancelButton = null;
+ private JTextField urlText = null;
+ private JTextField nameText = null;
+
+ private String name = null;
+ private String url = null;
+
+ /**
+ * This method initializes
+ *
+ */
+ public AddPluginSiteFrame(JDialog parent) {
+ super(parent,"Add plugin site", true);
+ initialize();
+ this.getRootPane().setDefaultButton(okButton);
+ }
+
+
+ /**
+ * This method initializes this
+ *
+ */
+ private void initialize() {
+ this.setSize(new Dimension(350, 140));
+ this.setContentPane(getJContentPane());
+
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ if (jContentPane == null) {
+ jContentPane = new JPanel();
+
+ GridBagConstraints gridBagContraintHeading = new GridBagConstraints();
+ gridBagContraintHeading.ipadx = 10;
+ gridBagContraintHeading.ipady = 5;
+ gridBagContraintHeading.gridx = 0;
+ gridBagContraintHeading.gridy = 0;
+ gridBagContraintHeading.gridwidth = 2;
+ gridBagContraintHeading.anchor = GridBagConstraints.CENTER;
+ gridBagContraintHeading.fill = GridBagConstraints.BOTH;
+
+ GridBagConstraints gridBagContraintNameLabel = new GridBagConstraints();
+
+ gridBagContraintNameLabel.ipadx = 10;
+ gridBagContraintNameLabel.ipady = 5;
+ gridBagContraintNameLabel.gridx = 0;
+ gridBagContraintNameLabel.gridy = 1;
+ gridBagContraintNameLabel.gridwidth = 1;
+ gridBagContraintNameLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintNameLabel.fill = GridBagConstraints.NONE;
+
+ GridBagConstraints gridBagContraintURLLabel = new GridBagConstraints();
+
+ gridBagContraintURLLabel.ipadx = 10;
+ gridBagContraintURLLabel.ipady = 5;
+ gridBagContraintURLLabel.gridx = 0;
+ gridBagContraintURLLabel.gridy = 2;
+ gridBagContraintURLLabel.gridwidth = 1;
+ gridBagContraintURLLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintURLLabel.fill = GridBagConstraints.NONE;
+
+
+
+
+ GridBagConstraints gridBagContraintNameText = new GridBagConstraints();
+ gridBagContraintNameText.ipadx = 10;
+ gridBagContraintNameText.ipady = 5;
+ gridBagContraintNameText.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintNameText.fill = GridBagConstraints.HORIZONTAL;
+ gridBagContraintNameText.gridx = 1;
+ gridBagContraintNameText.gridy = 1;
+ gridBagContraintNameText.weightx = 0.1;
+
+ GridBagConstraints gridBagContraintURLText = new GridBagConstraints();
+ gridBagContraintURLText.ipadx = 10;
+ gridBagContraintURLText.ipady = 5;
+ gridBagContraintURLText.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintURLText.fill = GridBagConstraints.HORIZONTAL;
+ gridBagContraintURLText.gridx = 1;
+ gridBagContraintURLText.gridy = 2;
+ gridBagContraintURLText.weightx = 0.1;
+
+
+ GridBagConstraints gridBagContraintButtons = new GridBagConstraints();
+ gridBagContraintButtons.gridwidth=2;
+ gridBagContraintButtons.ipadx = 10;
+ gridBagContraintButtons.ipady = 5;
+ gridBagContraintButtons.anchor = GridBagConstraints.SOUTH;
+ gridBagContraintButtons.fill = GridBagConstraints.BOTH;
+ gridBagContraintButtons.weightx = 0;
+ gridBagContraintButtons.weighty = 0.2;
+ gridBagContraintButtons.gridy = 3;
+ gridBagContraintButtons.gridx = 0;
+
+ JLabel name = new JLabel("Site Name:");
+ name.setHorizontalAlignment(SwingConstants.RIGHT);
+ JLabel url = new JLabel("Site URL:");
+ url.setHorizontalAlignment(SwingConstants.RIGHT);
+
+ urlText=new JTextField("http://");
+ nameText=new JTextField();
+
+
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(new JLabel("Enter update site name and url"),gridBagContraintHeading);
+ jContentPane.add(name, gridBagContraintNameLabel);
+ jContentPane.add(url, gridBagContraintURLLabel);
+ jContentPane.add(nameText, gridBagContraintNameText);
+ jContentPane.add(urlText, gridBagContraintURLText);
+ jContentPane.add(getButtonPanel(), gridBagContraintButtons);
+
+ }
+ return jContentPane;
+ }
+
+ public JPanel getButtonPanel() {
+ return new ButtonPanel(getOKButton(),getCancelButton());
+ }
+
+ public JButton getOKButton() {
+ if (okButton==null) {
+ okButton=new JButton("OK");
+ final ActionListener okAction = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ name=nameText.getText();
+ url=urlText.getText();
+ setVisible(false);
+ dispose();
+ }
+ };
+ okButton.addActionListener(okAction);
+ okButton.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyPressed(java.awt.event.KeyEvent evt) {
+ if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
+ okAction.actionPerformed(null);
+ }
+ }
+ });
+ }
+ return okButton;
+ }
+
+ public JButton getCancelButton() {
+ if (cancelButton==null) {
+ cancelButton=new JButton("Cancel");
+ final ActionListener cancelAction = new ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ };
+ cancelButton.addActionListener(cancelAction);
+ cancelButton.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyPressed(java.awt.event.KeyEvent evt) {
+ if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
+ cancelAction.actionPerformed(null);
+ }
+ }
+ });
+ }
+ return cancelButton;
+ }
+
+
+ public String getName() {
+ if (name!=null) name=name.trim();
+ return name;
+ }
+
+
+ public String getUrl() {
+ if (url!=null) url=url.trim();
+ if (!url.endsWith("/")) url+="/";
+ return url;
+ }
+
+} // @jve:decl-index=0:visual-constraint="73,21"
+
+
+@SuppressWarnings("serial")
+class ButtonPanel extends JPanel {
+ public ButtonPanel(JButton ok, JButton cancel) {
+ super(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.ipadx = 5;
+ c.gridy = GridBagConstraints.RELATIVE;
+ c.fill = GridBagConstraints.BOTH;
+ add(ok);
+ add(cancel);
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java
new file mode 100644
index 0000000..c40cfcf
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java
@@ -0,0 +1,143 @@
+/**
+ *
+ */
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.GraphicsEnvironment;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JOptionPane;
+
+import org.apache.log4j.Logger;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.io.FileUtils;
+
+import net.sf.taverna.raven.appconfig.ApplicationConfig;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.spi.Profile;
+import net.sf.taverna.raven.spi.ProfileFactory;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ *
+ * This class looks for a notice on the myGrid website that is later than the
+ * one (if any) in the application directory. It then displays the notice. This
+ * is intended to allow simple messages to be sent to all users.
+ *
+ * @author alanrw
+ *
+ */
+public class CheckForNoticeStartupHook implements StartupSPI {
+
+ private static Logger logger = Logger
+ .getLogger(CheckForNoticeStartupHook.class);
+
+ private static final String LAST_NOTICE_CHECK_FILE_NAME = "last_notice";
+
+
+ private static File checkForUpdatesDirectory = CheckForUpdatesStartupHook
+ .getCheckForUpdatesDirectory();
+ private static File lastNoticeCheckFile = new File(checkForUpdatesDirectory,
+ LAST_NOTICE_CHECK_FILE_NAME);
+
+ private static String pattern = "EEE, dd MMM yyyy HH:mm:ss Z";
+ private static SimpleDateFormat format = new SimpleDateFormat(pattern);
+
+ private static Profile profile = ProfileFactory.getInstance().getProfile();
+ private static String version = profile.getVersion();
+
+ private static String BASE_URL = "http://www.mygrid.org.uk/taverna/updates";
+ private static String SUFFIX = "notice";
+
+ private static int TIMEOUT = 5000;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.StartupSPI#positionHint()
+ */
+ public int positionHint() {
+ return 95;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.StartupSPI#startup()
+ */
+ public boolean startup() {
+
+ if (GraphicsEnvironment.isHeadless()) {
+ return true; // if we are running headlessly just return
+ }
+
+ long noticeTime = -1;
+ long lastCheckedTime = -1;
+
+ HttpClient client = new HttpClient();
+ client.setConnectionTimeout(TIMEOUT);
+ client.setTimeout(TIMEOUT);
+ PluginManager.setProxy(client);
+ String message = null;
+
+ try {
+ URI noticeURI = new URI(BASE_URL + "/" + version + "/" + SUFFIX);
+ HttpMethod method = new GetMethod(noticeURI.toString());
+ int statusCode = client.executeMethod(method);
+ if (statusCode != HttpStatus.SC_OK) {
+ logger.warn("HTTP status " + statusCode + " while getting "
+ + noticeURI);
+ return true;
+ }
+ String noticeTimeString = null;
+ Header h = method.getResponseHeader("Last-Modified");
+ message = method.getResponseBodyAsString();
+ if (h != null) {
+ noticeTimeString = h.getValue();
+ noticeTime = format.parse(noticeTimeString).getTime();
+ logger.info("NoticeTime is " + noticeTime);
+ }
+
+ } catch (URISyntaxException e) {
+ logger.error("URI problem", e);
+ return true;
+ } catch (IOException e) {
+ logger.info("Could not read notice", e);
+ } catch (ParseException e) {
+ logger.error("Could not parse last-modified time", e);
+ }
+
+ if (lastNoticeCheckFile.exists()) {
+ lastCheckedTime = lastNoticeCheckFile.lastModified();
+ }
+
+ if ((message != null) && (noticeTime != -1)) {
+ if (noticeTime > lastCheckedTime) {
+ // Show the notice dialog
+ JOptionPane.showMessageDialog(null, message, "Taverna notice",
+ JOptionPane.INFORMATION_MESSAGE,
+ WorkbenchIcons.tavernaCogs64x64Icon);
+ try {
+ FileUtils.touch(lastNoticeCheckFile);
+ } catch (IOException e) {
+ logger.error("Unable to touch file", e);
+ }
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java
new file mode 100644
index 0000000..3849c1b
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * 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.raven.plugins.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * Dialog that lets user know that there are updates available.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+public class CheckForUpdatesDialog extends HelpEnabledDialog {
+
+ private Logger logger = Logger.getLogger(CheckForUpdatesDialog.class);
+
+ public CheckForUpdatesDialog(){
+ super((Frame)null, "Updates available", true);
+ initComponents();
+ }
+
+ // For testing
+ public static void main (String[] args){
+ CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+ dialog.setVisible(true);
+ }
+
+ private void initComponents() {
+ // Base font for all components on the form
+ Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+
+ // Message saying that updates are available
+ JPanel messagePanel = new JPanel(new BorderLayout());
+ messagePanel.setBorder(new CompoundBorder(new EmptyBorder(10,10,10,10), new EtchedBorder(EtchedBorder.LOWERED)));
+ JLabel message = new JLabel(
+ "<html><body>Updates are available for some Taverna components. To review and <br>install them go to 'Updates and plugins' in the 'Advanced' menu.</body><html>");
+ message.setFont(baseFont.deriveFont(12f));
+ message.setBorder(new EmptyBorder(5,5,5,5));
+ message.setIcon(UpdatesAvailableIcon.updateIcon);
+ messagePanel.add(message, BorderLayout.CENTER);
+
+ // Buttons
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ JButton okButton = new JButton("OK"); // we'll check for updates again in 2 weeks
+ okButton.setFont(baseFont);
+ okButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e) {
+ okPressed();
+ }
+ });
+
+ buttonsPanel.add(okButton);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(messagePanel, BorderLayout.CENTER);
+ getContentPane().add(buttonsPanel, BorderLayout.SOUTH);
+
+ pack();
+ setResizable(false);
+ // Center the dialog on the screen (we do not have the parent)
+ Dimension dimension = getToolkit().getScreenSize();
+ Rectangle abounds = getBounds();
+ setLocation((dimension.width - abounds.width) / 2,
+ (dimension.height - abounds.height) / 2);
+ setSize(getPreferredSize());
+ }
+
+ protected void okPressed() {
+ try {
+ FileUtils.touch(CheckForUpdatesStartupHook.lastUpdateCheckFile);
+ } catch (IOException ioex) {
+ logger.error("Failed to touch the 'Last update check' file for Taverna updates.", ioex);
+ }
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java
new file mode 100644
index 0000000..6e8df5f
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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.raven.plugins.ui;
+
+import java.io.File;
+import java.util.Date;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+
+/**
+ * Startup hook for checking if there are available updates for Taverna plugins.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class CheckForUpdatesStartupHook implements StartupSPI {
+
+ public static final String CHECK_FOR_UPDATES_DIRECTORY_NAME = "updates";
+ public static final String LAST_UPDATE_CHECK_FILE_NAME = "last_update_check";
+
+ private PluginManager pluginManager;
+ private ApplicationConfiguration applicationConfiguration;
+
+ public static File checkForUpdatesDirectory = getCheckForUpdatesDirectory();
+ public static File lastUpdateCheckFile = new File(checkForUpdatesDirectory,
+ LAST_UPDATE_CHECK_FILE_NAME);
+
+ public int positionHint() {
+ return 90;
+ }
+
+ public boolean startup() {
+
+ // Check if more than 2 weeks passed since we checked for updates.
+ if (lastUpdateCheckFile.exists()) {
+ long lastModified = lastUpdateCheckFile.lastModified();
+ long now = new Date().getTime();
+
+ if (now - lastModified < 14 * 24 * 3600 * 1000) { // 2 weeks have not passed since we
+ // last asked
+ return true;
+ } else { // Check again for updates
+ if (pluginManager.checkForUpdates()) {
+ CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+ dialog.setVisible(true);
+ }
+ return true;
+ }
+ } else {
+ // If we are here - then this is the first time to check for updates
+ if (pluginManager.checkForUpdates()) {
+ CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+ dialog.setVisible(true);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Gets the registration directory where info about registration will be saved to.
+ */
+ public File getCheckForUpdatesDirectory() {
+
+ File home = applicationConfiguration.getApplicationHomeDir();
+
+ File registrationDirectory = new File(home, CHECK_FOR_UPDATES_DIRECTORY_NAME);
+ if (!registrationDirectory.exists()) {
+ registrationDirectory.mkdir();
+ }
+ return registrationDirectory;
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java
new file mode 100644
index 0000000..094f25e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginListCellRenderer.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 28 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.AbstractBorder;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+/**
+ *
+ * @author David Withers
+ */
+public class PluginListCellRenderer extends JPanel implements ListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private PluginManager pluginManager;
+
+ private JLabel name = null;
+
+ private JLabel description = null;
+
+ private JLabel version = null;
+
+ private JLabel status = null;
+ private JLabel status2 = null;
+
+ /**
+ * This is the default constructor
+ */
+ public PluginListCellRenderer(PluginManager pluginManager) {
+ super();
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ * @return void
+ */
+ private void initialize() {
+ GridBagConstraints gridBagStatus = new GridBagConstraints();
+ gridBagStatus.gridx = 0;
+ gridBagStatus.gridwidth = 2;
+ gridBagStatus.anchor = GridBagConstraints.NORTHWEST;
+ gridBagStatus.insets = new Insets(3, 3, 3, 3);
+ gridBagStatus.gridy = 2;
+
+ GridBagConstraints gridBagStatus2 = new GridBagConstraints();
+ gridBagStatus2.gridx = 0;
+ gridBagStatus2.gridwidth = 2;
+ gridBagStatus2.anchor = GridBagConstraints.NORTHWEST;
+ gridBagStatus2.insets = new Insets(3, 3, 3, 3);
+ gridBagStatus2.gridy = 3;
+
+ status = new JLabel();
+ status.setFont(getFont().deriveFont(Font.BOLD));
+ status.setForeground(Color.BLUE);
+ status.setText("status");
+ status2 = new JLabel();
+ status2.setFont(getFont().deriveFont(Font.BOLD));
+ status2.setForeground(Color.RED);
+ status2.setText("Status");
+
+
+ GridBagConstraints gridBagVersion = new GridBagConstraints();
+ gridBagVersion.gridx = 1;
+ gridBagVersion.insets = new Insets(3, 8, 3, 3);
+ gridBagVersion.anchor = GridBagConstraints.NORTHWEST;
+ gridBagVersion.fill = GridBagConstraints.NONE;
+ gridBagVersion.gridy = 0;
+
+ version = new JLabel();
+ version.setFont(getFont().deriveFont(Font.PLAIN));
+ version.setText("Version");
+
+ GridBagConstraints gridBagDescription = new GridBagConstraints();
+ gridBagDescription.gridx = 0;
+ gridBagDescription.anchor = GridBagConstraints.NORTHWEST;
+ gridBagDescription.fill = GridBagConstraints.HORIZONTAL;
+ gridBagDescription.weightx = 1.0;
+ gridBagDescription.insets = new Insets(3, 3, 3, 3);
+ gridBagDescription.gridwidth = 2;
+ gridBagDescription.gridy = 1;
+ description = new JLabel();
+ description.setFont(getFont().deriveFont(Font.PLAIN));
+ description.setText("Plugin description");
+
+ GridBagConstraints gridBagName = new GridBagConstraints();
+ gridBagName.gridx = 0;
+ gridBagName.anchor = GridBagConstraints.NORTHWEST;
+ gridBagName.fill = GridBagConstraints.NONE;
+ gridBagName.weightx = 0.0;
+ gridBagName.ipadx = 0;
+ gridBagName.insets = new Insets(3, 3, 3, 3);
+ gridBagName.gridwidth = 1;
+ gridBagName.gridy = 0;
+ name = new JLabel();
+ name.setFont(getFont().deriveFont(Font.BOLD));
+ name.setText("Plugin name");
+
+ this.setSize(297, 97);
+ this.setLayout(new GridBagLayout());
+ this.setBorder(new AbstractBorder() {
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Color oldColor = g.getColor();
+ g.setColor(Color.LIGHT_GRAY);
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ g.setColor(oldColor);
+ }
+ });
+ this.add(name, gridBagName);
+ this.add(description, gridBagDescription);
+ this.add(version, gridBagVersion);
+ this.add(status, gridBagStatus);
+ this.add(status2,gridBagStatus2);
+ }
+
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ if (isSelected) {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ } else {
+ setBackground(list.getBackground());
+ setForeground(list.getForeground());
+ }
+
+ if (value instanceof Plugin) {
+ Plugin plugin = (Plugin) value;
+ name.setText(plugin.getName());
+ version.setText(plugin.getVersion());
+ description.setText("<html>"+plugin.getDescription());
+
+ status2.setText("");
+ if (!plugin.isCompatible()) {
+ status2.setText("This plugin is incompatible.");
+ }
+
+ status.setText("");
+ if (pluginManager.isUpdateAvailable(plugin)) {
+ status.setText("An update is available for this plugin");
+ } else if (!plugin.isEnabled()) {
+ status.setText("This plugin is disabled");
+ }
+ }
+ return this;
+ }
+} // @jve:decl-index=0:visual-constraint="10,10"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java
new file mode 100644
index 0000000..5da76b3
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginListModel.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 28 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import javax.swing.AbstractListModel;
+
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class PluginListModel extends AbstractListModel implements PluginManagerListener {
+ private PluginManager pluginManager;
+
+ private static Logger logger = Logger.getLogger(PluginListModel.class);
+
+ public PluginListModel(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ PluginManager.addPluginManagerListener(this);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.ListModel#getElementAt(int)
+ */
+ public Object getElementAt(int index) {
+ return pluginManager.getPlugins().get(index);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.ListModel#getSize()
+ */
+ public int getSize() {
+ return pluginManager.getPlugins().size();
+ }
+
+ public void pluginAdded(PluginManagerEvent event) {
+ fireIntervalAdded(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginRemoved(PluginManagerEvent event) {
+ fireIntervalRemoved(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginUpdated(PluginManagerEvent event) {
+ //fireContentsChanged(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginStateChanged(PluginManagerEvent event) {
+ fireContentsChanged(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginIncompatible(PluginManagerEvent event) {
+
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java
new file mode 100644
index 0000000..f9f374e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginManagerFrame.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 27 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+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.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * GUI component for the <code>PluginManager</code>.
+ *
+ * @author David Withers
+ */
+public class PluginManagerFrame extends HelpEnabledDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private JPanel jContentPane = null;
+
+ private JButton updateButton = null;
+
+ private JButton findPluginsButton = null;
+
+ private PluginManager pluginManager;
+
+ private JScrollPane jScrollPane = null;
+
+ private JList jList = null;
+
+ private JButton enableButton = null;
+
+ private JButton uninstallButton = null;
+
+ private JButton findUpdatesButton = null;
+
+ private JButton closeButton = null;
+
+ private PluginManagerListener managerListener;
+
+ /**
+ * This is the default constructor
+ */
+ public PluginManagerFrame(PluginManager pluginManager) {
+ this((Frame)null, pluginManager);
+ }
+
+ /**
+ * This is the default constructor
+ */
+ public PluginManagerFrame(Frame parent,PluginManager pluginManager) {
+ super(parent, "Plugin manager", true);
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ /**
+ * This is the default constructor
+ */
+ public PluginManagerFrame(JDialog parent,PluginManager pluginManager) {
+ super(parent, "Plugin manager", true);
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ * @return void
+ */
+ private void initialize() {
+ this.setSize(613, 444);
+ this.setContentPane(getJContentPane());
+ this.setTitle("Updates and plugins");
+ this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ this.addWindowListener(new WindowAdapter() {
+
+ @Override
+ public void windowClosed(WindowEvent e) {
+ if (managerListener!=null) PluginManager.removePluginManagerListener(managerListener);
+ }
+
+ });
+ managerListener = new PluginManagerListener() {
+
+ public void pluginAdded(PluginManagerEvent event) {
+ // For some reason even if a plugin does not declare dependencies to system
+ // artifacts it is required for Taverna to be restarted to pick the new plugin up
+ // (probably because things like Service Panel and Perspectives where new plugins can
+ // have some effect are not listening to plugin changes).
+ // So, we have to show the "Restart Taverna" message in any case until this is fixed.
+ //if (event.getPlugin().getProfile().getSystemArtifacts().size()!=0) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this,"The plugin '"+event.getPlugin().getName()+"' will not be fully functional until Taverna is restarted","Restart Required", JOptionPane.WARNING_MESSAGE);
+ //}
+ }
+
+ public void pluginStateChanged(PluginManagerEvent event)
+ {
+ // As in the pluginAdded() method, it is currently always required
+ // to restart Taverna for any changes to the plugins to take effect
+ // (probably because things like Service Panel and Perspectives where
+ // changes to plugin can have some effect are not listening to these changes).
+ // So, we have to show the "Restart Taverna" message in any case until this is fixed.
+ //if (event.getPlugin().getProfile().getSystemArtifacts().size()!=0) {
+ if (event.getSource() instanceof PluginEvent) {
+ PluginEvent pluginEvent = (PluginEvent)event.getSource();
+ if (pluginEvent.getAction()==PluginEvent.ENABLED) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely enabled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+ else if (pluginEvent.getAction()==PluginEvent.DISABLED) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely disabled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ //}
+ }
+
+ public void pluginIncompatible(PluginManagerEvent event) {
+
+ }
+
+ public void pluginUpdated(PluginManagerEvent event) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely updated until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+
+ public void pluginRemoved(PluginManagerEvent event) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely uninstalled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+
+ };
+ PluginManager.addPluginManagerListener(managerListener);
+
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ if (jContentPane == null) {
+ GridBagConstraints findUpdatesConstraints = new GridBagConstraints();
+ findUpdatesConstraints.gridx = 0;
+ findUpdatesConstraints.insets = new Insets(5, 5, 5, 5);
+ findUpdatesConstraints.gridy = 3;
+ GridBagConstraints uninstallButtonConstraints = new GridBagConstraints();
+ uninstallButtonConstraints.gridx = 2;
+ uninstallButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+ uninstallButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+ uninstallButtonConstraints.insets = new Insets(5, 0, 0, 5);
+ uninstallButtonConstraints.gridy = 1;
+ GridBagConstraints enableButtonConstraints = new GridBagConstraints();
+ enableButtonConstraints.gridx = 2;
+ enableButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+ enableButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+ enableButtonConstraints.insets = new Insets(5, 0, 0, 5);
+ enableButtonConstraints.gridy = 0;
+ GridBagConstraints scrollPaneConstraints = new GridBagConstraints();
+ scrollPaneConstraints.fill = GridBagConstraints.BOTH;
+ scrollPaneConstraints.gridy = 0;
+ scrollPaneConstraints.weightx = 1.0;
+ scrollPaneConstraints.weighty = 1.0;
+ scrollPaneConstraints.gridwidth = 2;
+ scrollPaneConstraints.insets = new Insets(5, 5, 5, 5);
+ scrollPaneConstraints.gridx = 0;
+ scrollPaneConstraints.gridheight = 3;
+ scrollPaneConstraints.anchor = GridBagConstraints.NORTHWEST;
+ GridBagConstraints findPluginsConstraints = new GridBagConstraints();
+ findPluginsConstraints.gridx = 1;
+ findPluginsConstraints.anchor = GridBagConstraints.WEST;
+ findPluginsConstraints.insets = new Insets(5, 5, 5, 5);
+ findPluginsConstraints.gridy = 3;
+ GridBagConstraints updateButtonConstraints = new GridBagConstraints();
+ updateButtonConstraints.gridx = 2;
+ updateButtonConstraints.gridwidth = 1;
+ updateButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+ updateButtonConstraints.insets = new Insets(5, 0, 0, 5);
+ updateButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+ updateButtonConstraints.gridy = 2;
+
+ GridBagConstraints closeButtonConstraints = new GridBagConstraints();
+ closeButtonConstraints.gridx = 2;
+ closeButtonConstraints.insets = new Insets(5, 5, 5, 5);
+ closeButtonConstraints.gridy = 3;
+ closeButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+// closeButtonConstraints.gridx = 2;
+// closeButtonConstraints.gridwidth = 1;
+// closeButtonConstraints.anchor = GridBagConstraints.SOUTHEAST;
+// closeButtonConstraints.insets = new Insets(5, 0, 0, 5);
+// closeButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+// closeButtonConstraints.gridy = 3;
+
+
+ jContentPane = new JPanel();
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(getUpdateButton(), updateButtonConstraints);
+ jContentPane.add(getFindPluginsButton(), findPluginsConstraints);
+ jContentPane.add(getJScrollPane(), scrollPaneConstraints);
+ jContentPane.add(getEnableButton(), enableButtonConstraints);
+ jContentPane.add(getUninstallButton(), uninstallButtonConstraints);
+ jContentPane.add(getFindUpdatesButton(), findUpdatesConstraints);
+ jContentPane.add(getCloseButton(),closeButtonConstraints);
+ }
+ return jContentPane;
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getUpdateButton() {
+ if (updateButton == null) {
+ updateButton = new JButton();
+ updateButton.setText("Update");
+ updateButton.setEnabled(false);
+ updateButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = getJList().getSelectedValue();
+ if (selectedObject instanceof Plugin) {
+ pluginManager.updatePlugin((Plugin) selectedObject);
+ }
+ jList.setSelectedValue(selectedObject, true);
+ updateButton.setEnabled(false);
+ }
+ });
+ }
+ return updateButton;
+ }
+
+ /**
+ * This method initializes jScrollPane
+ *
+ * @return javax.swing.JScrollPane
+ */
+ private JScrollPane getJScrollPane() {
+ if (jScrollPane == null) {
+ jScrollPane = new JScrollPane();
+ jScrollPane.setViewportView(getJList());
+ }
+ return jScrollPane;
+ }
+
+ /**
+ * This method initializes jList
+ *
+ * @return javax.swing.JList
+ */
+ private JList getJList() {
+ if (jList == null) {
+ jList = new JList();
+ jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ jList.setModel(new PluginListModel(pluginManager));
+ jList.setSelectionBackground(new Color(135,206,250)); //LightSkyBlue
+ jList.setCellRenderer(new PluginListCellRenderer(pluginManager));
+ jList.addListSelectionListener(new ListSelectionListener() {
+
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ respondToSelectedPlugin();
+ }
+ }
+
+ });
+ if (jList.getComponentCount() > 0) {
+ jList.setSelectedIndex(0);
+ respondToSelectedPlugin();
+ }
+ }
+ return jList;
+ }
+
+ /**
+ * This method initializes jButton2
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getEnableButton() {
+ if (enableButton == null) {
+ enableButton = new JButton();
+ enableButton.setText("Enable");
+ enableButton.setEnabled(false);
+ enableButton.setActionCommand("enable");
+ enableButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = jList.getSelectedValue();
+ if (selectedObject instanceof Plugin) {
+ Plugin plugin = (Plugin) selectedObject;
+ if ("enable".equals(e.getActionCommand())) {
+ plugin.setEnabled(true);
+ enableButton.setText("Disable");
+ enableButton.setActionCommand("disable");
+ } else if ("disable".equals(e.getActionCommand())) {
+ plugin.setEnabled(false);
+ enableButton.setText("Enable");
+ enableButton.setActionCommand("enable");
+ }
+ }
+ jList.setSelectedValue(selectedObject, true);
+ }
+ });
+ }
+ return enableButton;
+ }
+
+ /**
+ * This method initializes jButton3
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getUninstallButton() {
+ if (uninstallButton == null) {
+ uninstallButton = new JButton();
+ uninstallButton.setText("Uninstall");
+ uninstallButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ int index = jList.getSelectedIndex();
+ Object selectedObject = jList.getSelectedValue();
+ if (selectedObject instanceof Plugin) {
+ pluginManager.removePlugin((Plugin) selectedObject);
+ pluginManager.savePlugins();
+ }
+ int listSize = jList.getModel().getSize();
+ if (listSize > index) {
+ jList.setSelectedIndex(index);
+ } else {
+ jList.setSelectedIndex(listSize - 1);
+ }
+ }
+ });
+ }
+ return uninstallButton;
+ }
+
+ private JButton getCloseButton() {
+ if (closeButton==null) {
+ closeButton = new JButton("Close");
+ closeButton.setEnabled(true);
+ closeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ });
+ }
+ return closeButton;
+ }
+
+ /**
+ * This method initializes jButton1
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getFindPluginsButton() {
+ if (findPluginsButton == null) {
+ findPluginsButton = new JButton();
+ findPluginsButton.setText("Find New Plugins");
+ findPluginsButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = getJList().getSelectedValue();
+ PluginSiteFrame pluginSiteFrame = new PluginSiteFrame(PluginManagerFrame.this);
+ pluginSiteFrame.setLocationRelativeTo(PluginManagerFrame.this);
+ pluginSiteFrame.setVisible(true);
+ if (selectedObject != null) {
+ jList.setSelectedValue(selectedObject, true);
+ } else {
+ jList.setSelectedIndex(0);
+ }
+ }
+ });
+ }
+ return findPluginsButton;
+ }
+
+ /**
+ * This method initializes jButton4
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getFindUpdatesButton() {
+ if (findUpdatesButton == null) {
+ findUpdatesButton = new JButton();
+ findUpdatesButton.setText("Find Updates");
+ findUpdatesButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = getJList().getSelectedValue();
+ if (!pluginManager.checkForUpdates()) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "No updates available");
+ }
+ if (selectedObject != null) {
+ jList.setSelectedValue(selectedObject, true);
+ } else {
+ jList.setSelectedIndex(0);
+ }
+ // Respond to selected plugin - i.e. enable/disable action buttons as appropriate
+ respondToSelectedPlugin();
+ }
+ });
+ }
+ return findUpdatesButton;
+ }
+
+ private void respondToSelectedPlugin() {
+ Object selectedObject = jList.getSelectedValue();
+ if (selectedObject!=null && selectedObject instanceof Plugin) {
+ Plugin plugin = (Plugin) selectedObject;
+
+ // If this is a build-in plugin - set the text of the enableButton
+ // to 'Disable' but also disable the button to indicate that
+ // built-in plugins cannot be disabled.
+ // Similarly, uninstallButton should be disabled in this case.
+ if (plugin.isBuiltIn()){
+ getEnableButton().setText("Disable");
+ getEnableButton().setActionCommand("disable");
+ getEnableButton().setEnabled(false);
+ }
+ else{
+ if (plugin.isEnabled()) {
+ getEnableButton().setText("Disable");
+ getEnableButton().setActionCommand("disable");
+ } else {
+ getEnableButton().setText("Enable");
+ getEnableButton().setActionCommand("enable");
+ }
+
+ //only allow plugin to be enabled if it is compatible.
+ if (plugin.isCompatible()) {
+ getEnableButton().setEnabled(true);
+ }
+ else {
+ getEnableButton().setEnabled(false);
+ }
+ }
+
+ if (pluginManager.isUpdateAvailable(plugin)) {
+ getUpdateButton().setEnabled(true);
+ } else {
+ getUpdateButton().setEnabled(false);
+ }
+
+ //disable the uninstall button if this is a built in plugin
+ getUninstallButton().setEnabled(!plugin.isBuiltIn());
+ }
+ }
+
+} // @jve:decl-index=0:visual-constraint="33,9"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java
new file mode 100644
index 0000000..c952e9c
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginRepositoryListener.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 7 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import javax.swing.JProgressBar;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.raven.RavenException;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.repository.Artifact;
+import net.sf.taverna.raven.repository.ArtifactStatus;
+import net.sf.taverna.raven.repository.DownloadStatus;
+import net.sf.taverna.raven.repository.RepositoryListener;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class PluginRepositoryListener implements
+ RepositoryListener {
+
+ private final JProgressBar bar = new JProgressBar();
+
+ private static Logger logger = Logger
+ .getLogger(PluginRepositoryListener.class);
+
+ public PluginRepositoryListener() {
+ bar.setMaximum(100);
+ bar.setMinimum(0);
+ bar.setStringPainted(true);
+ }
+
+ public void statusChanged(final Artifact a, ArtifactStatus oldStatus,
+ ArtifactStatus newStatus) {
+
+ bar.setString(a.getArtifactId() + "-" + a.getVersion() + " : "
+ + newStatus.toString());
+
+ if (newStatus.equals(ArtifactStatus.JarFetching)) {
+
+ final DownloadStatus dls;
+ try {
+ dls = PluginManager.getInstance().getRepository()
+ .getDownloadStatus(a);
+ } catch (RavenException ex) {
+ logger.warn("Could not get download status for: " + a, ex);
+ return;
+ }
+
+ Thread progressThread = new Thread(new Runnable() {
+ public void run() {
+ while (true) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ logger.warn("Progress thread interrupted", e);
+ return;
+ }
+ int progress = Math.min(100, (dls.getReadBytes() * 100)
+ / dls.getTotalBytes());
+ setProgress(progress);
+ if (dls.isFinished()) {
+ return;
+ }
+ }
+ }
+ }, "Update repository progress bar");
+ progressThread.start();
+ }
+ }
+
+
+ public JProgressBar getProgressBar() {
+ return bar;
+ }
+
+ public void setVisible(final boolean val) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ bar.setVisible(val);
+ }
+ });
+
+ }
+
+ public void setProgress(final int percentage) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (percentage > 0) {
+ bar.setValue(percentage);
+ } else {
+ bar.setValue(0);
+ }
+ }
+ });
+
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java
new file mode 100644
index 0000000..ecdb06e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java
@@ -0,0 +1,542 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginSiteFrame.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/10/27 13:39:56 $
+ * by $Author: stain $
+ * Created on 29 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Font;
+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.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.border.EtchedBorder;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+
+/**
+ *
+ * @author David Withers
+ */
+public class PluginSiteFrame extends HelpEnabledDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private PluginManager pluginManager;
+
+ private List<PluginSite> pluginSites;
+
+ private List<Plugin> installationScheduled = new ArrayList<Plugin>(); // @jve:decl-index=0:
+
+ private JButton installButton = null; // @jve:decl-index=0:visual-constraint="446,247"
+
+ private JButton cancelButton = null;
+
+ private JButton addSiteButton = null;
+
+ private Map<Plugin, PluginRepositoryListener> listeners = new HashMap<Plugin, PluginRepositoryListener>();
+
+ private AddPluginSiteFrame addSiteFrame = null;
+
+ private JScrollPane scrollPane = null;
+
+ /**
+ * This is the default constructor
+ */
+ public PluginSiteFrame(Frame owner) {
+ super(owner, "Update sites", true);
+ initialize();
+ }
+
+ /**
+ * This is the default constructor
+ */
+ public PluginSiteFrame(JDialog owner) {
+ super(owner, "Update sites", true);
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ * @return void
+ */
+ private void initialize() {
+ this.pluginManager = PluginManager.getInstance();
+ this.pluginSites = this.pluginManager.getPluginSites();
+ this.setSize(600, 450);
+ this.setContentPane(getJContentPane());
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ GridBagConstraints gridBagConstraints14 = new GridBagConstraints();
+ gridBagConstraints14.gridx = 1;
+ gridBagConstraints14.anchor = GridBagConstraints.SOUTHWEST;
+ gridBagConstraints14.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints14.insets = new Insets(5, 5, 5, 5);
+ GridBagConstraints gridBagConstraints12 = new GridBagConstraints();
+ gridBagConstraints12.fill = GridBagConstraints.BOTH;
+ gridBagConstraints12.gridx = 0;
+ gridBagConstraints12.gridy = 0;
+ gridBagConstraints12.weightx = 1.0;
+ gridBagConstraints12.weighty = 1.0;
+ gridBagConstraints12.gridwidth = 3;
+ gridBagConstraints12.anchor = GridBagConstraints.NORTHWEST;
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints.anchor = GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new Insets(5, 5, 5, 5);
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.gridx = 2;
+ gridBagConstraints1.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints1.anchor = GridBagConstraints.SOUTHEAST;
+ gridBagConstraints1.insets = new Insets(5, 5, 5, 5);
+ JPanel jContentPane = new JPanel();
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(getJScrollPane(), gridBagConstraints12);
+ jContentPane.add(getInstallButton(), gridBagConstraints);
+ jContentPane.add(getCloseButton(), gridBagConstraints1);
+ jContentPane.add(getAddPluginSiteButton(), gridBagConstraints14);
+ return jContentPane;
+ }
+
+ /**
+ * This method initializes jScrollPane
+ *
+ * @return javax.swing.JScrollPane
+ */
+ private JScrollPane getJScrollPane() {
+ scrollPane = new JScrollPane();
+ scrollPane.setViewportView(getJPanel());
+ return scrollPane;
+ }
+
+ /**
+ * This method initializes jPanel
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJPanel() {
+ JPanel jPanel = new JPanel();
+ jPanel.setLayout(new GridBagLayout());
+ for (PluginSite pluginSite : pluginSites) {
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 0.0;
+ gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new Insets(5, 5, 0, 5);
+ jPanel.add(getJPanel1(pluginSite), gridBagConstraints);
+ }
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.fill = GridBagConstraints.BOTH;
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints.weighty = 1.0;
+ jPanel.add(new JPanel(), gridBagConstraints);
+ return jPanel;
+ }
+
+ private JPanel getJPanel1(final PluginSite pluginSite) {
+ final JPanel pluginSitePanel = new JPanel();
+ pluginSitePanel.setBackground(Color.WHITE);
+ pluginSitePanel.setLayout(new GridBagLayout());
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = GridBagConstraints.WEST;
+ gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.insets = new Insets(5, 5, 5, 5);
+ gridBagConstraints.ipadx = 5;
+ gridBagConstraints.ipady = 5;
+ gridBagConstraints.weightx = 1.0;
+ //ShadedLabel siteNameLabel = getSiteLabel(pluginSite);
+ JLabel siteNameLabel = getSiteLabel(pluginSite);
+
+ pluginSitePanel.add(siteNameLabel, gridBagConstraints);
+
+ final GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.gridx = 0;
+ gridBagConstraints1.gridy = 1;
+ gridBagConstraints1.anchor = GridBagConstraints.WEST;
+ gridBagConstraints1.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints1.gridwidth = 2;
+ gridBagConstraints1.insets = new Insets(5, 5, 5, 5);
+ gridBagConstraints1.weightx = 1.0;
+ final JProgressBar progressBar = new JProgressBar();
+ progressBar.setIndeterminate(true);
+ progressBar.setStringPainted(true);
+ progressBar.setString("Checking for new plugins");
+ pluginSitePanel.add(progressBar, gridBagConstraints1);
+
+ new Thread("Checking update site " + pluginSite) {
+ public void run() {
+ try {
+ List<Plugin> plugins = pluginManager
+ .getUninstalledPluginsFromSite(pluginSite);
+ if (plugins.size() > 0) {
+ Collections.sort(plugins, new Comparator<Plugin>() {
+
+ public int compare(Plugin o1, Plugin o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+
+ });
+
+ pluginSitePanel.remove(progressBar);
+ int gridY = 0;
+ for (Plugin plugin : plugins) {
+ gridY++;
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.gridx = 0;
+ gridBagConstraints1.gridy = gridY;
+ gridBagConstraints1.anchor = GridBagConstraints.WEST;
+ gridBagConstraints1.insets = new Insets(5, 20, 0, 0);
+ pluginSitePanel.add(getJCheckBox(plugin),
+ gridBagConstraints1);
+
+ gridY++;
+
+ GridBagConstraints gridBagConstraintsDescription = new GridBagConstraints();
+ gridBagConstraintsDescription.gridx = 0;
+ gridBagConstraintsDescription.ipadx = 50;
+ gridBagConstraintsDescription.gridy = gridY;
+ gridBagConstraintsDescription.anchor = GridBagConstraints.WEST;
+ gridBagConstraintsDescription.insets = new Insets(5, 25, 10, 5);
+
+ gridY++;
+
+ GridBagConstraints gridBagConstraintsProgress = new GridBagConstraints();
+ gridBagConstraintsProgress.gridx = 0;
+ gridBagConstraintsProgress.gridy = gridY;
+ gridBagConstraintsProgress.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraintsProgress.anchor = GridBagConstraints.WEST;
+ gridBagConstraintsProgress.insets = new Insets(5, 5, 0, 0);
+
+ JLabel description = new JLabel();
+ description.setText("<html>"+plugin.getDescription());
+ description.setFont(getFont().deriveFont(Font.PLAIN));
+ pluginSitePanel.add(description,gridBagConstraintsDescription);
+
+ PluginRepositoryListener progress = new PluginRepositoryListener();
+ listeners.put(plugin, progress);
+ progress.setVisible(false);
+
+ pluginSitePanel.add(progress.getProgressBar(),
+ gridBagConstraintsProgress);
+ }
+ } else {
+ pluginSitePanel.remove(progressBar);
+ pluginSitePanel.add(new JLabel(
+ "This update site contains no new plugins"),
+ gridBagConstraints1);
+ }
+ } catch (Exception e) {
+ pluginSitePanel.remove(progressBar);
+ pluginSitePanel.add(new JLabel(
+ "Unable to contact the update site"),
+ gridBagConstraints1);
+ } finally {
+ pluginSitePanel.revalidate();
+ pluginSitePanel.repaint();
+ }
+ }
+ }.start();
+
+ pluginSitePanel.setBorder(new EtchedBorder());
+ return pluginSitePanel;
+ }
+
+ private JLabel getSiteLabel(final PluginSite pluginSite) {
+ //ShadedLabel result = new ShadedLabel(pluginSite.getName(),Color.LIGHT_GRAY);
+ JLabel result = new JLabel(pluginSite.getName());
+
+
+ //add popup remove option, except on the Taverna main plugin site.
+ if (!(pluginSite instanceof TavernaPluginSite)) {
+ result.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ popup(e);
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ popup(e);
+ }
+
+ private void popup(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ JPopupMenu menu=new JPopupMenu();
+ //JMenuItem item = new JMenuItem("Remove site",TavernaIcons.deleteIcon);
+ JMenuItem item = new JMenuItem("Remove site");
+
+ menu.add(item);
+ menu.show(e.getComponent(), e.getX(), e.getY());
+ item.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ int response=JOptionPane.showConfirmDialog(PluginSiteFrame.this, "Are you sure you want to remove the update site:"+pluginSite.getName(),"Remove update site",JOptionPane.YES_NO_OPTION);
+ if (response==JOptionPane.YES_OPTION) {
+ pluginManager.removePluginSite(pluginSite);
+ pluginManager.savePluginSites();
+ scrollPane.setViewportView(getJPanel());
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JCheckBox getJCheckBox(Plugin plugin) {
+ PluginCheckBox checkBox = new PluginCheckBox();
+ checkBox.plugin = plugin;
+ checkBox.setText(plugin.getName() + " " + plugin.getVersion());
+ checkBox.setOpaque(false);
+ checkBox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() instanceof PluginCheckBox) {
+ PluginCheckBox checkBox = (PluginCheckBox) e.getSource();
+ if (checkBox.isSelected()) {
+ installationScheduled.add(checkBox.plugin);
+
+
+
+ getInstallButton().setEnabled(true);
+ } else {
+ installationScheduled.remove(checkBox.plugin);
+ if (installationScheduled.size() == 0) {
+ getInstallButton().setEnabled(false);
+ }
+ }
+ }
+ }
+
+ });
+ return checkBox;
+ }
+
+ private final Thread getUpdateRepositoryThread() {
+ return new Thread("Update Repository Progress") {
+
+ public void run() {
+ cancelButton.setEnabled(false);
+ for (int i = 0; i < installationScheduled.size(); i++) {
+ final Plugin plugin = installationScheduled.get(i);
+ PluginRepositoryListener listener = listeners.get(plugin);
+ if (listener != null) {
+ pluginManager.getRepository().addRepositoryListener(
+ listener);
+ listener.getProgressBar().setVisible(true);
+ }
+
+ pluginManager.addPlugin(plugin);
+
+ if (listener != null) {
+ pluginManager.getRepository().removeRepositoryListener(
+ listener);
+ listener.getProgressBar().setVisible(false);
+ }
+ plugin.setEnabled(true);
+
+ }
+ pluginManager.savePlugins();
+ installationScheduled.clear();
+ setVisible(false);
+ dispose();
+ }
+
+ };
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getInstallButton() {
+ if (installButton == null) {
+ installButton = new JButton();
+ installButton.setText("Install");
+ installButton.setEnabled(false);
+ installButton
+ .addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ installButton.setEnabled(false);
+ getUpdateRepositoryThread().start();
+ }
+
+ });
+ }
+ return installButton;
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getCloseButton() {
+ if (cancelButton == null) {
+ cancelButton = new JButton();
+ cancelButton.setText("Close");
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ });
+ }
+ return cancelButton;
+ }
+
+ @SuppressWarnings("serial")
+ class PluginCheckBox extends JCheckBox {
+ public Plugin plugin;
+ }
+
+ private final void addPluginSite() {
+
+ addSiteFrame=new AddPluginSiteFrame(this);
+
+ addSiteFrame.setLocationRelativeTo(this);
+ addSiteFrame.setVisible(true);
+
+ if (addSiteFrame.getName()!=null) {
+ if (addSiteFrame.getName().length()==0) {
+ JOptionPane.showMessageDialog(this, "You must provide a name for your site.","Error adding update site",JOptionPane.ERROR_MESSAGE);
+ addPluginSite();
+ }
+ else {
+ if (addSiteFrame.getUrl()!=null) {
+ try {
+ PluginSite site = new PluginSite(addSiteFrame.getName(), new URL(addSiteFrame.getUrl()));
+ pluginManager.addPluginSite(site);
+ pluginManager.savePluginSites();
+
+ //refresh
+ scrollPane.setViewportView(getJPanel());
+ addSiteFrame=null; //so that the name and url are reset.
+ }
+ catch(Exception e) {
+ JOptionPane.showMessageDialog(this, "There was a problem adding the site you provided: "+e.getMessage(),"Error adding update site",JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getAddPluginSiteButton() {
+ if (addSiteButton == null) {
+ addSiteButton = new JButton();
+ addSiteButton.setText("Add update site");
+ addSiteButton.setEnabled(true);
+ addSiteButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ addSiteButton.setEnabled(false);
+ addPluginSite();
+ addSiteButton.setEnabled(true);
+ }
+ });
+
+ }
+ return addSiteButton;
+ }
+
+} // @jve:decl-index=0:visual-constraint="10,10"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java
new file mode 100644
index 0000000..a0b15bf
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: UpdatesAvailableIcon.java,v $
+ * Revision $Revision: 1.5 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/12/01 12:32:40 $
+ * by $Author: alaninmcr $
+ * Created on 12 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Component;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+
+import org.apache.log4j.Logger;
+
+/**
+ * A JLabel that periodically checks for updates, running on a daemon thread. If
+ * updates are available it makes itself visible and responds to click events to
+ * display the appropriate update response.
+ *
+ * Also acts as a pluginmanager listener to refresh itself whenever a new plugin
+ * is added.
+ *
+ * @author Stuart Owen
+ *
+ */
+
+@SuppressWarnings("serial")
+public class UpdatesAvailableIcon extends JLabel implements PluginManagerListener {
+
+ private UpdatePluginsMouseAdaptor updatePluginMouseAdaptor = new UpdatePluginsMouseAdaptor();
+ private static Logger logger = Logger.getLogger(UpdatesAvailableIcon.class);
+
+ private final int CHECK_INTERVAL = 1800000; // every 30 minutes
+
+ public static final Icon updateIcon = new ImageIcon(
+ UpdatesAvailableIcon.class.getResource("update.png"));
+ public static final Icon updateRecommendedIcon = new ImageIcon(
+ UpdatesAvailableIcon.class.getResource("updateRecommended.png"));
+
+ public UpdatesAvailableIcon() {
+ super();
+ setVisible(false);
+
+ startCheckThread();
+ PluginManager.addPluginManagerListener(this);
+ }
+
+ public void pluginAdded(PluginManagerEvent event) {
+ logger.info("Plugin Added");
+ if (!isVisible())
+ checkForUpdates();
+ }
+
+ public void pluginStateChanged(PluginManagerEvent event) {
+
+ }
+
+ public void pluginUpdated(PluginManagerEvent event) {
+ logger.info("Plugin Updated");
+ }
+
+ public void pluginRemoved(PluginManagerEvent event) {
+ logger.info("Plugin Removed");
+ if (isVisible())
+ checkForUpdates();
+ }
+
+ public void pluginIncompatible(PluginManagerEvent event) {
+ logger
+ .warn("Plugin found to be incompatible with the current version of Taverna: "
+ + event.getPlugin());
+ }
+
+ private void startCheckThread() {
+ Thread checkThread = new Thread("Check for updates thread") {
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ checkForUpdates();
+ Thread.sleep(CHECK_INTERVAL);
+ } catch (InterruptedException e) {
+ logger.warn("Interruption exception in checking for updates thread",
+ e);
+ }
+ }
+ }
+ };
+ checkThread.setDaemon(true); // daemon so that taverna will stop the
+ // thread and close on exit.
+ checkThread.start();
+ }
+
+ private Object updateLock = new Object();
+
+ private void checkForUpdates() {
+ synchronized (updateLock) {
+ if (pluginUpdateAvailable()) {
+ logger.info("Plugin update available");
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ // TODO Auto-generated method stub
+ setToolTipText("Plugin updates are available");
+
+ setVisible(true);
+ setIcon(updateIcon);
+ if (!Arrays.asList(getMouseListeners()).contains(
+ updatePluginMouseAdaptor)) {
+ addMouseListener(updatePluginMouseAdaptor);
+ }
+
+ }
+ });
+ } catch (InterruptedException e) {
+ logger.error("Could not check for updates", e);
+ } catch (InvocationTargetException e) {
+ logger.error("Could not check for updates", e);
+ }
+ } else {
+ setToolTipText("");
+ setVisible(false);
+
+ }
+ }
+
+ }
+
+ private boolean pluginUpdateAvailable() {
+ return PluginManager.getInstance().checkForUpdates();
+ }
+
+ private final class UpdatePluginsMouseAdaptor extends MouseAdapter {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ // FIXME: this assumes the button is on the toolbar.
+ Component parent = UpdatesAvailableIcon.this.getParent()
+ .getParent();
+
+ final PluginManagerFrame pluginManagerUI = new PluginManagerFrame(
+ PluginManager.getInstance());
+ pluginManagerUI.setLocationRelativeTo(parent);
+ pluginManagerUI.setVisible(true);
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java
new file mode 100644
index 0000000..b0c036c
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: ProfileVersionCellRenderer.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:52:06 $
+ * by $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.AbstractBorder;
+
+import net.sf.taverna.raven.profile.ProfileHandler;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.spi.Profile;
+import net.sf.taverna.raven.spi.ProfileFactory;
+
+public class ProfileVersionCellRenderer extends JPanel implements
+ ListCellRenderer {
+
+ private JLabel name;
+ private JLabel version;
+ private JLabel description;
+ private String currentVersion;
+
+ public ProfileVersionCellRenderer() {
+ super();
+ initialise();
+ }
+
+ private void initialise() {
+ Profile currentProfile = ProfileFactory.getInstance().getProfile();
+ if (currentProfile!=null) {
+ currentVersion=currentProfile.getVersion();
+ }
+ else {
+ currentVersion="UNKNOWN";
+ }
+ GridBagConstraints gridBagVersion = new GridBagConstraints();
+ gridBagVersion.gridx = 1;
+ gridBagVersion.insets = new Insets(3, 8, 3, 3);
+ gridBagVersion.anchor = GridBagConstraints.NORTHWEST;
+ gridBagVersion.fill = GridBagConstraints.NONE;
+ gridBagVersion.gridy = 0;
+ version = new JLabel();
+ version.setFont(getFont().deriveFont(Font.BOLD));
+ version.setText("Version");
+
+ GridBagConstraints gridBagDescription = new GridBagConstraints();
+ gridBagDescription.gridx = 0;
+ gridBagDescription.anchor = GridBagConstraints.NORTHWEST;
+ gridBagDescription.fill = GridBagConstraints.HORIZONTAL;
+ gridBagDescription.weightx = 1.0;
+ gridBagDescription.insets = new Insets(3, 3, 3, 3);
+ gridBagDescription.gridwidth = 2;
+ gridBagDescription.gridy = 1;
+ description = new JLabel();
+ description.setFont(getFont().deriveFont(Font.PLAIN));
+ description.setText("Plugin description");
+
+ GridBagConstraints gridBagName = new GridBagConstraints();
+ gridBagName.gridx = 0;
+ gridBagName.anchor = GridBagConstraints.NORTHWEST;
+ gridBagName.fill = GridBagConstraints.NONE;
+ gridBagName.weightx = 0.0;
+ gridBagName.ipadx = 0;
+ gridBagName.insets = new Insets(3, 3, 3, 3);
+ gridBagName.gridwidth = 1;
+ gridBagName.gridy = 0;
+ name = new JLabel();
+ name.setFont(getFont().deriveFont(Font.PLAIN));
+ name.setText("Plugin name");
+
+ this.setSize(297, 97);
+ this.setLayout(new GridBagLayout());
+ this.setBorder(new AbstractBorder() {
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Color oldColor = g.getColor();
+ g.setColor(Color.LIGHT_GRAY);
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ g.setColor(oldColor);
+ }
+ });
+ this.add(name, gridBagName);
+ this.add(description, gridBagDescription);
+ this.add(version, gridBagVersion);
+ }
+
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ if (isSelected) {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ } else {
+ setBackground(list.getBackground());
+ setForeground(list.getForeground());
+ }
+
+ if (value instanceof ProfileVersion) {
+ ProfileVersion version = (ProfileVersion) value;
+ this.name.setText(version.getName());
+ if (version.getVersion().equalsIgnoreCase(currentVersion)) {
+ this.name.setText(version.getName()+" (Current)");
+ this.name.setForeground(Color.BLUE);
+ }
+ else {
+ this.name.setText(version.getName());
+ this.name.setForeground(Color.BLACK);
+ }
+ this.version.setText(version.getVersion());
+ this.description.setText("<html>"+version.getDescription());
+ }
+
+
+ return this;
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java
new file mode 100644
index 0000000..861ed9a
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: ProfileVersionListFrame.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:52:06 $
+ * by $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+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.io.File;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.raven.appconfig.bootstrap.RavenProperties;
+import net.sf.taverna.raven.plugins.Plugin;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.profile.ProfileHandler;
+import net.sf.taverna.raven.profile.ProfileUpdateHandler;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.spi.ProfileFactory;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ProfileVersionListFrame extends HelpEnabledDialog {
+
+ private JPanel contentPane = null;
+ private JScrollPane scrollPane = null;
+ private JList list = null;
+ private JButton switchButton = null;
+ private JButton closeButton = null;
+ private String currentVersion = null;
+
+ private static Logger logger = Logger
+ .getLogger(ProfileVersionListFrame.class);
+
+ public ProfileVersionListFrame() {
+ this(null);
+ }
+
+ public ProfileVersionListFrame(Frame parent) {
+ super(parent,"Taverna versions", true);
+ initialise();
+ }
+
+ protected JPanel getJContentPane() {
+ if (contentPane==null) {
+ GridBagConstraints scrollPaneConstraints = new GridBagConstraints();
+ scrollPaneConstraints.fill = GridBagConstraints.BOTH;
+ scrollPaneConstraints.gridy = 0;
+ scrollPaneConstraints.weightx = 1.0;
+ scrollPaneConstraints.weighty = 1.0;
+ scrollPaneConstraints.gridwidth = 2;
+ scrollPaneConstraints.insets = new Insets(5, 5, 5, 5);
+ scrollPaneConstraints.gridx = 0;
+ scrollPaneConstraints.gridheight = 3;
+
+ GridBagConstraints switchButtonConstraints = new GridBagConstraints();
+ switchButtonConstraints.gridy=0;
+ switchButtonConstraints.gridx=2;
+ switchButtonConstraints.insets = new Insets(5,5,5,5);
+ switchButtonConstraints.fill=GridBagConstraints.HORIZONTAL;
+
+ GridBagConstraints closeButtonConstraints = new GridBagConstraints();
+ closeButtonConstraints.gridy=2;
+ closeButtonConstraints.gridx=2;
+ closeButtonConstraints.insets = new Insets(5,5,5,5);
+ closeButtonConstraints.anchor=GridBagConstraints.SOUTH;
+ closeButtonConstraints.fill=GridBagConstraints.HORIZONTAL;
+
+ contentPane = new JPanel();
+ contentPane.setLayout(new GridBagLayout());
+ contentPane.add(getScrollPane(),scrollPaneConstraints);
+ contentPane.add(getSwitchButton(),switchButtonConstraints);
+ contentPane.add(getCloseButton(),closeButtonConstraints);
+
+ }
+ return contentPane;
+ }
+
+ protected JScrollPane getScrollPane() {
+ if (scrollPane==null) {
+ scrollPane=new JScrollPane();
+ scrollPane.setViewportView(getJList());
+ }
+ return scrollPane;
+ }
+
+ protected JList getJList() {
+ if (list==null) {
+ list=new JList();
+ list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ list.setModel(new ProfileVersionListModel());
+ list.setCellRenderer(new ProfileVersionCellRenderer());
+ list.addListSelectionListener(new ListSelectionListener() {
+
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ respondToSelection();
+ }
+ }
+
+ });
+ if (list.getComponentCount() > 0) {
+ list.setSelectedIndex(0);
+ respondToSelection();
+
+ }
+ }
+ return list;
+ }
+
+ protected void respondToSelection() {
+ Object selected=list.getSelectedValue();
+ if (selected!=null && selected instanceof ProfileVersion) {
+ ProfileVersion version = (ProfileVersion)selected;
+ if (currentVersion==null || version.getVersion().equals(currentVersion)) {
+ getSwitchButton().setEnabled(false);
+ }
+ else {
+ getSwitchButton().setEnabled(true);
+ }
+ }
+ }
+
+ protected JButton getSwitchButton() {
+ if (switchButton==null) {
+ switchButton=new JButton("Switch");
+ switchButton.setEnabled(true);
+ switchButton.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ Object selected = getJList().getSelectedValue();
+ if (selected!=null && selected instanceof ProfileVersion) {
+ performSwitch((ProfileVersion)selected);
+ }
+ }
+
+ });
+ }
+ return switchButton;
+ }
+
+ protected JButton getCloseButton() {
+ if (closeButton==null) {
+ closeButton=new JButton("Close");
+ closeButton.setEnabled(true);
+ closeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ });
+ }
+ return closeButton;
+ }
+
+ protected void performSwitch(ProfileVersion newVersion) {
+ List<Plugin> incompatiblePlugins = PluginManager.getInstance().getIncompatiblePlugins(newVersion.getVersion(),true);
+ if (incompatiblePlugins.size()>0) {
+ int response=JOptionPane.showConfirmDialog(this, "Some plugins will be incompatible with the new version and will be disabled. Do you wish to continue?","Confirm version switch",JOptionPane.YES_OPTION);
+ if (response!=JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+ try {
+ URL localProfile = new URL(RavenProperties.getInstance().getRavenProfileLocation());
+ URL profileList = new URL(RavenProperties.getInstance().getRavenProfileListLocation());
+
+ ProfileUpdateHandler handler=new ProfileUpdateHandler(profileList,localProfile);
+ handler.updateLocalProfile(newVersion,new File(localProfile.toURI()));
+
+ //disable plugins after everything else has been acheived
+ for (Plugin plugin : incompatiblePlugins) {
+ plugin.setEnabled(false);
+ }
+ JOptionPane.showMessageDialog(this, "You must restart taverna for the version switch to be activated");
+ }
+ catch(Exception e) {
+ logger.error("Error occurred switching to a new profile",e);
+ JOptionPane.showMessageDialog(this, "An error occurred switching to your new profile, try again later.");
+ }
+ }
+
+
+ protected void initialise() {
+ try {
+ currentVersion = ProfileFactory.getInstance().getProfile().getVersion();
+ } catch (Exception e) {
+ logger.error("Unable to determine current taverna version",e);
+ currentVersion=null;
+ }
+ setSize(600,400);
+ setContentPane(getJContentPane());
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java
new file mode 100644
index 0000000..c952413
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: ProfileVersionListModel.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:52:06 $
+ * by $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+
+import net.sf.taverna.raven.appconfig.bootstrap.RavenProperties;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.profile.ProfileVersions;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ProfileVersionListModel extends AbstractListModel {
+
+ private static Logger logger = Logger
+ .getLogger(ProfileVersionListModel.class);
+
+ private List<ProfileVersion> versions;
+
+
+ public ProfileVersionListModel() {
+ try {
+ URL url = getProfileListLocation();
+ versions = ProfileVersions.getProfileVersions(url);
+ }
+ catch(MalformedURLException e) {
+ logger.error("Error with profile list URL",e);
+ versions = new ArrayList<ProfileVersion>();
+ }
+ }
+
+ public Object getElementAt(int index) {
+ return versions.get(index);
+ }
+
+ public int getSize() {
+ return versions.size();
+ }
+
+ private URL getProfileListLocation() throws MalformedURLException{
+ return new URL(RavenProperties.getInstance().getRavenProfileListLocation());
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..a58f87a
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.raven.plugins.ui.CheckForUpdatesStartupHook
+net.sf.taverna.raven.plugins.ui.CheckForNoticeStartupHook
\ No newline at end of file
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml
new file mode 100644
index 0000000..38c21e5
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="CheckForUpdatesStartupHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+
+</beans:beans>
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml
new file mode 100644
index 0000000..33e2207
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="CheckForUpdatesStartupHook" class="net.sf.taverna.raven.plugins.ui.CheckForUpdatesStartupHook" />
+
+</beans>
diff --git a/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png
new file mode 100644
index 0000000..e3695cb
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png
Binary files differ
diff --git a/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png
new file mode 100644
index 0000000..5adc4b1
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png
Binary files differ
diff --git a/taverna-workbench-reference-ui/pom.xml b/taverna-workbench-reference-ui/pom.xml
new file mode 100644
index 0000000..3755c20
--- /dev/null
+++ b/taverna-workbench-reference-ui/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>reference-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>T2 reference manager user interface</name>
+ <description>
+ UI Support for the T2Reference system, including support for
+ construction and registration of new entities through a UI
+ component and the rendering of existing persisted entities
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>report-view</artifactId>
+ <version>${project.version}</version>
+ </dependency> -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>baclava</artifactId>
+ <version>${t2.baclava.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-run-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.databundle</groupId>
+ <artifactId>databundle</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-database-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CheckWorkflowStatus.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CheckWorkflowStatus.java
new file mode 100644
index 0000000..04ae738
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CheckWorkflowStatus.java
@@ -0,0 +1,97 @@
+package net.sf.taverna.t2.reference.ui;
+
+import static java.util.Collections.synchronizedMap;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration.BEFORE_RUN;
+import static net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration.ERRORS_OR_WARNINGS;
+import static net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration.NONE;
+import static net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration.QUERY_BEFORE_RUN;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.scufl2.api.profiles.Profile;
+import uk.org.taverna.scufl2.validation.Status;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+public class CheckWorkflowStatus {
+ private static final long RUN_ANYWAY_EXPIRE_MILLIS = 60*60*1000; // 1 hour
+ protected static Map<String, Date> runAnyways = synchronizedMap(new HashMap<String, Date>());
+
+ private static ReportManagerConfiguration reportManagerConfig;
+
+ public static boolean checkWorkflow(Profile dataflow, Workbench workbench,
+ EditManager editManager, FileManager fileManager,
+ ReportManager reportManager) {
+ synchronized (runAnyways) {
+ Date runAnyway = runAnyways.remove(dataflow.getIdentifier());
+ Date now = new Date();
+ if (runAnyway != null
+ && now.getTime() - runAnyway.getTime() < RUN_ANYWAY_EXPIRE_MILLIS) {
+ // new expiration time (remember we removed it above)
+ runAnyways.put(dataflow.getName(), new Date());
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ String beforeRunSetting = reportManagerConfig.getProperty(BEFORE_RUN);
+// ReportOnWorkflowAction action = new ReportOnWorkflowAction("",
+// dataflow, beforeRunSetting
+// .equals(ReportManagerConfiguration.FULL_CHECK), false, editManager, fileManager, reportManager, workbench);
+// if (reportManager.isReportOutdated(dataflow))
+// action.validateWorkflow();
+ if (!reportManager.isStructurallySound(dataflow)) {
+ showMessageDialog(
+ getMainWindow(),
+ "The workflow has problems and cannot be run - see reports",
+ "Workflow problems", ERROR_MESSAGE);
+ showReport(workbench);
+ return false;
+ }
+ Status status = reportManager.getStatus(dataflow);
+ String queryBeforeRun = reportManagerConfig
+ .getProperty(QUERY_BEFORE_RUN);
+ if (status.equals(Status.SEVERE) && !queryBeforeRun.equals(NONE)) {
+ Object[] options = { "View validation report", "Run anyway" };
+ if (showOptionDialog(
+ getMainWindow(),
+ "Taverna has detected problems with this workflow. "
+ + "To fix them, please check the validation report.",
+ "Workflow problems", YES_NO_OPTION, ERROR_MESSAGE, null,
+ options, options[0]) == YES_OPTION) {
+ // View validation report
+ showReport(workbench);
+ return false;
+ }
+ runAnyways.put(dataflow.getName(), new Date());
+ } else if (status.equals(Status.WARNING)
+ && queryBeforeRun.equals(ERRORS_OR_WARNINGS)) {
+ if (showConfirmDialog(getMainWindow(),
+ "The workflow has warnings but can still be run "
+ + "- do you want to proceed?", "Workflow problems",
+ YES_NO_OPTION, WARNING_MESSAGE) != YES_OPTION) {
+ showReport(workbench);
+ return false;
+ }
+ runAnyways.put(dataflow.getName(), new Date());
+ }
+ return true;
+ }
+
+ private static void showReport(Workbench workbench) {
+ workbench.makeNamedComponentVisible("reportView");
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CopyWorkflowInProgressDialog.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CopyWorkflowInProgressDialog.java
new file mode 100644
index 0000000..3fb439e
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CopyWorkflowInProgressDialog.java
@@ -0,0 +1,91 @@
+
+/*******************************************************************************
+ * Copyright (C) 2009 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.reference.ui;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * Dialog that is popped up while we are copying the workflow in preparation for
+ * the workflow execution. This is just to let the user know that Taverna is
+ * doing something.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class CopyWorkflowInProgressDialog extends HelpEnabledDialog {
+ private boolean userCancelled = false;
+
+ public CopyWorkflowInProgressDialog() {
+ super(getMainWindow(), "Initialising workflow run", true);
+ setLocationRelativeTo(null);
+ setResizable(false);
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(new EmptyBorder(10,10,10,10));
+
+ JPanel textPanel = new JPanel();
+ JLabel text = new JLabel(workingIcon);
+ text.setText("Initialising workflow run...");
+ text.setBorder(new EmptyBorder(10, 0, 10, 0));
+ textPanel.add(text);
+ panel.add(textPanel, CENTER);
+
+ // Cancel button
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ userCancelled = true;
+ setVisible(false);
+ dispose();
+ }
+ });
+ JPanel cancelButtonPanel = new JPanel();
+ cancelButtonPanel.add(cancelButton);
+ panel.add(cancelButtonPanel, SOUTH);
+
+ setContentPane(panel);
+ setPreferredSize(new Dimension(300, 130));
+
+ pack();
+ }
+
+ public boolean hasUserCancelled() {
+ return userCancelled;
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CopyWorkflowSwingWorker.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CopyWorkflowSwingWorker.java
new file mode 100644
index 0000000..b2a7b63
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/CopyWorkflowSwingWorker.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.reference.ui;
+
+import javax.swing.SwingWorker;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class CopyWorkflowSwingWorker extends
+ SwingWorker<WorkflowBundle, String> {
+ @SuppressWarnings("unused")
+ private static final Logger logger = Logger
+ .getLogger(CopyWorkflowSwingWorker.class);
+
+ private WorkflowBundle dataflowOriginal;
+
+ public CopyWorkflowSwingWorker(WorkflowBundle dataflowOriginal) {
+ this.dataflowOriginal = dataflowOriginal;
+ }
+
+ @Override
+ protected WorkflowBundle doInBackground() throws Exception {
+ @SuppressWarnings("unused")
+ WorkflowBundle dataflowCopy = null;
+ return dataflowOriginal;
+// logger.info("CopyWorkflowSwingWorker: copying of the workflow started.");
+// dataflowCopy = WorkflowBundle.cloneWorkflowBean(dataflowOriginal);
+// logger.info("CopyWorkflowSwingWorker: copying of the workflow finished.");
+// return dataflowCopy;
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/InvalidDataflowReport.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/InvalidDataflowReport.java
new file mode 100644
index 0000000..cd74614
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/InvalidDataflowReport.java
@@ -0,0 +1,95 @@
+package net.sf.taverna.t2.reference.ui;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import net.sf.taverna.t2.workflowmodel.DataflowValidationReport;
+import net.sf.taverna.t2.workflowmodel.Datalink;
+import net.sf.taverna.t2.workflowmodel.TokenProcessingEntity;
+
+// FIXME This is a t2flow-related class, not a scufl2-related one
+public class InvalidDataflowReport {
+ public static void invalidDataflow(DataflowValidationReport report) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<html><h3>Workflow failed validation due to:</h3>");
+ sb.append(constructReport(report));
+ showErrorDialog(sb.toString(), "Workflow validation report");
+ }
+
+ public static void showErrorDialog(final String message, final String title) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ showMessageDialog(null, message, title, ERROR_MESSAGE);
+ }
+ });
+ }
+
+ static String constructReport(DataflowValidationReport report) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<dl>");
+ if (report.isWorkflowIncomplete()) {
+ sb.append("<dt><b>Workflow is incomplete</b></dt>");
+ sb.append("<dt><i>(Workflow should contain at least one service or a connected workflow output port)</i>");
+ }
+ List<? extends TokenProcessingEntity> unsatisfiedEntities = report
+ .getUnsatisfiedEntities();
+ if (unsatisfiedEntities.size() > 0) {
+ sb.append("<dt><b>Invalid services</b>");
+ sb.append("<dt><i>(Due to feedback loops in the workflow or upstream errors)</i>");
+ for (TokenProcessingEntity entity : unsatisfiedEntities)
+ sb.append("<dd>" + entity.getLocalName());
+ }
+ List<? extends DataflowOutputPort> unresolvedOutputs = report
+ .getUnresolvedOutputs();
+ if (unresolvedOutputs.size() > 0) {
+ boolean foundUnconnected = false;
+ for (DataflowOutputPort dataflowOutputPort : unresolvedOutputs) {
+ Datalink dl = dataflowOutputPort.getInternalInputPort()
+ .getIncomingLink();
+ if (dl == null) {
+ if (!foundUnconnected) {
+ sb.append("<dt><b>Unconnected workflow output ports</b>");
+ sb.append("<dt><i>(Workflow output ports must be connected to a valid link)</i>");
+ foundUnconnected = true;
+ }
+ sb.append("<dd>" + dataflowOutputPort.getName());
+ }
+ }
+ }
+ List<? extends TokenProcessingEntity> failedEntities = report
+ .getFailedEntities();
+ Set<TokenProcessingEntity> invalidDataflowProcessors = report
+ .getInvalidDataflows().keySet();
+ if (failedEntities.size() > 0) {
+ boolean foundfailure = false;
+ for (TokenProcessingEntity entity : failedEntities)
+ if (!invalidDataflowProcessors.contains(entity)) {
+ if (!foundfailure) {
+ sb.append("<dt><b>Invalid list handling</b>");
+ sb.append("<dt><i>(Generally dot product with different cardinalities)</i>");
+ foundfailure = true;
+ }
+ sb.append("<dd>" + entity.getLocalName());
+ }
+ }
+
+ Set<Entry<TokenProcessingEntity, DataflowValidationReport>> invalidDataflows = report
+ .getInvalidDataflows().entrySet();
+ if (invalidDataflows.size() > 0) {
+ sb.append("<dt><b>Invalid nested workflows</b>");
+ for (Entry<TokenProcessingEntity, DataflowValidationReport> entry : invalidDataflows) {
+ sb.append("<dd>" + entry.getKey().getLocalName());
+ sb.append(constructReport(entry.getValue()));
+ }
+ }
+ sb.append("</dl>");
+ return sb.toString();
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/RegistrationPanel.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/RegistrationPanel.java
new file mode 100644
index 0000000..bd9bdef
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/RegistrationPanel.java
@@ -0,0 +1,808 @@
+/*******************************************************************************
+ * 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.reference.ui;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.Color.black;
+import static java.awt.Color.red;
+import static java.lang.Math.round;
+import static java.nio.charset.Charset.availableCharsets;
+import static javax.swing.Action.NAME;
+import static javax.swing.Box.createRigidArea;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JFileChooser.FILES_AND_DIRECTORIES;
+import static javax.swing.JSplitPane.BOTTOM;
+import static javax.swing.JSplitPane.LEFT;
+import static javax.swing.JSplitPane.RIGHT;
+import static javax.swing.JSplitPane.TOP;
+import static javax.swing.JSplitPane.VERTICAL_SPLIT;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JSplitPane;
+import javax.swing.JToolBar;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.lang.ui.ValidatingUserInputDialog;
+import net.sf.taverna.t2.reference.ui.tree.PreRegistrationTree;
+import net.sf.taverna.t2.reference.ui.tree.PreRegistrationTreeModel;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.database.DatabaseConfiguration;
+
+/**
+ * A JPanel containing a pre-registration tree along with a toolbar for adding
+ * collections, strings, files and url's directly rather than through drag and
+ * drop on the tree. Any runtime exceptions thrown within this method are
+ * trapped and displayed as error messages in the status bar.
+ *
+ * @author Tom Oinn
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class RegistrationPanel extends JPanel {
+ private static final String NO_EXAMPLE_VALUE = "No example value";
+ private static final String NO_PORT_DESCRIPTION = "No port description";
+ private static final String NEW_VALUE = "Some input data goes here";
+
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(RegistrationPanel.class);
+
+ private static final ImageIcon addFileIcon = new ImageIcon(
+ RegistrationPanel.class.getResource("/icons/topic.gif"));
+ private static final ImageIcon addListIcon = new ImageIcon(
+ RegistrationPanel.class.getResource("/icons/newfolder_wiz.gif"));
+ private static final ImageIcon addTextIcon = new ImageIcon(
+ RegistrationPanel.class.getResource("/icons/addtext_co.gif"));
+ private static final ImageIcon addUrlIcon = new ImageIcon(
+ RegistrationPanel.class.getResource("/icons/web.gif"));
+ private static final ImageIcon deleteNodeIcon = new ImageIcon(
+ RegistrationPanel.class.getResource("/icons/delete_obj.gif"));
+ public static final ImageIcon infoIcon = new ImageIcon(
+ RegistrationPanel.class.getResource("/icons/information.gif"));
+
+ // If the depth is greater than 1 we can add sub-folders to the collection
+ // structure (it doesn't make sense to do this for single depth lists or
+ // individual items). This list is initialized to contain actions to add new
+ // folders at each valid collection level
+ private final List<Action> addCollectionActions = new ArrayList<>();
+
+ private AddFileAction addFileAction = null;
+ private AddTextAction addTextAction = null;
+ private AddURLAction addUrlAction = null;
+ private DeleteNodeAction deleteNodeAction = null;
+
+ private int depth;
+ private JSplitPane splitPaneVertical;
+ private JSplitPane splitPaneHorizontal;
+ private final JLabel status;
+ private DialogTextArea descriptionArea;
+ private DialogTextArea exampleArea;
+ private DialogTextArea textArea;
+ private final PreRegistrationTree tree;
+ private final PreRegistrationTreeModel treeModel;
+ private TextAreaDocumentListener textAreaDocumentListener;
+
+ @SuppressWarnings("unused")
+ private JSpinner datatypeSpinner;
+ private JSpinner charsetSpinner;
+ @SuppressWarnings("unused")
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+ private String example;
+
+ @SuppressWarnings("unused")
+ private DatabaseConfiguration config;
+
+ private boolean exposedDatanature = false;
+
+ private static String UNKNOWN = "UNKNOWN";
+
+ private static Map<String, String> charsetNameMap = new HashMap<>();
+
+ /**
+ * Construct a new registration panel for an input with the specified depth.
+ *
+ * @param depth
+ * Depth of the POJO to construct from this panel
+ * @param example
+ * @param inputDescription
+ * @param inputName
+ */
+ public RegistrationPanel(int depth, String name, String description,
+ String example, DatabaseConfiguration databaseConfiguration) {
+ super(new BorderLayout());
+ this.depth = depth;
+ this.example = example;
+ config = databaseConfiguration;
+ tree = new PreRegistrationTree(depth, name) {
+ @Override
+ public void setStatusMessage(String message, boolean isError) {
+ setStatus(message, isError ? red : black);
+ }
+ };
+ treeModel = tree.getPreRegistrationTreeModel();
+
+ final UpdateEditorPaneOnSelection treeSelectionListener = new UpdateEditorPaneOnSelection();
+ tree.addTreeSelectionListener(treeSelectionListener);
+
+ tree.setRootVisible(false);
+
+ new JPanel(new BorderLayout());
+
+ descriptionArea = new DialogTextArea(NO_PORT_DESCRIPTION, 4, 40);
+ descriptionArea.setBorder(new TitledBorder("Port description"));
+ descriptionArea.setEditable(false);
+ descriptionArea.setLineWrap(true);
+ descriptionArea.setWrapStyleWord(true);
+
+ exampleArea = new DialogTextArea(NO_EXAMPLE_VALUE, 2, 40);
+ exampleArea.setBorder(new TitledBorder("Example value"));
+ exampleArea.setEditable(false);
+ exampleArea.setLineWrap(true);
+ exampleArea.setWrapStyleWord(true);
+
+ setDescription(description);
+ setExample(example);
+
+ JPanel annotationsPanel = new JPanel();
+ annotationsPanel.setLayout(new BoxLayout(annotationsPanel, Y_AXIS));
+ JScrollPane descriptionAreaScrollPane = new JScrollPane(descriptionArea);
+ JScrollPane exampleAreaScrollPane = new JScrollPane(exampleArea);
+ annotationsPanel.add(descriptionAreaScrollPane);
+ annotationsPanel.add(createRigidArea(new Dimension(0, 5))); // add some empty space
+ annotationsPanel.add(exampleAreaScrollPane);
+
+ JToolBar toolbar = createToolBar();
+
+ JPanel textAreaPanel = new JPanel();
+ textAreaPanel.setLayout(new BorderLayout());
+
+ textArea = new DialogTextArea();
+ textAreaDocumentListener = new TextAreaDocumentListener(textArea);
+ textArea.setEditable(false);
+
+ textAreaPanel.add(new JScrollPane(textArea), CENTER);
+// if (config.isExposeDatanature()) {
+// textAreaPanel.add(createTypeAccessory(), SOUTH);
+// exposedDatanature = true;
+// }
+
+ splitPaneHorizontal = new JSplitPane();
+ splitPaneHorizontal.add(new JScrollPane(tree), LEFT);
+ splitPaneHorizontal.add(textAreaPanel, RIGHT);
+ splitPaneHorizontal.setDividerLocation(150);
+ JPanel toolbarAndInputsPanel = new JPanel(new BorderLayout());
+ toolbarAndInputsPanel.add(splitPaneHorizontal, CENTER);
+ toolbarAndInputsPanel.add(toolbar, NORTH);
+
+ splitPaneVertical = new JSplitPane(VERTICAL_SPLIT);
+ splitPaneVertical.add(annotationsPanel, TOP);
+ splitPaneVertical.add(toolbarAndInputsPanel, BOTTOM);
+ int dividerPosition = (int) round(annotationsPanel.getPreferredSize()
+ .getHeight()) + 10;
+ splitPaneVertical.setDividerLocation(dividerPosition);
+
+ add(splitPaneVertical, CENTER);
+
+ // Listen to selections on the tree to enable or disable actions
+ tree.addTreeSelectionListener(new UpdateActionsOnTreeSelection());
+ status = new JLabel();
+ status.setOpaque(false);
+ status.setBorder(new EmptyBorder(2, 2, 2, 2));
+ setStatus("Drag to re-arrange, or drag files, URLs, or text to add",
+ null);
+ add(status, SOUTH);
+ }
+
+ private static List<String> charsetNames() {
+ List<String> result = new ArrayList<>();
+ result.add(UNKNOWN);
+
+ for (String name : availableCharsets().keySet()) {
+ String upperCase = name.toUpperCase();
+ result.add(upperCase);
+ charsetNameMap.put(upperCase, name);
+ }
+ Collections.sort(result);
+ return result;
+ }
+
+// private static SpinnerListModel datatypeModel = new SpinnerListModel(new ReferencedDataNature[] {ReferencedDataNature.UNKNOWN, ReferencedDataNature.TEXT, ReferencedDataNature.BINARY});
+// private static SpinnerListModel charsetModel = new SpinnerListModel(charsetNames());
+
+// private JPanel createTypeAccessory() {
+// JPanel result = new JPanel();
+// result.setLayout(new GridLayout(0,2));
+// result.add(new JLabel("Data type:"));
+// datatypeSpinner = new JSpinner(datatypeModel);
+// datatypeSpinner.setValue(ReferencedDataNature.UNKNOWN);
+// datatypeSpinner.setEnabled(false);
+// result.add(datatypeSpinner);
+// result.add(new JLabel("Character Set:"));
+// charsetSpinner = new JSpinner(charsetModel);
+// charsetSpinner.setValue(UNKNOWN);
+// charsetSpinner.setEnabled(false);
+// datatypeSpinner.addChangeListener(new ChangeListener() {
+//
+// @Override
+// public void stateChanged(ChangeEvent e) {
+// DefaultMutableTreeNode selectedNode = getSelectedNode();
+// Object selectedUserObject = null;
+// if (selectedNode != null) {
+// selectedUserObject = selectedNode.getUserObject();
+// }
+// ReferencedDataNature nature = (ReferencedDataNature) datatypeSpinner.getValue();
+// if (nature.equals(ReferencedDataNature.UNKNOWN)) {
+// charsetSpinner.setEnabled(false);
+// } else if (nature.equals(ReferencedDataNature.TEXT)) {
+// charsetSpinner.setEnabled(true);
+// } else if (nature.equals(ReferencedDataNature.BINARY)) {
+// charsetSpinner.setEnabled(false);
+// }
+// if (selectedUserObject instanceof FileReference) {
+// FileReference ref = (FileReference) selectedUserObject;
+// ref.setDataNature(nature);
+// }
+// }});
+// charsetSpinner.addChangeListener(new ChangeListener() {
+//
+// @Override
+// public void stateChanged(ChangeEvent e) {
+// DefaultMutableTreeNode selectedNode = getSelectedNode();
+// Object selectedUserObject = null;
+// if (selectedNode != null) {
+// selectedUserObject = selectedNode.getUserObject();
+// }
+// String cSet = (String) charsetSpinner.getValue();
+// if (selectedUserObject instanceof FileReference) {
+// FileReference ref = (FileReference) selectedUserObject;
+// if (cSet.equals(UNKNOWN)) {
+// ref.setCharset(null);
+// } else {
+// ref.setCharset(charsetNameMap.get(cSet));
+// }
+// }
+// }
+//
+// });
+//
+// result.add(charsetSpinner);
+// return result;
+// }
+
+ private JToolBar createToolBar() {
+ JToolBar toolBar = new JToolBar();
+ buildActions();
+ toolBar.setFloatable(false);
+
+ JButton comp = new JButton(deleteNodeAction);
+ comp.setToolTipText("Remove the currently selected input");
+ toolBar.add(comp);
+
+ JButton comp2 = new JButton(addTextAction);
+ if (depth == 0)
+ comp2.setToolTipText("Set the input value");
+ else
+ comp2.setToolTipText("Add a new input value");
+
+ toolBar.add(comp2);
+ JButton comp3 = new JButton(addFileAction);
+ if (depth == 0)
+ comp3.setToolTipText("Set the input value from a file");
+ else
+ comp3.setToolTipText("Add an input value from a file");
+ toolBar.add(comp3);
+
+ JButton comp4 = new JButton(addUrlAction);
+ if (depth == 0)
+ comp4.setToolTipText("Load the input value from a URL");
+ else
+ comp4.setToolTipText("Load an input value from a URL");
+ toolBar.add(comp4);
+
+ // Do lists...
+ if (!addCollectionActions.isEmpty()) {
+ if (addCollectionActions.size() == 1) {
+ // Single item, add directly
+ Action addCollectionAction = addCollectionActions.get(0);
+ addCollectionAction.putValue(NAME, "New list");
+ toolBar.add(new JButton(addCollectionAction));
+ } else {
+ // Create pop-up menu
+ final JPopupMenu menu = new JPopupMenu();
+ for (Action a : addCollectionActions)
+ menu.add(a);
+ final JButton popup = new JButton("Add list...", addListIcon);
+ popup.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ menu.show(popup, 0, popup.getHeight());
+ }
+ });
+ popup.setComponentPopupMenu(menu);
+ toolBar.add(popup);
+ }
+ }
+ // toolBar.add(Box.createHorizontalGlue());
+ return toolBar;
+ }
+
+ public int getDepth() {
+ return depth;
+ }
+
+ public Object getUserInput() {
+ return treeModel.getAsPojo();
+ }
+
+ private void buildActions() {
+ addFileAction = new AddFileAction();
+ addTextAction = new AddTextAction();
+ addUrlAction = new AddURLAction();
+ deleteNodeAction = new DeleteNodeAction();
+
+ for (int i = 1; i < treeModel.getDepth(); i++) {
+ Action addCollectionAction = new NewListAction(i);
+ addCollectionActions.add(addCollectionAction);
+ }
+ }
+
+ /**
+ * @return
+ */
+ private DefaultMutableTreeNode getSelectedNode() {
+ DefaultMutableTreeNode node = null;
+ TreePath selectionPath = tree.getSelectionPath();
+ if (selectionPath != null)
+ node = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
+ return node;
+ }
+
+ public void setStatus(String statusString, Color textColour) {
+ status.setText(statusString);
+ if (textColour != null)
+ status.setForeground(textColour);
+ else
+ status.setForeground(black);
+ }
+
+ @SuppressWarnings("unused")
+ private void setCharsetSpinner(String charsetName) {
+ if (charsetName == null) {
+ charsetSpinner.setValue(UNKNOWN);
+ return;
+ }
+ String cName = charsetName.toUpperCase();
+ if (charsetNames().contains(cName))
+ charsetSpinner.setValue(cName);
+ else
+ charsetSpinner.setValue(UNKNOWN);
+ }
+
+ private void updateEditorPane(DefaultMutableTreeNode selection) {
+ textArea.setEditable(false);
+ textAreaDocumentListener.setSelection(null);
+
+ if (selection == null) {
+ textArea.setText("No selection");
+ if (exposedDatanature) {
+// datatypeSpinner.setEnabled(false);
+// datatypeSpinner.setValue(ReferencedDataNature.UNKNOWN);
+// charsetSpinner.setEnabled(false);
+// setCharsetSpinner(null);
+ }
+ return;
+ }
+ if (!selection.isLeaf()) {
+ textArea.setText("List selected");
+ if (exposedDatanature) {
+// datatypeSpinner.setEnabled(false);
+// datatypeSpinner.setValue(ReferencedDataNature.UNKNOWN);
+// charsetSpinner.setEnabled(false);
+// setCharsetSpinner(null);
+ }
+ return;
+ }
+ Object selectedUserObject = selection.getUserObject();
+ if (selectedUserObject == null) {
+ textArea.setText("List selected");
+ if (exposedDatanature) {
+// datatypeSpinner.setEnabled(false);
+// datatypeSpinner.setValue(ReferencedDataNature.UNKNOWN);
+ }
+ return;
+ }
+ if (selectedUserObject instanceof String) {
+ textArea.setText((String) selection.getUserObject());
+ textAreaDocumentListener.setSelection(selection);
+ textArea.setEditable(true);
+ textArea.requestFocusInWindow();
+ textArea.selectAll();
+ if (exposedDatanature) {
+// datatypeSpinner.setEnabled(false);
+// datatypeSpinner.setValue(ReferencedDataNature.TEXT);
+// charsetSpinner.setEnabled(false);
+// setCharsetSpinner(UTF8.name());
+ }
+ } else if (selectedUserObject instanceof File) {
+ File ref = (File) selectedUserObject;
+ textArea.setText("File : " + ref);
+ if (exposedDatanature) {
+// datatypeSpinner.setEnabled(true);
+// datatypeSpinner.setValue(ref.getDataNature());
+// setCharsetSpinner(ref.getCharset());
+// if (ref.getDataNature().equals(ReferencedDataNature.TEXT)) {
+// charsetSpinner.setEnabled(true);
+// } else {
+// charsetSpinner.setEnabled(false);
+// }
+ }
+ } else if (selectedUserObject instanceof URL) {
+ URL ref = (URL) selectedUserObject;
+ textArea.setText("URL : " + ref);
+ if (exposedDatanature) {
+// datatypeSpinner.setEnabled(false);
+// datatypeSpinner.setValue(ref.getDataNature());
+// charsetSpinner.setEnabled(false);
+// setCharsetSpinner(ref.getCharset());
+ }
+ } else
+ textArea.setText(selection.getUserObject().toString());
+ }
+
+ private final class UpdateEditorPaneOnSelection implements
+ TreeSelectionListener {
+ TreePath oldSelectionPath = null;
+
+ public void setSelectionPath(TreePath selectionPath) {
+ if (oldSelectionPath != null) {
+ DefaultMutableTreeNode lastPathComponent = (DefaultMutableTreeNode) oldSelectionPath
+ .getLastPathComponent();
+ if (lastPathComponent != null && textArea.isEditable())
+ lastPathComponent.setUserObject(textArea.getText());
+ }
+
+ oldSelectionPath = selectionPath;
+
+ DefaultMutableTreeNode selection = null;
+ if (selectionPath != null)
+ selection = (DefaultMutableTreeNode) selectionPath
+ .getLastPathComponent();
+ updateEditorPane(selection);
+ }
+
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ setSelectionPath(e.getNewLeadSelectionPath());
+ }
+ }
+
+ public class NewListAction extends AbstractAction {
+ private final int depth;
+
+ private NewListAction(int depth) {
+ super("New list (depth " + depth + ")", addListIcon);
+ this.depth = depth;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) treeModel
+ .getRoot();
+ DefaultMutableTreeNode selection = getSelectedNode();
+ if (selection != null)
+ parent = selection;
+ @SuppressWarnings("unused")
+ DefaultMutableTreeNode added = addPojo(parent,
+ new ArrayList<Object>(), depth);
+ setStatus("Added new collection with depth " + depth, null);
+ }
+ }
+
+ public class UpdateActionsOnTreeSelection implements
+ TreeSelectionListener {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree
+ .getLastSelectedPathComponent();
+ if (selectedNode == null)
+ // Selection cleared
+ deleteNodeAction.setEnabled(false);
+ else
+ deleteNodeAction.setEnabled(selectedNode != treeModel.getRoot());
+ }
+ }
+
+ public class AddFileAction extends AbstractAction {
+ public AddFileAction() {
+ super((depth == 0 ? "Set" : "Add") + " file location...",
+ addFileIcon);
+ }
+
+ @Override
+ @SuppressWarnings("unused")
+ public void actionPerformed(ActionEvent e) {
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String currentDir = prefs.get("currentDir", System
+ .getProperty("user.home"));
+ fileChooser.setDialogTitle("Choose files or directory");
+
+ fileChooser.setCurrentDirectory(new File(currentDir));
+ fileChooser.setMultiSelectionEnabled(true);
+ fileChooser.setFileSelectionMode(FILES_AND_DIRECTORIES);
+
+ if (fileChooser.showOpenDialog(RegistrationPanel.this) != APPROVE_OPTION)
+ return;
+ prefs.put("currentDir", fileChooser.getCurrentDirectory()
+ .toString());
+ DefaultMutableTreeNode node = getSelectedNode();
+
+ for (File file : fileChooser.getSelectedFiles()) {
+ if (!file.isDirectory()) {
+ DefaultMutableTreeNode added = addPojo(node, file, 0);
+ setStatus("Added file : " + file.getPath(), null);
+ continue;
+ }
+
+ if (treeModel.getDepth() < 1) {
+ // TODO add popup warning
+ setStatus("Can't add directory to single item input", null);
+ return;
+ }
+
+ /*
+ * Try to handle directories as flat lists, don't nest any
+ * deeper for now.
+ */
+ List<File> children = new ArrayList<>();
+ for (File child : file.listFiles())
+ if (child.isFile())
+ children.add(child);
+ DefaultMutableTreeNode added = addPojo(node, children, 1);
+ setStatus("Added directory : " + file.getPath(), null);
+ }
+ }
+ }
+
+ /**
+ * Add a new default text string, adding to the root node (which will
+ * cascade down until it hits the correct level through logic in the model)
+ */
+ public class AddTextAction extends AbstractAction {
+ public AddTextAction() {
+ super((depth == 0 ? "Set" : "Add") + " value", addTextIcon);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ DefaultMutableTreeNode node = getSelectedNode();
+ String newValue;
+ if (example != null && example.length() > 0)
+ newValue = example;
+ else
+ newValue = NEW_VALUE;
+
+ @SuppressWarnings("unused")
+ DefaultMutableTreeNode added = addPojo(node, newValue, 0);
+ setStatus("Added new value. Edit value on right.", null);
+ }
+ }
+
+ private DefaultMutableTreeNode addPojo(DefaultMutableTreeNode node,
+ Object newValue, int position) {
+ DefaultMutableTreeNode added = treeModel.addPojoStructure(node, node,
+ newValue, position);
+ tree.setSelectionPath(new TreePath(added.getPath()));
+ updateEditorPane(added);
+ return added;
+ }
+
+ public class AddURLAction extends AbstractAction {
+ private static final String URL_REGEX = "http:\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-\\/]))?";
+
+ public AddURLAction() {
+ super((depth == 0 ? "Set" : "Add") + " URL ...", addUrlIcon);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String currentUrl = prefs.get("currentUrl",
+ "http://www.mygrid.org.uk/");
+
+ UrlPanel urlPanel = new UrlPanel();
+
+ ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+ "Add an http URL", urlPanel);
+ vuid.addTextComponentValidation(urlPanel.getUrlField(),
+ "Set the URL.", null, "", URL_REGEX,
+ "Not a valid http URL.");
+ vuid.setSize(new Dimension(400, 200));
+ urlPanel.setUrl(currentUrl);
+
+ if (vuid.show(RegistrationPanel.this)) {
+ String urlString = urlPanel.getUrl();
+ try {
+ URL url = new URL(urlString);
+ prefs.put("currentUrl", url.toString());
+
+ DefaultMutableTreeNode node = getSelectedNode();
+
+ @SuppressWarnings("unused")
+ DefaultMutableTreeNode added = addPojo(node, url, 0);
+ setStatus("Added URL : " + url, null);
+ } catch (MalformedURLException e1) {
+ setStatus("Invalid URL.", null);
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove any children of the currently selected node
+ */
+ public class DeleteNodeAction extends AbstractAction {
+ public DeleteNodeAction() {
+ super("Delete", deleteNodeIcon);
+ // Starts off disabled
+ setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree
+ .getSelectionPath().getLastPathComponent();
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node
+ .getParent();
+ DefaultMutableTreeNode previousChild = null;
+ if (parent != null) {
+ int index = parent.getIndex(node);
+ if (index > 0)
+ previousChild = (DefaultMutableTreeNode) parent
+ .getChildAt(index - 1);
+ }
+ treeModel.removeNodeFromParent(node);
+ if (previousChild == null)
+ tree.setSelectionPath(null);
+ else
+ tree.setSelectionPath(new TreePath(previousChild.getPath()));
+ setStatus("Deleted node", null);
+ }
+ }
+
+ private class TextAreaDocumentListener implements DocumentListener {
+ private final DialogTextArea textArea;
+ private DefaultMutableTreeNode selection;
+
+ public TextAreaDocumentListener(DialogTextArea textArea) {
+ this.textArea = textArea;
+ textArea.getDocument().addDocumentListener(this);
+ this.setSelection(null);
+ }
+
+ /**
+ * @param selection
+ * the selection to set
+ */
+ public void setSelection(DefaultMutableTreeNode selection) {
+ this.selection = selection;
+ }
+
+ private void updateSelection() {
+ if (textArea.isEditable() && selection != null) {
+ selection.setUserObject(textArea.getText());
+ treeModel.nodeChanged(selection);
+ }
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ updateSelection();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ updateSelection();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ updateSelection();
+ }
+ }
+
+ public void setDescription(String inputDescription) {
+ if (inputDescription != null)
+ descriptionArea.setText(inputDescription);
+ else
+ descriptionArea.setText(NO_PORT_DESCRIPTION);
+ descriptionArea.setCaretPosition(0);
+ }
+
+ public String getExample() {
+ return example;
+ }
+
+ public void setExample(String inputExample) {
+ this.example = inputExample;
+ if (inputExample != null)
+ exampleArea.setText(inputExample);
+ else
+ exampleArea.setText(NO_EXAMPLE_VALUE);
+ exampleArea.setCaretPosition(0);
+ }
+
+ public void setValue(Object o) {
+ addPojo(null, o, 0);
+ }
+
+ public void setValue(Object o, int depth) {
+ addPojo(null, o, depth);
+ }
+
+ public Object getValue() {
+ return treeModel.getAsPojo();
+ }
+
+ public boolean checkUserInput() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/UrlPanel.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/UrlPanel.java
new file mode 100644
index 0000000..596407c
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/UrlPanel.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.reference.ui;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.VERTICAL;
+import static java.awt.GridBagConstraints.WEST;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * UI for editing url's.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class UrlPanel extends JPanel {
+ private JTextField urlField;
+
+ public UrlPanel() {
+ super(new GridBagLayout());
+
+ urlField = new JTextField();
+
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ GridBagConstraints constraints = new GridBagConstraints();
+
+ constraints.anchor = WEST;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.ipadx = 10;
+ add(new JLabel("Name:"), constraints);
+
+ constraints.gridx = 1;
+ constraints.gridwidth = 2;
+ constraints.ipadx = 0;
+ constraints.weightx = 1d;
+ constraints.fill = HORIZONTAL;
+ add(urlField, constraints);
+
+ constraints.gridx = 0;
+ constraints.gridy = 1;
+ constraints.fill = VERTICAL;
+ constraints.weighty = 1d;
+ add(new JPanel(), constraints);
+ }
+
+ /**
+ * Returns the urlField.
+ *
+ * @return the urlField
+ */
+ public JTextField getUrlField() {
+ return urlField;
+ }
+
+ /**
+ * Returns the url.
+ *
+ * @return the url
+ */
+ public String getUrl() {
+ return urlField.getText();
+ }
+
+ /**
+ * Sets the url.
+ *
+ * @param url
+ * the url
+ */
+ public void setUrl(String url) {
+ urlField.setText(url);
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/WorkflowLaunchWindow.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/WorkflowLaunchWindow.java
new file mode 100644
index 0000000..f7469e2
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/WorkflowLaunchWindow.java
@@ -0,0 +1,626 @@
+/*******************************************************************************
+ * 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.reference.ui;
+
+import static com.hp.hpl.jena.sparql.graph.GraphFactory.makeDefaultModel;
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.BorderLayout.WEST;
+import static javax.swing.BoxLayout.X_AXIS;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon;
+import static org.apache.jena.riot.Lang.TURTLE;
+import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JToolBar;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFDataMgr;
+import org.apache.jena.riot.RIOT;
+import org.apache.log4j.Logger;
+import org.purl.wf4ever.robundle.Bundle;
+
+import uk.org.taverna.configuration.database.DatabaseConfiguration;
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.scufl2.api.annotation.Annotation;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.common.URITools;
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+import com.hp.hpl.jena.rdf.model.Model;
+import com.hp.hpl.jena.rdf.model.Property;
+import com.hp.hpl.jena.rdf.model.Resource;
+
+/**
+ * A simple workflow launch window, uses a tabbed layout to display a set of named RegistrationPanel
+ * instances, and a 'run workflow' button. Also shows a pane contining a picture of the workflow,
+ * the author and the description.
+ * We use one WorkflowLaunchWindow per workflow, multiple runs of the same workflow get the same
+ * window.
+ *
+ * @author Tom Oinn
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public abstract class WorkflowLaunchWindow extends JFrame {
+ private static final Logger logger = Logger.getLogger(WorkflowLaunchWindow.class);
+ private static final String LAUNCH_WORKFLOW = "Run workflow";
+ private static final ImageIcon launchIcon = new ImageIcon(
+ WorkflowLaunchWindow.class.getResource("/icons/start_task.gif"));
+ private static final ImageIcon addTextIcon = new ImageIcon(
+ WorkflowLaunchWindow.class.getResource("/icons/addtext_co.gif"));
+ private static final String NO_WORKFLOW_DESCRIPTION = "No description";
+ private static final String NO_WORKFLOW_AUTHOR = "No author";
+
+ /**
+ * An action enabled when all inputs are enabled and used to trigger the
+ * {@link #handleLaunch(Bundle)} method
+ */
+ private Action launchAction;
+ /**
+ * A map of input port names to input registration panels (from the previous
+ * run of the same workflow, if any)
+ */
+ private Map<String, RegistrationPanel> inputPanelMap = new HashMap<>();
+ /** A pane holding various tabs for workflow input ports */
+ private JTabbedPane tabsPane;
+ private Workflow workflow;
+ private DialogTextArea workflowDescriptionArea;
+ private DialogTextArea workflowAuthorArea;
+ private JSVGCanvas createWorkflowGraphic;
+ /**
+ * Whether the original workflow has been modified in the design perspective
+ * so we know to refresh this dialog
+ */
+ private boolean workflowModified = false;
+ private FileManager fileManager;
+ /** Observer of workflow closing events so we can dispose off the window */
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+ private EditManager editManager;
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ private JPanel overallPanel;
+ private JPanel workflowPart;
+ private JPanel portsPart;
+ @SuppressWarnings("unused")
+ private final Workbench workbench;
+ @SuppressWarnings("unused")
+ private final ReportManager reportManager;
+ private final List<ReferenceActionSPI> referenceActionSPIs;
+ private final DatabaseConfiguration databaseConfiguration;
+ private final Scufl2Tools scufl2Tools = new Scufl2Tools();
+ private final URITools uriTools = new URITools();
+
+ public WorkflowLaunchWindow(Workflow workflow, EditManager editManager,
+ FileManager fileManager, ReportManager reportManager,
+ Workbench workbench, List<ReferenceActionSPI> referenceActionSPIs,
+ DatabaseConfiguration databaseConfiguration) {
+ super();
+ // Initialize RIOT reader
+ RIOT.register();
+
+ this.workflow = workflow;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManager = reportManager;
+ this.workbench = workbench;
+ this.referenceActionSPIs = referenceActionSPIs;
+ this.databaseConfiguration = databaseConfiguration;
+
+ initComponents();
+
+ // Handle refreshing the frame when it receives focus
+ addWindowFocusListener(new WindowAdapter() {
+ @Override
+ public void windowGainedFocus(WindowEvent e) {
+ if (workflowModified) {
+ // Clear all previous components
+ getContentPane().removeAll();
+
+ // Redraw the window
+ initComponents();
+
+ overallPanel.revalidate();
+ overallPanel.repaint();
+
+ workflowModified = false;
+ }
+ }
+ });
+
+ // Handle window closing
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent winEvt) {
+ handleCancel(); // do not dispose the window, just hide it
+ }
+ });
+
+ // Start observing workflow closing events on File Manager
+ fileManager.addObserver(fileManagerObserver);
+
+ // Start observing edit workflow events on Edit Manager
+ editManager.addObserver(editManagerObserver);
+ }
+
+ /**
+ * Set the title of the window to contain the workflow name and its file/url
+ * location so that users can easily identify which workflow is being run.
+ *
+ * @param title
+ */
+ private void setWindowTitle(String title) {
+ String windowTitle = "Input values for ";
+ if (title != null && !title.isEmpty())
+ windowTitle += "'" + title + "' ";
+ else
+ // Fall back to its name
+ windowTitle += "'" + workflow.getName() + "' ";
+
+ Object workflowLocation = fileManager.getDataflowSource(workflow.getParent());
+ windowTitle += (workflowLocation == null) ? "" : "from " + workflowLocation.toString();
+ setTitle(windowTitle);
+ }
+
+ /**
+ * Draw the components of the frame.
+ */
+ public void initComponents() {
+ workflowPart = new JPanel(new GridLayout(3, 1));
+ portsPart = new JPanel(new BorderLayout());
+
+ createWorkflowGraphic = createWorkflowGraphic(workflow);
+ createWorkflowGraphic.setBorder(new TitledBorder("Diagram"));
+
+ workflowPart.add(new JScrollPane(createWorkflowGraphic));
+
+ workflowDescriptionArea = new DialogTextArea(NO_WORKFLOW_DESCRIPTION, 5, 40);
+ workflowDescriptionArea.setBorder(new TitledBorder("Workflow description"));
+ workflowDescriptionArea.setEditable(false);
+ workflowDescriptionArea.setLineWrap(true);
+ workflowDescriptionArea.setWrapStyleWord(true);
+
+ workflowPart.add(new JScrollPane(workflowDescriptionArea));
+
+ workflowAuthorArea = new DialogTextArea(NO_WORKFLOW_AUTHOR, 1, 40);
+ workflowAuthorArea.setBorder(new TitledBorder("Workflow author"));
+ workflowAuthorArea.setEditable(false);
+ workflowAuthorArea.setLineWrap(true);
+ workflowAuthorArea.setWrapStyleWord(true);
+
+ workflowPart.add(new JScrollPane(workflowAuthorArea));
+
+ launchAction = new AbstractAction(LAUNCH_WORKFLOW, launchIcon) {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ // First of all - is the workflow valid?
+ // TODO convert to Scufl2 validation
+
+ // if (!CheckWorkflowStatus.checkWorkflow(dataflowOriginal, workbench, editManager,
+ // fileManager, reportManager)) {
+ // setVisible(false);
+ // return;
+ // }
+
+ /*
+ * Check if user had entered input values for all input ports -
+ * otherwise there is no point in attempting to run the workflow
+ */
+ for (InputWorkflowPort input : workflow.getInputPorts()) {
+ RegistrationPanel registrationPanel = inputPanelMap.get(input.getName());
+ Object userInput = registrationPanel.getUserInput();
+ if (userInput == null) {
+ showMessageDialog(
+ WorkflowLaunchWindow.this,
+ "You have not provided input values for all workflow inputs",
+ "Workflow input value error", ERROR_MESSAGE);
+ // exit
+ return;
+ }
+ }
+ setState(ICONIFIED);
+
+ try {
+ Bundle inputDataBundle = createInputDataBundle();
+ handleLaunch(inputDataBundle);
+ } catch (IOException e) {
+ showMessageDialog(WorkflowLaunchWindow.this,
+ "An error occurred while creating input values\n"
+ + e.getMessage(), "Error creating inputs",
+ ERROR_MESSAGE);
+ return;
+ }
+ }
+ };
+
+ WorkflowBundle workflowBundle = workflow.getParent();
+
+ Model annotations = annotationsForBean(workflowBundle, workflow);
+ Resource bean = annotations.getResource(uriTools.uriForBean(workflow)
+ .toASCIIString());
+
+ String title = null;
+ Property titleProp = annotations
+ .createProperty("http://purl.org/dc/terms/title");
+ if (bean.hasProperty(titleProp))
+ title = bean.getProperty(titleProp).getString();
+ setWindowTitle(title);
+
+ Property descProp = annotations
+ .createProperty("http://purl.org/dc/terms/description");
+ if (bean.hasProperty(descProp))
+ setWorkflowDescription(bean.getProperty(descProp).getString());
+
+ Property creatorProp = annotations
+ .createProperty("http://purl.org/dc/elements/1.1/creator");
+ if (bean.hasProperty(creatorProp))
+ setWorkflowAuthor(bean.getProperty(creatorProp).getString());
+
+ Action useExamplesAction = new AbstractAction("Use examples", addTextIcon) {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ List<InputWorkflowPort> inputPorts = new ArrayList<>(
+ workflow.getInputPorts());
+ /*
+ * Create tabs for input ports (but only for the one that are
+ * connected!)
+ */
+ for (InputWorkflowPort inputPort : inputPorts) {
+ RegistrationPanel rp = inputPanelMap.get(inputPort
+ .getName());
+ Object example = rp.getExample();
+ if (example != null && inputPort.getDepth() == 0
+ && rp.getValue() == null)
+ rp.setValue(example);
+ }
+ }
+ };
+
+ JButton useExamplesButton = new JButton(useExamplesAction);
+ useExamplesButton
+ .setToolTipText("Use the example value (if any) for ports that you have not set a value for");
+ // Construct tool bar
+ JToolBar toolBar = new JToolBar();
+ toolBar.setFloatable(false);
+ toolBar.add(useExamplesButton);
+ toolBar.add(new JButton(launchAction));
+ toolBar.add(new JButton(new AbstractAction("Cancel", closeIcon) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleCancel();
+ }
+ }));
+
+ JToolBar loadButtonsBar = new JToolBar();
+ loadButtonsBar.setFloatable(false);
+ for (ReferenceActionSPI spi : referenceActionSPIs) {
+ ReferenceActionSPI action = (ReferenceActionSPI) spi.getAction();
+ action.setInputPanelMap(inputPanelMap);
+ JButton loadButton = new JButton((AbstractAction) action);
+ loadButtonsBar.add(loadButton);
+ }
+
+ JPanel toolBarPanel = new JPanel(new BorderLayout());
+ toolBarPanel.add(loadButtonsBar, WEST);
+ toolBarPanel.add(toolBar, EAST);
+ toolBarPanel.setBorder(new EmptyBorder(5, 10, 5, 20));
+ portsPart.add(toolBarPanel, SOUTH);
+
+ /*
+ * Construct tab container - tabs will be populated based on the wf
+ * input ports
+ */
+ tabsPane = new JTabbedPane();
+
+ List<InputWorkflowPort> inputPorts = new ArrayList<>(workflow.getInputPorts());
+ Set<String> inputNames = new HashSet<>();
+
+ /*
+ * Create tabs for input ports (but only for the one that are
+ * connected!)
+ */
+ for (InputWorkflowPort inputPort : inputPorts) {
+ // Is this input port connected to anything?
+ if (scufl2Tools.datalinksFrom(inputPort).isEmpty())
+ continue;
+ String portDescription = "";
+ String portExample = "";
+
+ annotations = annotationsForBean(workflowBundle, inputPort);
+ bean = annotations.getResource(uriTools.uriForBean(inputPort)
+ .toASCIIString());
+ if (bean.hasProperty(descProp))
+ portDescription = bean.getProperty(descProp).getString();
+ Property exDataProp = annotations
+ .createProperty("http://biocatalogue.org/attribute/exampleData");
+ if (bean.hasProperty(exDataProp))
+ portExample = bean.getProperty(exDataProp).getString();
+
+ // add tabs for wf input ports
+ String name = inputPort.getName();
+ inputNames.add(name);
+ addInput(name, inputPort.getDepth(), portDescription, portExample);
+ }
+
+ portsPart.add(tabsPane, CENTER);
+
+ workflowPart.setPreferredSize(new Dimension(300, 500));
+ portsPart.setPreferredSize(new Dimension(650, 500));
+
+ overallPanel = new JPanel();
+ overallPanel.setLayout(new BoxLayout(overallPanel, X_AXIS));
+
+ overallPanel.add(workflowPart);
+ overallPanel.add(portsPart);
+
+ setLayout(new BorderLayout());
+ getContentPane().add(new JScrollPane(overallPanel), CENTER);
+
+ pack();
+ }
+
+ private Model annotationsForBean(WorkflowBundle workflowBundle,
+ WorkflowBean bean) {
+ Model model = makeDefaultModel();
+ for (Annotation annotation : scufl2Tools.annotationsFor(bean,
+ workflowBundle)) {
+// System.out.println(annotation.getBody());
+ URI base = uriTools.uriForBean(workflowBundle);
+ URI body = base.resolve(annotation.getBody());
+// System.out.println(body);
+ URI path = uriTools.relativePath(workflowBundle.getGlobalBaseURI(),
+ body);
+// System.out.println(path.getPath());
+
+ String mediaType = workflowBundle.getResources()
+ .getResourceEntry(path.getPath()).getMediaType();
+ Lang lang = contentTypeToLang(mediaType);
+ if (lang == null)
+ // Safe fallback
+ lang = TURTLE;
+// System.out.println(mediaType);
+ try (InputStream inputStream = workflowBundle.getResources()
+ .getResourceAsInputStream(path.getPath())) {
+ RDFDataMgr.read(model, inputStream, body.toASCIIString(), lang);
+// model.read(inputStream, body.toASCIIString(), mediaType);
+ } catch (IOException e) {
+ logger.warn("Can't read " + body, e);
+ continue;
+ }
+ }
+ return model;
+ }
+
+ /**
+ * User clicked the cancel button.
+ */
+ public void cancelPressed() {
+ this.setVisible(true);
+ }
+
+ /**
+ * Creates an SVGCanvas loaded with the SVGDocument for the Dataflow.
+ *
+ * @param dataflow
+ * @return
+ */
+ private JSVGCanvas createWorkflowGraphic(Workflow worklfow) {
+ JSVGCanvas svgCanvas = new JSVGCanvas();
+ //SVGGraphController graphController = GraphViewComponent.graphControllerMap.get(workflow);
+ //if (graphController != null) {
+ // SVGDocument svgDoc = graphController.getSVGDocument();
+ // svgCanvas.setDocument((SVGDocument) svgDoc.cloneNode(true));
+ //}
+ return svgCanvas;
+ }
+
+ public synchronized void addInput(final String inputName, final int inputDepth) {
+ addInput(inputName, inputDepth, null, null);
+ }
+
+ public void addInput(final String inputName, final int inputDepth, String inputDescription,
+ String inputExample) {
+ /*
+ * Don't do anything if we already have the input registration panel for
+ * this input port
+ */
+ RegistrationPanel inputRegistrationPanel = inputPanelMap.get(inputName);
+ if ((inputRegistrationPanel == null) || (inputRegistrationPanel.getDepth() != inputDepth)) {
+ inputRegistrationPanel = new RegistrationPanel(inputDepth, inputName, inputDescription,
+ inputExample, databaseConfiguration);
+ inputPanelMap.put(inputName, inputRegistrationPanel);
+ } else {
+ inputRegistrationPanel.setStatus(
+ "Drag to re-arrange, or drag files, URLs, or text to add", null);
+ inputRegistrationPanel.setDescription(inputDescription);
+ inputRegistrationPanel.setExample(inputExample);
+ }
+ tabsPane.addTab(inputName, inputRegistrationPanel);
+ tabsPane.revalidate();
+ tabsPane.repaint();
+ }
+
+ public synchronized void removeInputTab(final String inputName) {
+ /*
+ * Only do something if we have a registration panel for this input port
+ * to begin with
+ */
+ if (!inputPanelMap.containsKey(inputName))
+ return;
+ RegistrationPanel inputRegistrationPanelToRemove = inputPanelMap
+ .remove(inputName);
+ tabsPane.remove(inputRegistrationPanelToRemove);
+ }
+
+ private Bundle createInputDataBundle() throws IOException {
+ Bundle bundle = DataBundles.createBundle();
+ Path inputs = DataBundles.getInputs(bundle);
+ for (String input : inputPanelMap.keySet()) {
+ RegistrationPanel registrationPanel = inputPanelMap.get(input);
+ Object userInput = registrationPanel.getUserInput();
+
+ Path port = DataBundles.getPort(inputs, input);
+ setValue(port, userInput);
+ }
+ return bundle;
+ }
+
+ private void setValue(Path port, Object userInput) throws IOException {
+ if (userInput instanceof File)
+ DataBundles.setReference(port, ((File) userInput).toURI());
+ else if (userInput instanceof URL)
+ try {
+ DataBundles.setReference(port, ((URL) userInput).toURI());
+ } catch (URISyntaxException e) {
+ logger.warn(String.format("Error converting %1$s to URI",
+ userInput), e);
+ }
+ else if (userInput instanceof String)
+ DataBundles.setStringValue(port, (String) userInput);
+ else if (userInput instanceof List<?>) {
+ DataBundles.createList(port);
+ List<?> list = (List<?>) userInput;
+ for (Object object : list)
+ setValue(DataBundles.newListItem(port), object);
+ } else
+ logger.warn("Unknown input type : "
+ + userInput.getClass().getName());
+ }
+
+ /**
+ * Called when the run workflow action has been performed
+ *
+ * @param workflowInputs
+ * a map of named inputs in the form of T2Reference instances
+ */
+ public abstract void handleLaunch(Bundle workflowInputs);
+
+ public abstract void handleCancel();
+
+ private static void selectTopOfTextArea(DialogTextArea textArea) {
+ textArea.setSelectionStart(0);
+ textArea.setSelectionEnd(0);
+ }
+
+ public void setWorkflowDescription(String workflowDescription) {
+ if (workflowDescription != null && !workflowDescription.isEmpty()) {
+ workflowDescriptionArea.setText(workflowDescription);
+ selectTopOfTextArea(workflowDescriptionArea);
+ }
+ }
+
+ void setWorkflowAuthor(String workflowAuthor) {
+ if (workflowAuthor != null && !workflowAuthor.isEmpty()) {
+ workflowAuthorArea.setText(workflowAuthor);
+ selectTopOfTextArea(workflowAuthorArea);
+ }
+ }
+
+ public String getWorkflowDescription() {
+ return workflowDescriptionArea.getText();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ createWorkflowGraphic.stopProcessing();
+ super.finalize();
+ }
+
+ public class FileManagerObserver implements Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
+ throws Exception {
+ if (message instanceof ClosedDataflowEvent
+ && ((ClosedDataflowEvent) message).getDataflow() == workflow
+ .getParent()) {
+ // Remove listeners of various events
+ editManager.removeObserver(editManagerObserver);
+ fileManager.removeObserver(fileManagerObserver);
+ setVisible(false);
+ dispose(); // dispose off this window if the original workflow has been closed
+ }
+ }
+ }
+
+ public class EditManagerObserver implements Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ final EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent
+ && ((AbstractDataflowEditEvent) message).getDataFlow() == workflow
+ .getParent()) {
+ workflowModified = true;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/package.html b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/package.html
new file mode 100644
index 0000000..25da46b
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/package.html
@@ -0,0 +1,4 @@
+<body>
+Panels for use as high level components in a user interface to the
+reference system
+</body>
\ No newline at end of file
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/LoadInputsFromXML.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/LoadInputsFromXML.java
new file mode 100644
index 0000000..0d6ff2d
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/LoadInputsFromXML.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.reference.ui.referenceactions;
+
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static net.sf.taverna.t2.baclava.factory.DataThingXMLFactory.parseDataDocument;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.xmlNodeIcon;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+
+import net.sf.taverna.t2.baclava.DataThing;
+import net.sf.taverna.t2.lang.ui.ExtensionFileFilter;
+import net.sf.taverna.t2.reference.ui.RegistrationPanel;
+
+import org.jdom.Document;
+import org.jdom.input.SAXBuilder;
+
+/**
+ * Loads a set of input values from an XML document
+ */
+public class LoadInputsFromXML extends AbstractAction implements
+ ReferenceActionSPI {
+ private static final long serialVersionUID = -5031867688853589341L;
+ private static final String INPUT_DATA_DIR_PROPERTY = "inputDataValuesDir";
+
+ private Map<String, RegistrationPanel> inputPanelMap;
+
+ public LoadInputsFromXML() {
+ super();
+ putValue(NAME, "Load previous values");
+ putValue(SMALL_ICON, xmlNodeIcon);
+ }
+
+ @Override
+ public AbstractAction getAction() {
+ return new LoadInputsFromXML();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get(INPUT_DATA_DIR_PROPERTY, System.getProperty("user.home"));
+
+ JFileChooser chooser = new JFileChooser();
+ chooser.setDialogTitle("Select file to load input values from");
+
+ chooser.resetChoosableFileFilters();
+ chooser.setFileFilter(new ExtensionFileFilter(new String[]{"xml"}));
+ chooser.setCurrentDirectory(new File(curDir));
+
+ if (chooser.showOpenDialog(null) != APPROVE_OPTION)
+ return;
+ prefs.put(INPUT_DATA_DIR_PROPERTY, chooser.getCurrentDirectory()
+ .toString());
+ try {
+ File file = chooser.getSelectedFile();
+ InputStreamReader stream;
+ stream = new InputStreamReader(new FileInputStream(file),
+ Charset.forName("UTF-8"));
+ Document inputDoc = new SAXBuilder(false).build(stream);
+ Map<String, DataThing> inputMap = parseDataDocument(inputDoc);
+ for (String portName : inputMap.keySet()) {
+ RegistrationPanel panel = inputPanelMap.get(portName);
+ Object o = inputMap.get(portName).getDataObject();
+ if (o != null) {
+ int objectDepth = getObjectDepth(o);
+ if ((panel != null) && (objectDepth <= panel.getDepth()))
+ panel.setValue(o, objectDepth);
+ }
+ }
+ } catch (Exception ex) {
+ // Nothing
+ }
+ }
+
+ @Override
+ public void setInputPanelMap(Map<String, RegistrationPanel> inputPanelMap) {
+ this.inputPanelMap = inputPanelMap;
+ }
+
+ private int getObjectDepth(Object o) {
+ int result = 0;
+ if (o instanceof Iterable) {
+ result++;
+ @SuppressWarnings("unchecked")
+ Iterator<Object> i = ((Iterable<Object>) o).iterator();
+ if (i.hasNext())
+ result = result + getObjectDepth(i.next());
+ }
+ return result;
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/ReferenceActionSPI.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/ReferenceActionSPI.java
new file mode 100644
index 0000000..24e7cf6
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/ReferenceActionSPI.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * 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.reference.ui.referenceactions;
+
+import java.util.Map;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.reference.ui.RegistrationPanel;
+
+public interface ReferenceActionSPI {
+ public void setInputPanelMap(Map<String, RegistrationPanel> inputPanelMap);
+
+ /**
+ * Returns the action implementing this interface. The returned action will
+ * be bound to the appropriate UI component used to trigger the reference
+ * action.
+ */
+ public AbstractAction getAction();
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/SaveInputsAsXML.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/SaveInputsAsXML.java
new file mode 100644
index 0000000..a7a51be
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/referenceactions/SaveInputsAsXML.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * 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.reference.ui.referenceactions;
+
+import static java.lang.System.getProperty;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JFileChooser.FILES_ONLY;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.baclava.factory.DataThingFactory.bake;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.xmlNodeIcon;
+import static org.jdom.Namespace.getNamespace;
+import static org.jdom.output.Format.getPrettyFormat;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+
+import net.sf.taverna.t2.baclava.DataThing;
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.lang.ui.ExtensionFileFilter;
+import net.sf.taverna.t2.reference.ui.RegistrationPanel;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+import org.jdom.output.XMLOutputter;
+
+/**
+ * Stores the entire map of result objects to disk
+ * as a single XML data document.
+ *
+ * @author Tom Oinn
+ * @author Alex Nenadic
+ */
+public class SaveInputsAsXML extends AbstractAction implements ReferenceActionSPI {
+ private static final long serialVersionUID = 452360182978773176L;
+ private static final String INPUT_DATA_DIR_PROPERTY = "inputDataValuesDir";
+ private static final Logger logger = Logger
+ .getLogger(SaveInputsAsXML.class);
+ public static final String BACLAVA_NAMESPACE = "http://org.embl.ebi.escience/baclava/0.1alpha";
+ /** {@value #BACLAVA_NAMESPACE} */
+ private static final Namespace namespace = getNamespace("b",
+ BACLAVA_NAMESPACE);
+
+ @SuppressWarnings("unused")
+ private InvocationContext context = null;
+
+ private Map<String, RegistrationPanel> inputPanelMap;
+
+ public SaveInputsAsXML(){
+ super();
+ putValue(NAME, "Save values");
+ putValue(SMALL_ICON, xmlNodeIcon);
+ }
+
+ @Override
+ public AbstractAction getAction() {
+ return new SaveInputsAsXML();
+ }
+
+ // Must be called before actionPerformed()
+ public void setInvocationContext(InvocationContext context) {
+ this.context = context;
+ }
+
+ /**
+ * Shows a standard save dialog and dumps the entire input set to the
+ * specified XML file.
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get(INPUT_DATA_DIR_PROPERTY, getProperty("user.home"));
+
+ JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle("Select file to save input values to");
+
+ fc.resetChoosableFileFilters();
+ fc.setFileFilter(new ExtensionFileFilter(new String[] { "xml" }));
+ fc.setCurrentDirectory(new File(curDir));
+ fc.setFileSelectionMode(FILES_ONLY);
+
+ File file;
+ do {
+ if (fc.showSaveDialog(null) != APPROVE_OPTION)
+ return;
+ prefs.put(INPUT_DATA_DIR_PROPERTY, fc.getCurrentDirectory().toString());
+ file = fc.getSelectedFile();
+
+ /*
+ * If the user did not use the .xml extension for the file - append
+ * it to the file name now
+ */
+ if (!file.getName().toLowerCase().endsWith(".xml"))
+ file = new File(file.getParentFile(), file.getName() + ".xml");
+
+ // If the file exists, ask the user if they want to overwrite the file
+ } while (file.exists()
+ && showConfirmDialog(null, file.getAbsolutePath()
+ + " already exists. Do you want to overwrite it?",
+ "File already exists", YES_NO_OPTION) != YES_OPTION);
+ doSave(file);
+ }
+
+ private void doSave(final File file) {
+ // Do this in separate thread to avoid hanging UI
+ new Thread("Save(InputsAsXML: Saving inputs to " + file) {
+ @Override
+ public void run() {
+ try {
+ synchronized (inputPanelMap) {
+ saveData(file);
+ }
+ } catch (Exception ex) {
+ showMessageDialog(null, "Problem saving input data",
+ "Save Inputs Error", ERROR_MESSAGE);
+ logger.error("Problem saving input data as XML", ex);
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Saves the input data to an XML Baclava file.
+ */
+ private void saveData(File file) throws Exception {
+ // Build the DataThing map from the inputPanelMap
+ Map<String, Object> valueMap = new HashMap<>();
+ for (String portName : inputPanelMap.keySet()) {
+ RegistrationPanel panel = inputPanelMap.get(portName);
+ Object obj = panel.getValue();
+ if (obj != null)
+ valueMap.put(portName, obj);
+ }
+ Map<String, DataThing> dataThings = bakeDataThingMap(valueMap);
+
+ // Build the string containing the XML document from the panel map
+ String xmlString = new XMLOutputter(getPrettyFormat())
+ .outputString(getDataDocument(dataThings));
+ try (PrintWriter out = new PrintWriter(new FileWriter(file))) {
+ out.print(xmlString);
+ }
+ }
+
+ /**
+ * Returns a map of port names to DataThings from a map of port names to a
+ * list of (lists of ...) result objects.
+ */
+ Map<String, DataThing> bakeDataThingMap(Map<String, Object> resultMap) {
+ Map<String, DataThing> dataThingMap = new HashMap<>();
+ for (String portName : resultMap.keySet())
+ dataThingMap.put(portName, bake(resultMap.get(portName)));
+ return dataThingMap;
+ }
+
+ /**
+ * Returns a org.jdom.Document from a map of port named to DataThingS containing
+ * the port's results.
+ */
+ public static Document getDataDocument(Map<String, DataThing> dataThings) {
+ Element rootElement = new Element("dataThingMap", namespace);
+ Document theDocument = new Document(rootElement);
+ for (String key : dataThings.keySet()) {
+ DataThing value = (DataThing) dataThings.get(key);
+ Element dataThingElement = new Element("dataThing", namespace);
+ dataThingElement.setAttribute("key", key);
+ dataThingElement.addContent(value.getElement());
+ rootElement.addContent(dataThingElement);
+ }
+ return theDocument;
+ }
+
+ @Override
+ public void setInputPanelMap(Map<String, RegistrationPanel> inputPanelMap) {
+ this.inputPanelMap = inputPanelMap;
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTree.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTree.java
new file mode 100644
index 0000000..5b034ad
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTree.java
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * 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.reference.ui.tree;
+
+import static javax.swing.SwingUtilities.invokeLater;
+import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
+
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.dnd.Autoscroll;
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JTree;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A wrapper around JTree that installs the set of models, renderers and
+ * listeners used by the pre-registration tree control. Implements autoscroll
+ * and zooms to any new nodes when added. Handles drop of URLs (from e.g.
+ * FireFox), File structures and plain text by creating corresponding POJOs.
+ *
+ * @author Tom Oinn
+ */
+public class PreRegistrationTree extends JTree implements Autoscroll {
+ private static final long serialVersionUID = -8357524058131749686L;
+ private static Logger logger = Logger.getLogger(PreRegistrationTree.class);
+
+ private PreRegistrationTreeModel model;
+ private int margin = 15;
+
+ /**
+ * Get the PreRegistrationTreeModel for this tree. Used to get the contents
+ * of the tree as a POJO which can then be registered with the
+ * ReferenceService
+ *
+ * @return a POJO containing the contents of the tree.
+ */
+ public PreRegistrationTreeModel getPreRegistrationTreeModel() {
+ return this.model;
+ }
+
+ /**
+ * Override this to be informed of status messages from the tree
+ */
+ public void setStatusMessage(String message, boolean isError) {
+ //
+ }
+
+ /**
+ * Construct with the depth of the collection to be assembled. This will
+ * instantiate an appropriate internal model and set all the drag and drop
+ * handlers, renderers and cell editing components.
+ *
+ * @param depth
+ * the collection depth to use, 0 for single items, 1 for lists
+ * and so on.
+ */
+ public PreRegistrationTree(int depth) {
+ this(depth, null);
+ }
+
+ /**
+ * Construct with the depth of the collection to be assembled. This will
+ * instantiate an appropriate internal model and set all the drag and drop
+ * handlers, renderers and cell editing components.
+ *
+ * @param depth
+ * the collection depth to use, 0 for single items, 1 for lists
+ * and so on.
+ * @param name Name of the top root of the tree (typically the port name)
+ */
+ public PreRegistrationTree(int depth, String name) {
+ if (name == null)
+ model = new PreRegistrationTreeModel(depth);
+ else
+ model = new PreRegistrationTreeModel(depth, name);
+ setModel(model);
+ setInvokesStopCellEditing(true);
+
+ getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
+ DefaultTreeCellRenderer renderer = new PreRegistrationTreeCellRenderer();
+ setRowHeight(0);
+ setCellRenderer(renderer);
+
+ new PreRegistrationTreeDnDHandler(this) {
+ @Override
+ public void handleNodeMove(MutableTreeNode source,
+ MutableTreeNode target) {
+ model.moveNode(source, target);
+ }
+
+ @Override
+ public void handleFileDrop(MutableTreeNode target,
+ List<File> fileList) {
+ for (File f : fileList) {
+ if (f.isDirectory() == false) {
+ model.addPojoStructure(target, target, f, 0);
+ continue;
+ }
+
+ if (model.getDepth() < 1) {
+ setStatusMessage(
+ "Can't add directory to single item input",
+ true);
+ return;
+ }
+
+ /*
+ * Try to handle directories as flat lists, don't nest any
+ * deeper for now.
+ */
+ List<File> children = new ArrayList<>();
+ for (File child : f.listFiles())
+ if (child.isFile())
+ children.add(child);
+ model.addPojoStructure(target, target, children, 1);
+ }
+ }
+
+ @Override
+ public void handleUrlDrop(MutableTreeNode target, URL url) {
+ if (url.getProtocol().equalsIgnoreCase("http")) {
+ model.addPojoStructure(target, target, url, 0);
+ setStatusMessage("Added URL : " + url.toExternalForm(),
+ false);
+ } else
+ setStatusMessage("Only http URLs are supported for now.",
+ true);
+ }
+
+ @Override
+ public void handleStringDrop(MutableTreeNode target, String string) {
+ model.addPojoStructure(target, target, string, 0);
+ setStatusMessage("Added string from drag and drop", false);
+ }
+ };
+ }
+
+ @Override
+ public void setModel(TreeModel model) {
+ if (treeModel == model)
+ return;
+ if (treeModelListener == null)
+ treeModelListener = new TreeModelHandler() {
+ @Override
+ public void treeNodesInserted(final TreeModelEvent ev) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ TreePath path = ev.getTreePath();
+ setExpandedState(path, true);
+ fireTreeExpanded(path);
+ }
+ });
+ }
+ };
+ if (model != null)
+ model.addTreeModelListener(treeModelListener);
+ TreeModel oldValue = treeModel;
+ treeModel = model;
+ firePropertyChange(TREE_MODEL_PROPERTY, oldValue, model);
+ }
+
+ @Override
+ public void autoscroll(Point p) {
+ int realrow = getRowForLocation(p.x, p.y);
+ Rectangle outer = getBounds();
+ realrow = (p.y + outer.y <= margin ? realrow < 1 ? 0 : realrow - 1
+ : realrow < getRowCount() - 1 ? realrow + 1 : realrow);
+ scrollRowToVisible(realrow);
+ }
+
+ @Override
+ public Insets getAutoscrollInsets() {
+ Rectangle outer = getBounds();
+ Rectangle inner = getParent().getBounds();
+ return new Insets(inner.y - outer.y + margin, inner.x - outer.x
+ + margin, outer.height - inner.height - inner.y + outer.y
+ + margin, outer.width - inner.width - inner.x + outer.x
+ + margin);
+ }
+
+ @Override
+ public int getRowCount() {
+ int result = super.getRowCount();
+ logger.info("Row count is " + result);
+ return result;
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeCellRenderer.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeCellRenderer.java
new file mode 100644
index 0000000..b0c2d51
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeCellRenderer.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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.reference.ui.tree;
+
+import java.awt.Component;
+import java.io.File;
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+/**
+ * A cell renderer for the pre-registration tree model, with appropriate
+ * rendering for inline strings, web URLs and files. The renderer doesn't
+ * attempt to show the contents (other than in the case of inline strings), but
+ * does show the URL and File paths for those types along with sensible icons
+ * stolen from Eclipse.
+ *
+ * @author Tom Oinn
+ *
+ */
+public class PreRegistrationTreeCellRenderer extends DefaultTreeCellRenderer {
+ private static final long serialVersionUID = 5284952103994689024L;
+ private static int MAXIMUM_TEXT_LENGTH = 14;
+
+ private ImageIcon textIcon = new ImageIcon(getClass().getResource(
+ "/icons/wordassist_co.gif"));
+ private ImageIcon fileIcon = new ImageIcon(getClass().getResource(
+ "/icons/topic.gif"));
+ private ImageIcon urlIcon = new ImageIcon(getClass().getResource(
+ "/icons/web.gif"));
+ private ImageIcon binaryIcon = new ImageIcon(getClass().getResource(
+ "/icons/genericregister_obj.gif"));
+
+ @Override
+ public synchronized 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)
+ renderPreRegistrationCell(tree, value, expanded,
+ ((DefaultMutableTreeNode) value).getUserObject());
+ return this;
+ }
+
+ private void renderPreRegistrationCell(JTree tree, Object value,
+ boolean expanded, Object userObject) {
+ if (userObject == null) {
+ setText("List");
+ } else if (tree.getModel().getRoot() == value) {
+ setText(userObject.toString());
+ } else {
+ // Handle rendering of string, file, url, byte[] here
+ if (userObject instanceof String) {
+ setIcon(textIcon);
+ String string = (String) userObject;
+ if (string.length() < MAXIMUM_TEXT_LENGTH)
+ setText(string);
+ else
+ setText(string.substring(0, MAXIMUM_TEXT_LENGTH - 4)
+ + "...");
+ } else if (userObject instanceof byte[]) {
+ byte[] bytes = (byte[]) userObject;
+ setIcon(binaryIcon);
+ setText("byte[] " + getHumanReadableSize(bytes.length));
+ } else if (userObject instanceof File) {
+ setIcon(fileIcon);
+ File f = (File) userObject;
+ setText(f.getName());
+ } else if (userObject instanceof URL) {
+ setIcon(urlIcon);
+ URL url = (URL) userObject;
+ setText(url.getHost());
+ } else {
+ if (expanded) {
+ // setIcon(expandedIcon);
+ } else {
+ // setIcon(unexpandedIcon);
+ }
+ }
+ }
+ }
+
+ private static String getHumanReadableSize(int size) {
+ if (size < 10000)
+ return size + " bytes";
+ else if (size < 2000000)
+ return (int) (size / 1000) + " kB";
+ else if (size < 2000000000)
+ return (int) (size / 1000000) + " mB";
+ else
+ return (int) (size / 1000000000) + " gB";
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeDnDHandler.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeDnDHandler.java
new file mode 100644
index 0000000..f6b3e7f
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeDnDHandler.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * 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.reference.ui.tree;
+
+import static java.awt.datatransfer.DataFlavor.javaFileListFlavor;
+import static java.awt.dnd.DnDConstants.ACTION_COPY;
+import static java.awt.dnd.DnDConstants.ACTION_MOVE;
+import static java.awt.dnd.DragSource.DefaultMoveDrop;
+import static net.sf.taverna.t2.reference.ui.tree.PreRegistrationTreeDnDHandler.InternalNodeDragTransferable.INTERNAL_NODE_FLAVOR;
+
+import java.awt.Point;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.awt.dnd.DragSourceListener;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetContext;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import org.apache.log4j.Logger;
+
+/**
+ * An abstract handler class for drags within a {@link JTree} backed by a
+ * {@link DefaultTreeModel}. Implement the (@link
+ * {@link #handleNodeMove(MutableTreeNode, MutableTreeNode)} method to determine
+ * the behaviour when a node is dragged to another node in the tree. Additional
+ * methods are called when drags corresponding to URL, List<File> and
+ * String are made. Node drags have precedence, only one of the abstract methods
+ * is called for any given drop event.
+ *
+ * @author Tom Oinn
+ */
+public abstract class PreRegistrationTreeDnDHandler implements
+ DropTargetListener, DragGestureListener, DragSourceListener {
+ private static Logger logger = Logger
+ .getLogger(PreRegistrationTreeDnDHandler.class);
+
+ private JTree tree;
+ private MutableTreeNode draggedNode = null;
+ private DragSource source = new DragSource();
+
+ public PreRegistrationTreeDnDHandler(JTree tree) {
+ this.tree = tree;
+ new DropTarget(tree, this);
+ source.createDefaultDragGestureRecognizer(tree, ACTION_MOVE, this);
+ }
+
+ /**
+ * Called when a node has been dragged onto another node (nodes could be the
+ * same, client code should handle this correctly).
+ *
+ * @param source
+ * the node the drag was initiated from
+ * @param target
+ * the node the drag ended on
+ */
+ public abstract void handleNodeMove(MutableTreeNode source,
+ MutableTreeNode target);
+
+ /**
+ * Called when a file is dragged from the native OS to the tree.
+ *
+ * @param target
+ * the node the drag ended on
+ * @param fileList
+ * a list of File objects corresponding to the selection dragged.
+ * Files may be directories, remember to check!
+ */
+ public abstract void handleFileDrop(MutableTreeNode target,
+ List<File> fileList);
+
+ /**
+ * Called when a URL is dropped, on my system at least Firefox presents this
+ * type if a link or similar is dragged to the tree.
+ *
+ * @param target
+ * the node the drag ended on
+ * @param url
+ * a java URL object
+ */
+ public abstract void handleUrlDrop(MutableTreeNode target, URL url);
+
+ /**
+ * Called when a textual type is dragged to the tree
+ *
+ * @param target
+ * the node the drag ended on
+ * @param string
+ * a java String object containing the dragged text
+ */
+ public abstract void handleStringDrop(MutableTreeNode target, String string);
+
+ @Override
+ public void dragEnter(DropTargetDragEvent dtde) {
+ dtde.acceptDrag(ACTION_MOVE);
+ }
+
+ @Override
+ public void dragExit(DropTargetEvent dte) {
+ }
+
+ @Override
+ public void dragOver(DropTargetDragEvent dtde) {
+ dtde.acceptDrag(ACTION_MOVE);
+ }
+
+ @Override
+ public void drop(DropTargetDropEvent dtde) {
+ Point pt = dtde.getLocation();
+ DropTargetContext dtc = dtde.getDropTargetContext();
+ JTree tree = (JTree) dtc.getComponent();
+ TreePath targetPath = tree.getClosestPathForLocation(pt.x, pt.y);
+ MutableTreeNode target = null;
+ if (targetPath != null)
+ target = (MutableTreeNode) targetPath.getLastPathComponent();
+ Transferable tr = dtde.getTransferable();
+ if (tr.isDataFlavorSupported(INTERNAL_NODE_FLAVOR)) {
+ dtde.acceptDrop(ACTION_MOVE);
+ handleNodeMove(draggedNode, target);
+ draggedNode = null;
+ dtde.dropComplete(true);
+ return;
+ } else if (tr.isDataFlavorSupported(javaFileListFlavor)) {
+ dtde.acceptDrop(ACTION_COPY);
+ try {
+ @SuppressWarnings("unchecked")
+ List<File> fileList = (List<File>) tr
+ .getTransferData(javaFileListFlavor);
+ handleFileDrop(target, fileList);
+ dtde.dropComplete(true);
+ } catch (Exception ex) {
+ dtde.dropComplete(false);
+ }
+ return;
+ }
+
+ for (DataFlavor flavor : tr.getTransferDataFlavors()) {
+ String mimeType = flavor.getMimeType();
+ if (mimeType.startsWith("application/x-java-url")) {
+ dtde.acceptDrop(ACTION_COPY);
+ URL url;
+ try {
+ url = (URL) tr.getTransferData(flavor);
+ handleUrlDrop(target, url);
+ dtde.dropComplete(true);
+ } catch (UnsupportedFlavorException|IOException e) {
+ dtde.dropComplete(false);
+ logger.error("Cannot import data", e);
+ }
+ return;
+ } else if (mimeType.contains("class=java.lang.String;")) {
+ dtde.acceptDrop(ACTION_COPY);
+ String string;
+ try {
+ string = (String) tr.getTransferData(flavor);
+ handleStringDrop(target, string);
+ dtde.dropComplete(true);
+ } catch (UnsupportedFlavorException | IOException e) {
+ dtde.dropComplete(false);
+ logger.error("Cannot import data", e);
+ }
+ return;
+ }
+ }
+ dtde.rejectDrop();
+ }
+
+ @Override
+ public void dropActionChanged(DropTargetDragEvent dtde) {
+ }
+
+ @Override
+ public void dragGestureRecognized(DragGestureEvent dge) {
+ TreePath path = tree.getSelectionPath();
+ if (path == null || path.getPathCount() <= 1)
+ // We can't move the root node or an empty selection
+ return;
+ draggedNode = (MutableTreeNode) path.getLastPathComponent();
+ source.startDrag(dge, DefaultMoveDrop,
+ new InternalNodeDragTransferable(), this);
+ }
+
+ @Override
+ public void dragDropEnd(DragSourceDropEvent dsde) {
+ }
+
+ @Override
+ public void dragEnter(DragSourceDragEvent dsde) {
+ }
+
+ @Override
+ public void dragExit(DragSourceEvent dse) {
+ }
+
+ @Override
+ public void dragOver(DragSourceDragEvent dsde) {
+ }
+
+ @Override
+ public void dropActionChanged(DragSourceDragEvent dsde) {
+ }
+
+ /**
+ * Dummy data transfer flavor used to check that the drag and drop are
+ * managing the same events.
+ *
+ * @author Tom Oinn
+ */
+ static class InternalNodeDragTransferable implements Transferable {
+ static DataFlavor INTERNAL_NODE_FLAVOR = new DataFlavor(
+ InternalNodeDragTransferable.class, "Internal node move");
+
+ private DataFlavor flavors[] = { INTERNAL_NODE_FLAVOR };
+
+ @Override
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ // There is no data for this, so return null
+ return null;
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return flavors;
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return flavor.getRepresentationClass() == InternalNodeDragTransferable.class;
+ }
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeModel.java b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeModel.java
new file mode 100644
index 0000000..1e2bdf1
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/PreRegistrationTreeModel.java
@@ -0,0 +1,310 @@
+/*******************************************************************************
+ * 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.reference.ui.tree;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A subclass of DefaultTreeModel which is aware of the depth property of its
+ * leaf nodes. This is used to hold and assemble data collections prior to
+ * registration through the ReferenceService. The root node is an internal
+ * placeholder so that we can handle depth 0 inputs without having to replace
+ * the root all the time, it always has one or zero children.
+ * <p>
+ * Non-leaf nodes always have an empty user object, leaf nodes can be one of
+ * <ol>
+ * <li>java.io.File</li>
+ * <li>java.net.URL</li>
+ * <li>String</li>
+ * <li>byte[]</li>
+ * </ol>
+ * The getAsPojo method returns the appropriate object type for the entire
+ * contents of this tree, mapping empty nodes to List implementations. It
+ * returns null if the root node has zero children.
+ *
+ * @author Tom Oinn
+ *
+ */
+public class PreRegistrationTreeModel extends DefaultTreeModel {
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger
+ .getLogger(PreRegistrationTreeModel.class);
+ private static final String INPUT = "Input";
+ private static final String LIST_OF_DEPTH = "List of depth";
+ private static final String LIST_OF_LISTS_OF_LISTS = "List of lists of lists";
+ private static final String LIST_OF_LISTS = "List of lists";
+ private static final String LIST = "List";
+ private static final String SINGLE_VALUE = "Single value";
+ private static final long serialVersionUID = 4133642236173701467L;
+
+ private int depth = 0;
+
+ public PreRegistrationTreeModel(int depth) {
+ this(depth, INPUT);
+ }
+
+ public PreRegistrationTreeModel(int depth, String name) {
+ super(new DefaultMutableTreeNode(getRootName(depth, name)));
+ this.depth = depth;
+ }
+
+ private static String getRootName(int depth, String name) {
+ switch (depth) {
+ case 0:
+ return name + ": " + SINGLE_VALUE;
+ case 1:
+ return name + ": " + LIST;
+ case 2:
+ return name + ": " + LIST_OF_LISTS;
+ case 3:
+ return name + ": " + LIST_OF_LISTS_OF_LISTS;
+ default:
+ return name + ": " + LIST_OF_DEPTH + " " + depth;
+ }
+ }
+
+ public int getDepth() {
+ return this.depth;
+ }
+
+ /**
+ * Get the contents of this tree model as a POJO ready for registration with
+ * the ReferenceService, returns null if the root has no children and throws
+ * IllegalStateException if there are any objects other than File, URL,
+ * String or byte[] at leaf nodes.
+ *
+ * @return
+ */
+ public synchronized Object getAsPojo() {
+ if (getChildCount(getRoot()) == 0)
+ return null;
+ return getAsPojoInner(getChild(getRoot(), 0));
+ }
+
+ private synchronized Object getAsPojoInner(Object child) {
+ DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) child;
+ Object userObject = childNode.getUserObject();
+ if (userObject == null) {
+ List<Object> result = new ArrayList<>();
+ int children = getChildCount(childNode);
+ for (int i = 0; i < children; i++)
+ result.add(getAsPojoInner(getChild(childNode, i)));
+ return result;
+ }
+ if (userObject instanceof String || userObject instanceof File
+ || userObject instanceof URL || userObject instanceof byte[]) {
+ return userObject;
+ }
+ throw new IllegalStateException("Found an illegal object of type '"
+ + userObject.getClass().getCanonicalName()
+ + "' in collection structure.");
+ }
+
+ /**
+ * Nodes are leaves if they are not the root node and if they have a user
+ * object defined. All non-leaf nodes are either the root (special case) or
+ * have a null user object.
+ */
+ @Override
+ public boolean isLeaf(Object o) {
+ if (o == getRoot())
+ return false;
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
+ return node.getUserObject() != null;
+ }
+
+ /**
+ * Add the specified pojo at a particular place in the collection. The
+ * target node is, if required, re-written to ensure the depth property of
+ * the model is maintained. If specified as null the target is assumed to be
+ * the root node.
+ *
+ * @param parent
+ * @param pojo
+ * @param depth
+ */
+ @SuppressWarnings("unchecked")
+ public synchronized DefaultMutableTreeNode addPojoStructure(
+ MutableTreeNode parent, MutableTreeNode preceding, Object pojo,
+ int depth) {
+ /*
+ * Firstly check for a null target and set the root node to be the
+ * target if so.
+ */
+ if (parent == null)
+ parent = (MutableTreeNode) getRoot();
+ /*
+ * Now ensure that the target node has the correct depth. The target
+ * node must have depth of (depth - 1) to be correct, this means we can
+ * add the collection in place without any problems.
+ */
+ int targetDepth = getNodeDepth(parent);
+
+ if (targetDepth > depth + 1) {
+ /*
+ * Need to traverse down the structure to find an appropriate parent
+ * node, creating empty nodes as we go if required.
+ */
+ if (parent.getChildCount() == 0)
+ insertNodeInto(new DefaultMutableTreeNode(null), parent, 0);
+ return addPojoStructure((MutableTreeNode) parent.getChildAt(0), preceding, pojo,
+ depth);
+ } else if (targetDepth < depth + 1) {
+ /*
+ * Need to traverse up the structure to find an appropriate parent
+ * node
+ */
+ if (parent.getParent() == null)
+ throw new IllegalArgumentException(
+ "Can't add this pojo, depths are not compatible.");
+ return addPojoStructure((MutableTreeNode) parent.getParent(), preceding, pojo, depth);
+ }
+
+ /*
+ * Found an appropriate parent node, we can insert at position 0 here.
+ * If this is the root node then we need to clear it first, the root can
+ * only have zero or one child nodes.
+ */
+ if (parent == getRoot())
+ if (parent.getChildCount() == 1)
+ removeNodeFromParent((MutableTreeNode) parent.getChildAt(0));
+ int children = parent.getChildCount();
+ int position = children;
+ if (preceding != null && preceding.getParent() != null
+ && preceding.getParent().equals(parent))
+ position = parent.getIndex(preceding) + 1;
+ if (pojo instanceof List) {
+ DefaultMutableTreeNode newTarget = new DefaultMutableTreeNode(null);
+ insertNodeInto(newTarget, parent, position);
+ for (Object child : (List<Object>) pojo)
+ addPojoStructure(newTarget, preceding, child, depth - 1);
+ return newTarget;
+ } else {
+ DefaultMutableTreeNode newChild = new DefaultMutableTreeNode(pojo);
+ insertNodeInto(newChild, parent, position);
+ return newChild;
+ }
+ }
+
+ /**
+ * Move a node to another node, used to respond to internal drag and drop
+ * events corresponding to re-arrangements of this collection structure.
+ * Behaviour depends on the relative depths (in t2reference terms where a
+ * leaf node has depth 0) of the source and target nodes
+ * <ol>
+ * <li>If the target is the same depth as the source then this is
+ * interpreted as a request to move the source to become a sibling of the
+ * target positioned immediately after it in the target's parent's child
+ * list.</li>
+ * <li>If the target is a lower depth than the source then the target node
+ * is re-written to be the node on the target's path to the root with the
+ * same depth as the source and treated as above.</li>
+ * <li>If the target is at a higher depth than the source by exactly one
+ * then this is interpreted as a request to insert the source node at the
+ * start of the target's child list.</li>
+ * <li>If the target is at a higher depth by more than one the target node
+ * is rewritten to be the first child of the target node. If the target node
+ * has no children a new node is created, inserted into the target and set
+ * as the target for this method, which is called recursively</li>
+ * </ol>
+ * This method is called before any nodes are modified, and causes the
+ * modifications to take place.
+ */
+ public synchronized void moveNode(MutableTreeNode source,
+ MutableTreeNode target) {
+ // Check that we're not dragging onto ourselves!
+ if (source.equals(target))
+ return;
+ int targetDepth = getNodeDepth(target);
+ int sourceDepth = getNodeDepth(source);
+ // Handle drag onto a future sibling
+ if (sourceDepth == targetDepth) {
+ /*
+ * Move the source from wherever it currently is and add it as a
+ * sibling of the target node at an index one higher.
+ */
+ removeNodeFromParent(source);
+ // Capture the index of the target in its parent
+ int targetIndex = getIndexOfChild(target.getParent(), target);
+ /*
+ * Insert the source node into the target's parent at the
+ * appropriate index
+ */
+ insertNodeInto(source, (MutableTreeNode) target.getParent(),
+ targetIndex + 1);
+ }
+ // Traverse up to find a potential sibling node
+ else if (targetDepth < sourceDepth)
+ moveNode(source, (MutableTreeNode) target.getParent());
+ // Check for a move to an immediate future parent
+ else if (targetDepth == sourceDepth + 1) {
+ /*
+ * Insert at index 0 in the target, removing from our old parent
+ * first
+ */
+ removeNodeFromParent(source);
+ insertNodeInto(source, target, 0);
+ }
+ /*
+ * Otherwise traverse, picking the child at index 0 every time and
+ * creating a new one if required
+ */
+ else if (targetDepth > sourceDepth) {
+ // Create a new non-leaf node first if needed
+ if (target.getChildCount() == 0)
+ insertNodeInto(new DefaultMutableTreeNode(null), target, 0);
+ /*
+ * Recursively try to move the source to the target's child list at
+ * position 0
+ */
+ moveNode(source, (MutableTreeNode) target.getChildAt(0));
+ }
+ }
+
+ @Override
+ public synchronized void removeNodeFromParent(MutableTreeNode node) {
+ if (node.getParent() != null)
+ super.removeNodeFromParent(node);
+ }
+
+ /**
+ * Return the depth of the specified tree node. Depth is determined by the
+ * length of the path to the root, where a path of length 2 corresponds to
+ * the depth of this model structure. The result is therefore equal to
+ * <code>getDepth() - (getPathToRoot(o).length - 2)</code>
+ *
+ * @param o
+ * @return
+ */
+ private int getNodeDepth(TreeNode o) {
+ return getDepth() - (getPathToRoot(o).length - 2);
+ }
+}
diff --git a/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/package.html b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/package.html
new file mode 100644
index 0000000..33b0e21
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/java/net/sf/taverna/t2/reference/ui/tree/package.html
@@ -0,0 +1,6 @@
+<body>
+Tree models, drag handlers, renderers and editor support for both
+pre-registration trees of POJOs (constrained only in terms of overall
+collection depth) and for structure browsing from a registered (and
+therefore immutable) T2Reference.
+</body>
\ No newline at end of file
diff --git a/taverna-workbench-reference-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI b/taverna-workbench-reference-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI
new file mode 100644
index 0000000..accbcde
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.reference.ui.referenceactions.LoadInputsFromXML
+net.sf.taverna.t2.reference.ui.referenceactions.SaveInputsAsXML
\ No newline at end of file
diff --git a/taverna-workbench-reference-ui/src/main/resources/META-INF/spring/reference-ui-context-osgi.xml b/taverna-workbench-reference-ui/src/main/resources/META-INF/spring/reference-ui-context-osgi.xml
new file mode 100644
index 0000000..b9e3da1
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/META-INF/spring/reference-ui-context-osgi.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="LoadInputsFromXML" interface="net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI" />
+ <service ref="SaveInputsAsXML" interface="net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI" />
+
+</beans:beans>
diff --git a/taverna-workbench-reference-ui/src/main/resources/META-INF/spring/reference-ui-context.xml b/taverna-workbench-reference-ui/src/main/resources/META-INF/spring/reference-ui-context.xml
new file mode 100644
index 0000000..13b501f
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/META-INF/spring/reference-ui-context.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="LoadInputsFromXML" class="net.sf.taverna.t2.reference.ui.referenceactions.LoadInputsFromXML" />
+ <bean id="SaveInputsAsXML" class="net.sf.taverna.t2.reference.ui.referenceactions.SaveInputsAsXML" />
+
+</beans>
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/addtext_co.gif b/taverna-workbench-reference-ui/src/main/resources/icons/addtext_co.gif
new file mode 100644
index 0000000..fae7a9d
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/addtext_co.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/complete_status.gif b/taverna-workbench-reference-ui/src/main/resources/icons/complete_status.gif
new file mode 100644
index 0000000..23c97f0
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/complete_status.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/deadlock_view.gif b/taverna-workbench-reference-ui/src/main/resources/icons/deadlock_view.gif
new file mode 100644
index 0000000..8e3d48a
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/deadlock_view.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/delete_obj.gif b/taverna-workbench-reference-ui/src/main/resources/icons/delete_obj.gif
new file mode 100644
index 0000000..b6922ac
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/delete_obj.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/det_pane_hide.gif b/taverna-workbench-reference-ui/src/main/resources/icons/det_pane_hide.gif
new file mode 100644
index 0000000..42fca3f
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/det_pane_hide.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/error_tsk.gif b/taverna-workbench-reference-ui/src/main/resources/icons/error_tsk.gif
new file mode 100644
index 0000000..9b048d6
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/error_tsk.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/errorwarning_tab.gif b/taverna-workbench-reference-ui/src/main/resources/icons/errorwarning_tab.gif
new file mode 100644
index 0000000..004f4ac
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/errorwarning_tab.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/genericregister_obj.gif b/taverna-workbench-reference-ui/src/main/resources/icons/genericregister_obj.gif
new file mode 100644
index 0000000..7134210
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/genericregister_obj.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/information.gif b/taverna-workbench-reference-ui/src/main/resources/icons/information.gif
new file mode 100644
index 0000000..3679f84
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/information.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/invalid_build_tool.gif b/taverna-workbench-reference-ui/src/main/resources/icons/invalid_build_tool.gif
new file mode 100644
index 0000000..0bc6068
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/invalid_build_tool.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/newfolder_wiz.gif b/taverna-workbench-reference-ui/src/main/resources/icons/newfolder_wiz.gif
new file mode 100644
index 0000000..310eb18
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/newfolder_wiz.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/repo_rep.gif b/taverna-workbench-reference-ui/src/main/resources/icons/repo_rep.gif
new file mode 100644
index 0000000..c13bea1
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/repo_rep.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/start_task.gif b/taverna-workbench-reference-ui/src/main/resources/icons/start_task.gif
new file mode 100644
index 0000000..ec477ea
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/start_task.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/topic.gif b/taverna-workbench-reference-ui/src/main/resources/icons/topic.gif
new file mode 100644
index 0000000..b226e41
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/topic.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/web.gif b/taverna-workbench-reference-ui/src/main/resources/icons/web.gif
new file mode 100644
index 0000000..ec6cca4
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/web.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/wordassist_co.gif b/taverna-workbench-reference-ui/src/main/resources/icons/wordassist_co.gif
new file mode 100644
index 0000000..c9b97fe
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/wordassist_co.gif
Binary files differ
diff --git a/taverna-workbench-reference-ui/src/main/resources/icons/write_obj.gif b/taverna-workbench-reference-ui/src/main/resources/icons/write_obj.gif
new file mode 100644
index 0000000..feb8e94
--- /dev/null
+++ b/taverna-workbench-reference-ui/src/main/resources/icons/write_obj.gif
Binary files differ
diff --git a/taverna-workbench-renderers-api/pom.xml b/taverna-workbench-renderers-api/pom.xml
new file mode 100644
index 0000000..63100d7
--- /dev/null
+++ b/taverna-workbench-renderers-api/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>renderers-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Renderers API</name>
+ <dependencies>
+ <dependency>
+ <groupId>uk.org.taverna.databundle</groupId>
+ <artifactId>databundle</artifactId>
+ <version>${taverna.databundle.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons.io.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/Renderer.java b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/Renderer.java
new file mode 100644
index 0000000..518c3bf
--- /dev/null
+++ b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/Renderer.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * 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.renderers;
+
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+
+public interface Renderer {
+ boolean canHandle(String mimeType);
+
+ JComponent getComponent(Path path) throws RendererException;
+
+ String getType();
+}
diff --git a/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererException.java b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererException.java
new file mode 100644
index 0000000..0e619a0
--- /dev/null
+++ b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererException.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.renderers;
+
+/**
+ * If a renderer fails for any reason then throw one of these with an
+ * appropriate message.
+ *
+ * @author Ian Dunlop
+ */
+public class RendererException extends Exception {
+ private static final long serialVersionUID = 713914849694276998L;
+
+ public RendererException() {
+ }
+
+ public RendererException(String message) {
+ super(message);
+ }
+
+ public RendererException(Throwable cause) {
+ super(cause);
+ }
+
+ public RendererException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererRegistry.java b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererRegistry.java
new file mode 100644
index 0000000..0a8f0f6
--- /dev/null
+++ b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererRegistry.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.renderers;
+
+import java.util.List;
+
+/**
+ * Manages the collection of {@linkplain Renderer renderers}.
+ *
+ * @author David Withers
+ */
+public interface RendererRegistry {
+ /**
+ * Get all of the available renderers for a specific MIME type. If there is
+ * a problem with one then catch the exception and log the problem but carry
+ * on since there is probably more than one way to render the data
+ *
+ * @param path
+ * @param mimeType
+ * @return
+ */
+ List<Renderer> getRenderersForMimeType(String mimeType);
+
+ /**
+ * Return all the renderers.
+ *
+ * @return all the renderers
+ */
+ List<Renderer> getRenderers();
+}
diff --git a/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererUtils.java b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererUtils.java
new file mode 100644
index 0000000..6d61421
--- /dev/null
+++ b/taverna-workbench-renderers-api/src/main/java/net/sf/taverna/t2/renderers/RendererUtils.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.renderers;
+
+import static org.purl.wf4ever.robundle.Bundles.getReference;
+import static org.purl.wf4ever.robundle.Bundles.getStringValue;
+import static org.purl.wf4ever.robundle.Bundles.isReference;
+import static uk.org.taverna.databundle.DataBundles.isValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * @author David Withers
+ */
+public class RendererUtils {
+ public static long getSizeInBytes(Path path) throws IOException {
+ if (isValue(path))
+ return Files.size(path);
+ if (!isReference(path))
+ throw new IllegalArgumentException(
+ "Path is not a value or reference");
+
+ URL url = getReference(path).toURL();
+ switch (url.getProtocol().toLowerCase()) {
+ case "http":
+ case "https":
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("HEAD");
+ conn.connect();
+ String contentLength = conn.getHeaderField("Content-Length");
+ conn.disconnect();
+ if (contentLength != null && !contentLength.isEmpty())
+ return Long.parseLong(contentLength);
+ return -1;
+ case "file":
+ return FileUtils.toFile(url).length();
+ default:
+ return -1;
+ }
+ }
+
+ public static String getString(Path path) throws IOException {
+ if (isValue(path))
+ return getStringValue(path);
+ else if (isReference(path))
+ return IOUtils.toString(getReference(path));
+ else
+ throw new IllegalArgumentException(
+ "Path is not a value or reference");
+ }
+
+ public static InputStream getInputStream(Path path)
+ throws MalformedURLException, IOException {
+ if (isValue(path))
+ return Files.newInputStream(path);
+ else if (isReference(path))
+ return getReference(path).toURL().openStream();
+ else
+ throw new IllegalArgumentException(
+ "Path is not a value or reference");
+ }
+}
diff --git a/taverna-workbench-renderers-exts/pom.xml b/taverna-workbench-renderers-exts/pom.xml
new file mode 100644
index 0000000..2060886
--- /dev/null
+++ b/taverna-workbench-renderers-exts/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>renderers-exts</artifactId>
+ <name>Renderers extensions</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>renderers-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>workbench-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.databundle</groupId>
+ <artifactId>databundle</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.mygrid.resources</groupId>
+ <artifactId>Jmol</artifactId>
+ <version>NO-VERSION</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.mygrid.resources</groupId>
+ <artifactId>seqvista-hacked</artifactId>
+ <version>NO-VERSION</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.xmlgraphics</groupId>
+ <artifactId>batik-swing</artifactId>
+ <version>${batik.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>com.springsource.org.apache.commons.io</artifactId>
+ <version>${commons.io.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.icepdf</groupId>
+ <artifactId>icepdf-viewer</artifactId>
+ <!-- <version>${org.icepdf.version}</version> -->
+ <version>4.1.1-taverna</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/HTMLBrowserRenderer.java b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/HTMLBrowserRenderer.java
new file mode 100644
index 0000000..048c728
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/HTMLBrowserRenderer.java
@@ -0,0 +1,95 @@
+package net.sf.taverna.t2.renderers;
+
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * Web browser renderer for MIME type text/html.
+ *
+ * @author Peter Li
+ */
+public class HTMLBrowserRenderer implements Renderer {
+ private Logger logger = Logger.getLogger(HTMLBrowserRenderer.class);
+ private Pattern pattern;
+
+ public HTMLBrowserRenderer() {
+ pattern = Pattern.compile(".*text/html.*");
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public String getType() {
+ return "HTML (in Web browser)";
+ }
+
+ @Override
+ public JComponent getComponent(Path path) throws RendererException {
+ URI uri = null;
+ try {
+ if (DataBundles.isValue(path)) {
+ Path tempFile = Files.createTempFile(null, ".html");
+ Files.copy(path, tempFile, StandardCopyOption.REPLACE_EXISTING);
+ uri = tempFile.toUri();
+ } else if (DataBundles.isReference(path)) {
+ uri = DataBundles.getReference(path);
+ }
+ } catch (IOException e) {
+ logger.error("Error fetching data value", e);
+ return new JEditorPane("text/html", "Error fetching data value\n"
+ + e.toString());
+ }
+
+ // Start Web browser
+ try {
+ Desktop.getDesktop().browse(uri);
+ } catch (IOException e) {
+ logger.error("Error attempting to launch Web browser", e);
+ return new JEditorPane("text/html", "Error attempting to launch Web browser\n"
+ + e.toString());
+
+ }
+
+ return new JEditorPane("text/plain", "Launching a Web browser ...");
+ }
+
+}
diff --git a/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/JMolRenderer.java b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/JMolRenderer.java
new file mode 100644
index 0000000..e106a56
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/JMolRenderer.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * 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.renderers;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import org.apache.log4j.Logger;
+import org.jmol.adapter.smarter.SmarterJmolAdapter;
+import org.jmol.api.JmolAdapter;
+import org.jmol.api.JmolSimpleViewer;
+import org.jmol.api.JmolViewer;
+import org.jmol.viewer.Viewer;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * Renders using the Jmol software for chemical structures
+ *
+ * @author Tom Oinn
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ */
+public class JMolRenderer implements Renderer {
+
+ private Logger logger = Logger.getLogger(JMolRenderer.class);
+
+ private int MEGABYTE = 1024 * 1024;
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ if (mimeType.matches(".*chemical/x-pdb.*")
+ || mimeType.matches(".*chemical/x-mdl-molfile.*")
+ || mimeType.matches(".*chemical/x-cml.*")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ static final String proteinScriptString = "wireframe off; spacefill off; select protein; cartoon; colour structure; select ligand; spacefill; colour cpk; select dna; spacefill 0.4; wireframe on; colour cpk;";
+
+ static final String scriptString = "select *; spacefill 0.4; wireframe 0.2; colour cpk;";
+
+ @Override
+ public String getType() {
+ return "Jmol";
+ }
+
+ @Override
+ public JComponent getComponent(Path path) throws RendererException {
+ if (DataBundles.isValue(path) || DataBundles.isReference(path)) {
+ long approximateSizeInBytes = 0;
+ try {
+ approximateSizeInBytes = RendererUtils.getSizeInBytes(path);
+ } catch (Exception ex) {
+ logger.error("Failed to get the size of the data", ex);
+ return new JTextArea(
+ "Failed to get the size of the data (see error log for more details): \n"
+ + ex.getMessage());
+ }
+
+ if (approximateSizeInBytes > MEGABYTE) {
+ int response = JOptionPane
+ .showConfirmDialog(
+ null,
+ "Result is approximately "
+ + bytesToMeg(approximateSizeInBytes)
+ + " MB in size, there could be issues with rendering this inside Taverna\nDo you want to continue?",
+ "Render using Jmol?", JOptionPane.YES_NO_OPTION);
+
+ if (response != JOptionPane.YES_OPTION) {
+ return new JTextArea(
+ "Rendering cancelled due to size of data. Try saving and viewing in an external application.");
+ }
+ }
+
+ String resolve = null;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception e) {
+ logger.error("Reference Service failed to render data as string", e);
+ return new JTextArea(
+ "Reference Service failed to render data as string (see error log for more details): \n"
+ + e.getMessage());
+ }
+ JmolPanel panel = new JmolPanel();
+ JmolSimpleViewer viewer = null;
+ try {
+ viewer = panel.getViewer();
+ viewer.openStringInline(resolve);
+ if (((JmolViewer) viewer).getAtomCount() > 300) {
+ viewer.evalString(proteinScriptString);
+ } else {
+ viewer.evalString(scriptString);
+ }
+ } catch (Exception e) {
+ logger.error("Failed to create Jmol renderer", e);
+ return new JTextArea(
+ "Failed to create Jmol renderer (see error log for more details): \n"
+ + e.getMessage());
+ }
+ return panel;
+ } else {
+ logger.error("Failed to obtain the data to render: data is not a value or reference");
+ return new JTextArea(
+ "Failed to obtain the data to render: data is not a value or reference");
+ }
+ }
+
+ class JmolPanel extends JPanel {
+ private static final long serialVersionUID = 1L;
+
+ JmolSimpleViewer viewer;
+ JmolAdapter adapter;
+
+ JmolPanel() {
+ adapter = new SmarterJmolAdapter(null);
+ viewer = Viewer.allocateJmolSimpleViewer(this, adapter);
+ // viewer = JmolSimpleViewer.allocateSimpleViewer(this, adapter);
+ }
+
+ public JmolSimpleViewer getViewer() {
+ return viewer;
+ }
+
+ final Dimension currentSize = new Dimension();
+ final Rectangle rectClip = new Rectangle();
+
+ @Override
+ public void paint(Graphics g) {
+ getSize(currentSize);
+ g.getClipBounds(rectClip);
+ viewer.renderScreenImage(g, currentSize, rectClip);
+ }
+ }
+
+ /**
+ * Work out size of file in megabytes to 1 decimal place
+ *
+ * @param bytes
+ * @return
+ */
+ private int bytesToMeg(long bytes) {
+ float f = bytes / MEGABYTE;
+ return Math.round(f);
+ }
+}
diff --git a/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/PDFRenderer.java b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/PDFRenderer.java
new file mode 100644
index 0000000..cb86daa
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/PDFRenderer.java
@@ -0,0 +1,124 @@
+package net.sf.taverna.t2.renderers;
+
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import org.apache.log4j.Logger;
+import org.icepdf.ri.common.SwingController;
+import org.icepdf.ri.common.SwingViewBuilder;
+
+/**
+ * PDF renderer for MIME type application/pdf using the ICE PDF Java viewer.
+ *
+ * @author Peter Li
+ */
+public class PDFRenderer implements Renderer {
+ private Logger logger = Logger.getLogger(PDFRenderer.class);
+
+ private Pattern pattern;
+
+ private float MEGABYTE = 1024 * 1024;
+ private int meg = 1048576;
+
+ public PDFRenderer() {
+ pattern = Pattern.compile(".*application/pdf.*");
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public String getType() {
+ return "PDF";
+ }
+
+ @Override
+ public JComponent getComponent(Path path) throws RendererException {
+ long approximateSizeInBytes = 0;
+ try {
+ approximateSizeInBytes = RendererUtils.getSizeInBytes(path);
+ } catch (Exception ex) {
+ logger.error("Failed to get the size of the data", ex);
+ return new JTextArea(
+ "Failed to get the size of the data (see error log for more details): \n"
+ + ex.getMessage());
+ }
+
+ if (approximateSizeInBytes > meg) {
+ int response = JOptionPane
+ .showConfirmDialog(
+ null,
+ "Result is approximately "
+ + bytesToMeg(approximateSizeInBytes)
+ + " Mb in size, there could be issues with rendering this inside Taverna\nDo you want to continue?",
+ "Render this as text/html?", JOptionPane.YES_NO_OPTION);
+
+ if (response == JOptionPane.NO_OPTION) {
+ return new JTextArea(
+ "Rendering cancelled due to size of file. Try saving and viewing in an external application");
+ }
+ }
+
+ try (InputStream inputStream = RendererUtils.getInputStream(path)) {
+ // Build a controller
+ SwingController controller = new SwingController();
+ // Build a SwingViewFactory configured with the controller
+ SwingViewBuilder factory = new SwingViewBuilder(controller);
+ // Use the factory to build a JPanel that is pre-configured
+ // with a complete, active Viewer UI.
+ JPanel viewerComponentPanel = factory.buildViewerPanel();
+ // Open a PDF document to view
+ controller.openDocument(inputStream, "PDF Viewer", null);
+ return viewerComponentPanel;
+ } catch (Exception e) {
+ logger.error("Failed to create PDF renderer", e);
+ return new JTextArea(
+ "Failed to create PDF renderer (see error log for more details): \n" + e.getMessage());
+ }
+ }
+
+ /**
+ * Work out size of file in megabytes to 1 decimal place
+ *
+ * @param bytes
+ * @return
+ */
+ private int bytesToMeg(long bytes) {
+ float f = bytes / MEGABYTE;
+ Math.round(f);
+ return Math.round(f);
+ }
+}
diff --git a/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/SVGRenderer.java b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/SVGRenderer.java
new file mode 100644
index 0000000..e8edd91
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/SVGRenderer.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * 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.renderers;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * This class renders SVG Documents.
+ * Last edited by $Author: sowen70 $
+ *
+ * @author Mark
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ */
+public class SVGRenderer implements Renderer {
+
+ private int MEGABYTE = 1024 * 1024;
+
+ private static Logger logger = Logger.getLogger(SVGRenderer.class);
+
+ private Pattern pattern;
+
+ public SVGRenderer() {
+ pattern = Pattern.compile(".*image/svg[+]xml.*");
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "SVG";
+ }
+
+ @SuppressWarnings("serial")
+ public JComponent getComponent(Path path) throws RendererException {
+ if (DataBundles.isValue(path) || DataBundles.isReference(path)) {
+ long approximateSizeInBytes = 0;
+ try {
+ approximateSizeInBytes = RendererUtils.getSizeInBytes(path);
+ } catch (Exception ex) {
+ logger.error("Failed to get the size of the data", ex);
+ return new JTextArea(
+ "Failed to get the size of the data (see error log for more details): \n"
+ + ex.getMessage());
+ }
+
+ if (approximateSizeInBytes > MEGABYTE) {
+ int response = JOptionPane
+ .showConfirmDialog(
+ null,
+ "Result is approximately "
+ + bytesToMeg(approximateSizeInBytes)
+ + " MB in size, there could be issues with rendering this inside Taverna\nDo you want to continue?",
+ "Render as SVG?", JOptionPane.YES_NO_OPTION);
+
+ if (response != JOptionPane.YES_OPTION) {
+ return new JTextArea(
+ "Rendering cancelled due to size of data. Try saving and viewing in an external application.");
+ }
+ }
+
+ String resolve = null;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception e) {
+ logger.error("Reference Service failed to render data as string", e);
+ return new JTextArea(
+ "Reference Service failed to render data as string (see error log for more details): \n"
+ + e.getMessage());
+ }
+
+ final JSVGCanvas svgCanvas = new JSVGCanvas();
+ File tmpFile = null;
+ try {
+ tmpFile = File.createTempFile("taverna", "svg");
+ tmpFile.deleteOnExit();
+ FileUtils.writeStringToFile(tmpFile, resolve, "utf8");
+ } catch (IOException e) {
+ logger.error("SVG Renderer: Failed to write the data to temporary file", e);
+ return new JTextArea(
+ "Failed to write the data to temporary file (see error log for more details): \n"
+ + e.getMessage());
+ }
+ try {
+ svgCanvas.setURI(tmpFile.toURI().toASCIIString());
+ } catch (Exception e) {
+ logger.error("Failed to create SVG renderer", e);
+ return new JTextArea(
+ "Failed to create SVG renderer (see error log for more details): \n"
+ + e.getMessage());
+ }
+ JPanel jp = new JPanel() {
+ @Override
+ protected void finalize() throws Throwable {
+ svgCanvas.stopProcessing();
+ super.finalize();
+ }
+ };
+ jp.add(svgCanvas);
+ return jp;
+ } else {
+ logger.error("Failed to obtain the data to render: data is not a value or reference");
+ return new JTextArea(
+ "Failed to obtain the data to render: data is not a value or reference");
+ }
+ }
+
+ /**
+ * Work out size of file in megabytes to 1 decimal place
+ *
+ * @param bytes
+ * @return
+ */
+ private int bytesToMeg(long bytes) {
+ float f = bytes / MEGABYTE;
+ return Math.round(f);
+ }
+}
diff --git a/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/SeqVistaRenderer.java b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/SeqVistaRenderer.java
new file mode 100644
index 0000000..d4aaf5a
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/java/net/sf/taverna/t2/renderers/SeqVistaRenderer.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * 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.renderers;
+
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JTextArea;
+import javax.swing.LookAndFeel;
+import javax.swing.UIManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+import cht.svista.SeqVISTA;
+
+/**
+ * Uses the SeqVista renderer to draw an EMBL or SwissProt sequence
+ *
+ * @author Ian Dunlop
+ * @author anonymous from T1
+ * @author Alex Nenadic
+ */
+public class SeqVistaRenderer implements Renderer {
+
+ private Logger logger = Logger.getLogger(SeqVistaRenderer.class);
+
+ private int MEGABYTE = 1024 * 1024;
+
+ private String seqType = "fasta";
+ // 0 = auto, 1 = nucleotide, 2 = protein
+ private int np = 0;
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ if (mimeType.matches(".*chemical/x-swissprot.*")) {
+ seqType = "embl";
+ np = 2;
+ return true;
+ } else if (mimeType.matches(".*chemical/x-embl-dl-nucleotide.*")) {
+ seqType = "embl";
+ np = 1;
+ return true;
+ } else if (mimeType.matches(".*chemical/x-fasta.*")) {
+ seqType = "fasta";
+ np = 0;
+ return true;
+ } else if (mimeType.matches(".*chemical/x-ppd.*")) {
+ seqType = "ppd";
+ return true;
+ } else if (mimeType.matches(".*chemical/seq-na-genbank.*")) {
+ seqType = "auto";
+ np = 1;
+ return true;
+ } else if (mimeType.matches(".*chemical/seq-aa-genpept.*")) {
+ seqType = "auto";
+ np = 2;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String getType() {
+ return "Seq Vista";
+ }
+
+ @SuppressWarnings("serial")
+ public JComponent getComponent(Path path) throws RendererException {
+ if (DataBundles.isValue(path) || DataBundles.isReference(path)) {
+ long approximateSizeInBytes = 0;
+ try {
+ approximateSizeInBytes = RendererUtils.getSizeInBytes(path);
+ } catch (Exception ex) {
+ logger.error("Failed to get the size of the data", ex);
+ return new JTextArea(
+ "Failed to get the size of the data (see error log for more details): \n"
+ + ex.getMessage());
+ }
+
+ if (approximateSizeInBytes > MEGABYTE) {
+ int response = JOptionPane
+ .showConfirmDialog(
+ null,
+ "Result is approximately "
+ + bytesToMeg(approximateSizeInBytes)
+ + " MB in size, there could be issues with rendering this inside Taverna\nDo you want to continue?",
+ "Render as sequence?", JOptionPane.YES_NO_OPTION);
+
+ if (response != JOptionPane.YES_OPTION) {
+ return new JTextArea(
+ "Rendering cancelled due to size of data. Try saving and viewing in an external application.");
+ }
+ }
+
+ String resolve = null;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception e) {
+ logger.error("Reference Service failed to render data as string", e);
+ return new JTextArea(
+ "Reference Service failed to render data as string (see error log for more details): \n"
+ + e.getMessage());
+ }
+ // Save LAF as SeqVista is messing with it
+ LookAndFeel currentLookAndFeel = UIManager.getLookAndFeel();
+ SeqVISTA vista = new SeqVISTA() {
+ @Override
+ public java.awt.Dimension getPreferredSize() {
+ return new java.awt.Dimension(100, 100);
+ }
+ };
+ // Reset LAF messed up by SeqVista
+ try {
+ UIManager.setLookAndFeel(currentLookAndFeel);
+ } catch (Exception e) {
+ logger.info("Can't reset look and feel to " + currentLookAndFeel
+ + " after SeqVista renderer messed it up", e);
+ }
+
+ try {
+ vista.loadFromText(resolve, false, seqType, np);
+ } catch (Exception e) {
+ logger.error("Failed to create Sequence Vista renderer", e);
+ return new JTextArea(
+ "Failed to create Sequence Vista renderer (see error log for more details): \n"
+ + e.getMessage());
+ }
+ return vista;
+ } else {
+ logger.error("Failed to obtain the data to render: data is not a value or reference");
+ return new JTextArea(
+ "Failed to obtain the data to render: data is not a value or reference");
+ }
+ }
+
+ /**
+ * Work out size of file in megabytes to 1 decimal place
+ *
+ * @param bytes
+ * @return
+ */
+ private int bytesToMeg(long bytes) {
+ float f = bytes / MEGABYTE;
+ return Math.round(f);
+ }
+}
diff --git a/taverna-workbench-renderers-exts/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer b/taverna-workbench-renderers-exts/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
new file mode 100644
index 0000000..ca01b3b
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
@@ -0,0 +1,5 @@
+net.sf.taverna.t2.renderers.JMolRenderer
+net.sf.taverna.t2.renderers.SeqVistaRenderer
+net.sf.taverna.t2.renderers.SVGRenderer
+net.sf.taverna.t2.renderers.PDFRenderer
+net.sf.taverna.t2.renderers.HTMLBrowserRenderer
\ No newline at end of file
diff --git a/taverna-workbench-renderers-exts/src/main/resources/META-INF/spring/renderers-exts-context-osgi.xml b/taverna-workbench-renderers-exts/src/main/resources/META-INF/spring/renderers-exts-context-osgi.xml
new file mode 100644
index 0000000..0383fd6
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/resources/META-INF/spring/renderers-exts-context-osgi.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="JMolRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="SeqVistaRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="SVGRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="PDFRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="HTMLBrowserRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+
+</beans:beans>
diff --git a/taverna-workbench-renderers-exts/src/main/resources/META-INF/spring/renderers-exts-context.xml b/taverna-workbench-renderers-exts/src/main/resources/META-INF/spring/renderers-exts-context.xml
new file mode 100644
index 0000000..690aa3f
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/main/resources/META-INF/spring/renderers-exts-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="JMolRenderer" class="net.sf.taverna.t2.renderers.JMolRenderer" />
+ <bean id="SeqVistaRenderer" class="net.sf.taverna.t2.renderers.SeqVistaRenderer" />
+ <bean id="SVGRenderer" class="net.sf.taverna.t2.renderers.SVGRenderer" />
+ <bean id="PDFRenderer" class="net.sf.taverna.t2.renderers.PDFRenderer" />
+ <bean id="HTMLBrowserRenderer" class="net.sf.taverna.t2.renderers.HTMLBrowserRenderer" />
+
+</beans>
diff --git a/taverna-workbench-renderers-exts/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java b/taverna-workbench-renderers-exts/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
new file mode 100644
index 0000000..c113c7b
--- /dev/null
+++ b/taverna-workbench-renderers-exts/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * 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.renderers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestRendererSPI {
+
+ private static final String TEST_NS = "testNS";
+
+ @Test
+ public void doNothing() {
+ //do nothing for the moment
+ }
+
+// @Test
+// public void getAllRenderers() {
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// assertEquals(rendererRegistry.getInstances().size(), 10);
+// }
+//
+// @Test
+// public void checkTextHtmlMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType = "text/html";
+// String html = "<HTML><HEAD></HEAD><BODY>hello</BODY></HTML>";
+// EntityIdentifier entityIdentifier = facade.register(html, "utf-8");
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(),2);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextHtmlRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("text/html"));
+// }
+//
+// @Test
+// public void checkURLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType = "text/x-taverna-web-url.text";
+// String url = "http://google.com";
+// EntityIdentifier entityIdentifier = facade.register(url);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(),2);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextTavernaWebUrlRenderer");
+// assertTrue(renderersForMimeType.get(1).canHandle("text/x-taverna-web-url.text"));
+// }
+//
+// @Test
+// public void checkJMolMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="chemical/x-pdb";
+// String jmol = "jmol";
+// EntityIdentifier entityIdentifier = facade.register(jmol);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "JMolRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-mdl-molfile"));
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-cml"));
+// }
+//
+// @Test
+// public void checkSeqVistaMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="chemical/x-swissprot";
+// String type = "seqvista";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "SeqVistaRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-embl-dl-nucleotide"));
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-fasta"));
+// }
+//
+// @Test
+// public void checkSVGMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="image/svg+xml";
+// String type = "SVG";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "SVGRenderer");
+// }
+//
+// @Test
+// public void checkTextMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/text";
+// String type = "text";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// }
+//
+// @Test
+// public void checkTextRtfMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/rtf";
+// String type = "textRTF";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextRtfRenderer");
+// }
+//
+// @Test
+// public void checkTextXMLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/xml";
+// String type = "textXML";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextXMLRenderer");
+// }
+//
+// @Before
+// public void setDataManager() {
+// // dManager = new FileDataManager("testNS",
+// // new HashSet<LocationalContext>(), new File("/tmp/fish"));
+// dManager = new InMemoryDataManager(TEST_NS,
+// new HashSet<LocationalContext>());
+// facade = new DataFacade(dManager);
+// }
+
+}
diff --git a/taverna-workbench-renderers-impl/pom.xml b/taverna-workbench-renderers-impl/pom.xml
new file mode 100644
index 0000000..24e06c0
--- /dev/null
+++ b/taverna-workbench-renderers-impl/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>renderers-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Renderers Implementation</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Private-Package>org.fife.ui.hex.*,net.sf.taverna.t2.renderers.impl</Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>renderers-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.databundle</groupId>
+ <artifactId>databundle</artifactId>
+ <version>${taverna.databundle.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.fife.ui.hex</groupId>
+ <artifactId>hexeditor</artifactId>
+ <version>1.1-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java
new file mode 100644
index 0000000..14ef62f
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java
@@ -0,0 +1,124 @@
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static net.sf.taverna.t2.renderers.RendererUtils.getSizeInBytes;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.BIG_DATA_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.CANCELLED_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.NO_DATA_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.NO_SIZE_MSG;
+import static uk.org.taverna.databundle.DataBundles.isValue;
+import static uk.org.taverna.databundle.DataBundles.isReference;
+
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements some of the common code across the renderers.
+ *
+ * @author Donal Fellows
+ */
+abstract class AbstractRenderer implements Renderer {
+ protected Logger logger = Logger.getLogger(AbstractRenderer.class);
+ /** Size of a <s>mibibyte</s> megabyte. */
+ private int MEGABYTE = 1024 * 1024;
+
+ /**
+ * Work out size of file in megabytes
+ *
+ * @param bytes
+ * Number of bytes
+ * @return Number of megabytes
+ */
+ private int bytesToMeg(long bytes) {
+ float f = bytes / MEGABYTE;
+ return Math.round(f);
+ }
+
+ /**
+ * Implements basic checks on the entity to render before delegating to
+ * subclasses to actually do the rendering.
+ *
+ * @see #getRendererComponent(Path)
+ */
+ @Override
+ public JComponent getComponent(Path path) throws RendererException {
+ if (!isValue(path) && !isReference(path)) {
+ logger.error("unrenderable: data is not a value or reference");
+ return new JTextArea(NO_DATA_MSG);
+ }
+
+ // Get the size
+ long sizeInBytes;
+ try {
+ sizeInBytes = getSizeInBytes(path);
+ } catch (Exception ex) {
+ logger.error("unrenderable: failed to get data size", ex);
+ return new JTextArea(NO_SIZE_MSG + ex.getMessage());
+ }
+
+ // over the limit for this renderer?
+ if (sizeInBytes > MEGABYTE * getSizeLimit()) {
+ JComponent alternative = sizeCheck(path, bytesToMeg(sizeInBytes));
+ if (alternative != null)
+ return alternative;
+ }
+
+ return getRendererComponent(path);
+ }
+
+ /**
+ * Implements the user-visible part of the size check. Default version just
+ * does a simple yes/no proceed.
+ *
+ * @return <tt>null</tt> if the normal flow is to continue, or a component
+ * to show to the user if it is to be used instead (e.g., to show a
+ * message that the entity was too large).
+ */
+ protected JComponent sizeCheck(Path path, int approximateSizeInMB) {
+ int response = showConfirmDialog(null,
+ format(BIG_DATA_MSG, getResultType(), approximateSizeInMB),
+ getSizeQueryTitle(), YES_NO_OPTION);
+ if (response != YES_OPTION) // NO_OPTION or ESCAPE key
+ return new JTextArea(CANCELLED_MSG);
+ return null;
+ }
+
+ /**
+ * How should we describe the data to the user? Should be capitalised.
+ */
+ protected String getResultType() {
+ return "Result";
+ }
+
+ /**
+ * At what size (in megabytes) do we query the user?
+ *
+ * @see #sizeCheck(Path,log)
+ */
+ protected int getSizeLimit() {
+ return 1;
+ }
+
+ /**
+ * What title do we use on the dialog used to query the user?
+ *
+ * @see #sizeCheck(Path,log)
+ */
+ protected String getSizeQueryTitle() {
+ return "Render this image?";
+ }
+
+ /** Actually get the renderer; the basic checks have passed. */
+ protected abstract JComponent getRendererComponent(Path path)
+ throws RendererException;
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java
new file mode 100644
index 0000000..6c15834
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import static javax.imageio.ImageIO.getReaderMIMETypes;
+import static javax.imageio.ImageIO.getWriterFormatNames;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.awt.Image;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextArea;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Advanced renderer for mime type image/* that can render tiff files. Uses Java
+ * Advanced Imaging (JAI) ImageIO from https://jai-imageio.dev.java.net/.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class AdvancedImageRenderer extends AbstractRenderer {
+ private static final String BAD_FORMAT_MSG = "Data does not seem to contain an image in any of the recognised formats:\n";
+ private static final String UNRENDERABLE_MSG = "Failed to create image renderer " + SEE_LOG_MSG;
+
+ private Logger logger = Logger.getLogger(AdvancedImageRenderer.class);
+ private List<String> mimeTypesList;
+
+ public AdvancedImageRenderer() {
+ mimeTypesList = Arrays.asList(getReaderMIMETypes());
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return mimeTypesList.contains(mimeType);
+ }
+
+ @Override
+ public String getType() {
+ return "Image";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) {
+ // Render into a label
+ try (InputStream inputStream = getInputStream(path)) {
+ Image image = ImageIO.read(inputStream);
+ if (image == null)
+ return new JTextArea(BAD_FORMAT_MSG
+ + Arrays.asList(getWriterFormatNames()));
+ return new JLabel(new ImageIcon(image));
+ } catch (Exception e) {
+ logger.error("unrenderable: failed to create image renderer", e);
+ return new JTextArea(UNRENDERABLE_MSG + e.getMessage());
+ }
+ }
+
+ @Override
+ protected int getSizeLimit() {
+ return 4;
+ }
+
+ @Override
+ protected String getResultType() {
+ return "Image";
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java
new file mode 100644
index 0000000..2d90764
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/**
+ * This file is a component of the Taverna project,
+ * and is licensed under the GNU LGPL.
+ * Copyright Tom Oinn, EMBL-EBI
+ */
+package net.sf.taverna.t2.renderers.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A FileFilter implementation that can be configured to show only specific file
+ * suffixes.
+ *
+ * @author Tom Oinn
+ */
+public class ExtensionFileFilter extends FileFilter {
+ String[] allowedExtensions;
+
+ public ExtensionFileFilter(String[] allowedExtensions) {
+ this.allowedExtensions = allowedExtensions;
+ }
+
+ @Override
+ public boolean accept(File f) {
+ if (f.isDirectory())
+ return true;
+ String extension = getExtension(f);
+ if (extension != null)
+ for (int i = 0; i < allowedExtensions.length; i++)
+ if (extension.equalsIgnoreCase(allowedExtensions[i]))
+ return true;
+ return false;
+ }
+
+ String getExtension(File f) {
+ String ext = null;
+ String s = f.getName();
+ int i = s.lastIndexOf('.');
+ if (i > 0 && i < s.length() - 1)
+ ext = s.substring(i + 1).toLowerCase();
+ return ext;
+ }
+
+ @Override
+ public String getDescription() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Filter for extensions : ");
+ String sep = "";
+ for (String ext : allowedExtensions) {
+ sb.append(sep).append(ext);
+ sep = ", ";
+ }
+ return sb.toString();
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java
new file mode 100644
index 0000000..f1d8f29
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.io.InputStream;
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererException;
+
+import org.fife.ui.hex.swing.HexEditor;
+
+/**
+ * Renderer for binary data. Uses HexEditor from
+ * http://www.fifesoft.com/hexeditor/.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class HexBinaryRenderer extends AbstractRenderer {
+ private static final String RENDER_FAILED_MSG = "Failed to create binary hexadecimal renderer "
+ + SEE_LOG_MSG;
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return false;
+ /*
+ * can handle any data but return false here as we do not want this to
+ * be default renderer
+ */
+ }
+
+ @Override
+ protected String getSizeQueryTitle() {
+ return "Render binary data (in hexadecimal viewer)?";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) throws RendererException {
+ try (InputStream inputStream = getInputStream(path)) {
+ HexEditor editor = new HexEditor();
+ editor.open(inputStream);
+ return editor;
+ } catch (Exception e) {
+ logger.error("unrenderable: failed to create binhex renderer", e);
+ return new JTextArea(RENDER_FAILED_MSG + e.getMessage());
+ }
+ }
+
+ @Override
+ public String getType() {
+ return "Binary (HexDec)";
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java
new file mode 100644
index 0000000..d51c94c
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java
@@ -0,0 +1,13 @@
+package net.sf.taverna.t2.renderers.impl;
+
+interface RendererConstants {
+ String SEE_LOG_MSG = "(see error log for more details):\n";
+ String NO_DATA_MSG = "Failed to obtain the data to render: "
+ + "data is not a value or reference";
+ String NO_SIZE_MSG = "Failed to get the size of the data " + SEE_LOG_MSG;
+ String BIG_DATA_MSG = "%s is approximately %d MB in size, "
+ + "there could be issues with rendering this inside Taverna\n"
+ + "Do you want to continue?";
+ String CANCELLED_MSG = "Rendering cancelled due to size of image. "
+ + "Try saving and viewing in an external application.";
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java
new file mode 100644
index 0000000..eb41292
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+
+/**
+ * Implementation of a RendererRegistry.
+ *
+ * @author David Withers
+ */
+public class RendererRegistryImpl implements RendererRegistry {
+ private List<Renderer> renderers;
+
+ @Override
+ public List<Renderer> getRenderersForMimeType(String mimeType) {
+ ArrayList<Renderer> list = new ArrayList<>();
+ for (Renderer renderer : renderers)
+ if (renderer.canHandle(mimeType))
+ list.add(renderer);
+ return list;
+ }
+
+ @Override
+ public List<Renderer> getRenderers() {
+ return renderers;
+ }
+
+ public void setRenderers(List<Renderer> renderers) {
+ this.renderers = renderers;
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java
new file mode 100644
index 0000000..87fa364
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import static java.awt.Font.PLAIN;
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.RendererUtils.getString;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.CANCELLED_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.awt.Font;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.RendererException;
+
+/**
+ * Renderer for mime type text/*
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class TextRenderer extends AbstractRenderer {
+ private static final String UNREADABLE_MSG = "Reference Service failed to render data "
+ + SEE_LOG_MSG;
+ private static final String RENDERER_FAILED_MSG = "Failed to create text renderer "
+ + SEE_LOG_MSG;
+ private static final String QUERY_MSG = "Result is approximately %d MB "
+ + "in size, there could be issues with rendering this inside "
+ + "Taverna\nDo you want to cancel, render all of the result, "
+ + "or only the first part?";
+ private static final Pattern pattern = Pattern.compile(".*text/.*");
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "Text";
+ }
+
+ private JComponent textRender(String text) {
+ DialogTextArea theTextArea = new DialogTextArea();
+ theTextArea.setWrapStyleWord(true);
+ theTextArea.setEditable(false);
+ theTextArea.setText(text);
+ theTextArea.setFont(new Font("Monospaced", PLAIN, 12));
+ theTextArea.setCaretPosition(0);
+ return theTextArea;
+ }
+
+ private static final Object[] SIZE_OPTIONS = { "Continue rendering",
+ "Render partial", "Cancel" };
+
+ @Override
+ protected JComponent sizeCheck(Path path, int approximateSizeInMB) {
+ // allow partial rendering of text files
+ int response = showOptionDialog(null,
+ format(QUERY_MSG, approximateSizeInMB),
+ "Rendering large result", YES_NO_CANCEL_OPTION,
+ QUESTION_MESSAGE, null, SIZE_OPTIONS, SIZE_OPTIONS[2]);
+ if (response == YES_OPTION)
+ return null;
+ else if (response != NO_OPTION)
+ // if (response == CANCEL_OPTION) or ESCAPE key pressed
+ return new JTextArea(CANCELLED_MSG);
+
+ // Construct a partial result.
+ byte[] smallStringBytes = new byte[1048576];
+ try (InputStream inputStream = getInputStream(path)) {
+ // just copy the first MEGABYTE
+ inputStream.read(smallStringBytes);
+ } catch (Exception ex) {
+ logger.error("unrenderable: Reference Service failed "
+ + "to render data as byte array", ex);
+ return new JTextArea(UNREADABLE_MSG + ex.getMessage());
+ }
+ try {
+ // TODO beware of encoding problems!
+ return textRender(new String(smallStringBytes));
+ } catch (Exception e1) {
+ logger.error("Failed to create text renderer", e1);
+ return new JTextArea(RENDERER_FAILED_MSG + e1.getMessage());
+ }
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) throws RendererException {
+ String resolve;
+ try {
+ // Resolve it as a string
+ resolve = getString(path);
+ } catch (Exception e) {
+ logger.error("unrenderable: Reference Service failed "
+ + "to render data as string", e);
+ return new JTextArea(UNREADABLE_MSG + e.getMessage());
+ }
+ try {
+ return textRender(resolve);
+ } catch (Exception e1) {
+ logger.error("Failed to create text renderer", e1);
+ return new JTextArea(RENDERER_FAILED_MSG + e1.getMessage());
+ }
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java
new file mode 100644
index 0000000..6c3bf7f
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererException;
+import net.sf.taverna.t2.renderers.RendererUtils;
+
+/**
+ * Renderer for mime type text/rtf
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class TextRtfRenderer extends AbstractRenderer {
+ private static final String UNREADABLE_MSG = "Reference Service failed to render data "
+ + SEE_LOG_MSG;
+ private static final String RENDERER_FAILED_MSG = "Failed to create RTF renderer "
+ + SEE_LOG_MSG;
+ private static final Pattern pattern = Pattern.compile(".*text/rtf.*");
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "Text/RTF";
+ }
+
+ @Override
+ protected String getSizeQueryTitle() {
+ return "Render as RTF?";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) throws RendererException {
+ String resolve;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception e) {
+ logger.error("Reference Service failed to render data as string", e);
+ return new JTextArea(UNREADABLE_MSG + e.getMessage());
+ }
+ try {
+ return new JEditorPane("text/rtf", resolve);
+ } catch (Exception e) {
+ logger.error("Failed to create RTF renderer", e);
+ return new JTextArea(RENDERER_FAILED_MSG + e.getMessage());
+ }
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java
new file mode 100644
index 0000000..eaad9c6
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererUtils;
+
+/**
+ * Viewer to display XML as a tree.
+ *
+ * @author Matthew Pocock
+ * @auhor Ian Dunlop
+ * @author David Withers
+ */
+public class TextXMLRenderer extends AbstractRenderer {
+ private static final String UNREADABLE_MSG = "Reference Service failed to render data as string " + SEE_LOG_MSG;
+ private static final String RENDERER_FAILED_MSG = "Failed to create XML renderer " + SEE_LOG_MSG;
+ private Pattern pattern;
+
+ public TextXMLRenderer() {
+ pattern = Pattern.compile(".*text/xml.*");
+ }
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "XML tree";
+ }
+
+ @Override
+ protected String getSizeQueryTitle() {
+ return "Render this as XML?";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) {
+ String resolve = null;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception ex) {
+ logger.error("unrenderable: Reference Service failed to render data as string",
+ ex);
+ return new JTextArea(UNREADABLE_MSG + ex.getMessage());
+ }
+ try {
+ return new XMLTree(resolve);
+ } catch (Exception e) {
+ logger.error("unrenderable: failed to create XML renderer", e);
+ return new JTextArea(RENDERER_FAILED_MSG + e.getMessage());
+ }
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java
new file mode 100644
index 0000000..2d53218
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java
@@ -0,0 +1,329 @@
+/*******************************************************************************
+ * 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.renderers.impl;
+
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
+import static org.jdom.output.Format.getPrettyFormat;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.prefs.Preferences;
+
+import javax.swing.JFileChooser;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import org.jdom.Attribute;
+import org.jdom.Content;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.Parent;
+import org.jdom.Text;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.XMLOutputter;
+
+/**
+ * An extension of the {@link JTree} class, constructed with a String of XML and
+ * used to display the XML structure as an interactive tree. Derived from <a
+ * href="http://www.devx.com/gethelpon/10MinuteSolution/16694/0/page/1">original
+ * code by Kyle Gabhart</a> and then subsequently heavily rewritten to move to
+ * JDOM, and moved lots of the setup code to the renderer to cut down
+ * initialisation time. Added text node size limit as well. Displaying large
+ * gene sequences as base64 encoded text in a single node really, <i>really</i>
+ * hurts performance.
+ *
+ * @author Kyle Gabhart
+ * @author Tom Oinn
+ * @author Kevin Glover
+ * @author Ian Dunlop
+ */
+@SuppressWarnings("serial")
+class XMLTree extends JTree {
+ private class XMLNode extends DefaultMutableTreeNode {
+ public XMLNode(Content userObject) {
+ super(userObject);
+ }
+ }
+
+ int textSizeLimit = 1000;
+ final JFileChooser fc = new JFileChooser();
+ Element rootElement = null;
+
+ /**
+ * Build a new XMLTree from the supplied String containing XML.
+ *
+ * @param text
+ * @throws IOException
+ * @throws JDOMException
+ */
+ public XMLTree(String text) throws IOException, JDOMException {
+ super();
+ Document document = new SAXBuilder(false).build(new StringReader(text));
+ init(document.getRootElement());
+ revalidate();
+ }
+
+ public String getText() {
+ if (rootElement == null)
+ return "";
+ XMLOutputter xo = new XMLOutputter(getPrettyFormat());
+ return xo.outputString(rootElement);
+ }
+
+ public XMLTree(String text, boolean limit) throws IOException,
+ JDOMException {
+ if (!limit)
+ textSizeLimit = -1;
+ Document document = new SAXBuilder(false).build(new StringReader(text));
+ init(document.getRootElement());
+ revalidate();
+ }
+
+ public XMLTree(Document document) {
+ this(document.getRootElement());
+ }
+
+ public XMLTree(Element element) {
+ super();
+ init(element);
+ revalidate();
+ }
+
+ private void init(Content content) {
+ rootElement = (Element) content;
+ /*
+ * Fix for platforms other than metal which can't otherwise cope with
+ * arbitrary size rows
+ */
+ setRowHeight(0);
+ getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
+ setShowsRootHandles(true);
+ setEditable(false);
+ setModel(new DefaultTreeModel(createTreeNode(content)));
+ setCellRenderer(new DefaultTreeCellRenderer() {
+ @Override
+ public Color getBackgroundNonSelectionColor() {
+ return null;
+ }
+
+ @Override
+ public Color getBackground() {
+ return null;
+ }
+
+ @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);
+ setOpaque(false);
+ if (value instanceof XMLNode) {
+ XMLNode node = (XMLNode) value;
+ if (node.getUserObject() instanceof Element)
+ renderElementNode((Element) node.getUserObject());
+ else if (node.getUserObject() instanceof Text)
+ renderTextNode((Text) node.getUserObject());
+ // TODO what about other node types?
+ }
+ setBackground(new Color(0, 0, 0, 0));
+ return this;
+ }
+
+ private void renderElementNode(Element element) {
+ // setIcon(TavernaIcons.xmlNodeIcon);
+ StringBuilder nameBuffer = new StringBuilder("<html>")
+ .append(element.getQualifiedName());
+ /*
+ * Bit of a quick and dirty hack here to try to ensure that the
+ * element namespace is shown. There appears no way to get the
+ * actual xmlns declarations that are part of an element through
+ * jdom. Also, please note, there's no namespace handling at all
+ * for attributes...
+ */
+ if (element.getParent() instanceof Element) {
+ Element parent = (Element) element.getParent();
+ if (parent.getNamespace(element.getNamespacePrefix()) == null)
+ nameBuffer
+ .append(" <font color=\"purple\">xmlns:")
+ .append(element.getNamespacePrefix())
+ .append("</font>=\"<font color=\"green\">")
+ .append(element.getNamespaceURI() + "</font>\"");
+ } else
+ nameBuffer.append(" <font color=\"purple\">xmlns:")
+ .append(element.getNamespacePrefix())
+ .append("</font>=\"<font color=\"green\">")
+ .append(element.getNamespaceURI() + "</font>\"");
+
+ String sep = "";
+ for (Object a : element.getAttributes()) {
+ Attribute attribute = (Attribute) a;
+ String name = attribute.getName().trim();
+ String value = attribute.getValue().trim();
+ if (value != null && value.length() > 0) {
+ // TODO xml-quote name and value
+ nameBuffer.append(sep)
+ .append(" <font color=\"purple\">")
+ .append(name)
+ .append("</font>=\"<font color=\"green\">")
+ .append(value).append("</font>\"");
+ sep = ",";
+ }
+ }
+
+ nameBuffer.append("</html>");
+ setText(nameBuffer.toString());
+ }
+
+ private void renderTextNode(Text text) {
+ // setIcon(TavernaIcons.leafIcon);
+ String name = text.getText();
+ if (textSizeLimit > -1 && name.length() > textSizeLimit)
+ name = name.substring(0, textSizeLimit) + "...";
+ setText("<html><pre><font color=\"blue\">"
+ + name.replaceAll("<br>", "\n").replaceAll("<", "<")
+ + "</font></pre></html>");
+ }
+ });
+ setAllNodesExpanded();
+
+ // Add a listener to present the 'save as text' option
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (e.isPopupTrigger())
+ doEvent(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (e.isPopupTrigger())
+ doEvent(e);
+ }
+
+ public void doEvent(MouseEvent e) {
+ JPopupMenu menu = new JPopupMenu();
+ JMenuItem item = new JMenuItem("Save as XML text");
+ menu.add(item);
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ saveTreeXML();
+ }
+ });
+ menu.show(XMLTree.this, e.getX(), e.getY());
+ }
+ });
+ }
+
+ private void saveTreeXML() {
+ try {
+ Preferences prefs = userNodeForPackage(XMLTree.class);
+ String curDir = prefs.get("currentDir",
+ System.getProperty("user.home"));
+ fc.resetChoosableFileFilters();
+ fc.setFileFilter(new ExtensionFileFilter(new String[] { "xml" }));
+ fc.setCurrentDirectory(new File(curDir));
+ if (fc.showSaveDialog(this) == APPROVE_OPTION) {
+ prefs.put("currentDir", fc.getCurrentDirectory().toString());
+ saveTreeXML(fc.getSelectedFile());
+ }
+ } catch (Exception ex) {
+ showMessageDialog(this, "Problem saving XML:\n" + ex.getMessage(),
+ "Error!", ERROR_MESSAGE);
+ }
+ }
+
+ private void saveTreeXML(File file) throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(file))) {
+ out.print(this.getText());
+ }
+ }
+
+ public void setAllNodesExpanded() {
+ synchronized (this.getModel()) {
+ expandAll(this, new TreePath(this.getModel().getRoot()), true);
+ }
+ }
+
+ private void expandAll(JTree tree, TreePath parent, boolean expand) {
+ synchronized (this.getModel()) {
+ /*
+ * Traverse children
+ *
+ * Ignores nodes who's userObject is a Processor type to avoid
+ * overloading the UI with nodes at startup.
+ */
+ TreeNode node = (TreeNode) parent.getLastPathComponent();
+ for (Enumeration<?> e = node.children(); e.hasMoreElements(); ) {
+ TreeNode n = (TreeNode) e.nextElement();
+ expandAll(tree, parent.pathByAddingChild(n), expand);
+ }
+ // Expansion or collapse must be done bottom-up
+ if (expand)
+ tree.expandPath(parent);
+ else
+ tree.collapsePath(parent);
+ }
+ }
+
+ public void setTextNodeSizeLimit(int sizeLimit) {
+ textSizeLimit = sizeLimit;
+ }
+
+ private XMLNode createTreeNode(Content content) {
+ XMLNode node = new XMLNode(content);
+ if (content instanceof Parent) {
+ Parent parent = (Parent) content;
+ for (Object child : parent.getContent()) {
+ if (child instanceof Element)
+ node.add(createTreeNode((Content) child));
+ else if (textSizeLimit != 0 && child instanceof Text) {
+ Text text = (Text) child;
+ if (!text.getTextNormalize().isEmpty())
+ node.add(createTreeNode(text));
+ }
+ }
+ }
+ return node;
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer b/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
new file mode 100644
index 0000000..044a396
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
@@ -0,0 +1,7 @@
+net.sf.taverna.t2.renderers.impl.AdvancedImageRenderer
+net.sf.taverna.t2.renderers.impl.HexBinaryRenderer
+net.sf.taverna.t2.renderers.impl.TextRenderer
+net.sf.taverna.t2.renderers.impl.TextRtfRenderer
+net.sf.taverna.t2.renderers.impl.TextXMLRenderer
+
+
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml
new file mode 100644
index 0000000..436426a
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="AdvancedImageRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="HexBinaryRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="TextRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="TextRtfRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="TextXMLRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+
+ <service ref="RendererRegistry" interface="net.sf.taverna.t2.renderers.RendererRegistry" />
+
+ <list id="renderers" interface="net.sf.taverna.t2.renderers.Renderer" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml
new file mode 100644
index 0000000..a041c98
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="AdvancedImageRenderer" class="net.sf.taverna.t2.renderers.impl.AdvancedImageRenderer" />
+ <bean id="HexBinaryRenderer" class="net.sf.taverna.t2.renderers.impl.HexBinaryRenderer" />
+ <bean id="TextRenderer" class="net.sf.taverna.t2.renderers.impl.TextRenderer" />
+ <bean id="TextRtfRenderer" class="net.sf.taverna.t2.renderers.impl.TextRtfRenderer" />
+ <bean id="TextXMLRenderer" class="net.sf.taverna.t2.renderers.impl.TextXMLRenderer" />
+
+ <bean id="RendererRegistry" class="net.sf.taverna.t2.renderers.impl.RendererRegistryImpl">
+ <property name="renderers" ref="renderers" />
+ </bean>
+
+
+</beans>
diff --git a/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java b/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
new file mode 100644
index 0000000..ece7cf5
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * 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.renderers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+@SuppressWarnings("unused")
+public class TestRendererSPI {
+
+ private static final String TEST_NS = "testNS";
+
+ @Test
+ public void doNothing() {
+ //do nothing for the moment
+ }
+
+// @Test
+// public void getAllRenderers() {
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// assertEquals(rendererRegistry.getInstances().size(), 10);
+// }
+//
+// @Test
+// public void checkTextHtmlMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType = "text/html";
+// String html = "<HTML><HEAD></HEAD><BODY>hello</BODY></HTML>";
+// EntityIdentifier entityIdentifier = facade.register(html, "utf-8");
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(),2);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextHtmlRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("text/html"));
+// }
+//
+// @Test
+// public void checkURLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType = "text/x-taverna-web-url.text";
+// String url = "http://google.com";
+// EntityIdentifier entityIdentifier = facade.register(url);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(),2);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextTavernaWebUrlRenderer");
+// assertTrue(renderersForMimeType.get(1).canHandle("text/x-taverna-web-url.text"));
+// }
+//
+// @Test
+// public void checkJMolMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="chemical/x-pdb";
+// String jmol = "jmol";
+// EntityIdentifier entityIdentifier = facade.register(jmol);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "JMolRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-mdl-molfile"));
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-cml"));
+// }
+//
+// @Test
+// public void checkSeqVistaMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="chemical/x-swissprot";
+// String type = "seqvista";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "SeqVistaRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-embl-dl-nucleotide"));
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-fasta"));
+// }
+//
+// @Test
+// public void checkSVGMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="image/svg+xml";
+// String type = "SVG";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "SVGRenderer");
+// }
+//
+// @Test
+// public void checkTextMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/text";
+// String type = "text";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// }
+//
+// @Test
+// public void checkTextRtfMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/rtf";
+// String type = "textRTF";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextRtfRenderer");
+// }
+//
+// @Test
+// public void checkTextXMLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/xml";
+// String type = "textXML";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextXMLRenderer");
+// }
+//
+// @Before
+// public void setDataManager() {
+// // dManager = new FileDataManager("testNS",
+// // new HashSet<LocationalContext>(), new File("/tmp/fish"));
+// dManager = new InMemoryDataManager(TEST_NS,
+// new HashSet<LocationalContext>());
+// facade = new DataFacade(dManager);
+// }
+
+}
diff --git a/taverna-workbench-report-api/pom.xml b/taverna-workbench-report-api/pom.xml
new file mode 100644
index 0000000..706fd17
--- /dev/null
+++ b/taverna-workbench-report-api/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Reporting API</name>
+ <description>
+ API for creating reports about dataflows in the workbench.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-validation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+
+</project>
diff --git a/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ProfileReportEvent.java b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ProfileReportEvent.java
new file mode 100644
index 0000000..d5412bc
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ProfileReportEvent.java
@@ -0,0 +1,21 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report;
+
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * @author alanrw
+ */
+public class ProfileReportEvent implements ReportManagerEvent {
+ private final Profile profile;
+
+ public ProfileReportEvent(Profile d) {
+ this.profile = d;
+ }
+
+ public Profile getProfile() {
+ return profile;
+ }
+}
diff --git a/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ReportManager.java b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ReportManager.java
new file mode 100644
index 0000000..eb39c2f
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ReportManager.java
@@ -0,0 +1,44 @@
+package net.sf.taverna.t2.workbench.report;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import uk.org.taverna.scufl2.api.common.WorkflowBean;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+import uk.org.taverna.scufl2.validation.WorkflowBeanReport;
+import uk.org.taverna.scufl2.validation.Status;
+
+import net.sf.taverna.t2.lang.observer.Observer;
+//import net.sf.taverna.t2.visit.VisitReport;
+//import net.sf.taverna.t2.visit.VisitReport.Status;
+
+public interface ReportManager {
+ void updateReport(Profile p, boolean includeTimeConsuming, boolean remember);
+
+ void updateObjectSetReport(Profile p, Set<WorkflowBean> objects);
+
+ void updateObjectReport(Profile p, WorkflowBean o);
+
+ Set<WorkflowBeanReport> getReports(Profile p, WorkflowBean object);
+
+ Map<WorkflowBean, Set<WorkflowBeanReport>> getReports(Profile p);
+
+ boolean isStructurallySound(Profile p);
+
+ Status getStatus(Profile p);
+
+ Status getStatus(Profile p, WorkflowBean object);
+
+ String getSummaryMessage(Profile p, WorkflowBean object);
+
+ long getLastCheckedTime(Profile p);
+
+ long getLastFullCheckedTime(Profile p);
+
+ void addObserver(Observer<ReportManagerEvent> observer);
+
+ List<Observer<ReportManagerEvent>> getObservers();
+
+ void removeObserver(Observer<ReportManagerEvent> observer);
+}
diff --git a/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ReportManagerEvent.java b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ReportManagerEvent.java
new file mode 100644
index 0000000..c49c764
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/ReportManagerEvent.java
@@ -0,0 +1,10 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report;
+
+/**
+ * @author alanrw
+ */
+public interface ReportManagerEvent {
+}
diff --git a/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/config/ReportManagerConfiguration.java b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/config/ReportManagerConfiguration.java
new file mode 100644
index 0000000..0935910
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/java/net/sf/taverna/t2/workbench/report/config/ReportManagerConfiguration.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.report.config;
+
+import uk.org.taverna.configuration.Configurable;
+
+/**
+ * @author David Withers
+ */
+public interface ReportManagerConfiguration extends Configurable {
+ String TIMEOUT = "TIMEOUT";
+ String ON_EDIT = "ON_EDIT";
+ String ON_OPEN = "ON_OPEN";
+ String BEFORE_RUN = "BEFORE_RUN";
+ String NO_CHECK = "NoCheck";
+ String QUICK_CHECK = "QuickCheck";
+ String FULL_CHECK = "FullCheck";
+ String NONE = "Do not care";
+ String ERRORS_OR_WARNINGS = "Errors or warnings";
+ String ERRORS = "Errors";
+ String QUERY_BEFORE_RUN = "QUERY_BEFORE_RUN";
+ int DEFAULT_REPORT_EXPIRATION = 0;
+ String REPORT_EXPIRATION = "REPORT_EXPIRATION";
+
+ public void applySettings();
+}
diff --git a/taverna-workbench-report-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind b/taverna-workbench-report-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind
new file mode 100644
index 0000000..8d46fdd
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind
@@ -0,0 +1 @@
+net.sf.taverna.t2.visit.fragility.FragilityCheck
diff --git a/taverna-workbench-report-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.fragility.FragilityChecker b/taverna-workbench-report-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.fragility.FragilityChecker
new file mode 100644
index 0000000..b158b98
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.fragility.FragilityChecker
@@ -0,0 +1 @@
+net.sf.taverna.t2.visit.fragility.ProcessorFragilityChecker
diff --git a/taverna-workbench-report-api/src/main/resources/META-INF/spring/report-api-context-osgi.xml b/taverna-workbench-report-api/src/main/resources/META-INF/spring/report-api-context-osgi.xml
new file mode 100644
index 0000000..c808800
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/resources/META-INF/spring/report-api-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <!-- <service ref="ProcessorFragilityChecker" interface="net.sf.taverna.t2.visit.fragility.FragilityChecker" /> -->
+
+ <!-- <service ref="FragilityCheck" interface="net.sf.taverna.t2.visit.VisitKind" /> -->
+
+</beans:beans>
diff --git a/taverna-workbench-report-api/src/main/resources/META-INF/spring/report-api-context.xml b/taverna-workbench-report-api/src/main/resources/META-INF/spring/report-api-context.xml
new file mode 100644
index 0000000..2261013
--- /dev/null
+++ b/taverna-workbench-report-api/src/main/resources/META-INF/spring/report-api-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <!-- <bean id="ProcessorFragilityChecker" class="net.sf.taverna.t2.visit.fragility.ProcessorFragilityChecker" /> -->
+
+ <!-- <bean id="FragilityCheck" class="net.sf.taverna.t2.visit.fragility.FragilityCheck" /> -->
+
+</beans>
diff --git a/taverna-workbench-report-explainer/pom.xml b/taverna-workbench-report-explainer/pom.xml
new file mode 100644
index 0000000..4be9bd9
--- /dev/null
+++ b/taverna-workbench-report-explainer/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>report-explainer</artifactId>
+ <packaging>bundle</packaging>
+ <name>Report explainer</name>
+ <description>
+ Explanations of reports
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>retry-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>dataflow-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-activities</groupId>
+ <artifactId>disabled-activity-ui</artifactId>
+ <version>${t2.ui.activities.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.activities</groupId>
+ <artifactId>wsdl-activity</artifactId>
+ <version>${t2.activities.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.activities</groupId>
+ <artifactId>dataflow-activity</artifactId>
+ <version>${t2.activities.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-api</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>configuration-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>report-view</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>design-ui</artifactId>
+ <version>${t2.ui.components.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-impl</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>BrowserLauncher2</groupId>
+ <artifactId>BrowserLauncher2</artifactId>
+ <version>1.3</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-report-explainer/src/main/java/net/sf/taverna/t2/workbench/report/explainer/BasicExplainer.java b/taverna-workbench-report-explainer/src/main/java/net/sf/taverna/t2/workbench/report/explainer/BasicExplainer.java
new file mode 100644
index 0000000..cba6a98
--- /dev/null
+++ b/taverna-workbench-report-explainer/src/main/java/net/sf/taverna/t2/workbench/report/explainer/BasicExplainer.java
@@ -0,0 +1,1294 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.report.explainer;
+
+import java.awt.Desktop;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.SystemColor;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.net.ssl.SSLException;
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.activities.dataflow.DataflowActivity;
+import net.sf.taverna.t2.activities.dataflow.actions.EditNestedDataflowAction;
+import net.sf.taverna.t2.activities.disabled.actions.DisabledActivityConfigurationAction;
+import net.sf.taverna.t2.activities.wsdl.InputPortTypeDescriptorActivity;
+import net.sf.taverna.t2.activities.wsdl.xmlsplitter.AddXMLSplitterEdit;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.visit.DataflowCollation;
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.fragility.FragilityCheck;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowOutputAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.FailedEntityKind;
+import net.sf.taverna.t2.workbench.report.IncompleteDataflowKind;
+import net.sf.taverna.t2.workbench.report.InvalidDataflowKind;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.UnresolvedOutputKind;
+import net.sf.taverna.t2.workbench.report.UnsatisfiedEntityKind;
+import net.sf.taverna.t2.workbench.report.view.ReportViewConfigureAction;
+import net.sf.taverna.t2.workbench.retry.RetryConfigureAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.impl.configuration.ui.T2ConfigurationFrame;
+import net.sf.taverna.t2.workflowmodel.CompoundEdit;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import net.sf.taverna.t2.workflowmodel.Datalink;
+import net.sf.taverna.t2.workflowmodel.Edit;
+import net.sf.taverna.t2.workflowmodel.EditException;
+import net.sf.taverna.t2.workflowmodel.Merge;
+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.TokenProcessingEntity;
+import net.sf.taverna.t2.workflowmodel.health.HealthCheck;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+import net.sf.taverna.t2.workflowmodel.processor.activity.DisabledActivity;
+import net.sf.taverna.t2.workflowmodel.processor.dispatch.DispatchLayer;
+import net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry;
+import net.sf.taverna.t2.workflowmodel.utils.Tools;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+/**
+ * @author alanrw
+ *
+ */
+public class BasicExplainer implements VisitExplainer {
+
+ private static Logger logger = Logger.getLogger(BasicExplainer.class);
+
+ private static String PLEASE_CONTACT = "Please contact the service provider or workflow creator.";
+
+ private EditManager editManager;
+
+ private FileManager fileManager;
+
+ private ReportManager reportManager;
+
+ private SelectionManager selectionManager;
+
+ private List<ConfigurationUIFactory> configurationUIFactories;
+
+ private ActivityIconManager activityIconManager;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.explainer.VisitExplainer#canExplain
+ * (net.sf.taverna.t2.visit.VisitKind, int)
+ */
+ public boolean canExplain(VisitKind vk, int resultId) {
+ if (vk instanceof DataflowCollation) {
+ return true;
+ }
+ if (vk instanceof FailedEntityKind) {
+ return true;
+ }
+ if (vk instanceof IncompleteDataflowKind) {
+ return true;
+ }
+ if (vk instanceof InvalidDataflowKind) {
+ return true;
+ }
+ if (vk instanceof UnresolvedOutputKind) {
+ return true;
+ }
+ if (vk instanceof UnsatisfiedEntityKind) {
+ return true;
+ }
+ if (vk instanceof FragilityCheck) {
+ return true;
+ }
+ if ((vk instanceof HealthCheck)
+ && ((resultId == HealthCheck.INVALID_SCRIPT)
+ || (resultId == HealthCheck.CONNECTION_PROBLEM)
+ || (resultId == HealthCheck.INVALID_URL)
+ || (resultId == HealthCheck.IO_PROBLEM)
+ || (resultId == HealthCheck.TIME_OUT)
+ || (resultId == HealthCheck.MISSING_DEPENDENCY)
+ || (resultId == HealthCheck.DEFAULT_VALUE)
+ || (resultId == HealthCheck.BAD_WSDL)
+ || (resultId == HealthCheck.NOT_HTTP)
+ || (resultId == HealthCheck.UNSUPPORTED_STYLE)
+ || (resultId == HealthCheck.UNKNOWN_OPERATION)
+ || (resultId == HealthCheck.NO_ENDPOINTS)
+ || (resultId == HealthCheck.INVALID_CONFIGURATION)
+ || (resultId == HealthCheck.NULL_DATATYPE)
+ || (resultId == HealthCheck.DISABLED)
+ || (resultId == HealthCheck.DATATYPE_SOURCE)
+ || (resultId == HealthCheck.UNRECOGNIZED)
+ || (resultId == HealthCheck.LOOP_CONNECTION)
+ || (resultId == HealthCheck.UNMANAGED_LOCATION) || (resultId == HealthCheck.INCOMPATIBLE_MIMETYPES))) {
+ return true;
+ }
+ return false;
+ }
+
+ public JComponent getExplanation(VisitReport vr) {
+ VisitKind vk = vr.getKind();
+ int resultId = vr.getResultId();
+ if (vk instanceof DataflowCollation) {
+ return explanationDataflowCollation(vr);
+ }
+ if (vk instanceof FailedEntityKind) {
+ return explanationFailedEntity(vr);
+ }
+ if ((vk instanceof IncompleteDataflowKind)
+ && (resultId == IncompleteDataflowKind.INCOMPLETE_DATAFLOW)) {
+ return explanationDataflowIncomplete(vr);
+ }
+ if (vk instanceof InvalidDataflowKind) {
+ return explanationInvalidDataflow(vr);
+ }
+ if (vk instanceof UnresolvedOutputKind) {
+ return explanationUnresolvedOutput(vr);
+ }
+ if (vk instanceof UnsatisfiedEntityKind) {
+ return explanationUnsatisfiedEntity(vr);
+ }
+
+ if ((vk instanceof FragilityCheck)
+ && (resultId == FragilityCheck.SOURCE_FRAGILE)) {
+ return explanationSourceFragile(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INVALID_SCRIPT)) {
+ return explanationBeanshellInvalidScript(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.CONNECTION_PROBLEM)) {
+ return explanationConnectionProblem(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INVALID_URL)) {
+ return explanationInvalidUrl(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.TIME_OUT)) {
+ return explanationTimeOut(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.IO_PROBLEM)) {
+ return explanationIoProblem(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.MISSING_DEPENDENCY)) {
+ return explanationMissingDependency(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.DEFAULT_VALUE)) {
+ return explanationDefaultValue(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.BAD_WSDL)) {
+ return explanationBadWSDL(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.NOT_HTTP)) {
+ return explanationNotHTTP(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNSUPPORTED_STYLE)) {
+ return explanationUnsupportedStyle(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNKNOWN_OPERATION)) {
+ return explanationUnknownOperation(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.NO_ENDPOINTS)) {
+ return explanationNoEndpoints(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INVALID_CONFIGURATION)) {
+ return explanationInvalidConfiguration(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.NULL_DATATYPE)) {
+ return explanationNullDatatype(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.DISABLED)) {
+ return explanationDisabled(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.DATATYPE_SOURCE)) {
+ return explanationDatatypeSource(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNRECOGNIZED)) {
+ return explanationUnrecognized(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.LOOP_CONNECTION)) {
+ return explanationLoopConnection(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNMANAGED_LOCATION)) {
+ return explanationUnmanagedLocation(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INCOMPATIBLE_MIMETYPES)) {
+ return explanationIncompatibleMimetypes(vr);
+ }
+ return null;
+ }
+
+ public JComponent getSolution(VisitReport vr) {
+ VisitKind vk = vr.getKind();
+ int resultId = vr.getResultId();
+ if (vk instanceof DataflowCollation) {
+ return solutionDataflowCollation(vr);
+ }
+ if (vk instanceof FailedEntityKind) {
+ return solutionFailedEntity(vr);
+ }
+ if ((vk instanceof IncompleteDataflowKind)
+ && (resultId == IncompleteDataflowKind.INCOMPLETE_DATAFLOW)) {
+ return solutionDataflowIncomplete(vr);
+ }
+ if (vk instanceof InvalidDataflowKind) {
+ return solutionInvalidDataflow(vr);
+ }
+ if (vk instanceof UnresolvedOutputKind) {
+ return solutionUnresolvedOutput(vr);
+ }
+ if (vk instanceof UnsatisfiedEntityKind) {
+ return solutionUnsatisfiedEntity(vr);
+ }
+
+ if ((vk instanceof FragilityCheck)
+ && (resultId == FragilityCheck.SOURCE_FRAGILE)) {
+ return solutionSourceFragile(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INVALID_SCRIPT)) {
+ return solutionBeanshellInvalidScript(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.CONNECTION_PROBLEM)) {
+ return solutionConnectionProblem(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INVALID_URL)) {
+ return solutionInvalidUrl(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.TIME_OUT)) {
+ return solutionTimeOut(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.IO_PROBLEM)) {
+ return solutionIoProblem(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.MISSING_DEPENDENCY)) {
+ return solutionMissingDependency(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.DEFAULT_VALUE)) {
+ return solutionDefaultValue(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.BAD_WSDL)) {
+ return solutionBadWSDL(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.NOT_HTTP)) {
+ return solutionNotHTTP(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNSUPPORTED_STYLE)) {
+ return solutionUnsupportedStyle(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNKNOWN_OPERATION)) {
+ return solutionUnknownOperation(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.NO_ENDPOINTS)) {
+ return solutionNoEndpoints(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INVALID_CONFIGURATION)) {
+ return solutionInvalidConfiguration(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.NULL_DATATYPE)) {
+ return solutionNullDatatype(vr);
+ }
+ if ((vk instanceof HealthCheck) && (resultId == HealthCheck.DISABLED)) {
+ return solutionDisabled(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.DATATYPE_SOURCE)) {
+ return solutionDatatypeSource(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNRECOGNIZED)) {
+ return solutionUnrecognized(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.LOOP_CONNECTION)) {
+ return solutionLoopConnection(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.UNMANAGED_LOCATION)) {
+ return solutionUnmanagedLocation(vr);
+ }
+ if ((vk instanceof HealthCheck)
+ && (resultId == HealthCheck.INCOMPATIBLE_MIMETYPES)) {
+ return solutionIncompatibleMimetypes(vr);
+ }
+ return null;
+ }
+
+ private static JComponent explanationFailedEntity(VisitReport vr) {
+ if (vr.getSubject() instanceof Processor) {
+ Processor p = (Processor) (vr.getSubject());
+ DataflowActivity da = null;
+ for (Activity a : p.getActivityList()) {
+ if (a instanceof DataflowActivity) {
+ da = (DataflowActivity) a;
+ break;
+ }
+ }
+ String message = "There is possibly a problem with the service's list handling";
+ if (da != null) {
+ message += ", or a problem with the nested workflow";
+ }
+ return createPanel(new Object[] { message + "." });
+ } else if (vr.getSubject() instanceof Merge) {
+ return createPanel(new Object[] { "The merge is combining data of different depths." });
+ }
+ return null;
+ }
+
+ private static JComponent explanationInvalidDataflow(VisitReport vr) {
+ return createPanel(new Object[] { "The workflow contains errors - see other reports." });
+ }
+
+ private static JComponent explanationUnresolvedOutput(VisitReport vr) {
+ DataflowOutputPort dop = (DataflowOutputPort) vr.getSubject();
+ Datalink incomingLink = dop.getInternalInputPort().getIncomingLink();
+ String message;
+ if (incomingLink == null) {
+ message = "The workflow output port is not connected.";
+ } else {
+ message = "The workflow output port is connected to a service or port that has errors.";
+ }
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationUnsatisfiedEntity(VisitReport vr) {
+ return createPanel(new Object[] { "The service could not be properly checked." });
+ }
+
+ private static JComponent explanationDataflowCollation(VisitReport vr) {
+ if (vr.getStatus().equals(VisitReport.Status.SEVERE)) {
+ return createPanel(new Object[] { "There are errors in the nested workflow." });
+ } else {
+ return createPanel(new Object[] { "There are warnings in the nested workflow." });
+ }
+ }
+
+ private static JComponent explanationDataflowIncomplete(VisitReport vr) {
+ return createPanel(new Object[] { "A workflow must contain at least one service or at least one output port." });
+ }
+
+ private static JComponent explanationSourceFragile(VisitReport vr) {
+ ProcessorInputPort pip = (ProcessorInputPort) vr
+ .getProperty("sinkPort");
+ Processor sourceProcessor = (Processor) vr
+ .getProperty("sourceProcessor");
+ String message = "A single error input into ";
+ if (pip == null) {
+ message += "an input port ";
+ } else {
+ message += "\"" + pip.getName() + "\" ";
+ }
+ if (sourceProcessor != null) {
+ message += "from \"" + sourceProcessor.getLocalName() + "\"";
+ }
+ message += " will cause this service to fail. ";
+ message += "If "
+ + (sourceProcessor == null ? "the source" : sourceProcessor
+ .getLocalName())
+ + " is unlikely to fail (for example if it is a StringConstant) then this warning can be ignored.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationConnectionProblem(VisitReport vr) {
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ if (endpoint == null) {
+ endpoint = "the endpoint";
+ }
+ String responseCode = (String) (vr.getProperty("responseCode"));
+ if (responseCode == null) {
+ responseCode = "an unexpected response code";
+ } else {
+ responseCode = "response code: " + responseCode;
+ }
+ return createPanel(new Object[] { "Taverna connected to \"" + endpoint
+ + "\" but received " + responseCode
+ + ". The service may still work." });
+ }
+
+ private static JComponent explanationIoProblem(VisitReport vr) {
+ String message = "Reading from ";
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ if (endpoint == null) {
+ message += "the endpoint";
+ } else {
+ message += "\"" + endpoint + "\"";
+ }
+ message += " caused ";
+ Exception e = (Exception) (vr.getProperty("exception"));
+ if (e == null) {
+ message += "an error";
+ } else {
+ message += e.getClass().getCanonicalName() + ": \""
+ + e.getMessage() + "\"";
+ }
+ return createPanel(new Object[] { message + "." });
+ }
+
+ private static JComponent explanationInvalidUrl(VisitReport vr) {
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ if (endpoint == null) {
+ endpoint = "the endpoint";
+ }
+ return createPanel(new Object[] { "Taverna was unable to connect to \""
+ + endpoint + "\" because it is not a valid URL." });
+ }
+
+ private static JComponent explanationTimeOut(VisitReport vr) {
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ if (endpoint == null) {
+ endpoint = "the endpoint";
+ }
+ String timeOutString = (String) (vr.getProperty("timeOut"));
+ if (timeOutString == null) {
+ timeOutString = " the timeout limit";
+ } else {
+ try {
+ Integer timeOut = Integer.parseInt(timeOutString);
+ if (timeOut > 1000) {
+ timeOutString = Float.toString(timeOut / 1000) + "s";
+ } else {
+ timeOutString += "ms";
+ }
+ } catch (NumberFormatException ex) {
+ timeOutString = " the timeout limit";
+ }
+ }
+ return createPanel(new Object[] { "Taverna was unable to connect to \""
+ + endpoint + "\" within " + timeOutString + "." });
+ }
+
+ private static JComponent explanationMissingDependency(VisitReport vr) {
+ Set<String> dependencies = (Set<String>) (vr
+ .getProperty("dependencies"));
+ String message = "Taverna could not find ";
+ if (dependencies == null) {
+ message += "some dependencies";
+ } else {
+ for (Iterator i = dependencies.iterator(); i.hasNext();) {
+ String s = (String) i.next();
+ message += s;
+ if (i.hasNext()) {
+ message += " and ";
+ }
+ }
+ }
+ File directory = (File) vr.getProperty("directory");
+ if (directory != null) {
+ try {
+ message += " in directory " + directory.getCanonicalPath();
+ } catch (IOException e) {
+ logger.error("Could not get path", e);
+ }
+ }
+ return createPanel(new Object[] { message + "." });
+ }
+
+ private static JComponent explanationDefaultValue(VisitReport vr) {
+ String value = (String) (vr.getProperty("value"));
+ if (value == null) {
+ value = "the default value";
+ }
+ return createPanel(new Object[] { "The service still has its value set to \""
+ + value + "\"" });
+ }
+
+ private static JComponent explanationBadWSDL(VisitReport vr) {
+ Exception e = (Exception) (vr.getProperty("exception"));
+ String message = "Parsing the WSDL caused ";
+ if (e == null) {
+ message += " an exception";
+ } else {
+ message += "\"" + e.getMessage() + "\"";
+ }
+ return createPanel(new Object[] { message + "." });
+ }
+
+ private static JComponent explanationNotHTTP(VisitReport vr) {
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ if (endpoint == null) {
+ endpoint = "The endpoint";
+ } else {
+ endpoint = "\"" + endpoint + "\"";
+ }
+ return createPanel(new Object[] { endpoint
+ + " might not be accessible if you run the workflow on a different machine." });
+ }
+
+ private static JComponent explanationUnsupportedStyle(VisitReport vr) {
+ String message = "Taverna does not support ";
+ String style = (String) (vr.getProperty("style"));
+ String use = (String) (vr.getProperty("use"));
+ String kind = null;
+ if ((style != null) && (use != null)) {
+ kind = style + "/" + use;
+ }
+ if (kind == null) {
+ message += " the kind of message the service uses.";
+ } else {
+ message += " the \"" + kind + "\" messages that the service uses.";
+ }
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationUnknownOperation(VisitReport vr) {
+ String message = "Taverna could not find the operation ";
+ String operationName = (String) vr.getProperty("operationName");
+ if (operationName == null) {
+ operationName = "called by the service";
+ } else {
+ operationName = "\"" + operationName + "\"";
+ }
+ return createPanel(new Object[] { message + operationName + "." });
+ }
+
+ private static JComponent explanationNoEndpoints(VisitReport vr) {
+ String message = "Taverna found the operation ";
+ String operationName = (String) vr.getProperty("operationName");
+ if (operationName == null) {
+ operationName = "called by the service";
+ } else {
+ operationName = "\"" + operationName + "\"";
+ }
+ message += operationName;
+ message += " but is unable to call it due to lack of location information.";
+ return createPanel(new Object[] { message + operationName });
+ }
+
+ private static JComponent explanationInvalidConfiguration(VisitReport vr) {
+ Exception e = (Exception) (vr.getProperty("exception"));
+ String message = "Trying to understand the XML splitter caused ";
+ if (e == null) {
+ message += " an exception";
+ } else {
+ message += "\"" + e.getMessage() + "\"";
+ }
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationNullDatatype(VisitReport vr) {
+ String message = "The XML splitter appears to have a NULL datatype.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationDisabled(VisitReport vr) {
+ String message = "Taverna could not contact the service when the workflow was opened.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationDatatypeSource(VisitReport vr) {
+ String message = "The data going into ";
+ String sinkPortName = (String) vr.getProperty("sinkPortName");
+ if (sinkPortName == null) {
+ sinkPortName = "a port";
+ } else {
+ sinkPortName = "port \"" + sinkPortName + "\"";
+ }
+ message += sinkPortName;
+ String sourceName = (String) vr.getProperty("sourceName");
+ String isProcessorSource = (String) vr.getProperty("isProcessorSource");
+ if (sourceName != null) {
+ message += " from ";
+ if (isProcessorSource != null) {
+ if (isProcessorSource.equals("true")) {
+ message += "service ";
+ } else {
+ message += "port ";
+ }
+ }
+ message += "\"" + sourceName + "\"";
+ }
+ message += " may not be XML. The service requires XML as input.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationBeanshellInvalidScript(VisitReport vr) {
+ Exception e = (Exception) vr.getProperty("exception");
+ String exceptionMessage = null;
+ if (e != null) {
+ exceptionMessage = e.getMessage();
+ }
+ return createPanel(new Object[] {
+ "There are errors in the script of the service.\nWhen the workflow runs, any calls of the service will fail with error: ",
+ exceptionMessage + "." });
+ }
+
+ private static JComponent explanationUnrecognized(VisitReport vr) {
+ String message = "Taverna could not recognize the service when the workflow was opened.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent explanationLoopConnection(VisitReport vr) {
+ return createPanel(new Object[] { "Port \""
+ + vr.getProperty("portname") + "\" must be connected" });
+ }
+
+ private static JComponent explanationUnmanagedLocation(VisitReport vr) {
+ return createPanel(new Object[] { "The external tool service is configured to run on a location that is not currently known to the Location Manager. It is a good idea to change it to a known location" });
+ }
+
+ private static JComponent explanationIncompatibleMimetypes(VisitReport vr) {
+ ProcessorInputPort pip = (ProcessorInputPort) vr
+ .getProperty("sinkPort");
+ ProcessorOutputPort pop = (ProcessorOutputPort) vr
+ .getProperty("sourcePort");
+ Processor sourceProcessor = (Processor) vr
+ .getProperty("sourceProcessor");
+ String message = "The data";
+ if (pop != null) {
+ message += " from port \"" + pop.getName() + "\"";
+ if (sourceProcessor != null) {
+ message += " of service \"" + sourceProcessor.getLocalName()
+ + "\"";
+ }
+ }
+ if (pip != null) {
+ message += " into port \"" + pip.getName() + "\"";
+ }
+ message += " does not have a compatible mime type";
+ return createPanel(new Object[] { message });
+ }
+
+ private JComponent solutionFailedEntity(VisitReport vr) {
+ if (vr.getSubject() instanceof Processor) {
+ Processor p = (Processor) (vr.getSubject());
+ DataflowActivity da = null;
+ for (Activity a : p.getActivityList()) {
+ if (a instanceof DataflowActivity) {
+ da = (DataflowActivity) a;
+ break;
+ }
+ }
+ String message = "Check the list handling of the service, including the predicted behavior of the service's inputs and outputs";
+ JButton button = null;
+ if (da != null) {
+ message += ", or edit the nested workflow";
+ button = new JButton();
+ button.setAction(new EditNestedDataflowAction(da, fileManager));
+ button.setText("Edit \"" + p.getLocalName() + "\"");
+ }
+ return createPanel(new Object[] { message + ".", button });
+ } else if (vr.getSubject() instanceof Merge) {
+ return createPanel(new Object[] { "Check the predicted behaviour of the data being merged." });
+ }
+ return null;
+ }
+
+ private static JComponent solutionInvalidDataflow(VisitReport vr) {
+ String message = "Fix the errors within the workflow.";
+ return createPanel(new Object[] { message });
+ }
+
+ private JComponent solutionUnresolvedOutput(VisitReport vr) {
+ JButton deleteButton = null;
+
+ DataflowOutputPort port = (DataflowOutputPort) vr.getSubject();
+ DataflowOutputPort dop = (DataflowOutputPort) vr.getSubject();
+ Datalink incomingLink = dop.getInternalInputPort().getIncomingLink();
+ String message;
+ if (incomingLink == null) {
+ message = "Connect the workflow output port to a service or a workflow input port. Alternatively,";
+ final Dataflow d = fileManager.getCurrentDataflow();
+ final DataflowOutputPort p = port;
+ deleteButton = new JButton(new AbstractAction("Remove port") {
+ public void actionPerformed(ActionEvent e) {
+ Edit removeEdit = editManager.getEdits()
+ .getRemoveDataflowOutputPortEdit(d, p);
+ try {
+ editManager.doDataflowEdit(d, removeEdit);
+ } catch (EditException ex) {
+ logger.error("Could not perform edit", ex);
+ }
+ }
+ });
+ } else {
+ message = "Fix the errors of the service that the output port is connected to.";
+ }
+ return createPanel(new Object[] { message, deleteButton });
+ }
+
+ private JComponent solutionUnsatisfiedEntity(VisitReport vr) {
+ String message = "";
+ Dataflow currentDataflow = fileManager.getCurrentDataflow();
+ Tools.ProcessorSplit ps = Tools.splitProcessors(
+ currentDataflow.getProcessors(),
+ (TokenProcessingEntity) (vr.getSubject()));
+ Set<Processor> upStream = ps.getUpStream();
+ boolean plural = false;
+ for (Processor p : upStream) {
+ Set<VisitReport> reports = reportManager.getReports(currentDataflow, p);
+ for (VisitReport report : reports) {
+ if (report.getKind() instanceof FailedEntityKind) {
+ if (!message.equals("")) {
+ message += " and";
+ plural = true;
+ }
+ message += " " + p.getLocalName();
+ }
+ }
+ }
+ if (message.equals("")) {
+ return null;
+ }
+ if (!plural) {
+ message = "The underlying error is caused by" + message;
+ } else {
+ message = "The underlying errors are caused by" + message;
+ }
+ return createPanel(new Object[] { message + "." });
+ }
+
+ private JComponent solutionDataflowCollation(VisitReport vr) {
+ String message = "Edit the nested workflow to fix its problems.";
+ JButton button = null;
+ Processor p = (Processor) (vr.getSubject());
+ DataflowActivity da = null;
+ for (Activity a : p.getActivityList()) {
+ if (a instanceof DataflowActivity) {
+ da = (DataflowActivity) a;
+ break;
+ }
+ }
+ if (da != null) {
+ button = new JButton();
+ button.setAction(new EditNestedDataflowAction(da, fileManager));
+ button.setText("Edit \"" + p.getLocalName() + "\"");
+ }
+ String reminder = "Remember to save the nested workflow.";
+ return createPanel(new Object[] { message, button, reminder });
+ }
+
+ private JComponent solutionDataflowIncomplete(VisitReport vr) {
+ String message = "Add a service from the service panel to the workflow, or";
+ JButton button = new JButton();
+ button.setAction(new AddDataflowOutputAction(
+ (Dataflow) vr.getSubject(), null, editManager, selectionManager));
+ button.setText("Add an output port");
+ return createPanel(new Object[] { message, button });
+ }
+
+ private JComponent solutionSourceFragile(VisitReport vr) {
+ Processor sourceProcessor = (Processor) vr
+ .getProperty("sourceProcessor");
+ String labelText = "Make ";
+ if (sourceProcessor == null) {
+ labelText += "the source service ";
+ } else {
+ labelText += "\"" + sourceProcessor.getLocalName() + "\" ";
+ }
+ labelText += "more robust to failure by adding service retries.";
+ JButton button = null;
+ if (sourceProcessor != null) {
+ Retry retryLayer = null;
+ for (DispatchLayer dl : sourceProcessor.getDispatchStack()
+ .getLayers()) {
+ if (dl instanceof Retry) {
+ retryLayer = (Retry) dl;
+ break;
+ }
+ }
+ if (retryLayer != null) {
+ button = new JButton();
+ button.setAction(new RetryConfigureAction(null, null,
+ retryLayer, editManager, fileManager));
+ button.setText("Set retry");
+ }
+ }
+ return createPanel(new Object[] { labelText, button });
+ }
+
+ private static JComponent solutionBeanshellInvalidScript(VisitReport vr) {
+ JButton button = new JButton();
+ Processor p = (Processor) (vr.getSubject());
+ button.setAction(new ReportViewConfigureAction(p));
+ button.setText("Configure " + p.getLocalName());
+ return createPanel(new Object[] {
+ "Edit the service script, checking that the script is valid before saving it.",
+ button });
+ }
+
+ private JComponent solutionConnectionProblem(VisitReport vr) {
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ String connectMessage = "";
+ JButton connectButton = null;
+ if (endpoint == null) {
+ endpoint = "the endpoint";
+ connectMessage = "Try to connect to the endpoint.";
+ } else {
+ connectMessage = "Try to connect to " + endpoint + " in a browser.";
+ final String end = endpoint;
+ connectButton = new JButton(new AbstractAction("Open in browser") {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ Desktop.getDesktop().browse(new URI(end));
+ } catch (Exception ex) {
+ logger.error("Failed to open endpoint", ex);
+ }
+ }
+ });
+ }
+ String workedMessage = "If the connection did not work, please contact the service provider or workflow creator. Alternatively, check if you are using an HTTP Proxy, and edit Taverna's proxy settings.";
+ JButton preferencesButton = null;
+ if (endpoint != null) {
+ preferencesButton = new JButton(new AbstractAction(
+ "Change HTTP proxy") {
+ public void actionPerformed(ActionEvent e) {
+ T2ConfigurationFrame.showConfiguration("HTTP proxy", configurationUIFactories);
+ }
+ });
+ }
+ String editMessage = null;
+ JButton editButton = null;
+ DisabledActivity da = null;
+ for (Activity a : ((Processor) vr.getSubject()).getActivityList()) {
+ if (a instanceof DisabledActivity) {
+ da = (DisabledActivity) a;
+ break;
+ }
+ }
+ if (da != null) {
+ editMessage = "If the service has moved, change the service's properties to its new location.";
+ editButton = new JButton(new DisabledActivityConfigurationAction(
+ da, null, editManager, fileManager, reportManager, activityIconManager));
+ }
+ return createPanel(new Object[] { connectMessage, connectButton,
+ workedMessage, preferencesButton, editMessage, editButton });
+
+ }
+
+ private JComponent solutionInvalidUrl(VisitReport vr) {
+ String message = "Contact the service provider or workflow creator.";
+ String editMessage = "If the service has moved, change the service's properties to its new location.";
+ JButton editButton = null;
+ DisabledActivity da = null;
+ for (Activity a : ((Processor) vr.getSubject()).getActivityList()) {
+ if (a instanceof DisabledActivity) {
+ da = (DisabledActivity) a;
+ break;
+ }
+ }
+ if (da != null) {
+ editButton = new JButton(new DisabledActivityConfigurationAction(
+ da, null, editManager, fileManager, reportManager, activityIconManager));
+ }
+ return createPanel(new Object[] { message, editMessage, editButton });
+ }
+
+ private JComponent solutionTimeOut(VisitReport vr) {
+ String message = "Try to open ";
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ JButton connectButton = null;
+ if (endpoint == null) {
+ message += "the endpoint ";
+ } else {
+ message += "\"" + endpoint + "\" ";
+ final String end = endpoint;
+ connectButton = new JButton(new AbstractAction("Open in browser") {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ Desktop.getDesktop().browse(new URI(end));
+ } catch (Exception ex) {
+ logger.error("Failed to open endpoint", ex);
+ }
+ }
+ });
+ }
+ message += "in a file, or web, browser.";
+ String workedMessage = "If the browser opened the address, then alter the validation timeout in the preferences";
+ JButton preferencesButton = new JButton(new AbstractAction(
+ "Change timeout") {
+ public void actionPerformed(ActionEvent e) {
+ T2ConfigurationFrame.showConfiguration("Validation report", configurationUIFactories);
+ }
+ });
+ String didNotWorkMessage = "Alternatively, if the browser did not open the address, try later as the service may be temporarily offline. If the service remains offline, please contact the service provider or workflow creator.";
+ String editMessage = null;
+ JButton editButton = null;
+ DisabledActivity da = null;
+ for (Activity a : ((Processor) vr.getSubject()).getActivityList()) {
+ if (a instanceof DisabledActivity) {
+ da = (DisabledActivity) a;
+ break;
+ }
+ }
+ if (da != null) {
+ editMessage = "If the service has moved, change the service's properties to its new location.";
+ editButton = new JButton(new DisabledActivityConfigurationAction(
+ da, null, editManager, fileManager, reportManager, activityIconManager));
+ }
+ return createPanel(new Object[] { message, connectButton,
+ workedMessage, preferencesButton, didNotWorkMessage,
+ editMessage, editButton });
+ }
+
+ private JComponent solutionIoProblem(VisitReport vr) {
+ String message = "";
+ Exception e = (Exception) (vr.getProperty("exception"));
+ if (e != null && e instanceof SSLException) {
+ message += "There was a problem with establishing a HTTPS connection to the service. ";
+ if (e.getMessage().toLowerCase()
+ .contains("no trusted certificate found")) {
+ message += "Looks like the authenticity of the service could not be confirmed. Check that you have imported the service's certificate under 'Trusted Certificates' in Credential Manager. "
+ + "If this is a WSDL service, try restarting Taverna to refresh certificates used in HTTPS connections.\n\n";
+ } else if (e.getMessage().toLowerCase()
+ .contains("received fatal alert: bad_certificate")) {
+ message += "Looks like you could not be authenticated to the service. Check that you have imported your certificate under 'Your certificates' in Credential Manager. "
+ + "If this is a WSDL service, try restarting Taverna to refresh certificates used in HTTPS connections.\n\n";
+ } else {
+ message += "Check that you have imported the service's certificate under 'Trusted Certificates' in Credential Manager. "
+ + "If user authentication is required, also check that you have imported your certificate under 'Your certificates' in Credential Manager. "
+ + "If this is a WSDL service, try restarting Taverna to refresh certificates used in HTTPS connections.\n\n";
+ }
+ }
+ message += "Try to open ";
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ JButton connectButton = null;
+ if (endpoint == null) {
+ message += "the endpoint ";
+ } else {
+ message += "\"" + endpoint + "\" ";
+ final String end = endpoint;
+ connectButton = new JButton(new AbstractAction("Open in browser") {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ Desktop.getDesktop().browse(new URI(end));
+ } catch (Exception ex) {
+ logger.error("Failed to open endpoint", ex);
+ }
+ }
+ });
+ }
+ message += "in a file, or web, browser.";
+ String elseMessage = message.startsWith("Try to open") ? "If that does not work, please contact the service provider or workflow creator."
+ : null;
+ String editMessage = message.startsWith("Try to open") ? "If the service has moved, change the service's properties to its new location."
+ : null;
+ JButton editButton = null;
+ DisabledActivity da = null;
+ for (Activity a : ((Processor) vr.getSubject()).getActivityList()) {
+ if (a instanceof DisabledActivity) {
+ da = (DisabledActivity) a;
+ break;
+ }
+ }
+ if (da != null) {
+ editButton = new JButton(new DisabledActivityConfigurationAction(
+ da, null, editManager, fileManager, reportManager, activityIconManager));
+ }
+ return createPanel(new Object[] { message, connectButton, elseMessage,
+ editMessage, editButton });
+ }
+
+ private static JComponent solutionMissingDependency(VisitReport vr) {
+ String message = "Put ";
+ Set<String> dependencies = (Set<String>) (vr
+ .getProperty("dependencies"));
+ if (dependencies == null) {
+ message += "the dependencies";
+ } else {
+ for (Iterator i = dependencies.iterator(); i.hasNext();) {
+ String s = (String) i.next();
+ message += s;
+ if (i.hasNext()) {
+ message += " and ";
+ }
+ }
+ }
+ File directory = (File) vr.getProperty("directory");
+ if (directory != null) {
+ try {
+ message += " in directory " + directory.getCanonicalPath();
+ } catch (IOException e) {
+ logger.error("Could not get path", e);
+ }
+ } else {
+ message += " in the application directory.";
+ }
+ String elseMessage = "If you do not have the files, please contact the workflow creator.";
+ return createPanel(new Object[] { message, elseMessage });
+ }
+
+ private static JComponent solutionDefaultValue(VisitReport vr) {
+ String message = "Change the value of the service by clicking the \"Set value\" button";
+ JButton button = new JButton();
+ Processor p = (Processor) (vr.getSubject());
+ button.setAction(new ReportViewConfigureAction(p));
+ button.setText("Set value");
+ return createPanel(new Object[] { message, button });
+ }
+
+ private static JComponent solutionBadWSDL(VisitReport vr) {
+ return createPanel(new Object[] { PLEASE_CONTACT });
+ }
+
+ private static JComponent solutionNotHTTP(VisitReport vr) {
+ String endpoint = (String) (vr.getProperty("endpoint"));
+ if (endpoint == null) {
+ endpoint = "the endpoint";
+ } else {
+ endpoint = "\"" + endpoint + "\"";
+ }
+ String message = "Move the file at " + endpoint + " to a web server.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent solutionUnsupportedStyle(VisitReport vr) {
+ String message = "Contact the service provider to see if there is an alternative style of service available.";
+ return createPanel(new Object[] { message });
+ }
+
+ private JComponent solutionUnknownOperation(VisitReport vr) {
+ String message = "Contact the service provider to see if the operation has been renamed.";
+ String editMessage = "If you know its new name, then please edit the service's properties.";
+ JButton editButton = null;
+ DisabledActivity da = null;
+ for (Activity a : ((Processor) vr.getSubject()).getActivityList()) {
+ if (a instanceof DisabledActivity) {
+ da = (DisabledActivity) a;
+ break;
+ }
+ }
+ if (da != null) {
+ editButton = new JButton(new DisabledActivityConfigurationAction(
+ da, null, editManager, fileManager, reportManager, activityIconManager));
+ }
+ return createPanel(new Object[] { message, editMessage, editButton });
+ }
+
+ private static JComponent solutionNoEndpoints(VisitReport vr) {
+ return createPanel(new Object[] { PLEASE_CONTACT });
+ }
+
+ private static JComponent solutionInvalidConfiguration(VisitReport vr) {
+ return createPanel(new Object[] { PLEASE_CONTACT });
+ }
+
+ private static JComponent solutionNullDatatype(VisitReport vr) {
+ return createPanel(new Object[] { PLEASE_CONTACT });
+ }
+
+ private static JComponent solutionDisabled(VisitReport vr) {
+ String message = "Validate the workflow and fix any errors on the service.";
+ return createPanel(new Object[] { message });
+ }
+
+ private JComponent solutionDatatypeSource(VisitReport vr) {
+ String sinkPortName = (String) vr.getProperty("sinkPortName");
+ if (sinkPortName == null) {
+ return null;
+ }
+ String removeMessage = "1. Remove the link to " + "port \""
+ + sinkPortName + "\"";
+ ProcessorInputPort pip = (ProcessorInputPort) vr
+ .getProperty("sinkPort");
+ final InputPortTypeDescriptorActivity a = (InputPortTypeDescriptorActivity) vr
+ .getProperty("activity");
+ String addSplitterMessage = "2. Add an XML splitter for " + "port \""
+ + sinkPortName + "\"";
+ JButton button = null;
+ if (pip != null) {
+ Datalink incomingLink = pip.getIncomingLink();
+ if (incomingLink != null) {
+ button = new JButton();
+ final Dataflow d = fileManager.getCurrentDataflow();
+ final String portName = sinkPortName;
+ final Datalink link = incomingLink;
+ button.setAction(new AbstractAction(
+ "Remove link and add XML splitter") {
+ public void actionPerformed(ActionEvent e) {
+ Edit removeLinkEdit = Tools
+ .getDisconnectDatalinkAndRemovePortsEdit(link, editManager.getEdits());
+ Edit addXMLEdit = new AddXMLSplitterEdit(d,
+ (Activity<?>) a, portName, true, editManager.getEdits());
+ List<Edit<?>> editList = Arrays.asList(new Edit<?>[] {
+ removeLinkEdit, addXMLEdit });
+ CompoundEdit ce = new CompoundEdit(editList);
+ try {
+ editManager.doDataflowEdit(d, ce);
+ } catch (EditException ex) {
+ logger.error("Could not perform edit", ex);
+ }
+ }
+ });
+ }
+ }
+ String addConnectionMessage = "3. Make a connection to the relevant port of the new XML splitter.";
+ return createPanel(new Object[] { removeMessage, addSplitterMessage,
+ button, addConnectionMessage });
+ }
+
+ private static JComponent solutionUnrecognized(VisitReport vr) {
+ String message = "Please contact the workflow creator to find out what additional plugins, if any, need to be installed in Taverna.";
+ return createPanel(new Object[] { message });
+ }
+
+ private static JComponent solutionLoopConnection(VisitReport vr) {
+ return createPanel(new Object[] { "Connect port \""
+ + vr.getProperty("portname") + "\"" });
+ }
+
+ private static JComponent solutionUnmanagedLocation(VisitReport vr) {
+ JButton button = new JButton();
+ Processor p = (Processor) (vr.getSubject());
+ button.setAction(new ReportViewConfigureAction(p));
+ button.setText("Configure " + p.getLocalName());
+ return createPanel(new Object[] {
+ "Change the run locaton of the service", button });
+ }
+
+ private JComponent solutionIncompatibleMimetypes(VisitReport vr) {
+ JButton sinkButton = new JButton();
+ Processor sinkProcessor = (Processor) (vr.getProperty("sinkProcessor"));
+ Processor sourceProcessor = (Processor) vr
+ .getProperty("sourceProcessor");
+ sinkButton.setAction(new ReportViewConfigureAction(sinkProcessor));
+ sinkButton.setText("Configure " + sinkProcessor.getLocalName());
+ JButton sourceButton = new JButton();
+ sourceButton.setAction(new ReportViewConfigureAction(sourceProcessor));
+ sourceButton.setText("Configure " + sourceProcessor.getLocalName());
+
+ JButton deleteLinkButton = new JButton();
+ final Datalink link = (Datalink) vr.getProperty("link");
+ final Dataflow d = fileManager.getCurrentDataflow();
+ deleteLinkButton.setAction(new AbstractAction("Remove link") {
+ public void actionPerformed(ActionEvent e) {
+ Edit removeLinkEdit = Tools
+ .getDisconnectDatalinkAndRemovePortsEdit(link, editManager.getEdits());
+ try {
+ editManager.doDataflowEdit(d, removeLinkEdit);
+ } catch (EditException ex) {
+ logger.error("Could not perform edit", ex);
+ }
+ }
+ });
+
+ return createPanel(new Object[] {
+ "Change the source or destination mimetype or remove the link",
+ sourceButton, sinkButton, deleteLinkButton });
+ }
+
+ private static JPanel createPanel(Object[] components) {
+ JPanel result = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = GridBagConstraints.NORTHWEST;
+ gbc.gridwidth = 1;
+ gbc.weightx = 0.9;
+ for (Object o : components) {
+ if (o == null) {
+ continue;
+ }
+ JComponent component = null;
+ if (o instanceof String) {
+ component = new ReadOnlyTextArea((String) o);
+ } else if (o instanceof JComponent) {
+ component = (JComponent) o;
+ } else {
+ logger.error("Unrecognized component " + o.getClass());
+ continue;
+ }
+ gbc.gridy++;
+ if (component instanceof JButton) {
+ gbc.weightx = 0;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ } else {
+ gbc.weightx = 0.9;
+ gbc.gridwidth = 2;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ }
+ result.add(component, gbc);
+ }
+ // gbc.weightx = 0.9;
+ // gbc.weighty = 0.9;
+ // gbc.gridx = 0;
+ // gbc.gridy++;
+ // gbc.gridwidth = 2;
+ // gbc.fill = GridBagConstraints.BOTH;
+ // result.add(new JPanel(), gbc);
+ result.setBackground(SystemColor.text);
+ return result;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setConfigurationUIFactories(List<ConfigurationUIFactory> configurationUIFactories) {
+ this.configurationUIFactories = configurationUIFactories;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+}
diff --git a/taverna-workbench-report-explainer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.report.explainer.VisitExplainer b/taverna-workbench-report-explainer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.report.explainer.VisitExplainer
new file mode 100644
index 0000000..109e3d1
--- /dev/null
+++ b/taverna-workbench-report-explainer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.report.explainer.VisitExplainer
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.report.explainer.BasicExplainer
diff --git a/taverna-workbench-report-explainer/src/main/resources/META-INF/spring/report-explainer-context-osgi.xml b/taverna-workbench-report-explainer/src/main/resources/META-INF/spring/report-explainer-context-osgi.xml
new file mode 100644
index 0000000..610d13c
--- /dev/null
+++ b/taverna-workbench-report-explainer/src/main/resources/META-INF/spring/report-explainer-context-osgi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="BasicExplainer" interface="net.sf.taverna.t2.workbench.report.explainer.VisitExplainer" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="reportManager" interface="net.sf.taverna.t2.workbench.report.ReportManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+
+ <list id="configurationUIFactories" interface="uk.org.taverna.configuration.ConfigurationUIFactory" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-report-explainer/src/main/resources/META-INF/spring/report-explainer-context.xml b/taverna-workbench-report-explainer/src/main/resources/META-INF/spring/report-explainer-context.xml
new file mode 100644
index 0000000..9bb083e
--- /dev/null
+++ b/taverna-workbench-report-explainer/src/main/resources/META-INF/spring/report-explainer-context.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="BasicExplainer" class="net.sf.taverna.t2.workbench.report.explainer.BasicExplainer">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="configurationUIFactories" ref="configurationUIFactories" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-report-impl/pom.xml b/taverna-workbench-report-impl/pom.xml
new file mode 100644
index 0000000..c0e72bb
--- /dev/null
+++ b/taverna-workbench-report-impl/pom.xml
@@ -0,0 +1,40 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>ui-impl</artifactId>
+ <groupId>net.sf.taverna.t2</groupId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>report-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Reporting Implementation</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-api</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java
new file mode 100644
index 0000000..b5986b5
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.config.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workflowmodel.health.RemoteHealthChecker;
+
+/**
+ * @author alanrw
+ *
+ */
+public final class ReportManagerConfigurationImpl extends AbstractConfigurable implements ReportManagerConfiguration {
+
+ private static final int DEFAULT_TIMEOUT = 10;
+
+ private Map<String, String> defaultPropertyMap;
+
+ public ReportManagerConfigurationImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ public String getCategory() {
+ return "general";
+ }
+
+ public Map<String, String> getDefaultPropertyMap() {
+
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<String, String>();
+ defaultPropertyMap.put(TIMEOUT, Integer.toString(DEFAULT_TIMEOUT));
+ defaultPropertyMap.put(ON_EDIT, QUICK_CHECK);
+ defaultPropertyMap.put(ON_OPEN, QUICK_CHECK);
+ defaultPropertyMap.put(BEFORE_RUN, FULL_CHECK);
+ defaultPropertyMap.put(QUERY_BEFORE_RUN, ERRORS_OR_WARNINGS);
+ defaultPropertyMap.put(REPORT_EXPIRATION, Integer.toString(DEFAULT_REPORT_EXPIRATION));
+ }
+ return defaultPropertyMap;
+ }
+
+ public String getDisplayName() {
+ return "Validation report";
+ }
+
+ public String getFilePrefix() {
+ return "ReportManager";
+ }
+
+ public String getUUID() {
+ return "F86378E5-0EC4-4DE9-8A55-6098595413DC";
+ }
+
+ @Override
+ public void applySettings() {
+ RemoteHealthChecker.setTimeoutInSeconds(Integer.parseInt(this.getProperty(TIMEOUT)));
+ }
+
+ public void setProperty(String key, String value) {
+ super.setProperty(key, value);
+ if (key.equals(TIMEOUT)) {
+ applySettings();
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java
new file mode 100644
index 0000000..825efee
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java
@@ -0,0 +1,564 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.visit.HierarchyTraverser;
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.visit.Visitor;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.report.DataflowReportEvent;
+import net.sf.taverna.t2.workbench.report.FailedEntityKind;
+import net.sf.taverna.t2.workbench.report.IncompleteDataflowKind;
+import net.sf.taverna.t2.workbench.report.InvalidDataflowKind;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.ReportManagerEvent;
+import net.sf.taverna.t2.workbench.report.UnresolvedOutputKind;
+import net.sf.taverna.t2.workbench.report.UnsatisfiedEntityKind;
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.DataflowValidationReport;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Alan R Williams
+ *
+ */
+public class ReportManagerImpl implements Observable<ReportManagerEvent>, ReportManager {
+
+ private static final long MAX_AGE_OUTDATED_MILLIS = 1 * 60 * 60 * 1000; // 1 hour
+ private static Logger logger = Logger.getLogger(ReportManagerImpl.class);
+
+ private ReportManagerConfiguration reportManagerConfiguration;
+ private HierarchyTraverser traverser = null;
+ private Map<Dataflow, Map<Object, Set<VisitReport>>> reportMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Set<VisitReport>>>());;
+ private Map<Dataflow, Map<Object, Status>> statusMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Status>>());
+ private Map<Dataflow, Map<Object, String>> summaryMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Map<Object, String>>());
+ private Map<Dataflow, Long> lastCheckedMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Long>());
+ private Map<Dataflow, Long> lastFullCheckedMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Long>());
+ private Map<Dataflow, String> lastFullCheckedDataflowIdMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, String>());
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+ // private Set<Visitor<?>> visitors;
+
+ protected ReportManagerImpl(EditManager editManager, FileManager fileManager,
+ Set<Visitor<?>> visitors, ReportManagerConfiguration reportManagerConfiguration)
+ throws IllegalStateException {
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManagerConfiguration = reportManagerConfiguration;
+ // this.visitors = visitors;
+ traverser = new HierarchyTraverser(visitors);
+ ReportManagerFileObserver fileObserver = new ReportManagerFileObserver();
+ fileManager.addObserver(fileObserver);
+ addEditObserver();
+ reportManagerConfiguration.applySettings();
+ }
+
+ private void addEditObserver() {
+ synchronized (editManager) {
+ List<Observer<EditManagerEvent>> currentObservers = editManager.getObservers();
+ for (Observer<EditManagerEvent> o : currentObservers) {
+ editManager.removeObserver(o);
+ }
+ editManager.addObserver(new ReportManagerEditObserver());
+ for (Observer<EditManagerEvent> o : currentObservers) {
+ editManager.addObserver(o);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#updateReport(net.sf.taverna.t2.workflowmodel
+ * .Dataflow, boolean, boolean)
+ */
+ @Override
+ public void updateReport(Dataflow d, boolean includeTimeConsuming, boolean remember) {
+ Set<VisitReport> oldTimeConsumingReports = null;
+ long time = System.currentTimeMillis();
+ long expiration = Integer.parseInt(reportManagerConfiguration
+ .getProperty(ReportManagerConfiguration.REPORT_EXPIRATION)) * 60 * 1000;
+ if (remember && !includeTimeConsuming
+ && ((expiration == 0) || ((time - getLastFullCheckedTime(d)) < expiration))) {
+ oldTimeConsumingReports = getTimeConsumingReports(d);
+ }
+ Map<Object, Set<VisitReport>> reportsEntry = new HashMap<Object, Set<VisitReport>>();
+ Map<Object, Status> statusEntry = new HashMap<Object, Status>();
+ Map<Object, String> summaryEntry = new HashMap<Object, String>();
+ reportMap.put(d, reportsEntry);
+ statusMap.put(d, statusEntry);
+ summaryMap.put(d, summaryEntry);
+ validateDataflow(d, reportsEntry, statusEntry, summaryEntry);
+
+ Set<VisitReport> newReports = new HashSet<VisitReport>();
+ traverser.traverse(d, new ArrayList<Object>(), newReports, includeTimeConsuming);
+ for (VisitReport vr : newReports) {
+ addReport(reportsEntry, statusEntry, summaryEntry, vr);
+ }
+ if (oldTimeConsumingReports != null) {
+ for (VisitReport vr : oldTimeConsumingReports) {
+ addReport(reportsEntry, statusEntry, summaryEntry, vr);
+ }
+ }
+ time = System.currentTimeMillis();
+ lastCheckedMap.put(d, time);
+ if (includeTimeConsuming) {
+ lastFullCheckedMap.put(d, time);
+ lastFullCheckedDataflowIdMap.put(d, d.getIdentifier());
+ }
+ multiCaster.notify(new DataflowReportEvent(d));
+ }
+
+ private void updateObjectReportInternal(Dataflow d, Object o) {
+ Map<Object, Set<VisitReport>> reportsEntry = reportMap.get(d);
+ Map<Object, Status> statusEntry = statusMap.get(d);
+ Map<Object, String> summaryEntry = summaryMap.get(d);
+ if (reportsEntry == null) {
+ logger.error("Attempt to update reports on an object in a dataflow that has not been checked");
+ reportsEntry = new HashMap<Object, Set<VisitReport>>();
+ statusEntry = new HashMap<Object, Status>();
+ summaryEntry = new HashMap<Object, String>();
+ reportMap.put(d, reportsEntry);
+ statusMap.put(d, statusEntry);
+ summaryMap.put(d, summaryEntry);
+ } else {
+ reportsEntry.remove(o);
+ statusEntry.remove(o);
+ summaryEntry.remove(o);
+ }
+ // Assume o is directly inside d
+ List<Object> ancestry = new ArrayList<Object>();
+ ancestry.add(d);
+ Set<VisitReport> newReports = new HashSet<VisitReport>();
+ traverser.traverse(o, ancestry, newReports, true);
+ for (VisitReport vr : newReports) {
+ addReport(reportsEntry, statusEntry, summaryEntry, vr);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectSetReport(net.sf.taverna.t2
+ * .workflowmodel.Dataflow, java.util.Set)
+ */
+ @Override
+ public void updateObjectSetReport(Dataflow d, Set<Object> objects) {
+ for (Object o : objects) {
+ updateObjectReportInternal(d, o);
+ }
+ multiCaster.notify(new DataflowReportEvent(d));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectReport(net.sf.taverna.t2.
+ * workflowmodel.Dataflow, java.lang.Object)
+ */
+ @Override
+ public void updateObjectReport(Dataflow d, Object o) {
+ updateObjectReportInternal(d, o);
+ multiCaster.notify(new DataflowReportEvent(d));
+ }
+
+ private Set<VisitReport> getTimeConsumingReports(Dataflow d) {
+ Set<VisitReport> result = new HashSet<VisitReport>();
+ Map<Object, Set<VisitReport>> currentReports = getReports(d);
+ if (currentReports != null) {
+ for (Object o : currentReports.keySet()) {
+ for (VisitReport vr : currentReports.get(o)) {
+ if (vr.wasTimeConsuming()) {
+ result.add(vr);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private void removeReport(Dataflow d) {
+ reportMap.remove(d);
+ statusMap.remove(d);
+ summaryMap.remove(d);
+ }
+
+ private void addReport(Map<Object, Set<VisitReport>> reports, Map<Object, Status> statusEntry,
+ Map<Object, String> summaryEntry, VisitReport newReport) {
+ if (newReport.getCheckTime() == 0) {
+ newReport.setCheckTime(System.currentTimeMillis());
+ }
+ Object subject = newReport.getSubject();
+ Set<VisitReport> currentReports = reports.get(subject);
+ Status newReportStatus = newReport.getStatus();
+ if (currentReports == null) {
+ currentReports = new HashSet<VisitReport>();
+ reports.put(subject, currentReports);
+ statusEntry.put(subject, newReportStatus);
+ summaryEntry.put(subject, newReport.getMessage());
+ } else {
+ Status currentStatus = statusEntry.get(subject);
+ if (currentStatus.compareTo(newReportStatus) < 0) {
+ statusEntry.put(subject, newReportStatus);
+ summaryEntry.put(subject, newReport.getMessage());
+ } else if (currentStatus.compareTo(newReportStatus) == 0) {
+ if (currentStatus.equals(Status.WARNING)) {
+ summaryEntry.put(subject, "Multiple warnings");
+ } else if (currentStatus.equals(Status.SEVERE)) {
+ summaryEntry.put(subject, "Multiple errors");
+ }
+ }
+ }
+ currentReports.add(newReport);
+ }
+
+ private void validateDataflow(Dataflow d, Map<Object, Set<VisitReport>> reportsEntry,
+ Map<Object, Status> statusEntry, Map<Object, String> summaryEntry) {
+ DataflowValidationReport validationReport = d.checkValidity();
+ if (validationReport.isWorkflowIncomplete()) {
+ addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
+ IncompleteDataflowKind.getInstance(), d, "Incomplete workflow",
+ IncompleteDataflowKind.INCOMPLETE_DATAFLOW, VisitReport.Status.SEVERE));
+ } else if (!validationReport.isValid()) {
+ addReport(reportsEntry, statusEntry, summaryEntry,
+ new VisitReport(InvalidDataflowKind.getInstance(), d, "Invalid workflow",
+ InvalidDataflowKind.INVALID_DATAFLOW, VisitReport.Status.SEVERE));
+ }
+ fillInReport(reportsEntry, statusEntry, summaryEntry, validationReport);
+ }
+
+ private void fillInReport(Map<Object, Set<VisitReport>> reportsEntry,
+ Map<Object, Status> statusEntry, Map<Object, String> summaryEntry,
+ DataflowValidationReport report) {
+ for (Object o : report.getUnresolvedOutputs()) {
+ addReport(reportsEntry, statusEntry, summaryEntry,
+ new VisitReport(UnresolvedOutputKind.getInstance(), o,
+ "Invalid workflow output", UnresolvedOutputKind.OUTPUT,
+ VisitReport.Status.SEVERE));
+ }
+ for (Object o : report.getFailedEntities()) {
+ addReport(reportsEntry, statusEntry, summaryEntry,
+ new VisitReport(FailedEntityKind.getInstance(), o,
+ "Mismatch of input list depths", FailedEntityKind.FAILED_ENTITY,
+ VisitReport.Status.SEVERE));
+ }
+ for (Object o : report.getUnsatisfiedEntities()) {
+ addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
+ UnsatisfiedEntityKind.getInstance(), o, "Unknown prior list depth",
+ UnsatisfiedEntityKind.UNSATISFIED_ENTITY, VisitReport.Status.SEVERE));
+ }
+ // for (DataflowValidationReport subReport : report.getInvalidDataflows().values()) {
+ // fillInReport(descriptionMap, subReport);
+ // }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
+ * .Dataflow, java.lang.Object)
+ */
+ @Override
+ public Set<VisitReport> getReports(Dataflow d, Object object) {
+ Set<VisitReport> result = new HashSet<VisitReport>();
+ Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+ if (objectReports != null) {
+ Set<Object> objects = new HashSet<Object>();
+ objects.add(object);
+ if (object instanceof Processor) {
+ objects.addAll(((Processor) object).getActivityList());
+ }
+ for (Object o : objects) {
+ if (objectReports.containsKey(o)) {
+ result.addAll(objectReports.get(o));
+ }
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
+ * .Dataflow)
+ */
+ @Override
+ public Map<Object, Set<VisitReport>> getReports(Dataflow d) {
+ return reportMap.get(d);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#isStructurallySound(net.sf.taverna.t2.
+ * workflowmodel.Dataflow)
+ */
+ @Override
+ public boolean isStructurallySound(Dataflow d) {
+ Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+ if (objectReports == null) {
+ return false;
+ }
+ for (Set<VisitReport> visitReportSet : objectReports.values()) {
+ for (VisitReport vr : visitReportSet) {
+ if (vr.getStatus().equals(Status.SEVERE)) {
+ VisitKind vk = vr.getKind();
+ if ((vk instanceof IncompleteDataflowKind)
+ || (vk instanceof InvalidDataflowKind)
+ || (vk instanceof UnresolvedOutputKind)
+ || (vk instanceof FailedEntityKind)
+ || (vk instanceof UnsatisfiedEntityKind)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
+ * .Dataflow)
+ */
+ @Override
+ public Status getStatus(Dataflow d) {
+ Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+ if (objectReports == null) {
+ return Status.OK;
+ }
+ Status currentStatus = Status.OK;
+ for (Set<VisitReport> visitReportSet : objectReports.values()) {
+ for (VisitReport vr : visitReportSet) {
+ Status status = vr.getStatus();
+ if (status.compareTo(currentStatus) > 0) {
+ currentStatus = status;
+ }
+ if (currentStatus.equals(Status.SEVERE)) {
+ return currentStatus;
+ }
+ }
+ }
+ return currentStatus;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
+ * .Dataflow, java.lang.Object)
+ */
+ @Override
+ public Status getStatus(Dataflow d, Object object) {
+ Status result = Status.OK;
+ Map<Object, Status> statusEntry = statusMap.get(d);
+ if (statusEntry != null) {
+ Status value = statusEntry.get(object);
+ if (value != null) {
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getSummaryMessage(net.sf.taverna.t2.
+ * workflowmodel.Dataflow, java.lang.Object)
+ */
+ @Override
+ public String getSummaryMessage(Dataflow d, Object object) {
+ String result = null;
+ if (!getStatus(d, object).equals(Status.OK)) {
+ Map<Object, String> summaryEntry = summaryMap.get(d);
+ if (summaryEntry != null) {
+ result = summaryEntry.get(object);
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getLastCheckedTime(net.sf.taverna.t2.
+ * workflowmodel.Dataflow)
+ */
+ @Override
+ public long getLastCheckedTime(Dataflow d) {
+ Long l = lastCheckedMap.get(d);
+ if (l == null) {
+ return 0;
+ } else {
+ return l.longValue();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getLastFullCheckedTime(net.sf.taverna.t2
+ * .workflowmodel.Dataflow)
+ */
+ @Override
+ public long getLastFullCheckedTime(Dataflow d) {
+ Long l = lastFullCheckedMap.get(d);
+ if (l == null) {
+ return 0;
+ } else {
+ return l.longValue();
+ }
+ }
+
+ /**
+ * @author alanrw
+ *
+ */
+ public class ReportManagerFileObserver implements Observer<FileManagerEvent> {
+
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
+ throws Exception {
+ String onOpen = reportManagerConfiguration.getProperty(
+ ReportManagerConfiguration.ON_OPEN);
+ if (message instanceof ClosedDataflowEvent) {
+ ReportManagerImpl.this.removeReport(((ClosedDataflowEvent) message).getDataflow());
+ } else if (message instanceof SetCurrentDataflowEvent) {
+ Dataflow dataflow = ((SetCurrentDataflowEvent) message).getDataflow();
+ if (!reportMap.containsKey(dataflow)) {
+ if (!onOpen.equals(ReportManagerConfiguration.NO_CHECK)) {
+ updateReport(dataflow,
+ onOpen.equals(ReportManagerConfiguration.FULL_CHECK), true);
+ } else {
+ ReportManagerImpl.this.multiCaster
+ .notify(new DataflowReportEvent(dataflow));
+ }
+ } else {
+ ReportManagerImpl.this.multiCaster.notify(new DataflowReportEvent(dataflow));
+ }
+ }
+ }
+
+ }
+
+ private MultiCaster<ReportManagerEvent> multiCaster = new MultiCaster<ReportManagerEvent>(this);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#addObserver(net.sf.taverna.t2.lang.observer
+ * .Observer)
+ */
+ @Override
+ public void addObserver(Observer<ReportManagerEvent> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getObservers()
+ */
+ @Override
+ public List<Observer<ReportManagerEvent>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#removeObserver(net.sf.taverna.t2.lang.observer
+ * .Observer)
+ */
+ @Override
+ public void removeObserver(Observer<ReportManagerEvent> observer) {
+ multiCaster.removeObserver(observer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#isReportOutdated(net.sf.taverna.t2.
+ * workflowmodel.Dataflow)
+ */
+ @Override
+ public boolean isReportOutdated(Dataflow dataflow) {
+ String lastCheckedId = lastFullCheckedDataflowIdMap.get(dataflow);
+ Long lastCheck = lastFullCheckedMap.get(dataflow);
+ if (lastCheckedId == null || lastCheck == null) {
+ // Unknown, so outdated
+ return true;
+ }
+ if (!lastCheckedId.equals(dataflow.getIdentifier())) {
+ // Workflow changed, so outdaeted
+ return true;
+ }
+ long now = System.currentTimeMillis();
+ long age = now - lastCheck;
+ // Outdated if it is older than the maximum
+ return age > MAX_AGE_OUTDATED_MILLIS;
+ }
+
+ public class ReportManagerEditObserver implements Observer<EditManagerEvent> {
+ public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+ throws Exception {
+ String onEdit = reportManagerConfiguration
+ .getProperty(ReportManagerConfiguration.ON_EDIT);
+ Dataflow dataflow = fileManager.getCurrentDataflow();
+ if (message instanceof AbstractDataflowEditEvent) {
+ AbstractDataflowEditEvent adee = (AbstractDataflowEditEvent) message;
+ if (adee.getDataFlow().equals(dataflow)) {
+ if (onEdit.equals(ReportManagerConfiguration.QUICK_CHECK)) {
+ updateReport(dataflow, false, true);
+ } else if (onEdit.equals(ReportManagerConfiguration.FULL_CHECK)) {
+ updateReport(dataflow, true, true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml
new file mode 100644
index 0000000..31c7432
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <!-- <service ref="ProcessorFragilityChecker" interface="net.sf.taverna.t2.visit.fragility.FragilityChecker" /> -->
+
+ <!-- <service ref="FragilityCheck" interface="net.sf.taverna.t2.visit.VisitKind" /> -->
+
+ <service ref="ReportManagerImpl" interface="net.sf.taverna.t2.workbench.report.ReportManager" />
+
+ <service ref="ReportManagerConfiguration" interface="net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+
+ <set id="visitors" interface="net.sf.taverna.t2.visit.Visitor" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml
new file mode 100644
index 0000000..966d50d
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <!-- <bean id="ProcessorFragilityChecker" class="net.sf.taverna.t2.visit.fragility.impl.ProcessorFragilityChecker" /> -->
+
+ <!-- <bean id="FragilityCheck" class="net.sf.taverna.t2.visit.fragility.impl.FragilityCheck" /> -->
+
+ <bean id="ReportManagerImpl" class="net.sf.taverna.t2.workbench.report.impl.ReportManagerImpl">
+ <constructor-arg ref="editManager" />
+ <constructor-arg ref="fileManager" />
+ <constructor-arg ref="visitors" />
+ <constructor-arg ref="ReportManagerConfiguration"/>
+ </bean>
+
+ <bean id="ReportManagerConfiguration" class="net.sf.taverna.t2.workbench.report.config.impl.ReportManagerConfigurationImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-report-view/pom.xml b/taverna-workbench-report-view/pom.xml
new file mode 100644
index 0000000..9bc61d3
--- /dev/null
+++ b/taverna-workbench-report-view/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>report-view</artifactId>
+ <packaging>bundle</packaging>
+ <name>Reporting view</name>
+ <description>
+ View of reports about dataflows in the workbench.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+<!-- <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>workbench-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ --> <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/config/ui/ReportManagerConfigurationPanel.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/config/ui/ReportManagerConfigurationPanel.java
new file mode 100644
index 0000000..f570c69
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/config/ui/ReportManagerConfigurationPanel.java
@@ -0,0 +1,363 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.config.ui;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.ui.DeselectingButton;
+import net.sf.taverna.t2.workbench.helper.Helper;
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+
+
+/**
+ * @author alanrw
+ *
+ */
+public class ReportManagerConfigurationPanel extends JPanel {
+
+ private static final String RESET = "Reset";
+ private static final String APPLY = "Apply";
+ private static final String HELP = "Help";
+ private static final String ASK_ON_ERRORS_OR_WARNINGS = "Ask on errors or warnings";
+ private static final String ASK_ON_ERRORS = "Ask on errors";
+ private static final String DESCRIPTION = "Configure if and how the validation report is generated";
+ private static final String NEVER_ASK = "Never ask";
+ private static final String FULL_CHECKS = "Full checks";
+ private static final String QUICK_CHECKS = "Quick checks";
+ private static final String NO_CHECKS = "No checks";
+ private static final String DEFAULT_TIMEOUT_STRING = "Reporting timeout in seconds (per service)";
+ private static final String REPORT_EXPIRATION_STRING = "Minutes before reports expire; 0 = never";
+ private static final String CHECKS_ON_OPEN = "Checks when opening a workflow";
+ private static final String CHECKS_ON_EDIT = "Checks after each edit";
+ private static final String CHECKS_BEFORE_RUN = "Checks before running a workflow";
+ private static final String QUERY_USER_BEFORE_RUN = "Ask before run";
+
+ private ReportManagerConfiguration configuration;
+
+ /**
+ * The size of the field for the JTextFields.
+ */
+ private static int TEXTFIELD_SIZE = 25;
+
+ private JTextField timeoutField;
+ private JTextField expirationField;
+ private JComboBox openCombo;
+ private JComboBox editCombo;
+ private JComboBox runCombo;
+ private JComboBox queryBeforeRunCombo;
+
+ public ReportManagerConfigurationPanel(ReportManagerConfiguration reportManagerConfiguration) {
+ super();
+ configuration = reportManagerConfiguration;
+ this.setLayout(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea(DESCRIPTION);
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(descriptionText, gbc);
+
+ openCombo = new JComboBox(new Object[] {NO_CHECKS, QUICK_CHECKS, FULL_CHECKS});
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(10,0,0,0);
+ this.add(new JLabel(CHECKS_ON_OPEN), gbc);
+ gbc.gridx = 1;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(openCombo, gbc);
+
+ editCombo = new JComboBox(new Object[] {NO_CHECKS, QUICK_CHECKS});
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(10,0,0,0);
+ this.add(new JLabel(CHECKS_ON_EDIT), gbc);
+ gbc.gridx = 1;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(editCombo, gbc);
+
+ runCombo = new JComboBox(new Object[] {QUICK_CHECKS, FULL_CHECKS});
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(10,0,0,0);
+ this.add(new JLabel(CHECKS_BEFORE_RUN), gbc);
+ gbc.gridx = 1;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(runCombo, gbc);
+
+ timeoutField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(10,0,0,0);
+ this.add(new JLabel(DEFAULT_TIMEOUT_STRING), gbc);
+ gbc.gridx = 1;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(timeoutField, gbc);
+
+ expirationField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(10,0,0,0);
+ this.add(new JLabel(REPORT_EXPIRATION_STRING), gbc);
+ gbc.gridx = 1;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(expirationField, gbc);
+
+ queryBeforeRunCombo = new JComboBox(new Object[] {NEVER_ASK, ASK_ON_ERRORS, ASK_ON_ERRORS_OR_WARNINGS});
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.insets = new Insets(10,0,0,0);
+ this.add(new JLabel(QUERY_USER_BEFORE_RUN), gbc);
+ gbc.gridx = 1;
+ gbc.gridwidth = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ this.add(queryBeforeRunCombo, gbc);
+
+ // Add buttons panel
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.weightx = 1;
+ gbc.weighty = 1;
+ gbc.gridwidth = 2;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.anchor = GridBagConstraints.SOUTH;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(createButtonPanel(), gbc);
+
+ setFields();
+ }
+
+ /**
+ * Create the panel to contain the buttons
+ *
+ * @return
+ */
+ @SuppressWarnings("serial")
+ private JPanel createButtonPanel() {
+ final JPanel panel = new JPanel();
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new DeselectingButton(HELP, new AbstractAction() {
+ public void actionPerformed(ActionEvent arg0) {
+ Helper.showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new DeselectingButton(RESET, new AbstractAction() {
+ public void actionPerformed(ActionEvent arg0) {
+ setFields();
+ }
+ });
+ panel.add(resetButton);
+
+ /**
+ * The applyButton applies the shown field values to the
+ * {@link HttpProxyConfiguration} and saves them for future.
+ */
+ JButton applyButton = new DeselectingButton(APPLY, new AbstractAction() {
+ public void actionPerformed(ActionEvent arg0) {
+ applySettings();
+ setFields();
+ }
+ });
+ panel.add(applyButton);
+
+ return panel;
+ }
+
+ /**
+ * Set the shown field values to those currently in use
+ * (i.e. last saved configuration).
+ */
+ private void setFields() {
+ timeoutField.setText(Integer.toString(Integer.parseInt(configuration.getProperty(ReportManagerConfiguration.TIMEOUT))));
+ expirationField.setText(Integer.toString(Integer.parseInt(configuration.getProperty(ReportManagerConfiguration.REPORT_EXPIRATION))));
+
+ String openSetting = configuration.getProperty(ReportManagerConfiguration.ON_OPEN);
+ if (openSetting.equals(ReportManagerConfiguration.NO_CHECK)) {
+ openCombo.setSelectedIndex(0);
+ } else if (openSetting.equals(ReportManagerConfiguration.QUICK_CHECK)) {
+ openCombo.setSelectedIndex(1);
+ } else {
+ openCombo.setSelectedIndex(2);
+ }
+
+ String editSetting = configuration.getProperty(ReportManagerConfiguration.ON_EDIT);
+ if (editSetting.equals(ReportManagerConfiguration.NO_CHECK)) {
+ editCombo.setSelectedIndex(0);
+ } else if (editSetting.equals(ReportManagerConfiguration.QUICK_CHECK)) {
+ editCombo.setSelectedIndex(1);
+ } else {
+ editCombo.setSelectedIndex(2);
+ }
+
+ String runSetting = configuration.getProperty(ReportManagerConfiguration.BEFORE_RUN);
+ if (runSetting.equals(ReportManagerConfiguration.QUICK_CHECK)) {
+ runCombo.setSelectedIndex(0);
+ } else {
+ runCombo.setSelectedIndex(1);
+ }
+
+ String queryBeforeRunSetting = configuration.getProperty(ReportManagerConfiguration.QUERY_BEFORE_RUN);
+ if (queryBeforeRunSetting.equals(ReportManagerConfiguration.NONE)) {
+ queryBeforeRunCombo.setSelectedIndex(0);
+ } else if (queryBeforeRunSetting.equals(ReportManagerConfiguration.ERRORS)) {
+ queryBeforeRunCombo.setSelectedIndex(1);
+ } else {
+ queryBeforeRunCombo.setSelectedIndex(2);
+ }
+ }
+
+ /**
+ * Save the currently set field values (if valid) to the
+ * configuration. Also applies those values to the
+ * currently running Taverna.
+ */
+ private void applySettings() {
+ if (validateFields()) {
+ saveSettings();
+ }
+ }
+
+ private boolean validateFields() {
+ return (validateTimeoutField() && validateExpirationField());
+ }
+
+ private boolean validateTimeoutField() {
+ String timeoutText = timeoutField.getText();
+ String errorText = "";
+ int newTimeout = -1;
+ try {
+ newTimeout = Integer.parseInt(timeoutText);
+ if (newTimeout <= 0) {
+ errorText += "The timeout must be greater than zero";
+ }
+ } catch (NumberFormatException e) {
+ errorText += "The timeout must be an integer value";
+ }
+ if (errorText.length() > 0) {
+ JOptionPane.showMessageDialog(this, errorText, "", JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean validateExpirationField() {
+ String expirationText = expirationField.getText();
+ String errorText = "";
+ int newExpiration = -1;
+ try {
+ newExpiration = Integer.parseInt(expirationText);
+ if (newExpiration < 0) {
+ errorText += "The expiration delay must be zero or greater";
+ }
+ } catch (NumberFormatException e) {
+ errorText += "The expiration delay must be an integer value";
+ }
+ if (errorText.length() > 0) {
+ JOptionPane.showMessageDialog(this, errorText, "", JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * saveSettings saves the specified values for future use.
+ */
+ private void saveSettings() {
+ configuration.setProperty(ReportManagerConfiguration.TIMEOUT, Integer
+ .toString(Integer.parseInt(timeoutField.getText())));
+
+ configuration.setProperty(ReportManagerConfiguration.REPORT_EXPIRATION, Integer
+ .toString(Integer.parseInt(expirationField.getText())));
+
+ int openSetting = openCombo.getSelectedIndex();
+ if (openSetting == 0) {
+ configuration.setProperty(ReportManagerConfiguration.ON_OPEN,
+ ReportManagerConfiguration.NO_CHECK);
+ } else if (openSetting == 1) {
+ configuration.setProperty(ReportManagerConfiguration.ON_OPEN,
+ ReportManagerConfiguration.QUICK_CHECK);
+ } else {
+ configuration.setProperty(ReportManagerConfiguration.ON_OPEN,
+ ReportManagerConfiguration.FULL_CHECK);
+ }
+
+ int editSetting = editCombo.getSelectedIndex();
+ if (editSetting == 0) {
+ configuration.setProperty(ReportManagerConfiguration.ON_EDIT,
+ ReportManagerConfiguration.NO_CHECK);
+ } else {
+ configuration.setProperty(ReportManagerConfiguration.ON_EDIT,
+ ReportManagerConfiguration.QUICK_CHECK);
+ }
+
+ int runSetting = runCombo.getSelectedIndex();
+ if (runSetting == 0) {
+ configuration.setProperty(ReportManagerConfiguration.BEFORE_RUN,
+ ReportManagerConfiguration.QUICK_CHECK);
+ } else {
+ configuration.setProperty(ReportManagerConfiguration.BEFORE_RUN,
+ ReportManagerConfiguration.FULL_CHECK);
+ }
+
+ int queryBeforeRunSetting = queryBeforeRunCombo.getSelectedIndex();
+ if (queryBeforeRunSetting == 0) {
+ configuration.setProperty(ReportManagerConfiguration.QUERY_BEFORE_RUN, ReportManagerConfiguration.NONE);
+ } else if (queryBeforeRunSetting == 1) {
+ configuration.setProperty(ReportManagerConfiguration.QUERY_BEFORE_RUN, ReportManagerConfiguration.ERRORS);
+ } else {
+ configuration.setProperty(ReportManagerConfiguration.QUERY_BEFORE_RUN, ReportManagerConfiguration.ERRORS_OR_WARNINGS);
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/config/ui/ReportManagerConfigurationUIFactory.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/config/ui/ReportManagerConfigurationUIFactory.java
new file mode 100644
index 0000000..2965a98
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/config/ui/ReportManagerConfigurationUIFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.report.config.ui;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+
+/**
+ * ConfigurationFactory for the ReportManagerConfiguration.
+ *
+ * @author Alan R Williams
+ */
+public class ReportManagerConfigurationUIFactory implements ConfigurationUIFactory {
+
+ private ReportManagerConfiguration reportManagerConfiguration;
+
+ public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ public JPanel getConfigurationPanel() {
+ return new ReportManagerConfigurationPanel(reportManagerConfiguration);
+ }
+
+ public Configurable getConfigurable() {
+ return reportManagerConfiguration;
+ }
+
+ public void setReportManagerConfiguration(ReportManagerConfiguration reportManagerConfiguration) {
+ this.reportManagerConfiguration = reportManagerConfiguration;
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportOnObjectContextualMenuAction.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportOnObjectContextualMenuAction.java
new file mode 100644
index 0000000..1fdfd45
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportOnObjectContextualMenuAction.java
@@ -0,0 +1,189 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.report.view;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.visit.DataflowCollation;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+import org.apache.log4j.Logger;
+
+
+public class ReportOnObjectContextualMenuAction extends AbstractContextualMenuAction {
+ private ReportManager reportManager;
+ private FileManager fileManager;
+ private Workbench workbench;
+
+ private static final String VALIDATE_SERVICE = "Validate service";
+ private String namedComponent = "reportView";
+ private EditManager editManager;
+
+ public static final URI configureSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configure");
+
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(ReportOnObjectContextualMenuAction.class);
+
+ public ReportOnObjectContextualMenuAction() {
+ super(configureSection, 43);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (super.isEnabled() && (getContextualSelection().getSelection() instanceof Processor));
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ Dataflow parent;
+ if (getContextualSelection().getParent() instanceof Dataflow) {
+ parent = (Dataflow)getContextualSelection().getParent();
+ } else {
+ parent = fileManager.getCurrentDataflow();
+ }
+
+ final Dataflow df = parent;
+ return new AbstractAction(VALIDATE_SERVICE) {
+ public void actionPerformed(ActionEvent e) {
+ Object o = getContextualSelection().getSelection();
+ if (o instanceof Processor) {
+ Processor p = (Processor) o;
+ ValidateObjectSwingWorker worker = new ValidateObjectSwingWorker(df, p, editManager, reportManager);
+ ValidateObjectInProgressDialog dialog = new ValidateObjectInProgressDialog();
+ worker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
+ worker.execute();
+
+ // Give a chance to the SwingWorker to finish so we do not have to display
+ // the dialog if checking of the object is quick (so it won't flicker on the screen)
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ex) {
+
+ }
+ if (!worker.isDone()){
+ dialog.setVisible(true); // this will block the GUI
+ }
+
+ checkStatus(df, p);
+ }
+ }
+ };
+ }
+
+ /**
+ * Check the status and pop up a warning if something is wrong.
+ *
+ */
+ public void checkStatus(Dataflow dataflow, Processor p) {
+ Status status = reportManager.getStatus(dataflow, p);
+ int messageType;
+ String message;
+ String name = p.getLocalName();
+ if (status.equals(Status.OK)) {
+ messageType = JOptionPane.INFORMATION_MESSAGE;
+ message = name + " validated OK.";
+
+ } else {
+ Set<VisitReport> immediateReports = reportManager
+ .getReports(dataflow, p);
+ int errorCount = 0;
+ int warningCount = 0;
+
+ Set<VisitReport> reports = new HashSet<VisitReport>();
+ for (VisitReport report : immediateReports) {
+ if (report.getKind() instanceof DataflowCollation) {
+ reports.addAll(report.getSubReports());
+ } else {
+ reports.add(report);
+ }
+ }
+
+ // Find warnings
+ for (VisitReport report : reports) {
+ if (report.getStatus().equals(Status.SEVERE)) {
+ errorCount++;
+ } else if (report.getStatus().equals(Status.WARNING)) {
+ warningCount++;
+ }
+ }
+ if (status.equals(Status.WARNING)) {
+ messageType = JOptionPane.WARNING_MESSAGE;
+ message = "Validation of " + name + " reported ";
+ } else { // SEVERE
+ messageType = JOptionPane.ERROR_MESSAGE;
+ message = "Validation of " + name + " reported ";
+ if (errorCount == 1) {
+ message += "one error";
+ } else {
+ message += errorCount + " errors";
+ }
+ if (warningCount != 0) {
+ message += " and ";
+ }
+ }
+ if (warningCount == 1) {
+ message += "one warning";
+ } else if (warningCount > 0) {
+ message += warningCount + " warnings";
+ }
+ }
+ JOptionPane.showMessageDialog(MainWindow.getMainWindow(), message,
+ "Service validation", messageType);
+ workbench.getPerspectives().setWorkflowPerspective();
+ workbench.makeNamedComponentVisible(namedComponent);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportOnWorkflowAction.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportOnWorkflowAction.java
new file mode 100644
index 0000000..a9ba839
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportOnWorkflowAction.java
@@ -0,0 +1,177 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.view;
+
+import java.awt.event.ActionEvent;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+/**
+ * @author alanrw
+ *
+ */
+public class ReportOnWorkflowAction extends AbstractAction {
+
+ private final boolean includeTimeConsuming;
+ private final boolean remember;
+ private Dataflow specifiedDataflow;
+ private static final String namedComponent = "reportView";
+
+ private final FileManager fileManager;
+ private final ReportManager reportManager;
+ private final Workbench workbench;
+ private final EditManager editManager;
+
+ public ReportOnWorkflowAction(String name, boolean includeTimeConsuming, boolean remember,
+ EditManager editManager, FileManager fileManager, ReportManager reportManager, Workbench workbench) {
+ super(name);
+ this.includeTimeConsuming = includeTimeConsuming;
+ this.remember = remember;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManager = reportManager;
+ this.workbench = workbench;
+ this.specifiedDataflow = null;
+ }
+
+ public ReportOnWorkflowAction(String name, Dataflow dataflow, boolean includeTimeConsuming,
+ boolean remember, EditManager editManager, FileManager fileManager, ReportManager reportManager,
+ Workbench workbench) {
+ super(name);
+ this.specifiedDataflow = dataflow;
+ this.includeTimeConsuming = includeTimeConsuming;
+ this.remember = remember;
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManager = reportManager;
+ this.workbench = workbench;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+ */
+ public void actionPerformed(ActionEvent e) {
+ if (validateWorkflow()) {
+ checkStatus();
+ }
+ }
+
+ /**
+ * Check the status and pop up a warning if something is wrong.
+ *
+ */
+ public void checkStatus() {
+ Dataflow dataflow;
+ if (specifiedDataflow == null) {
+ dataflow = fileManager.getCurrentDataflow();
+ } else {
+ dataflow = specifiedDataflow;
+ }
+ Status status = reportManager.getStatus(dataflow);
+ int messageType;
+ String message;
+ if (status.equals(Status.OK)) {
+ messageType = JOptionPane.INFORMATION_MESSAGE;
+ message = "Workflow validated OK.";
+
+ } else {
+ StringBuffer sb = new StringBuffer();
+ Map<Object, Set<VisitReport>> reports = reportManager.getReports(dataflow);
+ int errorCount = 0;
+ int warningCount = 0;
+ // Find warnings
+ for (Entry<Object, Set<VisitReport>> entry : reports.entrySet()) {
+ for (VisitReport report : entry.getValue()) {
+ if (report.getStatus().equals(Status.SEVERE)) {
+ errorCount++;
+ } else if (report.getStatus().equals(Status.WARNING)) {
+ warningCount++;
+ }
+ }
+ }
+ if (status.equals(Status.WARNING)) {
+ messageType = JOptionPane.WARNING_MESSAGE;
+ message = "Validation reported ";
+ } else { // SEVERE
+ messageType = JOptionPane.ERROR_MESSAGE;
+ message = "Validation reported ";
+ if (errorCount == 1) {
+ message += "one error";
+ } else {
+ message += errorCount + " errors";
+ }
+ if (warningCount != 0) {
+ message += " and ";
+ }
+ }
+ if (warningCount == 1) {
+ message += "one warning";
+ } else if (warningCount > 0) {
+ message += warningCount + " warnings";
+ }
+ }
+ JOptionPane.showMessageDialog(MainWindow.getMainWindow(), message, "Workflow validation",
+ messageType);
+ workbench.getPerspectives().setWorkflowPerspective();
+ workbench.makeNamedComponentVisible(namedComponent);
+ }
+
+ /**
+ * Perform validation on workflow.
+ *
+ * @return <code>true</code> if the validation was not cancelled.
+ */
+ public boolean validateWorkflow() {
+ Dataflow dataflow;
+ if (specifiedDataflow == null) {
+ dataflow = fileManager.getCurrentDataflow();
+ } else {
+ dataflow = specifiedDataflow;
+ }
+ ValidateSwingWorker validateSwingWorker = new ValidateSwingWorker(dataflow,
+ includeTimeConsuming, remember, editManager, reportManager);
+ ValidateInProgressDialog dialog = new ValidateInProgressDialog();
+ validateSwingWorker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
+ validateSwingWorker.execute();
+
+ // Give a chance to the SwingWorker to finish so we do not have to
+ // display
+ // the dialog if copying of the workflow is quick (so it won't flicker
+ // on the screen)
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ex) {
+
+ }
+ if (!validateSwingWorker.isDone()) {
+ dialog.setVisible(true); // this will block the GUI
+ }
+ boolean userCancelled = dialog.hasUserCancelled(); // see if user
+ // cancelled the
+ // dialog
+
+ if (userCancelled) {
+ validateSwingWorker.cancel(true);
+ }
+ return !userCancelled;
+
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewComponent.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewComponent.java
new file mode 100644
index 0000000..4067505
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewComponent.java
@@ -0,0 +1,574 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.report.view;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.SystemColor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.ui.DeselectingButton;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.report.DataflowReportEvent;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.ReportManagerEvent;
+import net.sf.taverna.t2.workbench.report.explainer.VisitExplainer;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.Processor;
+import net.sf.taverna.t2.workflowmodel.health.RemoteHealthChecker;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Alan R Williams
+ *
+ */
+@SuppressWarnings("serial")
+public class ReportViewComponent extends JPanel implements UIComponentSPI {
+
+ private static Logger logger = Logger.getLogger(ReportViewComponent.class);
+
+ private static int TABLE_MARGIN = 5;
+
+ private ReportManager reportManager;
+
+ private List<VisitExplainer> visitExplainers;
+
+ private JLabel dataflowName;
+
+ // private static JTextArea solutionDescription;
+ // private static JTextArea issueDescription;
+ // private static JSplitPane subSplitPane = new JSplitPane();
+
+ private JTable table;
+ private static final JComponent defaultExplanation = new ReadOnlyTextArea(
+ "No additional explanation available");
+ private static final JComponent defaultSolution = new ReadOnlyTextArea("No suggested solutions");
+
+ private static final JComponent okExplanation = new ReadOnlyTextArea("No problem found");
+ private static final JComponent okSolution = new ReadOnlyTextArea("No change necessary");
+
+ private static final JComponent nothingToExplain = new ReadOnlyTextArea("No message selected");
+ private static final JComponent nothingToSolve = new ReadOnlyTextArea("No message selected");
+
+ private JComponent explanation = okExplanation;
+ private JComponent solution = okSolution;
+
+ private JTabbedPane messagePane;
+ private final JScrollPane explanationScrollPane = new JScrollPane();
+ private final JScrollPane solutionScrollPane = new JScrollPane();
+
+ private VisitReport lastSelectedReport = null;
+
+ private ReportViewTableModel reportViewTableModel;
+ private ReportViewConfigureAction reportViewConfigureAction = new ReportViewConfigureAction();
+ private JComboBox shownReports = null;
+
+ private TableListener tableListener = null;
+
+ private VisitReportProxySet ignoredReports = new VisitReportProxySet();
+
+ JButton ignoreReportButton;
+
+ public static String ALL_INCLUDING_IGNORED = "All";
+ public static String ALL_EXCEPT_IGNORED = "All except ignored";
+
+ protected FileManager fileManager;
+ protected FileManagerObserver fileManagerObserver = new FileManagerObserver();
+ private SelectionManager openedWorkflowsManager;
+ private Observer<DataflowSelectionMessage> workflowSelectionListener = new DataflowSelectionListener();
+
+ private final Workbench workbench;
+
+ private final EditManager editManager;
+
+ private final MenuManager menuManager;
+
+ public ReportViewComponent(EditManager editManager, FileManager fileManager, MenuManager menuManager,
+ ReportManager reportManager, Workbench workbench,
+ SelectionManager selectionManager, List<VisitExplainer> visitExplainers) {
+ super();
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.reportManager = reportManager;
+ this.workbench = workbench;
+ openedWorkflowsManager = selectionManager;
+ this.visitExplainers = visitExplainers;
+ reportManager.addObserver(new ReportManagerObserver());
+ fileManager.addObserver(fileManagerObserver);
+ initialise();
+ }
+
+ private JScrollPane tableScrollPane;
+
+ private void initialise() {
+ shownReports = new JComboBox(new String[] { ALL_INCLUDING_IGNORED, ALL_EXCEPT_IGNORED,
+ ReportViewTableModel.WARNINGS_AND_ERRORS, ReportViewTableModel.JUST_ERRORS });
+ shownReports.setSelectedIndex(1);
+ shownReports.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ex) {
+ showReport(fileManager.getCurrentDataflow());
+ }
+ });
+ this.setLayout(new BorderLayout());
+ JPanel headerPanel = new JPanel();
+ headerPanel.setLayout(new BorderLayout());
+ dataflowName = new JLabel();
+ dataflowName.setText("No workflow");
+
+ headerPanel.add(dataflowName, BorderLayout.WEST);
+
+ JPanel shownReportsPanel = new JPanel();
+ shownReportsPanel.setLayout(new BorderLayout());
+ shownReportsPanel.add(new JLabel("Show messages:"), BorderLayout.WEST);
+ shownReportsPanel.add(shownReports, BorderLayout.EAST);
+ headerPanel.add(shownReportsPanel, BorderLayout.EAST);
+
+ this.add(headerPanel, BorderLayout.NORTH);
+
+ JPanel splitPanel = new JPanel();
+ splitPanel.setLayout(new GridLayout(2, 1));
+ tableScrollPane = new JScrollPane();
+ splitPanel.add(tableScrollPane);
+
+ messagePane = new JTabbedPane();
+ messagePane.addTab("Explanation", explanationScrollPane);
+ messagePane.addTab("Solution", solutionScrollPane);
+ splitPanel.add(messagePane);
+
+ this.add(splitPanel, BorderLayout.CENTER);
+ ignoreReportButton = new DeselectingButton("Hide message", new AbstractAction() {
+ public void actionPerformed(ActionEvent ex) {
+ if (lastSelectedReport != null) {
+ if (ignoredReports.contains(lastSelectedReport)) {
+ ignoredReports.remove(lastSelectedReport);
+ } else {
+ ignoredReports.add(lastSelectedReport);
+ if (shownReports.getSelectedItem().equals(ALL_INCLUDING_IGNORED)) {
+ shownReports.setSelectedItem(ALL_EXCEPT_IGNORED);
+ }
+ showReport();
+ }
+ }
+ }
+ });
+ // JButton quickCheckButton = new JButton(new
+ // ReportOnWorkflowAction("Quick check", false, true));
+ JButton fullCheckButton = new DeselectingButton("Validate workflow",
+ new ReportOnWorkflowAction("Validate workflow", true, false, editManager,
+ fileManager, reportManager, workbench) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // Full check always starts from scratch
+ RemoteHealthChecker.clearCachedEndpointStatus();
+ super.actionPerformed(e);
+ }
+ });
+ JPanel validateButtonPanel = new JPanel();
+ validateButtonPanel.add(ignoreReportButton);
+ // validateButtonPanel.add(quickCheckButton);
+ validateButtonPanel.add(fullCheckButton);
+ this.add(validateButtonPanel, BorderLayout.SOUTH);
+ showReport(fileManager.getCurrentDataflow());
+ }
+
+ public void onDisplay() {
+ }
+
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ public void onDispose() {
+
+ }
+
+ private void createTable(Dataflow dataflow, Map<Object, Set<VisitReport>> reportEntries) {
+ reportViewTableModel = new ReportViewTableModel(dataflow, reportEntries,
+ (String) shownReports.getSelectedItem(), ignoredReports, reportManager);
+ if (table == null) {
+ table = new JTable(reportViewTableModel);
+ table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ table.setRowSelectionAllowed(true);
+ tableListener = new TableListener();
+ table.getSelectionModel().addListSelectionListener(tableListener);
+ table.setSurrendersFocusOnKeystroke(false);
+ table.getInputMap(JInternalFrame.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
+ KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "configure");
+ table.getInputMap(JInternalFrame.WHEN_FOCUSED).put(
+ KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "configure");
+
+ table.getActionMap().put("configure", reportViewConfigureAction);
+
+ table.setDefaultRenderer(Status.class, new StatusRenderer());
+ table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
+ } else {
+ table.setModel(reportViewTableModel);
+ }
+ packColumn(table, 0, TABLE_MARGIN, true);
+ packColumn(table, 1, TABLE_MARGIN, true);
+ packColumn(table, 2, TABLE_MARGIN, true);
+ packColumn(table, 3, TABLE_MARGIN, false);
+ packColumn(table, 4, TABLE_MARGIN, false);
+ }
+
+ private void showReport() {
+ showReport(fileManager.getCurrentDataflow());
+ }
+
+ private void showReport(final Dataflow dataflow) {
+ if (dataflow != null) {
+ String dfName = dataflow.getLocalName();
+ if (dfName.length() > 20) {
+ dfName = dfName.substring(0, 17) + "...";
+ }
+ dataflowName.setText(dfName);
+ } else {
+ dataflowName.setText("No workflow");
+ }
+
+ createTable(dataflow, reportManager.getReports(dataflow));
+ tableScrollPane.setViewportView(table);
+ boolean found = false;
+ DataflowSelectionModel selectionModel = openedWorkflowsManager
+ .getDataflowSelectionModel(fileManager.getCurrentDataflow());
+
+ Set<Object> selection = selectionModel.getSelection();
+ Object selectedObject = null;
+ if (selection.size() == 1) {
+ selectedObject = selection.iterator().next();
+ }
+ if ((lastSelectedReport != null)
+ && (lastSelectedReport.getSubject().equals(selectedObject))) {
+ VisitReportProxy lastSelectedReportProxy = new VisitReportProxy(lastSelectedReport);
+ for (int i = 0; i < table.getRowCount(); i++) {
+ VisitReport vr = reportViewTableModel.getReport(i);
+ VisitReportProxy vrProxy = new VisitReportProxy(vr);
+ if (vrProxy.equals(lastSelectedReportProxy)) {
+ table.setRowSelectionInterval(i, i);
+ found = true;
+ scrollToVisible(i);
+ break;
+ }
+ }
+ /*
+ * if (!found) { found =
+ * selectSubject(lastSelectedReport.getSubject()); }
+ */
+ }
+ if ((!found) && (selectedObject != null)) {
+ found = selectSubject(selectedObject);
+ }
+ if (!found) {
+ lastSelectedReport = null;
+ table.clearSelection();
+ }
+ updateExplanation(lastSelectedReport);
+ updateMessages();
+ messagePane.revalidate();
+ this.revalidate();
+ }
+
+ private boolean selectSubject(Object subject) {
+ int currentlySelected = table.getSelectedRow();
+ if (currentlySelected != -1) {
+ Object currentSubject = reportViewTableModel.getReport(currentlySelected).getSubject();
+ if (currentSubject == subject) {
+ return true;
+ }
+ }
+ for (int i = 0; i < table.getRowCount(); i++) {
+ VisitReport vr = reportViewTableModel.getReport(i);
+ if (vr.getSubject() == subject) {
+ table.setRowSelectionInterval(i, i);
+ scrollToVisible(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void updateMessages() {
+ JPanel explainPanel = wrapComponent(explanation);
+ explanationScrollPane.setViewportView(explainPanel);
+ solutionScrollPane.setViewportView(wrapComponent(solution));
+ if (lastSelectedReport != null) {
+ ignoreReportButton.setEnabled(true);
+ if (ignoredReports.contains(lastSelectedReport)) {
+ ignoreReportButton.setText("Include message");
+ } else {
+ ignoreReportButton.setText("Ignore message");
+ }
+ } else {
+ ignoreReportButton.setEnabled(false);
+ ignoreReportButton.setText("Ignore message");
+ }
+ messagePane.revalidate();
+ }
+
+ private final class ReportManagerObserver implements Observer<ReportManagerEvent> {
+ public void notify(Observable<ReportManagerEvent> sender, ReportManagerEvent event)
+ throws Exception {
+ Dataflow currentDataflow = fileManager.getCurrentDataflow();
+
+ if (event instanceof DataflowReportEvent) {
+ DataflowReportEvent dre = (DataflowReportEvent) event;
+ final Dataflow dataflow = dre.getDataflow();
+ if (dataflow.equals(currentDataflow)) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ showReport(dataflow);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ private void scrollToVisible(int rowIndex) {
+ Rectangle rect = table.getCellRect(rowIndex, 0, true);
+ table.scrollRectToVisible(rect);
+ }
+
+ final class TableListener implements ListSelectionListener {
+
+ public TableListener() {
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ int row = table.getSelectedRow();
+ if (row >= 0) {
+ DataflowSelectionModel dsm = openedWorkflowsManager
+ .getDataflowSelectionModel(fileManager.getCurrentDataflow());
+ dsm.clearSelection();
+ VisitReport vr = reportViewTableModel.getReport(row);
+ final Object subject = reportViewTableModel.getSubject(row);
+ dsm.addSelection(subject);
+ updateExplanation(vr);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateMessages();
+ if (subject instanceof Processor) {
+ reportViewConfigureAction.setConfiguredProcessor((Processor) subject, menuManager);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private void updateExplanation(VisitReport vr) {
+ lastSelectedReport = vr;
+ if (vr == null) {
+ explanation = nothingToExplain;
+ solution = nothingToSolve;
+ return;
+ }
+ if (vr.getStatus().equals(Status.OK)) {
+ explanation = okExplanation;
+ solution = okSolution;
+ return;
+ }
+ for (VisitExplainer ve : visitExplainers) {
+ if (ve.canExplain(vr.getKind(), vr.getResultId())) {
+ try {
+ explanation = ve.getExplanation(vr);
+ } catch (Exception e) {
+ logger.error("Error creating explanation", e);
+ explanation = null;
+ }
+ if (explanation == null) {
+ explanation = defaultExplanation;
+ }
+ try {
+ solution = ve.getSolution(vr);
+ } catch (Exception e) {
+ logger.error("Error creating soluttion", e);
+ solution = null;
+ }
+ if (solution == null) {
+ solution = defaultSolution;
+ }
+ return;
+ }
+ }
+ explanation = defaultExplanation;
+ solution = defaultSolution;
+ }
+
+ // Sets the preferred width of the visible column specified by vColIndex.
+ // The column
+ // will be just wide enough to show the column head and the widest cell in
+ // the column.
+ // margin pixels are added to the left and right
+ // (resulting in an additional width of 2*margin pixels).
+ public void packColumn(JTable table, int vColIndex, int margin, boolean fixWidth) {
+ // TableModel model = table.getModel();
+ DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel();
+ TableColumn col = colModel.getColumn(vColIndex);
+ int width = 0;
+ // Get width of column header
+ TableCellRenderer renderer = col.getHeaderRenderer();
+ if (renderer == null) {
+ renderer = table.getTableHeader().getDefaultRenderer();
+ }
+ Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false,
+ false, 0, 0);
+ width = comp.getPreferredSize().width;
+ // Get maximum width of column data
+ for (int r = 0; r < table.getRowCount(); r++) {
+ renderer = table.getCellRenderer(r, vColIndex);
+ comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex),
+ false, false, r, vColIndex);
+ width = Math.max(width, comp.getPreferredSize().width);
+ }
+ // Add margin
+ width += 2 * margin;
+ // Set the width
+ col.setPreferredWidth(width);
+ if (fixWidth) {
+ col.setMaxWidth(width);
+ col.setMinWidth(width);
+ }
+
+ }
+
+ private static Insets rightGap = new Insets(0, 0, 20, 0);
+
+ private JPanel wrapComponent(JComponent c) {
+ JPanel result = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = GridBagConstraints.NORTHWEST;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.9;
+ gbc.insets = rightGap;
+ result.add(c, gbc);
+ c.setBackground(SystemColor.text);
+ gbc.weightx = 0.9;
+ gbc.weighty = 0.9;
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 2;
+ gbc.fill = GridBagConstraints.BOTH;
+ JPanel filler = new JPanel();
+ filler.setBackground(SystemColor.text);
+ result.setBackground(SystemColor.text);
+ result.add(filler, gbc);
+ return result;
+ }
+
+ /**
+ * Update workflow explorer when current dataflow changes or closes.
+ *
+ */
+ public class FileManagerObserver implements Observer<FileManagerEvent> {
+
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
+ throws Exception {
+
+ if (message instanceof SetCurrentDataflowEvent) { // switched the
+ // current
+ // workflow
+
+ final Dataflow newWF = ((SetCurrentDataflowEvent) message).getDataflow(); // the
+ // newly
+ // switched
+ // to
+ // workflow
+ if (newWF != null) {
+ openedWorkflowsManager.getDataflowSelectionModel(newWF).addObserver(
+ workflowSelectionListener);
+ }
+ }
+ }
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow node
+ * is selected in the graph view.
+ */
+ private final class DataflowSelectionListener implements Observer<DataflowSelectionMessage> {
+
+ public void notify(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) throws Exception {
+
+ DataflowSelectionModel selectionModel = openedWorkflowsManager
+ .getDataflowSelectionModel(fileManager.getCurrentDataflow());
+
+ Set<Object> selection = selectionModel.getSelection();
+ if (selection.size() == 1) {
+ if (!selectSubject(selection.iterator().next())) {
+ lastSelectedReport = null;
+ table.clearSelection();
+ }
+
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewComponentFactory.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewComponentFactory.java
new file mode 100644
index 0000000..ff0b7d4
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewComponentFactory.java
@@ -0,0 +1,75 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.view;
+
+import java.util.List;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.explainer.VisitExplainer;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+/**
+ * @author alanrw
+ *
+ */
+public class ReportViewComponentFactory implements UIComponentFactorySPI {
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ReportManager reportManager;
+ private Workbench workbench;
+ private SelectionManager selectionManager;
+ private MenuManager menuManager;
+ private List<VisitExplainer> visitExplainers;
+
+ public UIComponentSPI getComponent() {
+ return new ReportViewComponent(editManager, fileManager, menuManager, reportManager,
+ workbench, selectionManager, visitExplainers);
+ }
+
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ public String getName() {
+ return "Reports";
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setVisitExplainers(List<VisitExplainer> visitExplainers) {
+ this.visitExplainers = visitExplainers;
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewConfigureAction.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewConfigureAction.java
new file mode 100644
index 0000000..a4575b0
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewConfigureAction.java
@@ -0,0 +1,45 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.view;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+/**
+ * @author alanrw
+ *
+ */
+public class ReportViewConfigureAction extends AbstractAction {
+
+ private Processor configuredProcessor = null;
+ private MenuManager menuManager;
+
+ public ReportViewConfigureAction() {
+
+ }
+
+ public void setConfiguredProcessor(Processor configuredProcessor, MenuManager menuManager) {
+ this.configuredProcessor = configuredProcessor;
+ this.menuManager = menuManager;
+ }
+
+ public ReportViewConfigureAction(Processor p) {
+ super();
+ this.configuredProcessor = p;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ Action action = WorkflowView.getConfigureAction(configuredProcessor, menuManager);
+ if (action != null) {
+ action.actionPerformed(e);
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewTableModel.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewTableModel.java
new file mode 100644
index 0000000..b3cc195
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ReportViewTableModel.java
@@ -0,0 +1,284 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.view;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.table.DefaultTableModel;
+
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workflowmodel.Condition;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.DataflowInputPort;
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import net.sf.taverna.t2.workflowmodel.Datalink;
+import net.sf.taverna.t2.workflowmodel.Merge;
+import net.sf.taverna.t2.workflowmodel.NamedWorkflowEntity;
+import net.sf.taverna.t2.workflowmodel.Port;
+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.ProcessorPort;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityInputPort;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityPort;
+
+
+/**
+ * @author alanrw
+ *
+ */
+public class ReportViewTableModel extends DefaultTableModel {
+
+ public static String ALL_REPORTS = "All";
+ public static String WARNINGS_AND_ERRORS = "Warnings and errors";
+ public static String JUST_ERRORS = "Only errors";
+
+ private ArrayList<VisitReport> reports;
+
+ private Dataflow dataflow;
+ private final ReportManager reportManager;
+
+ private static Comparator<VisitReport> descriptionComparator = new Comparator<VisitReport>() {
+ public int compare(VisitReport o1, VisitReport o2) {
+ int result = o1.getMessage().compareTo(o2.getMessage());
+ return result;
+ }
+ };
+
+ private static Comparator<VisitReport> severityComparator = new Comparator<VisitReport>() {
+ public int compare(VisitReport o1, VisitReport o2) {
+ Status o1Status = o1.getStatus();
+ Status o2Status = o2.getStatus();
+ if ((o1Status.equals(Status.SEVERE)) && (o2Status.equals(Status.SEVERE))) {
+ return descriptionComparator.compare(o1, o2);
+ }
+ if (o1Status.equals(Status.SEVERE)) {
+ return -1;
+ }
+ if (o2Status.equals(Status.SEVERE)) {
+ return 1;
+ }
+ if (o1Status.equals(Status.WARNING) && o2Status.equals(Status.WARNING)) {
+ return descriptionComparator.compare(o1, o2);
+ }
+ if (o1Status.equals(Status.WARNING)) {
+ return -1;
+ }
+ if (o2Status.equals(Status.WARNING)) {
+ return 1;
+ }
+ return descriptionComparator.compare(o1, o2);
+ }
+ };
+
+ private static Comparator<VisitReport> nameComparator = new Comparator<VisitReport>() {
+ public int compare(VisitReport o1, VisitReport o2) {
+ int nameComparison = getName(o1.getSubject()).compareTo(getName(o2.getSubject()));
+ if (nameComparison == 0) {
+ return severityComparator.compare(o1, o2);
+ } else {
+ return nameComparison;
+ }
+ }
+ };
+
+ private static Comparator<VisitReport> comparator = new Comparator<VisitReport>() {
+ public int compare(VisitReport o1, VisitReport o2) {
+ Object o1Subject = o1.getSubject();
+ Object o2Subject = o2.getSubject();
+
+ if (o1Subject == o2Subject) {
+ return severityComparator.compare(o1,o2);
+ }
+ if ((o1Subject instanceof Dataflow) && (o2.getSubject() instanceof Dataflow)) {
+ return (nameComparator.compare(o1,o2));
+ }
+ if (o1Subject instanceof Dataflow) {
+ return -1;
+ }
+ if (o2Subject instanceof Dataflow) {
+ return 1;
+ }
+ if ((o1Subject instanceof DataflowInputPort) && (o2Subject instanceof DataflowInputPort)) {
+ return (nameComparator.compare(o1,o2));
+ }
+ if (o1Subject instanceof DataflowInputPort) {
+ return -1;
+ }
+ if (o2Subject instanceof DataflowInputPort) {
+ return 1;
+ }
+ if ((o1Subject instanceof DataflowOutputPort) && (o2Subject instanceof DataflowOutputPort)) {
+ return (nameComparator.compare(o1,o2));
+ }
+ if (o1Subject instanceof DataflowOutputPort) {
+ return -1;
+ }
+ if (o2Subject instanceof DataflowOutputPort) {
+ return 1;
+ }
+ if ((o1Subject instanceof Processor) && (o2Subject instanceof Processor)) {
+ return (nameComparator.compare(o1,o2));
+ }
+ if (o1Subject instanceof Processor) {
+ return -1;
+ }
+ if (o2Subject instanceof Processor) {
+ return 1;
+ }
+ return -1;
+ }
+ };
+
+ public ReportViewTableModel(Dataflow df,
+ Map<Object, Set<VisitReport>> reportEntries,
+ String shownReports,
+ VisitReportProxySet ignoredReports, ReportManager reportManager) {
+ super(new String[] { "Severity", "Age", "Type",
+ "Name", "Description" }, 0);
+ this.dataflow = df;
+ this.reportManager = reportManager;
+ reports = new ArrayList();
+ if (reportEntries != null) {
+ for (Object o : reportEntries.keySet()) {
+ for (VisitReport vr : reportEntries.get(o)) {
+ if (!shownReports.equals(ReportViewComponent.ALL_INCLUDING_IGNORED) && (ignoredReports.contains(vr))){
+ continue;
+ }
+ Status status = vr.getStatus();
+ if (shownReports.equals(WARNINGS_AND_ERRORS) && status.equals(Status.OK)) {
+ continue;
+ }
+ if (shownReports.equals(JUST_ERRORS) && !status.equals(Status.SEVERE)) {
+ continue;
+ }
+ Object subject = vr.getSubject();
+ reports.add(vr);
+ }
+ }
+ }
+ Collections.sort(reports, comparator);
+ for (VisitReport vr : reports) {
+ this.addRow(new Object[] {
+ vr.getStatus(),
+ calculateTimeDifference(vr),
+ getType(vr.getSubject()),
+ getName(vr.getSubject()),
+ vr.getMessage() });
+ }
+ }
+
+ private String calculateTimeDifference(VisitReport vr) {
+ if (!vr.wasTimeConsuming()) {
+ return "-";
+ }
+ long time = vr.getCheckTime();
+ long dataflowTime = reportManager.getLastCheckedTime(this.dataflow);
+ long difference = dataflowTime - time;
+ if (difference < 1000) {
+ return "-";
+ }
+ difference /= 1000; // time in seconds
+ long seconds = difference % 60;
+ long minutes = difference / 60;
+
+ if (minutes != 0) {
+ return (Long.toString(minutes) + "m " + Long.toString(seconds) + "s");
+ } else {
+ return (Long.toString(seconds) + "s");
+ }
+ }
+ public Object getSubject(int rowIndex) {
+ return reports.get(rowIndex).getSubject();
+ }
+
+ public VisitReport getReport(int rowIndex) {
+ return reports.get(rowIndex);
+ }
+
+ public Class getColumnClass(int columnIndex) {
+ if (columnIndex == 0) {
+ return Status.class;
+ }
+ return String.class;
+ }
+
+ public boolean isCellEditable(int row, int column) {
+ return false;
+ }
+
+
+ private static String getType(Object o) {
+ if (o instanceof Dataflow) {
+ return "Workflow";
+ }
+ if (o instanceof DataflowInputPort) {
+ return "Workflow input port";
+ }
+ if (o instanceof DataflowOutputPort) {
+ return "Workflow output port";
+ }
+ if ((o instanceof Processor) || (o instanceof Activity)) {
+ return "Service";
+ }
+ if ((o instanceof ProcessorInputPort) || (o instanceof ActivityInputPort)) {
+ return "Service input port";
+ }
+ if ((o instanceof ProcessorOutputPort) || (o instanceof ActivityOutputPort)) {
+ return "Service output port";
+ }
+ if (o instanceof Datalink) {
+ return "Datalink";
+ }
+ if (o instanceof Condition) {
+ return "Control link";
+ }
+ if (o instanceof Merge) {
+ return "Merge";
+ }
+ return "?";
+ }
+
+ private static String getName(Object o) {
+ if (o instanceof NamedWorkflowEntity) {
+ return ((NamedWorkflowEntity) o).getLocalName();
+ }
+ if (o instanceof Port) {
+ String prefix = "";
+ if (o instanceof ProcessorPort) {
+ prefix = ((ProcessorPort) o).getProcessor().getLocalName();
+ }
+ if (!(prefix.length()==0)) {
+ prefix += " : ";
+ }
+ return (prefix + ((Port) o).getName());
+ }
+ if (o instanceof Activity) {
+ return "?";
+ }
+ if (o instanceof ActivityPort) {
+ return "?";
+ }
+ if (o instanceof Datalink) {
+ return "?";
+ }
+ if (o instanceof Condition) {
+ return "?";
+ }
+ if (o instanceof Merge) {
+ return "?";
+ }
+ return "?";
+ }
+
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/StatusRenderer.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/StatusRenderer.java
new file mode 100644
index 0000000..af42185
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/StatusRenderer.java
@@ -0,0 +1,46 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.view;
+
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import net.sf.taverna.t2.lang.ui.icons.Icons;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+
+/**
+ * @author alanrw
+ *
+ */
+public class StatusRenderer extends DefaultTableCellRenderer {
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ Component result = null;
+ if (value instanceof Status) {
+ result = chooseLabel((Status)value);
+ } else {
+ result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ }
+ return result;
+ }
+
+ private static JLabel okLabel = new JLabel(Icons.okIcon);
+ private static JLabel warningLabel = new JLabel(Icons.warningIcon);
+ private static JLabel severeLabel = new JLabel(Icons.severeIcon);
+
+ private static JLabel chooseLabel (Status status) {
+ if (status == Status.OK) {
+ return okLabel;
+ }
+ else if (status == Status.WARNING) {
+ return warningLabel;
+ } else if (status == Status.SEVERE) {
+ return severeLabel;
+ }
+ return null;
+ }
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateInProgressDialog.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateInProgressDialog.java
new file mode 100644
index 0000000..b2e2782
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateInProgressDialog.java
@@ -0,0 +1,92 @@
+
+/*******************************************************************************
+ * Copyright (C) 2009 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.report.view;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ * Dialog that is popped up while we are validating the workflow. This is just to let
+ * the user know that Taverna is doing something.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+public class ValidateInProgressDialog extends HelpEnabledDialog {
+
+
+ private boolean userCancelled = false;
+
+ public ValidateInProgressDialog() {
+
+ super(MainWindow.getMainWindow(), "Validating workflow", true, null);
+ setResizable(false);
+ setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(new EmptyBorder(10,10,10,10));
+
+ JPanel textPanel = new JPanel();
+ JLabel text = new JLabel(WorkbenchIcons.workingIcon);
+ text.setText("Validating workflow...");
+ text.setBorder(new EmptyBorder(10,0,10,0));
+ textPanel.add(text);
+ panel.add(textPanel, BorderLayout.CENTER);
+
+ /**
+ * Cancellation does not work
+
+ // Cancel button
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ userCancelled = true;
+ setVisible(false);
+ dispose();
+ }
+ });
+ JPanel cancelButtonPanel = new JPanel();
+ cancelButtonPanel.add(cancelButton);
+ panel.add(cancelButtonPanel, BorderLayout.SOUTH);
+*/
+ setContentPane(panel);
+ setPreferredSize(new Dimension(300, 100));
+
+ pack();
+ }
+
+ public boolean hasUserCancelled() {
+ return userCancelled;
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateObjectInProgressDialog.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateObjectInProgressDialog.java
new file mode 100644
index 0000000..37ac334
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateObjectInProgressDialog.java
@@ -0,0 +1,93 @@
+
+/*******************************************************************************
+ * Copyright (C) 2009 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.report.view;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Frame;
+
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ * Dialog that is popped up while we are validating the workflow. This is just to let
+ * the user know that Taverna is doing something.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+public class ValidateObjectInProgressDialog extends JDialog{
+
+
+ private boolean userCancelled = false;
+
+ public ValidateObjectInProgressDialog() {
+
+ super((Frame) null, "Validating service", true);
+ setLocationRelativeTo(null);
+ setResizable(false);
+ setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(new EmptyBorder(10,10,10,10));
+
+ JPanel textPanel = new JPanel();
+ JLabel text = new JLabel(WorkbenchIcons.workingIcon);
+ text.setText("Validating service...");
+ text.setBorder(new EmptyBorder(10,0,10,0));
+ textPanel.add(text);
+ panel.add(textPanel, BorderLayout.CENTER);
+
+ /**
+ * Cancellation does not work
+
+ // Cancel button
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ userCancelled = true;
+ setVisible(false);
+ dispose();
+ }
+ });
+ JPanel cancelButtonPanel = new JPanel();
+ cancelButtonPanel.add(cancelButton);
+ panel.add(cancelButtonPanel, BorderLayout.SOUTH);
+*/
+ setContentPane(panel);
+ setPreferredSize(new Dimension(300, 100));
+
+ pack();
+ }
+
+ public boolean hasUserCancelled() {
+ return userCancelled;
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateObjectSwingWorker.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateObjectSwingWorker.java
new file mode 100644
index 0000000..66069c3
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateObjectSwingWorker.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.report.view;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.SwingWorker;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workflowmodel.CompoundEdit;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.Edit;
+import net.sf.taverna.t2.workflowmodel.EditException;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+import org.apache.log4j.Logger;
+
+public class ValidateObjectSwingWorker extends SwingWorker<String, String>{
+
+ private static Logger logger = Logger.getLogger(ValidateObjectSwingWorker.class);
+
+ private Dataflow df;
+ private Processor p;
+
+ private final EditManager editManager;
+ private final ReportManager reportManager;
+
+ public ValidateObjectSwingWorker(Dataflow df, Processor p, EditManager editManager, ReportManager reportManager){
+ this.df = df;
+ this.p = p;
+ this.editManager = editManager;
+ this.reportManager = reportManager;
+ }
+
+ @Override
+ protected String doInBackground() throws Exception {
+ reportManager.updateObjectReport(df, p);
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ try {
+ if (ValidateSwingWorker.checkProcessorDisability(p, reportManager.getReports(df).get(p), editList, editManager, reportManager)) {
+ editManager.doDataflowEdit(df, new CompoundEdit(editList));
+ reportManager.updateObjectReport(df, p);
+ }
+ }
+ catch (EditException ex) {
+ logger.error("Enabled of disabled activity failed", ex);
+ }
+ return "done";
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateSwingWorker.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateSwingWorker.java
new file mode 100644
index 0000000..7765413
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/ValidateSwingWorker.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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.report.view;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.SwingWorker;
+
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workflowmodel.CompoundEdit;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.Edit;
+import net.sf.taverna.t2.workflowmodel.EditException;
+import net.sf.taverna.t2.workflowmodel.Processor;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+import net.sf.taverna.t2.workflowmodel.processor.activity.DisabledActivity;
+import net.sf.taverna.t2.workflowmodel.utils.Tools;
+
+import org.apache.log4j.Logger;
+
+public class ValidateSwingWorker extends SwingWorker<Dataflow, String>{
+
+ private static Logger logger = Logger.getLogger(ValidateSwingWorker.class);
+ private Dataflow dataflow;
+ private final boolean full;
+ private final boolean remember;
+ private final EditManager editManager;
+ private final ReportManager reportManager;
+
+ public ValidateSwingWorker(Dataflow dataflow, boolean full, boolean remember, EditManager editManager, ReportManager reportManager){
+ this.dataflow = dataflow;
+ this.full = full;
+ this.remember = remember;
+ this.editManager = editManager;
+ this.reportManager = reportManager;
+ }
+
+ @Override
+ protected Dataflow doInBackground() throws Exception {
+ reportManager.updateReport(dataflow, full, remember);
+ checkDisabledActivities(dataflow, editManager, reportManager);
+ return dataflow;
+ }
+
+ public static boolean checkProcessorDisability(Processor processor, Set<VisitReport> reports, List<Edit<?>> editList, EditManager editManager, ReportManager reportManager) {
+ boolean isAlreadyDisabled = false;
+ DisabledActivity disabledActivity = null;
+ List<? extends Activity<?>> activityList = processor.getActivityList();
+ for (Activity a : activityList) {
+ if (a instanceof DisabledActivity) {
+ isAlreadyDisabled = true;
+ disabledActivity = (DisabledActivity) a;
+ break;
+ }
+ }
+ if (isAlreadyDisabled) {
+ int severeCount = 0;
+ for (VisitReport vr : reports) {
+ if (vr.getStatus().equals(VisitReport.Status.SEVERE)) {
+ severeCount++;
+ }
+ }
+ if ((severeCount <= 1) && disabledActivity.configurationWouldWork()) {
+ logger.info(processor.getLocalName() + " is no longer disabled");
+ Edit e = Tools.getEnableDisabledActivityEdit(processor, disabledActivity, editManager.getEdits());
+ if (e != null) {
+ editList.add(e);
+ return true;
+ }
+
+ }
+ }
+ return false;
+ }
+
+ private static void checkDisabledActivities(
+ Dataflow d, EditManager editManager, ReportManager reportManager) {
+ Set<Object> reVisit = new HashSet<Object>();
+ List<Edit<?>> editList = new ArrayList<Edit<?>>();
+ Map<Object, Set<VisitReport>> reportsEntry = reportManager.getReports(d);
+ for (Object o : reportsEntry.keySet()) {
+ if (o instanceof Processor) {
+ if (checkProcessorDisability((Processor) o, reportsEntry.get(o), editList, editManager, reportManager)) {
+ reVisit.add((Processor)o);
+ }
+ }
+ }
+ if (!editList.isEmpty()) {
+ CompoundEdit ce = new CompoundEdit(editList);
+ try {
+ editManager.doDataflowEdit(d, ce);
+ reportManager.updateObjectSetReport(d, reVisit);
+ } catch (EditException e) {
+ logger.error(e);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/VisitReportProxy.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/VisitReportProxy.java
new file mode 100644
index 0000000..eb932f5
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/VisitReportProxy.java
@@ -0,0 +1,42 @@
+package net.sf.taverna.t2.workbench.report.view;
+
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+
+public class VisitReportProxy {
+
+ Status status;
+ int subjectHashCode;
+ VisitKind kind;
+ String message;
+
+ public VisitReportProxy(VisitReport vr) {
+ this.status = vr.getStatus();
+ this.subjectHashCode = vr.getSubject().hashCode();
+ this.kind = vr.getKind();
+ this.message = vr.getMessage();
+ }
+
+ public boolean equals(Object o) {
+ if ((o == null) || !((o instanceof VisitReportProxy) || (o instanceof VisitReport))) {
+ return false;
+ }
+ if (o instanceof VisitReport) {
+ return this.equals(new VisitReportProxy((VisitReport) o));
+ }
+ VisitReportProxy vrp = (VisitReportProxy) o;
+ return (vrp.status.equals(this.status) &&
+ (vrp.subjectHashCode == this.subjectHashCode) &&
+ (vrp.kind.equals(this.kind)) &&
+ (vrp.message.equals(this.message)));
+ }
+
+ public int hashCode() {
+ return ((status.hashCode() >> 2) +
+ (subjectHashCode >> 2) +
+ (kind.hashCode() >> 2) +
+ (message.hashCode() >> 2));
+ }
+ }
+
diff --git a/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/VisitReportProxySet.java b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/VisitReportProxySet.java
new file mode 100644
index 0000000..957b0ba
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/java/net/sf/taverna/t2/workbench/report/view/VisitReportProxySet.java
@@ -0,0 +1,36 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.view;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+
+public class VisitReportProxySet {
+
+ private Set<VisitReportProxy> elements;
+
+ public VisitReportProxySet() {
+ elements = new HashSet<VisitReportProxy>();
+ }
+
+ public boolean add(VisitReport newElement) {
+ VisitReportProxy proxy = new VisitReportProxy(newElement);
+ return elements.add(proxy);
+ }
+
+ public boolean remove(VisitReport removedElement) {
+ VisitReportProxy proxy = new VisitReportProxy(removedElement);
+ return elements.remove(proxy);
+ }
+
+ public boolean contains(VisitReport vr) {
+ VisitReportProxy proxy = new VisitReportProxy(vr);
+ return elements.contains(proxy);
+ }
+
+}
diff --git a/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..d91b987
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.report.view.ReportOnObjectContextualMenuAction
\ No newline at end of file
diff --git a/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..639aad7
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.report.config.ui.ReportManagerConfigurationUIFactory
diff --git a/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..037bb92
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.report.view.ReportViewComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-report-view/src/main/resources/META-INF/spring/report-view-context-osgi.xml b/taverna-workbench-report-view/src/main/resources/META-INF/spring/report-view-context-osgi.xml
new file mode 100644
index 0000000..13a36ef
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/resources/META-INF/spring/report-view-context-osgi.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ReportManagerConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <service ref="ReportViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <service ref="ReportOnObjectContextualMenuAction" auto-export="interfaces" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="reportManager" interface="net.sf.taverna.t2.workbench.report.ReportManager" />
+ <reference id="workbench" interface="net.sf.taverna.t2.workbench.ui.Workbench" cardinality="0..1" />
+ <reference id="reportManagerConfiguration" interface="net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration" />
+
+ <list id="visitExplainers" interface="net.sf.taverna.t2.workbench.report.explainer.VisitExplainer" cardinality="0..N"/>
+
+</beans:beans>
diff --git a/taverna-workbench-report-view/src/main/resources/META-INF/spring/report-view-context.xml b/taverna-workbench-report-view/src/main/resources/META-INF/spring/report-view-context.xml
new file mode 100644
index 0000000..37651a5
--- /dev/null
+++ b/taverna-workbench-report-view/src/main/resources/META-INF/spring/report-view-context.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ReportManagerConfigurationUIFactory" class="net.sf.taverna.t2.workbench.report.config.ui.ReportManagerConfigurationUIFactory">
+ <property name="reportManagerConfiguration" ref="reportManagerConfiguration" />
+ </bean>
+
+
+ <bean id="ReportViewComponentFactory" class="net.sf.taverna.t2.workbench.report.view.ReportViewComponentFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="workbench" ref="workbench" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="visitExplainers" ref="visitExplainers" />
+ </bean>
+
+ <bean id="ReportOnObjectContextualMenuAction" class="net.sf.taverna.t2.workbench.report.view.ReportOnObjectContextualMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="workbench" ref="workbench" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-results-view/pom.xml b/taverna-workbench-results-view/pom.xml
new file mode 100644
index 0000000..063e6cc
--- /dev/null
+++ b/taverna-workbench-results-view/pom.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>results-view</artifactId>
+ <packaging>bundle</packaging>
+ <name>Results and outputs from a workflow run</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Private-Package>org.apache.poi.*;-split-package:=merge-first</Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>results</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>baclava</artifactId>
+ <version>${t2.baclava.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>renderers-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>graph-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-database-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-report-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-run-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.databundle</groupId>
+ <artifactId>databundle</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>provenanceconnector</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>uk.org.mygrid.resources.mimeutil</groupId>
+ <artifactId>mime-util</artifactId>
+ <version>${mimeutil.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.poi</groupId>
+ <artifactId>poi</artifactId>
+ <version>${poi.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationTreeModel.java
new file mode 100644
index 0000000..543d4b8
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationTreeModel.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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;
+
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import uk.org.taverna.platform.report.Invocation;
+import uk.org.taverna.platform.report.StatusReport;
+
+/**
+ * TreeModel for displaying invocations.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class InvocationTreeModel extends DefaultTreeModel implements Updatable {
+ private final StatusReport<?, ?> report;
+ private Map<String, InvocationTreeNode> nodes = new HashMap<>();
+
+ /**
+ * Constructs a new TreeModel for displaying invocations.
+ *
+ * @param report
+ * the report to display
+ */
+ public InvocationTreeModel(StatusReport<?, ?> report) {
+ super(new DefaultMutableTreeNode());
+ this.report = report;
+ updateTree(report.getInvocations());
+ }
+
+ public InvocationTreeNode getFirstInvocationNode() {
+ return nodes.get(report.getInvocations().first().getId());
+ }
+
+ private void updateTree(SortedSet<Invocation> invocations) {
+ for (Invocation invocation : invocations) {
+ String invocationId = invocation.getId();
+ if (!nodes.containsKey(invocationId))
+ nodes.put(invocationId, createNode(invocation));
+ }
+ }
+
+ private InvocationTreeNode createNode(Invocation invocation) {
+ InvocationTreeNode node = new InvocationTreeNode(invocation);
+ Invocation parent = invocation.getParent();
+ if (parent != null) {
+ Invocation grandParent = parent.getParent();
+ if (grandParent != null) {
+ Invocation greatGrandParent = grandParent.getParent();
+ if (greatGrandParent != null) {
+ String invocationId = greatGrandParent.getId();
+ if (!nodes.containsKey(invocationId))
+ nodes.put(invocationId, createNode(greatGrandParent));
+ MutableTreeNode parentNode = nodes.get(invocationId);
+ insertNodeInto(node, parentNode, parentNode.getChildCount());
+ return node;
+ }
+ }
+ }
+
+ MutableTreeNode parentNode = ((MutableTreeNode) getRoot());
+ insertNodeInto(node, parentNode, parentNode.getChildCount());
+ return node;
+ }
+
+ @Override
+ public void update() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ updateTree(report.getInvocations());
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationTreeNode.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationTreeNode.java
new file mode 100644
index 0000000..47ac8a9
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationTreeNode.java
@@ -0,0 +1,100 @@
+package net.sf.taverna.t2.workbench.views.results;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+import uk.org.taverna.platform.report.Invocation;
+
+@SuppressWarnings("serial")
+public class InvocationTreeNode extends DefaultMutableTreeNode {
+ public static enum ErrorState {
+ NO_ERRORS,
+ INPUT_ERRORS,
+ OUTPUT_ERRORS;
+ }
+
+ private ErrorState errorState = ErrorState.NO_ERRORS;
+ private final Invocation invocation;
+
+ public InvocationTreeNode(Invocation invocation) {
+ super(invocation);
+ this.invocation = invocation;
+ }
+
+ public InvocationTreeNode getParentInvocationTreeNode() {
+ TreeNode parentNode = getParent();
+ if (parentNode instanceof InvocationTreeNode)
+ return (InvocationTreeNode) parentNode;
+ return null;
+ }
+
+ public Invocation getInvocation() {
+ return invocation;
+ }
+
+ public String getIndex() {
+ StringBuilder sb = new StringBuilder();
+ InvocationTreeNode parentNode = getParentInvocationTreeNode();
+ if (parentNode != null) {
+ String index = parentNode.getIndex();
+ if (!index.isEmpty()) {
+ sb.append(index);
+ sb.append(".");
+ }
+ }
+ int[] index = invocation.getIndex();
+ if (index.length == 0)
+ sb.append(1);
+ else {
+ String sep = "";
+ for (int i = 0; i < index.length; i++) {
+ sb.append(sep).append(index[i]+1);
+ sep = ".";
+ }
+ }
+ return sb.toString();
+ }
+
+ public Invocation getParentInvocation() {
+ InvocationTreeNode parentIterationTreeNode = getParentInvocationTreeNode();
+ if (parentIterationTreeNode != null)
+ return parentIterationTreeNode.getInvocation();
+ return null;
+ }
+
+ @Override
+ public String toString(){
+ boolean isNested = getChildCount() > 0;
+ StringBuilder sb = new StringBuilder();
+ if (isNested)
+ sb.append("Nested invocation ");
+ else
+ sb.append("Invocation ");
+ sb.append(getIndex());
+ return sb.toString();
+ }
+
+ public void setErrorState(ErrorState errorState) {
+ this.errorState = errorState;
+ notifyParentErrorState();
+ }
+
+ private void notifyParentErrorState() {
+ InvocationTreeNode parentIterationTreeNode = getParentInvocationTreeNode();
+ if (parentIterationTreeNode == null)
+ return;
+ if (parentIterationTreeNode.getErrorState().compareTo(errorState) < 0)
+ parentIterationTreeNode.setErrorState(errorState);
+ }
+
+ @Override
+ public void setParent(MutableTreeNode newParent) {
+ super.setParent(newParent);
+ notifyParentErrorState();
+ }
+
+ public ErrorState getErrorState() {
+ return errorState;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationView.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationView.java
new file mode 100644
index 0000000..4863e15
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/InvocationView.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.inputIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.outputIcon;
+
+import java.awt.Component;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+
+import javax.swing.JTabbedPane;
+
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+import net.sf.taverna.t2.workbench.views.results.workflow.PortResultsViewTab;
+import uk.org.taverna.platform.report.Invocation;
+import uk.org.taverna.scufl2.api.common.Ported;
+import uk.org.taverna.scufl2.api.port.InputPort;
+import uk.org.taverna.scufl2.api.port.Port;
+
+/**
+ * View displaying input and output values of an invocation.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class InvocationView extends JTabbedPane implements Updatable {
+ private final RendererRegistry rendererRegistry;
+ private final List<SaveIndividualResultSPI> saveIndividualActions;
+ private Map<String, PortResultsViewTab> inputPortTabMap = new HashMap<>(),
+ outputPortTabMap = new HashMap<>();
+ private final Invocation invocation;
+
+ public InvocationView(Invocation invocation,
+ RendererRegistry rendererRegistry,
+ List<SaveIndividualResultSPI> saveIndividualActions) {
+ this.invocation = invocation;
+ this.rendererRegistry = rendererRegistry;
+ this.saveIndividualActions = saveIndividualActions;
+ init();
+ }
+
+ public void init() {
+ SortedMap<String, Path> inputs = invocation.getInputs();
+ SortedMap<String, Path> outputs = invocation.getOutputs();
+ Ported ported = invocation.getReport().getSubject();
+
+ // Input ports
+ for (Port port : ported.getInputPorts()) {
+ String name = port.getName();
+ Path value = inputs.get(name);
+ /*
+ * Create a tab containing a tree view of per-port results and a
+ * rendering component for displaying individual results
+ */
+ PortResultsViewTab resultTab = new PortResultsViewTab(port, value,
+ rendererRegistry, saveIndividualActions);
+
+ inputPortTabMap.put(name, resultTab);
+
+ addTab(name, inputIcon, resultTab, "Input port " + name);
+ }
+
+ // Output ports
+ for (Port port : ported.getOutputPorts()) {
+ String name = port.getName();
+ Path value = outputs.get(name);
+ /*
+ * Create a tab containing a tree view of per-port results and a
+ * rendering component for displaying individual results
+ */
+ PortResultsViewTab resultTab = new PortResultsViewTab(port, value,
+ rendererRegistry, saveIndividualActions);
+ outputPortTabMap.put(name, resultTab);
+
+ addTab(name, outputIcon, resultTab, "Output port " + name);
+ }
+ // Select the first output port tab
+ if (!outputs.isEmpty())
+ setSelectedIndex(inputs.size());
+ else if (!inputs.isEmpty())
+ setSelectedIndex(0);
+
+ revalidate();
+ }
+
+ public void selectPortTab(Port port) {
+ PortResultsViewTab tab;
+ if (port instanceof InputPort)
+ tab = inputPortTabMap.get(port.getName());
+ else
+ tab = outputPortTabMap.get(port.getName());
+ if (tab != null)
+ setSelectedComponent(tab);
+ }
+
+ public Port getSelectedPort() {
+ Component selectedComponent = getSelectedComponent();
+ if (selectedComponent instanceof PortResultsViewTab) {
+ PortResultsViewTab portView = (PortResultsViewTab) selectedComponent;
+ return portView.getPort();
+ }
+ return null;
+ }
+
+ @Override
+ public void update() {
+ for (PortResultsViewTab portResultsViewTab : inputPortTabMap.values())
+ portResultsViewTab.update();
+ for (PortResultsViewTab portResultsViewTab : outputPortTabMap.values())
+ portResultsViewTab.update();
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/ReportView.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/ReportView.java
new file mode 100644
index 0000000..0586c02
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/ReportView.java
@@ -0,0 +1,420 @@
+/*******************************************************************************
+ * 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;
+
+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.Font.BOLD;
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.lang.Math.round;
+import static javax.swing.JSplitPane.HORIZONTAL_SPLIT;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+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.saveAllIcon;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+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.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.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTree;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+import uk.org.taverna.platform.report.Invocation;
+import uk.org.taverna.platform.report.StatusReport;
+import uk.org.taverna.platform.report.WorkflowReport;
+import uk.org.taverna.scufl2.api.port.Port;
+
+/**
+ * View showing the results of iterations from a workflow or processor report.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ReportView extends JPanel implements Updatable {
+ private final RendererRegistry rendererRegistry;
+ private final List<SaveAllResultsSPI> saveActions;
+ private final List<SaveIndividualResultSPI> saveIndividualActions;
+ private int invocationCount = 0;
+ private CardLayout cardLayout = new CardLayout();
+ private Map<Invocation, InvocationView> invocationComponents = new HashMap<>();
+ private InvocationTreeModel invocationTreeModel;
+ private StatusReport<?, ?> report;
+ private Invocation selectedInvocation;
+ private JPanel invocationPanel;
+ private JButton saveButton;
+ private Port selectedPort;
+
+ public ReportView(StatusReport<?, ?> report,
+ RendererRegistry rendererRegistry,
+ List<SaveAllResultsSPI> saveActions,
+ List<SaveIndividualResultSPI> saveIndividualActions) {
+ super(new BorderLayout());
+ this.report = report;
+ this.rendererRegistry = rendererRegistry;
+ this.saveActions = saveActions;
+ this.saveIndividualActions = saveIndividualActions;
+ init();
+ }
+
+ private void init() {
+ removeAll();
+
+ SortedSet<Invocation> invocations = report.getInvocations();
+ invocationCount = invocations.size();
+
+ if (invocationCount == 0) {
+ JLabel noDataMessage = new JLabel("No data available", JLabel.CENTER);
+ Font font = noDataMessage.getFont();
+ if (font != null) {
+ font = font.deriveFont(round(font.getSize() * 1.5)).deriveFont(BOLD);
+ noDataMessage.setFont(font);
+ }
+ add(noDataMessage, CENTER);
+ return;
+ }
+
+ JPanel saveButtonsPanel = new JPanel(new BorderLayout());
+ if (report instanceof WorkflowReport)
+ saveButton = new JButton(new SaveAllAction("Save all values", this));
+ else
+ saveButton = new JButton(new SaveAllAction("Save invocation values", this));
+ saveButtonsPanel.add(saveButton, EAST);
+ add(saveButtonsPanel, NORTH);
+
+ invocationPanel = new JPanel();
+ invocationPanel.setLayout(cardLayout);
+ invocationPanel.add(new JPanel(), "BLANK");
+
+ if (invocationCount == 1) {
+ add(invocationPanel, CENTER);
+ showInvocation(invocations.first());
+ return;
+ }
+
+ invocationTreeModel = new InvocationTreeModel(report);
+ JTree invocationTree = new JTree(invocationTreeModel);
+ invocationTree.setExpandsSelectedPaths(true);
+ invocationTree.setRootVisible(false);
+ invocationTree.setShowsRootHandles(true);
+ invocationTree.getSelectionModel().setSelectionMode(
+ SINGLE_TREE_SELECTION);
+ invocationTree.addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ Object selectedComponent = e.getPath().getLastPathComponent();
+ if (selectedComponent instanceof InvocationTreeNode) {
+ InvocationTreeNode selectedNode = (InvocationTreeNode) selectedComponent;
+ if (selectedNode.isLeaf())
+ showInvocation(selectedNode.getInvocation());
+ else
+ showInvocation(null);
+ }
+ }
+ });
+ invocationTree.setCellRenderer(new DefaultTreeCellRenderer() {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value, boolean selected, boolean expanded,
+ boolean leaf, int row, boolean hasFocus) {
+ Component renderer = super.getTreeCellRendererComponent(tree,
+ value, selected, expanded, leaf, row, hasFocus);
+ if (renderer instanceof JLabel) {
+ JLabel label = (JLabel) renderer;
+ label.setIcon(null);
+ }
+ return renderer;
+ }
+ });
+
+ JScrollPane jScrollPane = new JScrollPane(invocationTree,
+ VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ jScrollPane.setMinimumSize(new Dimension(150, 0));
+
+ JSplitPane splitPane = new JSplitPane(HORIZONTAL_SPLIT);
+ splitPane.setLeftComponent(jScrollPane);
+ splitPane.setRightComponent(invocationPanel);
+
+ add(splitPane, CENTER);
+ invocationTree.setSelectionPath(new TreePath(invocationTreeModel
+ .getFirstInvocationNode().getPath()));
+ }
+
+ public void selectPort(Port port) {
+ InvocationView invocationView = invocationComponents.get(selectedInvocation);
+ if (invocationView != null)
+ invocationView.selectPortTab(port);
+ }
+
+ @Override
+ public void update() {
+ SortedSet<Invocation> invocations = report.getInvocations();
+ if (invocationCount < 2) {
+ if (invocations.size() != invocationCount)
+ init();
+ else if (invocationCount == 1) {
+ if (selectedInvocation != null)
+ invocationComponents.get(selectedInvocation).update();
+ }
+ } else {
+ if (invocations.size() != invocationCount) {
+ // update invocations tree
+ invocationCount = invocations.size();
+ invocationTreeModel.update();
+ // int selectedIndex = invocationList.getSelectedIndex();
+ // invocationList.setListData(invocations.toArray(new Invocation[invocationCount]));
+ // invocationList.setSelectedIndex(selectedIndex);
+ }
+ if (selectedInvocation != null)
+ invocationComponents.get(selectedInvocation).update();
+ }
+ }
+
+ private void showInvocation(Invocation invocation) {
+ if (selectedInvocation != null) {
+ InvocationView invocationView = invocationComponents.get(selectedInvocation);
+ selectedPort = invocationView.getSelectedPort();
+ }
+ if (invocation != null) {
+ if (!invocationComponents.containsKey(invocation)) {
+ InvocationView invocationView = new InvocationView(invocation, rendererRegistry,
+ saveIndividualActions);
+ invocationComponents.put(invocation, invocationView);
+ invocationPanel.add(invocationView, invocation.getId());
+ }
+ if (selectedPort != null)
+ invocationComponents.get(invocation).selectPortTab(selectedPort);
+ cardLayout.show(invocationPanel, invocation.getId());
+ saveButton.setEnabled(true);
+ } else {
+ saveButton.setEnabled(false);
+ cardLayout.show(invocationPanel, "BLANK");
+ }
+ selectedInvocation = invocation;
+ }
+
+ private class SaveAllAction extends AbstractAction {
+ public SaveAllAction(String name, ReportView resultViewComponent) {
+ super(name);
+ putValue(SMALL_ICON, saveAllIcon);
+ }
+
+ private static final String TITLE = "Data saver";
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ 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 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, Path> checkReferences = new HashMap<>();
+ final Map<String, Path> chosenReferences = new HashMap<>();
+ final Set<Action> actionSet = new HashSet<>();
+
+ 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));
+ }
+ };
+
+ SortedMap<String, Path> inputPorts = selectedInvocation.getInputs();
+ SortedMap<String, Path> outputPorts = selectedInvocation.getOutputs();
+
+ JPanel portsPanel = new JPanel();
+ portsPanel.setLayout(new GridBagLayout());
+ if (!inputPorts.isEmpty()) {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = WEST;
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(5, 10, 5, 10);
+ portsPanel.add(new JLabel("Workflow inputs:"), gbc);
+
+ TreeMap<String, JCheckBox> sortedBoxes = new TreeMap<>();
+ for (Entry<String, Path> entry : inputPorts.entrySet()) {
+ String portName = entry.getKey();
+ Path value = entry.getValue();
+ if (value != null) {
+ JCheckBox checkBox = new JCheckBox(portName);
+ checkBox.setSelected(!outputPorts.containsKey(portName));
+ checkBox.addItemListener(listener);
+ inputChecks.put(portName, checkBox);
+ sortedBoxes.put(portName, checkBox);
+ checkReferences.put(checkBox, value);
+ }
+ }
+ 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 (!outputPorts.isEmpty()) {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ gbc.anchor = WEST;
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(5, 10, 5, 10);
+ portsPanel.add(new JLabel("Workflow outputs:"), gbc);
+ TreeMap<String, JCheckBox> sortedBoxes = new TreeMap<>();
+ for (Entry<String, Path> entry : outputPorts.entrySet()) {
+ String portName = entry.getKey();
+ Path value = entry.getValue();
+ if (value != null) {
+ JCheckBox checkBox = new JCheckBox(portName);
+ checkBox.setSelected(true);
+
+ checkReferences.put(checkBox, value);
+ 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);
+ }
+ 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);
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/ResultsComponent.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/ResultsComponent.java
new file mode 100644
index 0000000..34e016a
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/ResultsComponent.java
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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;
+
+import java.awt.CardLayout;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.platform.report.ActivityReport;
+import uk.org.taverna.platform.report.ProcessorReport;
+import uk.org.taverna.platform.report.WorkflowReport;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.port.Port;
+
+/**
+ * Component for displaying the input and output values of workflow and processor invocations.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ResultsComponent extends JPanel implements Updatable {
+ private static final Logger logger = Logger.getLogger(ResultsComponent.class);
+
+ private final RunService runService;
+ private final SelectionManager selectionManager;
+ private final RendererRegistry rendererRegistry;
+ private final List<SaveAllResultsSPI> saveAllResultsSPIs;
+ private final List<SaveIndividualResultSPI> saveIndividualResultSPIs;
+
+ private CardLayout cardLayout = new CardLayout();
+ private Updatable updatableComponent;
+ private Map<String, ReportView> workflowResults = new HashMap<>();
+ private Map<String, Map<Processor, ReportView>> processorResults = new HashMap<>();
+ private SelectionManagerObserver selectionManagerObserver = new SelectionManagerObserver();
+ private String workflowRun;
+
+ public ResultsComponent(RunService runService, SelectionManager selectionManager,
+ RendererRegistry rendererRegistry, List<SaveAllResultsSPI> saveAllResultsSPIs,
+ List<SaveIndividualResultSPI> saveIndividualResultSPIs) {
+ this.runService = runService;
+ this.selectionManager = selectionManager;
+ this.rendererRegistry = rendererRegistry;
+ this.saveAllResultsSPIs = saveAllResultsSPIs;
+ this.saveIndividualResultSPIs = saveIndividualResultSPIs;
+
+ setLayout(cardLayout);
+
+ selectionManager.addObserver(selectionManagerObserver);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ selectionManager.removeObserver(selectionManagerObserver);
+ }
+
+ @Override
+ public void update() {
+ if (updatableComponent != null)
+ updatableComponent.update();
+ }
+
+ public void setWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ if (workflowRun == null)
+ return;
+ this.workflowRun = workflowRun;
+
+ DataflowSelectionModel selectionModel = selectionManager
+ .getWorkflowRunSelectionModel(workflowRun);
+ Set<Object> selectionSet = selectionModel.getSelection();
+ if (selectionSet.size() == 1) {
+ Object selection = selectionSet.iterator().next();
+ if (selection instanceof Processor) {
+ showProcessorResults((Processor) selection);
+ return;
+ }
+ }
+
+ showWorkflowResults();
+ }
+
+ public void addWorkflowRun(String workflowRun) throws InvalidRunIdException {
+ WorkflowReport workflowReport = runService.getWorkflowReport(workflowRun);
+ ReportView reportView = new ReportView(workflowReport, rendererRegistry,
+ saveAllResultsSPIs, saveIndividualResultSPIs);
+ add(reportView, workflowRun);
+ workflowResults.put(workflowRun, reportView);
+ DataflowSelectionModel selectionModel = selectionManager
+ .getWorkflowRunSelectionModel(workflowRun);
+ selectionModel.addObserver(new DataflowSelectionObserver());
+ }
+
+ public void removeWorkflowRun(String workflowRun) {
+ ReportView removedWorkflowResults = workflowResults.remove(workflowRun);
+ if (removedWorkflowResults != null)
+ remove(removedWorkflowResults);
+ Map<Processor, ReportView> removedProcessorResults = processorResults.remove(workflowRun);
+ if (removedProcessorResults != null)
+ for (ReportView reportView: removedProcessorResults.values())
+ remove(reportView);
+ }
+
+ private void showWorkflowResults() throws InvalidRunIdException {
+ if (!workflowResults.containsKey(workflowRun))
+ addWorkflowRun(workflowRun);
+ updatableComponent = workflowResults.get(workflowRun);
+ cardLayout.show(this, workflowRun);
+ update();
+ }
+
+ private void showProcessorResults(Processor processor) throws InvalidRunIdException {
+ if (!processorResults.containsKey(workflowRun))
+ processorResults.put(workflowRun, new HashMap<Processor, ReportView>());
+ Map<Processor, ReportView> components = processorResults.get(workflowRun);
+ if (!components.containsKey(processor)) {
+ WorkflowReport workflowReport = runService.getWorkflowReport(workflowRun);
+ ProcessorReport processorReport = findProcessorReport(workflowReport, processor);
+ ReportView reportView = new ReportView(processorReport, rendererRegistry,
+ saveAllResultsSPIs, saveIndividualResultSPIs);
+ components.put(processor, reportView);
+ add(reportView, String.valueOf(reportView.hashCode()));
+ }
+ updatableComponent = components.get(processor);
+ cardLayout.show(this, String.valueOf(updatableComponent.hashCode()));
+ update();
+ }
+
+ private ProcessorReport findProcessorReport(WorkflowReport workflowReport,
+ Processor processor) {
+ URI workflowIdentifier = workflowReport.getSubject().getIdentifier();
+ if (processor.getParent().getIdentifier().equals(workflowIdentifier)) {
+ for (ProcessorReport processorReport : workflowReport
+ .getProcessorReports())
+ if (processorReport.getSubject().getName()
+ .equals(processor.getName()))
+ return processorReport;
+ return null;
+ }
+
+ for (ProcessorReport processorReport : workflowReport
+ .getProcessorReports())
+ for (ActivityReport activityReport : processorReport
+ .getActivityReports()) {
+ WorkflowReport nestedWorkflowReport = activityReport
+ .getNestedWorkflowReport();
+ if (nestedWorkflowReport != null) {
+ ProcessorReport report = findProcessorReport(
+ nestedWorkflowReport, processor);
+ if (report != null)
+ return report;
+ }
+ }
+ return null;
+ }
+
+ private class SelectionManagerObserver extends SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ try {
+ if (message instanceof WorkflowRunSelectionEvent)
+ setWorkflowRun(((WorkflowRunSelectionEvent) message)
+ .getSelectedWorkflowRun());
+ } catch (InvalidRunIdException e) {
+ logger.warn("Invalid workflow run", e);
+ }
+ }
+ }
+
+ private final class DataflowSelectionObserver implements Observer<DataflowSelectionMessage> {
+ @Override
+ public void notify(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) throws Exception {
+ if (message.getType() != DataflowSelectionMessage.Type.ADDED)
+ return;
+
+ Object element = message.getElement();
+ if (element instanceof Processor) {
+ showProcessorResults((Processor) element);
+ return;
+ }
+
+ showWorkflowResults();
+
+ if (element instanceof Port) {
+ Port port = (Port) element;
+ if (updatableComponent instanceof ReportView) {
+ ReportView reportView = (ReportView) updatableComponent;
+ reportView.selectPort(port);
+ }
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/SimpleFilteredTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/SimpleFilteredTreeModel.java
new file mode 100644
index 0000000..b03a03e
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/SimpleFilteredTreeModel.java
@@ -0,0 +1,68 @@
+package net.sf.taverna.t2.workbench.views.results;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+@SuppressWarnings("serial")
+public abstract class SimpleFilteredTreeModel extends DefaultTreeModel {
+ public DefaultTreeModel delegate;
+
+ public SimpleFilteredTreeModel(DefaultTreeModel delegate) {
+ super((DefaultMutableTreeNode) delegate.getRoot());
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Object getChild(Object parent, int index) {
+ int count = -1;
+ for (int i = 0; i < delegate.getChildCount(parent); i++) {
+ final Object child = delegate.getChild(parent, i);
+ if (isShown(child))
+ if (++count == index)
+ return child;
+ }
+ return null;
+ }
+
+ protected int getFilteredIndexOfChild(DefaultMutableTreeNode parent, Object child) {
+ int count = -1;
+ for (int i = 0; i < delegate.getChildCount(parent); i++) {
+ final Object c = delegate.getChild(parent, i);
+ if (isShown(c)) {
+ count++;
+ if (c.equals(child))
+ return count;
+ }
+ if (c.equals(child))
+ return -1;
+ }
+ return -1;
+ }
+
+ @Override
+ public int getIndexOfChild(Object parent, Object child) {
+ return delegate.getIndexOfChild(parent, child);
+ }
+
+ @Override
+ public int getChildCount(Object parent) {
+ int count = 0;
+ for (int i = 0; i < delegate.getChildCount(parent); i++) {
+ final Object child = delegate.getChild(parent, i);
+ if (isShown(child))
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public boolean isLeaf(Object node) {
+ if (node == null)
+ return true;
+ if (delegate == null)
+ return true;
+ return delegate.isLeaf(node);
+ }
+
+ public abstract boolean isShown(Object o);
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/FilteredIterationTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/FilteredIterationTreeModel.java
new file mode 100644
index 0000000..fac18a5
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/FilteredIterationTreeModel.java
@@ -0,0 +1,91 @@
+
+package net.sf.taverna.t2.workbench.views.results.processor;
+
+import java.util.Enumeration;
+
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.workbench.views.results.SimpleFilteredTreeModel;
+import net.sf.taverna.t2.workbench.views.results.processor.IterationTreeNode.ErrorState;
+
+@SuppressWarnings("serial")
+public class FilteredIterationTreeModel extends SimpleFilteredTreeModel {
+ public enum FilterType {
+ ALL {
+ @Override
+ public String toString() {
+ return "View all";
+ }
+ },
+ RESULTS {
+ @Override
+ public String toString() {
+ return "View results";
+ }
+ },
+ ERRORS {
+ @Override
+ public String toString() {
+ return "View errors";
+ }
+ },
+ SKIPPED {
+ @Override
+ public String toString() {
+ return "View skipped";
+ }
+ };
+ }
+
+ private FilterType filter;
+
+ public FilteredIterationTreeModel(DefaultTreeModel delegate) {
+ super(delegate);
+ this.filter = FilterType.ALL;
+ }
+
+ public void setFilter(FilterType filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean isShown(Object o) {
+ if (!(o instanceof IterationTreeNode))
+ return false;
+ IterationTreeNode node = (IterationTreeNode) o;
+ switch (filter) {
+ case ALL:
+ return true;
+ case RESULTS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e
+ .hasMoreElements();) {
+ IterationTreeNode subNode = (IterationTreeNode) e.nextElement();
+ if (subNode.isLeaf()
+ && subNode.getErrorState().equals(ErrorState.NO_ERRORS)) {
+ return true;
+ }
+ }
+ return false;
+ case ERRORS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e
+ .hasMoreElements();) {
+ IterationTreeNode subNode = (IterationTreeNode) e.nextElement();
+ if (subNode.isLeaf()
+ && subNode.getErrorState() == ErrorState.OUTPUT_ERRORS)
+ return true;
+ }
+ return false;
+ case SKIPPED:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e
+ .hasMoreElements();) {
+ IterationTreeNode subNode = (IterationTreeNode) e.nextElement();
+ if (subNode.isLeaf()
+ && subNode.getErrorState() == ErrorState.INPUT_ERRORS)
+ return true;
+ }
+ return false;
+ default:
+ return true;
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/FilteredProcessorValueTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/FilteredProcessorValueTreeModel.java
new file mode 100644
index 0000000..12d49f4
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/FilteredProcessorValueTreeModel.java
@@ -0,0 +1,70 @@
+package net.sf.taverna.t2.workbench.views.results.processor;
+
+import java.util.Enumeration;
+
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.workbench.views.results.SimpleFilteredTreeModel;
+import uk.org.taverna.databundle.DataBundles;
+
+@SuppressWarnings("serial")
+public class FilteredProcessorValueTreeModel extends SimpleFilteredTreeModel {
+ public enum FilterType {
+ ALL {
+ @Override
+ public String toString() {
+ return "view values";
+ }
+ },
+ RESULTS {
+ @Override
+ public String toString() {
+ return "view results";
+ }
+ },
+ ERRORS {
+ @Override
+ public String toString() {
+ return "view errors";
+ }
+ };
+ }
+
+ private FilterType filter;
+
+ public FilteredProcessorValueTreeModel(DefaultTreeModel delegate) {
+ super(delegate);
+ this.filter = FilterType.ALL;
+ }
+
+ public void setFilter(FilterType filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean isShown(Object o) {
+ if (!(o instanceof ProcessorResultTreeNode))
+ return false;
+ ProcessorResultTreeNode node = (ProcessorResultTreeNode) o;
+ if (node.getReference() == null)
+ // root of the model
+ return true;
+ switch (filter) {
+ case RESULTS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e
+ .hasMoreElements();) {
+ ProcessorResultTreeNode subNode = (ProcessorResultTreeNode) e
+ .nextElement();
+ if ((subNode.getReference() != null)
+ && !DataBundles.isError(subNode.getReference())) {
+ return true;
+ }
+ }
+ return false;
+ case ERRORS:
+ return DataBundles.isError(node.getReference());
+ default:
+ return true;
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IntermediateValuesInProgressDialog.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IntermediateValuesInProgressDialog.java
new file mode 100644
index 0000000..3111211
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IntermediateValuesInProgressDialog.java
@@ -0,0 +1,89 @@
+
+/*******************************************************************************
+ * Copyright (C) 2009 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.SOUTH;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+@SuppressWarnings("serial")
+public class IntermediateValuesInProgressDialog extends HelpEnabledDialog {
+ /**
+ * Cancellation does not work; disable the button for it.
+ */
+ private static final boolean CANCELLATION_ENABLED = false;
+
+ private boolean userCancelled = false;
+
+ public IntermediateValuesInProgressDialog() {
+ super(getMainWindow(), "Fetching intermediate values", true, null);
+ setResizable(false);
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ JPanel textPanel = new JPanel();
+ JLabel text = new JLabel(workingIcon);
+ text.setText("Fetching intermediate values...");
+ text.setBorder(new EmptyBorder(10, 0, 10, 0));
+ textPanel.add(text);
+ panel.add(textPanel, CENTER);
+
+ // Cancel button
+ if (CANCELLATION_ENABLED) {
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ userCancelled = true;
+ setVisible(false);
+ dispose();
+ }
+ });
+ JPanel cancelButtonPanel = new JPanel();
+ cancelButtonPanel.add(cancelButton);
+ panel.add(cancelButtonPanel, SOUTH);
+ }
+ setContentPane(panel);
+ setPreferredSize(new Dimension(300, 100));
+
+ pack();
+ }
+
+ public boolean hasUserCancelled() {
+ return userCancelled;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IntermediateValuesSwingWorker.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IntermediateValuesSwingWorker.java
new file mode 100644
index 0000000..6cc056b
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IntermediateValuesSwingWorker.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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 javax.swing.SwingWorker;
+
+public class IntermediateValuesSwingWorker extends
+ SwingWorker<ProcessorResultsComponent, String> {
+ private ProcessorResultsComponent component;
+ private Exception exception = null;
+
+ public IntermediateValuesSwingWorker(ProcessorResultsComponent component) {
+ this.component = component;
+ }
+
+ @Override
+ protected ProcessorResultsComponent doInBackground() throws Exception {
+ try {
+ component.populateEnactmentsMaps();
+ } catch (Exception e) {
+ this.exception = e;
+ }
+ return component;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IterationTreeNode.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IterationTreeNode.java
new file mode 100644
index 0000000..b815403
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/IterationTreeNode.java
@@ -0,0 +1,99 @@
+package net.sf.taverna.t2.workbench.views.results.processor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+public class IterationTreeNode extends DefaultMutableTreeNode {
+ private static final long serialVersionUID = -7522904828725470216L;
+
+ public static enum ErrorState {
+ NO_ERRORS, INPUT_ERRORS, OUTPUT_ERRORS;
+ }
+
+ private ErrorState errorState = ErrorState.NO_ERRORS;
+ private List<Integer> iteration;
+
+ public IterationTreeNode() {
+ this.setIteration(new ArrayList<Integer>());
+ }
+
+ public IterationTreeNode(List<Integer> iteration) {
+ this.setIteration(iteration);
+ }
+
+ public void setIteration(List<Integer> iteration) {
+ this.iteration = iteration;
+ }
+
+ public List<Integer> getIteration() {
+ return iteration;
+ }
+
+ public IterationTreeNode getParentIterationTreeNode() {
+ TreeNode parentNode = getParent();
+ if (parentNode instanceof IterationTreeNode)
+ return (IterationTreeNode) parentNode;
+ return null;
+ }
+
+ public List<Integer> getParentIteration() {
+ IterationTreeNode parentIterationTreeNode = getParentIterationTreeNode();
+ if (parentIterationTreeNode != null)
+ return parentIterationTreeNode.getIteration();
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ boolean isNested = getChildCount() > 0;
+ StringBuilder sb = new StringBuilder();
+ if (!getIteration().isEmpty() || isNested) {
+ // Iteration 3.1.3
+ if (isNested)
+ sb.append("Nested iteration ");
+ else {
+ if (getUserObject() == null)
+ sb.append("Waiting for iteration ");
+ else
+ sb.append("Iteration ");
+ }
+ for (Integer index : getIteration()) {
+ sb.append(index + 1);
+ sb.append(".");
+ }
+ if (!getIteration().isEmpty())
+ // Remove last .
+ sb.delete(sb.length() - 1, sb.length());
+ } else
+ sb.append("Invocation");
+
+ return sb.toString();
+ }
+
+ public void setErrorState(ErrorState errorState) {
+ this.errorState = errorState;
+ notifyParentErrorState();
+ }
+
+ private void notifyParentErrorState() {
+ IterationTreeNode parentIterationTreeNode = getParentIterationTreeNode();
+ if (parentIterationTreeNode == null)
+ return;
+ if (parentIterationTreeNode.getErrorState().compareTo(errorState) < 0)
+ parentIterationTreeNode.setErrorState(errorState);
+ }
+
+ @Override
+ public void setParent(MutableTreeNode newParent) {
+ super.setParent(newParent);
+ notifyParentErrorState();
+ }
+
+ public ErrorState getErrorState() {
+ return errorState;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorEnactmentsTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorEnactmentsTreeModel.java
new file mode 100644
index 0000000..e6ee152
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorEnactmentsTreeModel.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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 java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.provenance.lineageservice.utils.ProcessorEnactment;
+import net.sf.taverna.t2.workbench.views.results.processor.IterationTreeNode.ErrorState;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Model of the tree that contains enactments of a processor. Clicking on the
+ * nodes of this tree triggers showing of results for this processor for this
+ * particular enactment (invocation).
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ */
+@SuppressWarnings("serial")
+public class ProcessorEnactmentsTreeModel extends DefaultTreeModel {
+ private static Logger logger = Logger
+ .getLogger(ProcessorEnactmentsTreeModel.class);
+
+ private Map<ProcessorEnactment, ProcessorEnactmentsTreeNode> processorEnactments = new ConcurrentHashMap<>();
+ private Map<String, ProcessorEnactment> processorEnactmentsById = new ConcurrentHashMap<>();
+ private final Set<ProcessorEnactment> enactmentsWithErrorOutputs;
+ private final Set<ProcessorEnactment> enactmentsWithErrorInputs;
+
+ public ProcessorEnactmentsTreeModel(
+ Set<ProcessorEnactment> enactmentsGotSoFar,
+ Set<ProcessorEnactment> enactmentsWithErrorInputs,
+ Set<ProcessorEnactment> enactmentsWithErrorOutputs) {
+ super(new DefaultMutableTreeNode("Invocations of processor"));
+ this.enactmentsWithErrorInputs = enactmentsWithErrorInputs;
+ this.enactmentsWithErrorOutputs = enactmentsWithErrorOutputs;
+ update(enactmentsGotSoFar);
+ }
+
+ public void update(Set<ProcessorEnactment> newEnactments) {
+ // First populate the ID map, so we can find parents later
+ for (ProcessorEnactment processorEnactment : newEnactments)
+ processorEnactmentsById.put(
+ processorEnactment.getProcessEnactmentId(),
+ processorEnactment);
+ for (ProcessorEnactment processorEnactment : newEnactments)
+ addProcessorEnactment(processorEnactment);
+ }
+
+ public ProcessorEnactmentsTreeNode addProcessorEnactment(
+ ProcessorEnactment processorEnactment) {
+ ProcessorEnactmentsTreeNode treeNode = processorEnactments
+ .get(processorEnactment);
+ boolean containsErrorsInOutputs = enactmentsWithErrorOutputs
+ .contains(processorEnactment);
+ boolean containsErrorsInInputs = enactmentsWithErrorInputs
+ .contains(processorEnactment);
+ if (treeNode != null) {
+ if (treeNode.getProcessorEnactment() != processorEnactment)
+ // Update it
+ treeNode.setProcessorEnactment(processorEnactment);
+ if (containsErrorsInInputs)
+ treeNode.setErrorState(ErrorState.INPUT_ERRORS);
+ else if (containsErrorsInOutputs)
+ treeNode.setErrorState(ErrorState.OUTPUT_ERRORS);
+ return treeNode;
+ }
+
+ List<Integer> iteration = iterationToIntegerList(processorEnactment
+ .getIteration());
+ String parentId = processorEnactment.getParentProcessorEnactmentId();
+ ProcessorEnactment parentProc = null;
+ List<Integer> parentIteration = null;
+ DefaultMutableTreeNode parentNode = getRoot();
+ if (parentId != null) {
+ parentProc = processorEnactmentsById.get(parentId);
+ if (parentProc == null)
+ logger.error("Can't find parent " + parentId);
+ else {
+ // Use treenode parent instead
+ parentNode = addProcessorEnactment(parentProc);
+ parentIteration = ((ProcessorEnactmentsTreeNode) parentNode)
+ .getIteration();
+ }
+ }
+
+ DefaultMutableTreeNode nodeToReplace = getNodeFor(parentNode,
+ iteration, parentIteration);
+ DefaultMutableTreeNode iterationParent = (DefaultMutableTreeNode) nodeToReplace
+ .getParent();
+ int position;
+ if (iterationParent == null) {
+ // nodeToReplace is the root, insert as first child
+ iterationParent = getRoot();
+ position = 0;
+ } else {
+ if (nodeToReplace.getChildCount() > 0)
+ logger.error("Replacing node " + nodeToReplace
+ + " with unexpected " + nodeToReplace.getChildCount()
+ + " children");
+ position = iterationParent.getIndex(nodeToReplace);
+ removeNodeFromParent(nodeToReplace);
+ }
+
+ ProcessorEnactmentsTreeNode newNode = new ProcessorEnactmentsTreeNode(
+ processorEnactment, parentIteration);
+ if (containsErrorsInInputs)
+ newNode.setErrorState(ErrorState.INPUT_ERRORS);
+ else if (containsErrorsInOutputs)
+ newNode.setErrorState(ErrorState.OUTPUT_ERRORS);
+
+ insertNodeInto(newNode, iterationParent, position);
+ processorEnactments.put(processorEnactment, newNode);
+ return newNode;
+ }
+
+ public static List<Integer> iterationToIntegerList(String iteration) {
+ // Strip []
+ iteration = iteration.substring(1, iteration.length()-1);
+ String[] iterationSlit = iteration.split(",");
+ List<Integer> integers = new ArrayList<Integer>();
+ for (String index : iterationSlit) {
+ if (index.isEmpty())
+ continue;
+ integers.add(Integer.valueOf(index));
+ }
+ return integers;
+ }
+
+ @Override
+ public DefaultMutableTreeNode getRoot() {
+ return (DefaultMutableTreeNode) super.getRoot();
+ }
+
+ private DefaultMutableTreeNode getNodeFor(DefaultMutableTreeNode node,
+ List<Integer> remainingIteration, List<Integer> parentIteration) {
+ if (remainingIteration.isEmpty())
+ return node;
+ if (parentIteration == null)
+ parentIteration = new ArrayList<>();
+ int childPos = remainingIteration.get(0);
+ int needChildren = childPos + 1;
+ while (node.getChildCount() < needChildren) {
+ List<Integer> childIteration = new ArrayList<>(parentIteration);
+ childIteration.add(node.getChildCount());
+ DefaultMutableTreeNode newChild = new IterationTreeNode(
+ childIteration);
+ insertNodeInto(newChild, node, node.getChildCount());
+ }
+ DefaultMutableTreeNode child = (DefaultMutableTreeNode) node
+ .getChildAt(childPos);
+
+ List<Integer> childIteration = new ArrayList<>(parentIteration);
+ childIteration.add(childPos);
+ // Iteration 3.1.3
+// if (iteration.size() > 1) {
+ // Recurse next iteration levels
+ return getNodeFor(child,
+ remainingIteration.subList(1, remainingIteration.size()),
+ childIteration);
+// }
+// return child;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorEnactmentsTreeNode.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorEnactmentsTreeNode.java
new file mode 100644
index 0000000..37e4344
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorEnactmentsTreeNode.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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 net.sf.taverna.t2.workbench.views.results.processor.ProcessorEnactmentsTreeModel.iterationToIntegerList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.provenance.lineageservice.utils.ProcessorEnactment;
+
+/**
+ * Node in a processor enactments tree. Contains a particular enactment of the
+ * processor.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class ProcessorEnactmentsTreeNode extends IterationTreeNode {
+ private List<Integer> myIteration = new ArrayList<>();
+ private List<Integer> parentIteration = new ArrayList<>();
+
+ public ProcessorEnactmentsTreeNode(ProcessorEnactment processorEnactment,
+ List<Integer> parentIteration) {
+ super();
+ this.parentIteration = parentIteration;
+ setProcessorEnactment(processorEnactment);
+ }
+
+ protected void updateFullIteration() {
+ List<Integer> fullIteration = new ArrayList<>();
+ if (getParentIteration() != null)
+ fullIteration.addAll(getParentIteration());
+ fullIteration.addAll(getMyIteration());
+ setIteration(fullIteration);
+ }
+
+ public final List<Integer> getMyIteration() {
+ return myIteration;
+ }
+
+ @Override
+ public final List<Integer> getParentIteration() {
+ return parentIteration;
+ }
+
+ public final ProcessorEnactment getProcessorEnactment() {
+ return (ProcessorEnactment) getUserObject();
+ }
+
+ public final void setMyIteration(List<Integer> myIteration) {
+ this.myIteration = myIteration;
+ updateFullIteration();
+ }
+
+ public final void setParentIteration(List<Integer> parentIteration) {
+ this.parentIteration = parentIteration;
+ updateFullIteration();
+ }
+
+ public final void setProcessorEnactment(
+ ProcessorEnactment processorEnactment) {
+ setUserObject(processorEnactment);
+ setMyIteration(iterationToIntegerList(processorEnactment.getIteration()));
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorPortResultsViewTab.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorPortResultsViewTab.java
new file mode 100644
index 0000000..0006d81
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorPortResultsViewTab.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * 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.NORTH;
+import static java.awt.BorderLayout.WEST;
+import static net.sf.taverna.t2.workbench.views.results.processor.ProcessorResultTreeNode.ProcessorResultTreeNodeState.RESULT_REFERENCE;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.views.results.processor.FilteredProcessorValueTreeModel.FilterType;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+/**
+ * A tab containing result tree for an input or output port of a processor
+ * and a panel with rendered result
+ * of the currently selected node in the tree.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+public class ProcessorPortResultsViewTab extends JPanel{
+ /** Rendered result component */
+ private RenderedProcessorResultComponent renderedResultComponent;
+ private boolean isOutputPortTab = true;
+ private JTree resultsTree;
+ private String portName;
+ /** Panel holding the results tree*/
+ private JPanel treePanel;
+ /**
+ * Split pane holding the result tree panel on the left and rendering
+ * component on the right
+ */
+ private JSplitPane splitPanel;
+ private JComboBox<FilterType> filterChoiceBox;
+ private FilteredProcessorValueTreeModel filteredTreeModel;
+
+ private final RendererRegistry rendererRegistry;
+ private final List<SaveIndividualResultSPI> saveActions;
+
+ public ProcessorPortResultsViewTab(String portName, RendererRegistry rendererRegistry, List<SaveIndividualResultSPI> saveActions) {
+ this.portName = portName;
+ this.rendererRegistry = rendererRegistry;
+ this.saveActions = saveActions;
+ initComponents();
+ }
+
+ private void initComponents() {
+ setLayout(new BorderLayout());
+
+ /*
+ * Split pane containing a tree with results from an output port for the
+ * selected enactment and rendered result component for rendering an
+ * individual result currently selected from the results tree.
+ */
+ splitPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+
+ /*
+ * Results tree (containing T2References to all individual results for
+ * this port)
+ */
+ //resultsTree = new JTree(); // initially tree is empty - will be set when user selects a particular enactment
+
+ // Component for rendering individual results
+ renderedResultComponent = new RenderedProcessorResultComponent(
+ rendererRegistry, saveActions);
+
+ treePanel = new JPanel();
+ treePanel.setLayout(new BorderLayout());
+ treePanel.add(new JLabel("Click to view values"), NORTH);
+ treePanel.add(new JScrollPane(resultsTree), CENTER);
+ splitPanel.setTopComponent(treePanel);
+ splitPanel.setBottomComponent(renderedResultComponent);
+ splitPanel.setDividerLocation(400);
+
+ // Add all to main panel
+ add(splitPanel, CENTER);
+ }
+
+ public void setIsOutputPortTab(boolean isOutputPortTab) {
+ this.isOutputPortTab = isOutputPortTab;
+ }
+
+ public boolean getIsOutputPortTab() {
+ return this.isOutputPortTab;
+ }
+
+ private List<TreePath> expandedPaths = new ArrayList<TreePath>();
+ private TreePath selectionPath = null;
+
+ private void rememberPaths() {
+ expandedPaths.clear();
+ for (Enumeration<?> e = resultsTree
+ .getExpandedDescendants(new TreePath(filteredTreeModel
+ .getRoot())); (e != null) && e.hasMoreElements();)
+ expandedPaths.add((TreePath) e.nextElement());
+ selectionPath = resultsTree.getSelectionPath();
+ }
+
+ private void reinstatePaths() {
+ for (TreePath path : expandedPaths)
+ if (filteredTreeModel.isShown((DefaultMutableTreeNode) path
+ .getLastPathComponent()))
+ resultsTree.expandPath(path);
+ if (selectionPath != null) {
+ if (filteredTreeModel
+ .isShown((DefaultMutableTreeNode) selectionPath
+ .getLastPathComponent()))
+ resultsTree.setSelectionPath(selectionPath);
+ else {
+ resultsTree.clearSelection();
+ renderedResultComponent.clearResult();
+ }
+ }
+ }
+
+ private void updateTree() {
+ filteredTreeModel = (FilteredProcessorValueTreeModel) resultsTree
+ .getModel();
+ filteredTreeModel.setFilter((FilterType) filterChoiceBox.getSelectedItem());
+ rememberPaths();
+ filteredTreeModel.reload();
+ resultsTree.setModel(filteredTreeModel);
+ reinstatePaths();
+ }
+
+ public void setResultsTree(JTree tree) {
+ resultsTree = tree;
+
+ treePanel.removeAll();
+ treePanel = new JPanel(new BorderLayout());
+ if (tree == null) {
+ splitPanel.setVisible(false);
+ revalidate();
+ return;
+ }
+
+ TreeModel treeModel = tree.getModel();
+ int childCount = treeModel.getChildCount(treeModel.getRoot());
+ if (childCount == 0) {
+ splitPanel.setVisible(false);
+ revalidate();
+ return;
+ }
+
+ splitPanel.setTopComponent(treePanel);
+ splitPanel.setBottomComponent(renderedResultComponent);
+ splitPanel.setVisible(true);
+
+ JPanel treeSubPanel = new JPanel();
+ treeSubPanel.setLayout(new BorderLayout());
+ treeSubPanel.add(new JLabel("Click in tree to"), WEST);
+ filterChoiceBox = new JComboBox<>(new FilterType[] { FilterType.ALL,
+ FilterType.RESULTS, FilterType.ERRORS });
+ filterChoiceBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateTree();
+ }
+ });
+ treeSubPanel.add(filterChoiceBox);
+ treePanel.add(treeSubPanel, NORTH);
+ treePanel.add(new JScrollPane(resultsTree), CENTER);
+ splitPanel.setTopComponent(treePanel);
+
+ if (childCount == 1) {
+ Object child = treeModel.getChild(treeModel.getRoot(), 0);
+ ProcessorResultTreeNode node = (ProcessorResultTreeNode) child;
+ if (node.getState() == RESULT_REFERENCE)
+ if (treeModel.getChildCount(child) == 0) {
+ TreePath path = new TreePath(new Object[] {
+ treeModel.getRoot(), child });
+ tree.setSelectionPath(path);
+ splitPanel.setTopComponent(new JPanel());
+ splitPanel.setDividerLocation(0);
+ }
+ }
+
+ revalidate();
+ }
+
+ public void setPortName(String portName) {
+ this.portName = portName;
+ }
+
+ public String getPortName() {
+ return portName;
+ }
+
+ public RenderedProcessorResultComponent getRenderedResultComponent() {
+ return renderedResultComponent;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultCellRenderer.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultCellRenderer.java
new file mode 100644
index 0000000..79f685f
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultCellRenderer.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * 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.Color.RED;
+import static net.sf.taverna.t2.workbench.views.results.processor.ProcessorResultTreeNode.ProcessorResultTreeNodeState.RESULT_LIST;
+import static net.sf.taverna.t2.workbench.views.results.processor.ProcessorResultTreeNode.ProcessorResultTreeNodeState.RESULT_REFERENCE;
+import static net.sf.taverna.t2.workbench.views.results.processor.ProcessorResultTreeNode.ProcessorResultTreeNodeState.RESULT_TOP;
+import static uk.org.taverna.databundle.DataBundles.isError;
+
+import java.awt.Component;
+import java.nio.file.Path;
+
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreeNode;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public class ProcessorResultCellRenderer extends DefaultTreeCellRenderer {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ Component result = super.getTreeCellRendererComponent(tree, value,
+ selected, expanded, leaf, row, hasFocus);
+ if (value instanceof ProcessorResultTreeNode) {
+ ProcessorResultTreeNode value2 = (ProcessorResultTreeNode) value;
+ String text = "";
+ ProcessorResultTreeNode parent = (ProcessorResultTreeNode) value2
+ .getParent();
+ if (value2.getState() == RESULT_LIST) {
+ if (value2.getChildCount() == 0)
+ text = "Empty list";
+ else {
+ text = "List";
+ if (parent.getState() != RESULT_TOP)
+ text += " " + (parent.getIndex(value2) + 1);
+ text += " with " + value2.getValueCount() + " value";
+ if (value2.getValueCount() != 1)
+ text += "s";
+ if (value2.getSublistCount() > 0)
+ text += " in " + value2.getSublistCount() + " sublists";
+ }
+ } else if (value2.getState() == RESULT_REFERENCE)
+ text = "Value " + (parent.getIndex(value2) + 1);
+
+ ((JLabel) result).setText(text);
+ if (containsError(value2))
+ result.setForeground(RED);
+ }
+ return result;
+ }
+
+ private static boolean containsError(TreeNode node) {
+ boolean result = false;
+ if (node instanceof ProcessorResultTreeNode) {
+ ProcessorResultTreeNode rtn = (ProcessorResultTreeNode) node;
+ Path reference = rtn.getReference();
+ if (reference != null && isError(reference))
+ result = true;
+ }
+ int childCount = node.getChildCount();
+ for (int i = 0; (i < childCount) && !result; i++)
+ result = containsError(node.getChildAt(i));
+ return result;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultTreeNode.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultTreeNode.java
new file mode 100644
index 0000000..5290365
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultTreeNode.java
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * 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 java.nio.file.Path;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A node in the processor result tree - can be a single data item, a list of data items or
+ * tree root.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ProcessorResultTreeNode extends DefaultMutableTreeNode {
+ public enum ProcessorResultTreeNodeState {
+ RESULT_TOP, RESULT_LIST, RESULT_REFERENCE
+ };
+
+ @SuppressWarnings("unused")
+ private static final Logger logger = Logger
+ .getLogger(ProcessorResultTreeNode.class);
+
+ private ProcessorResultTreeNodeState state;
+ private Path reference; // reference to actual data if this node is a data node
+ private int listSize; // number of element if this node is a list
+
+ // Create root node
+ public ProcessorResultTreeNode() {
+ this.state = ProcessorResultTreeNodeState.RESULT_TOP;
+ }
+
+ // Create data node
+ public ProcessorResultTreeNode(Path reference) {
+ this.reference = reference;
+ this.state = ProcessorResultTreeNodeState.RESULT_REFERENCE;
+ }
+
+ // Create list node
+ public ProcessorResultTreeNode(int listSize, Path reference) {
+ this.listSize = listSize;
+ this.reference = reference;
+ this.state = ProcessorResultTreeNodeState.RESULT_LIST;
+ }
+
+ public ProcessorResultTreeNodeState getState() {
+ return state;
+ }
+
+ public void setState(ProcessorResultTreeNodeState state) {
+ this.state = state;
+ }
+
+ public Path getReference() {
+ return reference;
+ }
+
+ public void setReference(Path reference) {
+ this.reference = reference;
+ }
+
+ @Override
+ public String toString() {
+ if (state.equals(ProcessorResultTreeNodeState.RESULT_TOP))
+ return "Results:";
+ if (state.equals(ProcessorResultTreeNodeState.RESULT_LIST)) {
+ if (getChildCount() == 0)
+ return "Empty list";
+ return "List with " + listSize + " values";
+ }
+ return reference.toString();
+ }
+
+ public boolean isState(ProcessorResultTreeNodeState state) {
+ return this.state.equals(state);
+ }
+
+ public int getValueCount() {
+ int result = 0;
+ if (isState(ProcessorResultTreeNodeState.RESULT_REFERENCE))
+ result = 1;
+ else if (isState(ProcessorResultTreeNodeState.RESULT_LIST)) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ ProcessorResultTreeNode child = (ProcessorResultTreeNode) getChildAt(i);
+ result += child.getValueCount();
+ }
+ }
+ return result;
+ }
+
+ public int getSublistCount() {
+ int result = 0;
+ if (isState(ProcessorResultTreeNodeState.RESULT_LIST)) {
+ int childCount = this.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ ProcessorResultTreeNode child = (ProcessorResultTreeNode) getChildAt(i);
+ if (child.isState(ProcessorResultTreeNodeState.RESULT_LIST))
+ result++;
+ }
+ }
+ return result;
+ }
+
+// public Object getAsObject() {
+// if (reference != null) {
+// Identified identified = referenceService
+// .resolveIdentifier(reference, null, null);
+// if (identified instanceof ErrorDocument) {
+// ErrorDocument errorDocument = (ErrorDocument) identified;
+// return errorDocument.getMessage();
+// }
+// }
+// if (isState(ProcessorResultTreeNodeState.RESULT_TOP)) {
+// if (getChildCount() == 0) {
+// return null;
+// }
+// else {
+// return ((ProcessorResultTreeNode) getChildAt(0)).getAsObject();
+// }
+// }
+// if (isState(ProcessorResultTreeNodeState.RESULT_LIST)) {
+// List<Object> result = new ArrayList<Object>();
+// for (int i = 0; i < getChildCount(); i++) {
+// ProcessorResultTreeNode child = (ProcessorResultTreeNode) getChildAt(i);
+// result.add (child.getAsObject());
+// }
+// return result;
+// }
+// if (reference == null) {
+// return null;
+// }
+//// if (context.getReferenceService() == null) {
+//// return null;
+//// }
+// try {
+// Object result = referenceService.renderIdentifier(reference, Object.class, null);
+// return result;
+// }
+// catch (Exception e) {
+// // Not good to catch exception but
+// return null;
+// }
+// }
+
+ public boolean isState(ResultTreeNodeState state) {
+ return this.state.equals(state);
+ }
+
+ public void setListSize(int listSize) {
+ this.listSize = listSize;
+ }
+
+ public int getListSize() {
+ return listSize;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultsComponent.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultsComponent.java
new file mode 100644
index 0000000..33f92d6
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultsComponent.java
@@ -0,0 +1,1004 @@
+/*******************************************************************************
+ * 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);
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultsTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultsTreeModel.java
new file mode 100644
index 0000000..c823287
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/ProcessorResultsTreeModel.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (C) 2009 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 java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+
+import javax.swing.tree.DefaultTreeModel;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * Tree model for the results of a processor (workflow's intermediate results).
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ProcessorResultsTreeModel extends DefaultTreeModel {
+ private static final Logger logger = Logger
+ .getLogger(ProcessorResultsTreeModel.class);
+ // Tree root
+ private ProcessorResultTreeNode root;
+
+ public ProcessorResultsTreeModel(Path path) {
+ super(new ProcessorResultTreeNode());
+ root = (ProcessorResultTreeNode) getRoot();
+ createTree(path, root);
+ }
+
+ private void createTree(Path path, ProcessorResultTreeNode parentNode){
+ // If reference contains a list of data references
+ if (DataBundles.isList(path)) {
+ try {
+ List<Path> list = DataBundles.getList(path);
+ ProcessorResultTreeNode listNode = new ProcessorResultTreeNode(
+ list.size(), path); // list node
+ parentNode.add(listNode);
+ for (Path ref : list)
+ createTree(ref, listNode);
+ } catch (IOException e) {
+ logger.error("Could not resolve list " + path + ", was run with in-memory storage?");
+ }
+ } else // reference to single data or an error
+ insertDataNode(path, parentNode);
+ }
+
+ private void insertDataNode(Path path, ProcessorResultTreeNode parent) {
+ ProcessorResultTreeNode dataNode = new ProcessorResultTreeNode(path); // data node
+ parent.add(dataNode);
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/RenderedProcessorResultComponent.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/RenderedProcessorResultComponent.java
new file mode 100644
index 0000000..0a50b21
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/processor/RenderedProcessorResultComponent.java
@@ -0,0 +1,575 @@
+/*******************************************************************************
+ * 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.NORTH;
+import static java.awt.event.ItemEvent.SELECTED;
+import static javax.swing.BoxLayout.LINE_AXIS;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.results.ResultsUtils.getMimeTypes;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.refreshIcon;
+import static net.sf.taverna.t2.workbench.views.results.processor.ProcessorResultTreeNode.ProcessorResultTreeNodeState.RESULT_REFERENCE;
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BoxLayout;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTree;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import javax.swing.text.JTextComponent;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererException;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.renderers.RendererUtils;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.databundle.ErrorDocument;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import eu.medsea.mimeutil.MimeType;
+
+/**
+ * Creates a component that renders an individual result from an output port.
+ * The component can render the result according to the renderers existing for
+ * the output port's MIME type or display an error document.
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class RenderedProcessorResultComponent extends JPanel {
+ private static final String WRAP_TEXT = "Wrap text";
+ private static final String ERROR_DOCUMENT = "Error Document";
+ private static Logger logger = Logger
+ .getLogger(RenderedProcessorResultComponent.class);
+
+ /** Panel containing rendered result */
+ private JPanel renderedResultPanel;
+ /** Combo box containing possible result types */
+ private JComboBox<String> renderersComboBox;
+ /**
+ * Button to refresh (re-render) the result, especially needed for large
+ * results that are not rendered or are partially rendered and the user
+ * wished to re-render them
+ */
+ private JButton refreshButton;
+ /**
+ * Preferred result type renderers (the ones recognised to be able to handle
+ * the result's MIME type)
+ */
+ private List<Renderer> recognisedRenderersForMimeType;
+ /**
+ * All other result type renderers (the ones not recognised to be able to
+ * handle the result's MIME type) In case user wants to use them.
+ */
+ private List<Renderer> otherRenderers;
+ /** Renderers' registry */
+ private final RendererRegistry rendererRegistry;
+ /**
+ * List of all MIME strings from all available renderers to be used for
+ * renderersComboBox. Those that come from recognisedRenderersForMimeType
+ * are the preferred ones. Those from otherRenderers will be greyed-out in
+ * the combobox list but could still be used.
+ */
+ private String[] mimeList;
+ /**
+ * List of all available renderers but ordered to match the corresponding
+ * MIME type strings in mimeList: first the preferred renderers from
+ * recognisedRenderersForMimeType then the ones from otherRenderers.
+ */
+ private ArrayList<Renderer> rendererList;
+ /**
+ * Remember the MIME type of the last used renderer; use "text/plain" by
+ * default until user changes it - then use that one for all result items of
+ * the port (in case result contains a list). "text/plain" will always be
+ * added to the mimeList.
+ */
+ private String lastUsedMIMEtype = "text/plain";
+ // text renderer will always be available
+ /** If result is "text/plain" - provide possibility to wrap wide text */
+ private JCheckBox wrapTextCheckBox;
+ /** Reference to the object being displayed (contained in the tree node) */
+ private Path path;
+ /** Currently selected node from the ResultViewComponent, if any. */
+ private ProcessorResultTreeNode node = null;
+ /**
+ * In case the node can be rendered as "text/plain", map the hash code of
+ * the node to the wrap text check box selection value for that node (that
+ * remembers if user wanted the text wrapped or not). We are using hash code
+ * as using node's user object might be too large.
+ */
+ private Map<Integer, Boolean> nodeToWrapSelection = new HashMap<>();
+ /** List of all output ports - needs to be passed to 'save result' actions. */
+ List<? extends OutputWorkflowPort> dataflowOutputPorts = null;
+ /** Panel containing all 'save results' buttons */
+ JPanel saveButtonsPanel = null;
+
+ /**
+ * Creates the component.
+ */
+ public RenderedProcessorResultComponent(RendererRegistry rendererRegistry,
+ List<SaveIndividualResultSPI> saveActions) {
+ this.rendererRegistry = rendererRegistry;
+ setLayout(new BorderLayout());
+ setBorder(new EtchedBorder());
+
+ // Results type combo box
+ renderersComboBox = new JComboBox<>();
+ renderersComboBox.setModel(new DefaultComboBoxModel<String>()); // initially empty
+
+ renderersComboBox.setRenderer(new ColorCellRenderer());
+ renderersComboBox.setEditable(false);
+ renderersComboBox.setEnabled(false); // initially disabled
+
+ // Set the new listener - listen for changes in the currently selected renderer
+ renderersComboBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == SELECTED
+ && !ERROR_DOCUMENT.equals(e.getItem()))
+ // render the result using the newly selected renderer
+ renderResult();
+ }
+ });
+
+ JPanel resultsTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ resultsTypePanel.add(new JLabel("Value type"));
+ resultsTypePanel.add(renderersComboBox);
+
+ // Refresh (re-render) button
+ refreshButton = new JButton("Refresh", refreshIcon);
+ refreshButton.setEnabled(false);
+ refreshButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ renderResult();
+ refreshButton.getParent().requestFocusInWindow();
+ /*
+ * so that the button does not stay focused after it is clicked
+ * on and did its action
+ */
+ }
+ });
+ resultsTypePanel.add(refreshButton);
+
+ // Check box for wrapping text if result is of type "text/plain"
+ wrapTextCheckBox = new JCheckBox(WRAP_TEXT);
+ wrapTextCheckBox.setVisible(false);
+ wrapTextCheckBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ // Should have only one child component holding the rendered result
+ // Check for empty just as well
+ if (renderedResultPanel.getComponents().length == 0)
+ return;
+ if (renderedResultPanel.getComponent(0) instanceof DialogTextArea) {
+ nodeToWrapSelection.put(node.hashCode(),
+ e.getStateChange() == SELECTED);
+ renderResult();
+ }
+ }
+ });
+
+ resultsTypePanel.add(wrapTextCheckBox);
+ // 'Save result' buttons panel
+ saveButtonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ for (SaveIndividualResultSPI action : saveActions) {
+ action.setResultReference(null);
+ final JButton saveButton = new JButton(action.getAction());
+ saveButton.setEnabled(false);
+ saveButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveButton.getParent().requestFocusInWindow();
+ /*
+ * so that the button does not stay focused after it is
+ * clicked on and did its action
+ */
+ }
+ });
+ saveButtonsPanel.add(saveButton);
+ }
+
+ // Top panel contains result type combobox and various save buttons
+ JPanel topPanel = new JPanel();
+ topPanel.setLayout(new BoxLayout(topPanel, LINE_AXIS));
+ topPanel.add(resultsTypePanel);
+ topPanel.add(saveButtonsPanel);
+
+ // Rendered results panel - initially empty
+ renderedResultPanel = new JPanel(new BorderLayout());
+ renderedResultPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+
+ // Add all components
+ add(topPanel, NORTH);
+ add(new JScrollPane(renderedResultPanel), CENTER);
+ }
+
+ /**
+ * Sets the tree node this components renders the results for, and update
+ * the rendered results panel.
+ */
+ public void setNode(final ProcessorResultTreeNode node) {
+ this.node = node;
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (node.isState(RESULT_REFERENCE))
+ updateResult();
+ else
+ clearResult();
+ }
+ });
+ }
+
+ /**
+ * Update the component based on the node selected from the
+ * ResultViewComponent tree.
+ */
+ public void updateResult() {
+ if (recognisedRenderersForMimeType == null)
+ recognisedRenderersForMimeType = new ArrayList<>();
+ if (otherRenderers == null)
+ otherRenderers = new ArrayList<>();
+
+ ProcessorResultTreeNode result = (ProcessorResultTreeNode) node;
+
+ // Reference to the result data
+ path = result.getReference();
+
+ // Enable the combo box
+ renderersComboBox.setEnabled(true);
+
+ /*
+ * Update the 'save result' buttons appropriately as the result node had
+ * changed
+ */
+ for (int i = 0; i < saveButtonsPanel.getComponents().length; i++) {
+ JButton saveButton = (JButton) saveButtonsPanel.getComponent(i);
+ SaveIndividualResultSPI action = (SaveIndividualResultSPI) (saveButton
+ .getAction());
+ // Update the action with the new result reference
+ action.setResultReference(path);
+ saveButton.setEnabled(true);
+ }
+
+ if (DataBundles.isValue(path) || DataBundles.isReference(path)) {
+ // Enable refresh button
+ refreshButton.setEnabled(true);
+
+ List<MimeType> mimeTypes = new ArrayList<>();
+ try (InputStream inputstream = RendererUtils.getInputStream(path)) {
+ mimeTypes.addAll(getMimeTypes(inputstream));
+ } catch (IOException e) {
+ logger.warn("Error getting mimetype", e);
+ }
+
+ if (mimeTypes.isEmpty()) {
+ // If MIME types is empty - add "plain/text" MIME type
+ mimeTypes.add(new MimeType("text/plain"));
+ } else if (mimeTypes.size() == 1
+ && mimeTypes.get(0).toString().equals("chemical/x-fasta")) {
+ /*
+ * If MIME type is recognised as "chemical/x-fasta" only then
+ * this might be an error from MIME magic (i.e., sometimes it
+ * recognises stuff that is not "chemical/x-fasta" as
+ * "chemical/x-fasta" and then Seq Vista renderer is used that
+ * causes errors) - make sure we also add the renderers for
+ * "text/plain" and "text/xml" as it is most probably just
+ * normal xml text and push the "chemical/x-fasta" to the bottom
+ * of the list.
+ */
+ mimeTypes.add(0, new MimeType("text/plain"));
+ mimeTypes.add(1, new MimeType("text/xml"));
+ }
+
+ for (MimeType mimeType : mimeTypes)
+ for (Renderer renderer : rendererRegistry
+ .getRenderersForMimeType(mimeType.toString()))
+ if (!recognisedRenderersForMimeType.contains(renderer))
+ recognisedRenderersForMimeType.add(renderer);
+ // if there are no renderers then force text/plain
+ if (recognisedRenderersForMimeType.isEmpty())
+ recognisedRenderersForMimeType = rendererRegistry
+ .getRenderersForMimeType("text/plain");
+
+ /*
+ * Add all other available renderers that are not recognised to be
+ * able to handle the MIME type of the result
+ */
+ otherRenderers = rendererRegistry.getRenderers();
+ otherRenderers.removeAll(recognisedRenderersForMimeType);
+
+ mimeList = new String[recognisedRenderersForMimeType.size()
+ + otherRenderers.size()];
+ rendererList = new ArrayList<>();
+
+ /*
+ * First add the ones that can handle the MIME type of the result
+ * item
+ */
+ for (int i = 0; i < recognisedRenderersForMimeType.size(); i++) {
+ mimeList[i] = recognisedRenderersForMimeType.get(i).getType();
+ rendererList.add(recognisedRenderersForMimeType.get(i));
+ }
+ // Then add the other renderers just in case
+ for (int i = 0; i < otherRenderers.size(); i++) {
+ mimeList[recognisedRenderersForMimeType.size() + i] = otherRenderers
+ .get(i).getType();
+ rendererList.add(otherRenderers.get(i));
+ }
+
+ renderersComboBox.setModel(new DefaultComboBoxModel<>(mimeList));
+
+ if (mimeList.length > 0) {
+ int index = 0;
+
+ // Find the index of the current MIME type for this output port.
+ for (int i = 0; i < mimeList.length; i++)
+ if (mimeList[i].equals(lastUsedMIMEtype)) {
+ index = i;
+ break;
+ }
+
+ int previousindex = renderersComboBox.getSelectedIndex();
+ renderersComboBox.setSelectedIndex(index);
+ /*
+ * force rendering as setSelectedIndex will not fire an
+ * itemstatechanged event if previousindex == index and we still
+ * need render the result as we may have switched from a
+ * different result item in a result list but the renderer index
+ * stayed the same
+ */
+ if (previousindex == index)
+ renderResult(); // draw the rendered result component
+ }
+ } else if (DataBundles.isError(path)) {
+ // Disable refresh button
+ refreshButton.setEnabled(false);
+
+ @SuppressWarnings("unused")
+ ErrorDocument errorDocument;
+ try {
+ errorDocument = DataBundles.getError(path);
+ } catch (IOException e) {
+ logger.warn("Error getting the error document", e);
+ }
+
+ // Reset the renderers as we have an error item
+ recognisedRenderersForMimeType = null;
+ otherRenderers = null;
+
+ DefaultMutableTreeNode root = new DefaultMutableTreeNode(
+ "Error Trace");
+
+ // TODO handle error documents
+ // ResultsUtils.buildErrorDocumentTree(root, errorDocument, referenceService);
+
+ JTree errorTree = new JTree(root);
+
+ errorTree.setCellRenderer(new DefaultTreeCellRenderer() {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value, boolean selected, boolean expanded,
+ boolean leaf, int row, boolean hasFocus) {
+ Component renderer = null;
+ if (value instanceof DefaultMutableTreeNode) {
+ Object userObject = ((DefaultMutableTreeNode) value)
+ .getUserObject();
+ if (userObject instanceof ErrorDocument)
+ renderer = getErrorDocumentRenderer(tree, selected,
+ expanded, leaf, row, hasFocus,
+ (ErrorDocument) userObject);
+ }
+ if (renderer == null)
+ renderer = super.getTreeCellRendererComponent(tree,
+ value, selected, expanded, leaf, row, hasFocus);
+ if (renderer instanceof JLabel) {
+ JLabel label = (JLabel) renderer;
+ label.setIcon(null);
+ }
+ return renderer;
+ }
+
+ private Component getErrorDocumentRenderer(JTree tree,
+ boolean selected, boolean expanded, boolean leaf,
+ int row, boolean hasFocus, ErrorDocument errorDocument) {
+ return super.getTreeCellRendererComponent(tree, "<html>"
+ + escapeHtml(errorDocument.getMessage())
+ + "</html>", selected, expanded, leaf, row,
+ hasFocus);
+ }
+ });
+
+ renderersComboBox.setModel(new DefaultComboBoxModel<>(
+ new String[] { ERROR_DOCUMENT }));
+ renderedResultPanel.removeAll();
+ renderedResultPanel.add(errorTree, CENTER);
+ repaint();
+ }
+ }
+
+ /**
+ * Renders the result panel using the last used renderer.
+ */
+ public void renderResult() {
+ if (ERROR_DOCUMENT.equals(renderersComboBox.getSelectedItem())) {
+ // skip error documents - do not (re)render
+ return;
+ }
+
+ int selectedIndex = renderersComboBox.getSelectedIndex();
+ if (mimeList != null && selectedIndex >= 0) {
+ Renderer renderer = rendererList.get(selectedIndex);
+
+ if (renderer.getType().equals("Text")){ // if the result is "text/plain"
+ /*
+ * We use node's hash code as the key in the nodeToWrapCheckBox
+ * map as node's user object may be too large
+ */
+ if (nodeToWrapSelection.get(node.hashCode()) == null) {
+ // initially not selected
+ nodeToWrapSelection.put(node.hashCode(), false);
+ }
+ wrapTextCheckBox.setSelected(nodeToWrapSelection.get(node.hashCode()));
+ wrapTextCheckBox.setVisible(true);
+ } else {
+ wrapTextCheckBox.setVisible(false);
+ }
+
+ // Remember the last used renderer - use it for all result items of this port
+ //currentRendererIndex = selectedIndex;
+ lastUsedMIMEtype = mimeList[selectedIndex];
+
+ JComponent component;
+ try {
+ component = renderer.getComponent(path);
+ if (component instanceof DialogTextArea
+ && wrapTextCheckBox.isSelected())
+ ((JTextArea) component).setLineWrap(wrapTextCheckBox
+ .isSelected());
+ if (component instanceof JTextComponent)
+ ((JTextComponent) component).setEditable(false);
+ else if (component instanceof JTree)
+ ((JTree) component).setEditable(false);
+ } catch (RendererException ex) {// maybe this should be Exception
+ /*
+ * show the user that something unexpected has happened but
+ * continue
+ */
+ component = new DialogTextArea(
+ "Could not render using renderer type "
+ + renderer.getClass()
+ + "\nPlease try with a different renderer"
+ + " if available and consult log"
+ + " for details of problem");
+ ((DialogTextArea) component).setEditable(false);
+ logger.warn("Couln not render using " + renderer.getClass(), ex);
+ }
+ renderedResultPanel.removeAll();
+ renderedResultPanel.add(component, CENTER);
+ repaint();
+ revalidate();
+ }
+ }
+
+ /**
+ * Clears the result panel.
+ */
+ public void clearResult() {
+ refreshButton.setEnabled(false);
+ renderedResultPanel.removeAll();
+
+ // Update the 'save result' buttons appropriately
+ for (int i = 0; i < saveButtonsPanel.getComponents().length; i++) {
+ JButton saveButton = (JButton) saveButtonsPanel.getComponent(i);
+ SaveIndividualResultSPI action = (SaveIndividualResultSPI) (saveButton
+ .getAction());
+ // Update the action
+ action.setResultReference(null);
+ saveButton.setEnabled(false);
+ }
+
+ renderersComboBox.setModel(new DefaultComboBoxModel<String>());
+ renderersComboBox.setEnabled(false);
+
+ revalidate();
+ repaint();
+ }
+
+ class ColorCellRenderer implements ListCellRenderer<Object> {
+ protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
+
+ @Override
+ public Component getListCellRendererComponent(JList<?> list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ JLabel renderer = (JLabel) defaultRenderer
+ .getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+
+ if (value instanceof Color)
+ renderer.setBackground((Color) value);
+
+ if (recognisedRenderersForMimeType == null) // error occurred
+ return renderer;
+
+ if (value != null && index >= recognisedRenderersForMimeType.size())
+ // one of the non-preferred renderers - show it in grey
+ renderer.setForeground(Color.GRAY);
+
+ return renderer;
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsAsExcel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsAsExcel.java
new file mode 100644
index 0000000..0a06383
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsAsExcel.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * 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.saveactions;
+
+import static java.lang.Math.max;
+import static java.util.Arrays.asList;
+import static net.sf.taverna.t2.baclava.factory.DataThingFactory.bake;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveIcon;
+import static org.apache.poi.ss.usermodel.CellStyle.BORDER_NONE;
+import static org.apache.poi.ss.usermodel.CellStyle.BORDER_THIN;
+import static org.apache.poi.ss.usermodel.CellStyle.SOLID_FOREGROUND;
+
+import java.beans.IntrospectionException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.baclava.DataThing;
+import net.sf.taverna.t2.baclava.iterator.BaclavaIterator;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+
+/**
+ * Stores the entire map of result objects to disk as a single XML data document.
+ *
+ * @author Tom Oinn
+ */
+public class SaveAllResultsAsExcel extends SaveAllResultsSPI {
+ private static final long serialVersionUID = -2759817859804112070L;
+
+ HSSFWorkbook wb = null;
+ HSSFSheet sheet = null;
+ HSSFCellStyle headingStyle = null;
+ HSSFCellStyle[] styles = null;
+
+ public SaveAllResultsAsExcel() {
+ super();
+ putValue(NAME, "Save as Excel");
+ putValue(SMALL_ICON, saveIcon);
+ }
+
+ @Override
+ public AbstractAction getAction() {
+ return new SaveAllResultsAsExcel();
+ }
+
+ @Override
+ protected void saveData(File f) throws IOException {
+ try {
+ generateSheet();
+ } catch (IntrospectionException e) {
+ throw new IOException("failed to generate excel sheet model", e);
+ }
+ saveSheet(f);
+ }
+
+ /**
+ * Generate the Excel sheet from the DataThing's in the map. All of the
+ * results are shown in the same spreadsheet, but in different columns. Flat
+ * lists are shown vertically, 2d lists as a matrix, and deeper lists are
+ * flattened to 2d.
+ *
+ * @throws IntrospectionException
+ */
+ void generateSheet() throws IntrospectionException {
+ wb = new HSSFWorkbook();
+ setStyles();
+ sheet = wb.createSheet("Workflow results");
+ sheet.setDisplayGridlines(false);
+ int currentCol = 0;
+
+ for (String portName : chosenReferences.keySet()) {
+ logger.debug("Output for : " + portName);
+ DataThing resultValue = bake(getObjectForName(portName));
+ // Check whether there's a textual type
+ Boolean textualType = isTextual(resultValue.getDataObject());
+ if (textualType == null || !textualType)
+ continue;
+ logger.debug("Output is textual");
+ getCell(currentCol, 0).setCellValue(portName);
+ getCell(currentCol, 0).setCellStyle(headingStyle);
+ int numCols = 1;
+ int numRows = 1;
+ int currentRow = 0;
+ BaclavaIterator rows;
+ try {
+ rows = resultValue.iterator("l('')");
+ } catch (IntrospectionException ex) {
+ // Not a list, single value. We'll fake the iterator
+ DataThing fakeValues = new DataThing(
+ asList(resultValue.getDataObject()));
+ rows = fakeValues.iterator("l('')");
+ }
+ /*
+ * If we only have one row, we'll show each value on a new row
+ * instead
+ */
+ boolean isFlat = rows.size() == 1;
+ while (rows.hasNext()) {
+ DataThing row = (DataThing) rows.next();
+ /*
+ * Even increase first time, as we don't want to overwrite our
+ * header
+ */
+ currentRow++;
+ BaclavaIterator bi = row.iterator("''");
+ while (bi.hasNext()) {
+ DataThing containedThing = (DataThing) bi.next();
+ String containedValue = (String) containedThing.getDataObject();
+ int columnOffset = 0;
+ int[] location = bi.getCurrentLocation();
+ if (!isFlat && location.length > 0) {
+ columnOffset = location[location.length - 1];
+ numCols = Math.max(numCols, columnOffset + 1);
+ }
+ logger.debug("Storing in cell " + (currentCol + columnOffset) + " "
+ + currentRow + ": " + containedValue);
+ getCell(currentCol + columnOffset, currentRow).setCellValue(containedValue);
+ if (isFlat)
+ currentRow++;
+ }
+ }
+ numRows = max(numRows, currentRow);
+
+ // Set the styles
+ for (int x = currentCol; x < currentCol + numCols; x++)
+ for (int y = 1; y < numRows + 1; y++)
+ setStyle(currentCol, x, y);
+ sheet.setColumnWidth(currentCol + numCols, 200);
+ currentCol += numCols + 1;
+ }
+ }
+
+ void setStyle(int currentCol, int column, int row) {
+ if (!hasValue(column, row))
+ return;
+ HSSFCell cell = getCell(column, row);
+ int n = 0, s = 0, w = 0, e = 0;
+ if (row < 2 || !hasValue(column, row - 1))
+ n = 1;
+ if (column == currentCol || !hasValue(column - 1, row))
+ w = 1;
+ if (!hasValue(column, row + 1))
+ s = 1;
+ if (!hasValue(column + 1, row))
+ e = 1;
+ int index = n + 2 * s + 4 * e + 8 * w;
+ cell.setCellStyle(styles[index]);
+ }
+
+ void setStyles() {
+ headingStyle = wb.createCellStyle();
+ headingStyle.setBorderTop(BORDER_THIN);
+ headingStyle.setBorderBottom(BORDER_THIN);
+ headingStyle.setBorderLeft(BORDER_THIN);
+ headingStyle.setBorderRight(BORDER_THIN);
+ headingStyle.setFillBackgroundColor(HSSFColor.LIGHT_YELLOW.index);
+ headingStyle.setFillForegroundColor(HSSFColor.LIGHT_YELLOW.index);
+ headingStyle.setFillPattern(SOLID_FOREGROUND);
+ styles = new HSSFCellStyle[16];
+ for (int n = 0; n < 2; n++)
+ for (int s = 0; s < 2; s++)
+ for (int e = 0; e < 2; e++)
+ for (int w = 0; w < 2; w++) {
+ int index = n + 2 * s + 4 * e + 8 * w;
+ styles[index] = wb.createCellStyle();
+ styles[index].setBorderTop(n == 1 ? BORDER_THIN
+ : BORDER_NONE);
+ styles[index].setBorderBottom(s == 1 ? BORDER_THIN
+ : BORDER_NONE);
+ styles[index].setBorderRight(e == 1 ? BORDER_THIN
+ : BORDER_NONE);
+ styles[index].setBorderLeft(w == 1 ? BORDER_THIN
+ : BORDER_NONE);
+ styles[index].setFillBackgroundColor(HSSFColor.GOLD.index);
+ styles[index].setFillForegroundColor(HSSFColor.GOLD.index);
+ styles[index].setFillPattern(SOLID_FOREGROUND);
+ }
+ }
+
+ /**
+ * Check if o is a String or contains elements that satisfy isTextual(o)
+ * <p>
+ * Traverse down the Collection o if possible, and check the tree of collection at the deepest
+ * level.
+ * </p>
+ *
+ * @param o
+ * Object to check
+ * @return true if o is a String or is a Collection that contains a string at the deepest level.
+ * false if o is not a String or Collection, or if it is a collection that contains
+ * non-strings.
+ * null if o is a Collection, but it is empty or contains nothing but Collections.
+ */
+ Boolean isTextual(Object o) {
+ if (o instanceof String)
+ // We dug down and found a string. Hurray!
+ return true;
+ if (o instanceof Collection) {
+ for (Object child : (Collection<?>) o) {
+ Boolean isTxt = isTextual(child);
+ if (isTxt == null)
+ // Unknown, try next one
+ continue;
+ return isTxt;
+ }
+ /*
+ * We looped through and found just empty collections (or we are an
+ * empty collection), we don't know.
+ */
+ return null;
+ }
+ // No, sorry mate.. o was neither a String or Collection
+ return false;
+ }
+
+ /**
+ * Get a cell at the given coordinates, create it if needed.
+ *
+ * @param column
+ * @param row
+ * @return
+ */
+ HSSFCell getCell(int column, int row) {
+ HSSFRow srow = sheet.getRow(row);
+ if (srow == null)
+ srow = sheet.createRow(row);
+ HSSFCell scell = srow.getCell(column);
+ if (scell == null)
+ scell = srow.createCell(column);
+ return scell;
+ }
+
+ /**
+ * Check if a cell has a value.
+ *
+ * @param column
+ * @param row
+ * @return
+ */
+ boolean hasValue(int column, int row) {
+ HSSFRow srow = sheet.getRow(row);
+ if (srow == null)
+ return false;
+ HSSFCell scell = srow.getCell(column);
+ if (scell == null)
+ return false;
+ return true;
+ }
+
+ /**
+ * Save the generated worksheet to a file
+ *
+ * @param file
+ * to save to
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ void saveSheet(File file) throws IOException {
+ FileOutputStream fos = new FileOutputStream(file);
+ wb.write(fos);
+ fos.close();
+ }
+
+ @Override
+ protected String getFilter() {
+ return "xls";
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsAsXML.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsAsXML.java
new file mode 100644
index 0000000..a769705
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsAsXML.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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.saveactions;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.xmlNodeIcon;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.results.BaclavaDocumentPathHandler;
+
+/**
+ * Stores the entire map of result objects to disk as a single XML data
+ * document. For the most part, this class delegates to
+ * {@link BaclavaDocumentPathHandler}
+ *
+ * @author Tom Oinn
+ * @author Alex Nenadic
+ * @author Stuart Owen
+ * @author David Withers
+ */
+public class SaveAllResultsAsXML extends SaveAllResultsSPI {
+ private static final long serialVersionUID = 452360182978773176L;
+
+ private BaclavaDocumentPathHandler baclavaDocumentHandler = new BaclavaDocumentPathHandler();
+
+ public SaveAllResultsAsXML() {
+ super();
+ putValue(NAME, "Save in single XML document");
+ putValue(SMALL_ICON, xmlNodeIcon);
+ }
+
+ @Override
+ public AbstractAction getAction() {
+ return new SaveAllResultsAsXML();
+ }
+
+ /**
+ * Saves the result data to an XML Baclava file.
+ *
+ * @throws IOException
+ */
+ @Override
+ protected void saveData(File file) throws IOException {
+ baclavaDocumentHandler.saveData(file);
+ }
+
+ @Override
+ public void setChosenReferences(Map<String, Path> chosenReferences) {
+ super.setChosenReferences(chosenReferences);
+ baclavaDocumentHandler.setChosenReferences(chosenReferences);
+ }
+
+ @Override
+ protected String getFilter() {
+ return "xml";
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsSPI.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsSPI.java
new file mode 100644
index 0000000..c898493
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsSPI.java
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * 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.saveactions;
+
+import static java.lang.System.getProperty;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JFileChooser.FILES_ONLY;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.results.ResultsUtils.convertPathToObject;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+
+import net.sf.taverna.t2.lang.ui.ExtensionFileFilter;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementing classes are capable of storing a collection
+ * of Paths held in a result map.
+ *
+ * @author Tom Oinn
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class SaveAllResultsSPI extends AbstractAction {
+ protected static final Logger logger = getLogger(SaveAllResultsSPI.class);
+
+ protected Map<String, Path> chosenReferences;
+ protected JDialog dialog;
+
+ /**
+ * Returns the save result action implementing this interface. The returned
+ * action will be bound to the appropriate UI component used to trigger the
+ * save action.
+ */
+ public abstract AbstractAction getAction();
+
+ /**
+ * The Map passed into this method contains the String -> T2Reference (port
+ * name to reference to value pairs) returned by the current set of results.
+ * The actual listener may well wish to display some kind of dialog, for
+ * example in the case of an Excel export plugin it would be reasonable to
+ * give the user some choice over where the results would be inserted into
+ * the sheet, and also where the generated file would be stored.
+ * <p>
+ * The parent parameter is optional and may be set to null, if not it is assumed to be the
+ * parent component in the UI which caused this action to be created, this allows save dialogs
+ * etc to be placed correctly.
+ */
+ public void setChosenReferences(Map<String, Path> chosenReferences) {
+ this.chosenReferences = chosenReferences;
+ }
+
+ public void setParent(JDialog dialog) {
+ this.dialog = dialog;
+ }
+
+ /**
+ * Get the extension for the filename.
+ *
+ * @return The extension, in lower case, without any leading "<tt>.</tt>"
+ */
+ protected abstract String getFilter();
+
+ protected int getFileSelectionMode() {
+ return FILES_ONLY;
+ }
+
+ /**
+ * Shows a standard save dialog and dumps the entire result
+ * set to the specified XML file.
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(false);
+
+ JFileChooser fc = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get("currentDir", getProperty("user.home"));
+ fc.resetChoosableFileFilters();
+ if (getFilter() != null)
+ fc.setFileFilter(new ExtensionFileFilter(
+ new String[] { getFilter() }));
+ fc.setCurrentDirectory(new File(curDir));
+ fc.setFileSelectionMode(getFileSelectionMode());
+
+ while (true) {
+ if (fc.showSaveDialog(null) != APPROVE_OPTION)
+ return;
+ prefs.put("currentDir", fc.getCurrentDirectory().toString());
+ File file = fc.getSelectedFile();
+
+ /*
+ * If the user did not use the extension for the file, append it to
+ * the file name now
+ */
+ if (getFilter() != null
+ && !file.getName().toLowerCase()
+ .endsWith("." + getFilter())) {
+ String newFileName = file.getName() + "." + getFilter();
+ file = new File(file.getParentFile(), newFileName);
+ }
+ final File finalFile = file;
+
+ if (file.exists()) // File already exists
+ // Ask the user if they want to overwrite the file
+ if (showConfirmDialog(null, file.getAbsolutePath()
+ + " already exists. Do you want to overwrite it?",
+ "File already exists", YES_NO_OPTION) != YES_OPTION)
+ continue;
+
+ // File doesn't exist, or user has OK'd overwriting it
+
+ // Do this in separate thread to avoid hanging UI
+ new Thread("SaveAllResults: Saving results to " + finalFile) {
+ @Override
+ public void run() {
+ saveDataToFile(finalFile);
+ }
+ }.start();
+ return;
+ }
+ }
+
+ private void saveDataToFile(final File finalFile) {
+ try {
+ synchronized (chosenReferences) {
+ saveData(finalFile);
+ }
+ } catch (Exception ex) {
+ showMessageDialog(null,
+ "Problem saving result data\n" + ex.getMessage(),
+ "Save Result Error", ERROR_MESSAGE);
+ logger.error("SaveAllResults Error: Problem saving result data", ex);
+ }
+ }
+
+ protected abstract void saveData(File f) throws IOException;
+
+ protected Object getObjectForName(String name) {
+ Object result = null;
+ try {
+ if (chosenReferences.containsKey(name))
+ result = convertPathToObject(chosenReferences.get(name));
+ } catch (IOException e) {
+ logger.warn("Error getting value for " + name, e);
+ }
+ if (result == null)
+ result = "null";
+ return result;
+ }
+
+ public Map<String, Path> getChosenReferences() {
+ return chosenReferences;
+ }
+
+ public JDialog getDialog() {
+ return dialog;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsToFileSystem.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsToFileSystem.java
new file mode 100644
index 0000000..764f32b
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveAllResultsToFileSystem.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * 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.saveactions;
+
+import static java.nio.file.Files.copy;
+import static java.nio.file.Files.exists;
+import static java.nio.file.Files.isDirectory;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static javax.swing.JFileChooser.DIRECTORIES_ONLY;
+import static org.purl.wf4ever.robundle.Bundles.copyRecursively;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ * Stores results to the file system.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class SaveAllResultsToFileSystem extends SaveAllResultsSPI {
+ public SaveAllResultsToFileSystem(){
+ super();
+ putValue(NAME, "Save as directory");
+ putValue(SMALL_ICON, WorkbenchIcons.saveAllIcon);
+ }
+
+ @Override
+ public AbstractAction getAction() {
+ return new SaveAllResultsToFileSystem();
+ }
+
+ /**
+ * Saves the result data as a file structure
+ *
+ * @throws IOException
+ */
+ @Override
+ protected void saveData(File directory) throws IOException {
+ if (directory.exists() && !directory.isDirectory())
+ throw new IOException(directory.getName() + " is not a directory.");
+ for (String portName : chosenReferences.keySet())
+ writeToFileSystem(chosenReferences.get(portName), new File(
+ directory, portName));
+ }
+
+ /**
+ * Write a specific object to the filesystem this has no access to metadata
+ * about the object and so is not particularly clever. A File object
+ * representing the file or directory that has been written is returned.
+ */
+ public File writeToFileSystem(Path source, File destination)
+ throws IOException {
+ destination.mkdirs();
+ if (isDirectory(source))
+ copyRecursively(source, destination.toPath(), REPLACE_EXISTING);
+ else if (exists(source))
+ copy(source, destination.toPath(), REPLACE_EXISTING);
+ return destination;
+ }
+
+ @Override
+ protected int getFileSelectionMode() {
+ return DIRECTORIES_ONLY;
+ }
+
+ @Override
+ protected String getFilter() {
+ return null;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveIndividualResult.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveIndividualResult.java
new file mode 100644
index 0000000..ae1d787
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveIndividualResult.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * 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.saveactions;
+
+import static java.lang.System.getProperty;
+import static java.nio.file.Files.copy;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveIcon;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.ui.ExtensionFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * Saves individual result to a file. A T2Reference to the result data is held
+ * in the tree node.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ * @author David Withers
+ */
+public class SaveIndividualResult extends AbstractAction implements
+ SaveIndividualResultSPI {
+ private static final long serialVersionUID = 4637392234806851345L;
+ private static final Logger logger = Logger
+ .getLogger(SaveIndividualResult.class);
+
+ /**
+ * Path pointing to the result to be saved.
+ */
+ private Path resultReference = null;
+
+ public SaveIndividualResult(){
+ super();
+ putValue(NAME, "Save value");
+ putValue(SMALL_ICON, saveIcon);
+ }
+
+ @Override
+ public AbstractAction getAction() {
+ return new SaveIndividualResult();
+ }
+
+ /**
+ * Saves a result either as a text or a binary file - depending on the
+ * result data type.
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (DataBundles.isValue(resultReference)) {
+ // Node contains a data value
+ // Popup a save dialog and allow the user to store the data to disc
+ JFileChooser fc = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get("currentDir", getProperty("user.home"));
+ fc.resetChoosableFileFilters();
+ fc.setCurrentDirectory(new File(curDir));
+
+ File file;
+ do {
+ if (fc.showSaveDialog(null) != APPROVE_OPTION)
+ return;
+ prefs.put("currentDir", fc.getCurrentDirectory().toString());
+ file = fc.getSelectedFile();
+ /*
+ * If we know the extension and the user did not use it, append
+ * it to the file name.
+ *
+ * TODO the comment is inconsistent with the functionality
+ */
+ if (!file.exists())
+ file = new File(file.getParentFile(), file.getName());
+
+ // Ask the user if they want to overwrite the file if it exists
+ } while (!shouldWrite(file));
+
+ // File does not already exist, or user has OK'd overwrite
+ doSaveThread(file, "result data");
+ } else if (DataBundles.isError(resultReference)) {
+ // Node contains a reference to ErrorDocument
+ // Popup a save dialog and allow the user to store the data to disc
+ JFileChooser fc = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs.get("currentDir", getProperty("user.home"));
+ fc.resetChoosableFileFilters();
+ FileFilter ff = new ExtensionFileFilter(new String[] { "txt" });
+ fc.setFileFilter(ff);
+ fc.setCurrentDirectory(new File(curDir));
+
+ File file;
+ do {
+ if (fc.showSaveDialog(null) != APPROVE_OPTION)
+ return;
+ prefs.put("currentDir", fc.getCurrentDirectory().toString());
+ file = fc.getSelectedFile();
+
+ // If user did not use the file extension - append it to the file name
+ if (!file.exists()) {
+ if (fc.getFileFilter().equals(ff) && !file.getName().contains(".")) {
+ String newFileName = file.getName() + ".txt";
+ file = new File(file.getParentFile(), newFileName);
+ } else
+ file = new File(file.getParentFile(), file.getName());
+ }
+
+ // Ask the user if they want to overwrite the file if it exists
+ } while (!shouldWrite(file));
+
+ // File does not already exist, or user has OK'd overwrite
+ doSaveThread(file, "error document");
+ }
+ }
+
+ private boolean shouldWrite(File file) {
+ return !file.exists()
+ || showConfirmDialog(null, file.getAbsolutePath()
+ + " already exists. Do you want to overwrite it?",
+ "File already exists", YES_NO_OPTION) == YES_OPTION;
+ }
+
+ private void doSaveThread(final File file, final String type) {
+ // Do this in separate thread to avoid hanging UI
+ new Thread("SaveIndividualResult: Saving " + type + " to " + file) {
+ @Override
+ public void run() {
+ try {
+ doSave(file);
+ } catch (Exception ex) {
+ showMessageDialog(null, "Problem saving " + type,
+ "Save Result Error", ERROR_MESSAGE);
+ logger.error("SaveIndividualResult Error: Problem saving "
+ + type, ex);
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * The core of how to save a result. This is called in a context that
+ * handles exceptions and is running inside a worker thread.
+ */
+ protected void doSave(File file) throws IOException {
+ copy(resultReference, file.toPath());
+ }
+
+ // Must be called before actionPerformed()
+ @Override
+ public void setResultReference(Path reference) {
+ this.resultReference = reference;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveIndividualResultSPI.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveIndividualResultSPI.java
new file mode 100644
index 0000000..499814e
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/saveactions/SaveIndividualResultSPI.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.saveactions;
+
+import java.nio.file.Path;
+
+import javax.swing.AbstractAction;
+
+/**
+ * Defines an interface for various actions for saving results of a workflow
+ * run. Path to a single result data is contained inside a MutableTreeNode,
+ * which can be used by actions that only want to save the current result. The
+ * interface also contains a list of output ports that can be used to
+ * dereference all outputs, for actions wishing to save a all results (e.g. in
+ * different formats).
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public interface SaveIndividualResultSPI {
+ /**
+ * Sets the Path pointing to the result to be saved.
+ */
+ void setResultReference(Path reference);
+
+ /**
+ * Returns the save result action implementing this interface.
+ */
+ AbstractAction getAction();
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/DataBundleTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/DataBundleTreeModel.java
new file mode 100644
index 0000000..4c2abfc
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/DataBundleTreeModel.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.workflow;
+
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.workbench.ui.Updatable;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * TreeModel for displaying DataBundle Paths.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class DataBundleTreeModel extends DefaultTreeModel implements Updatable {
+ private static final Logger logger = Logger.getLogger(DataBundleTreeModel.class);
+
+ private Path path;
+
+ /**
+ * Constructs a new TreeModel for displaying DataBundle Paths.
+ *
+ * @param root
+ * the root path of the tree
+ */
+ public DataBundleTreeModel(Path root) {
+ super(createTree(root));
+ path = root;
+ }
+
+ public void setPath(Path path) {
+ this.path = path;
+ }
+
+ private static DefaultMutableTreeNode createTree(Path path) {
+ if (path == null || DataBundles.isMissing(path))
+ return new DefaultMutableTreeNode(null);
+ else if (!DataBundles.isList(path))
+ return new DefaultMutableTreeNode(path);
+
+ DefaultMutableTreeNode node = new DefaultMutableTreeNode(path);
+ try {
+ for (Path element : DataBundles.getList(path))
+ node.add(createTree(element));
+ } catch (IOException e) {
+ logger.error("Error resolving data entity list " + path, e);
+ }
+ return node;
+ }
+
+ @Override
+ public void update() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ DefaultMutableTreeNode oldNode = (DefaultMutableTreeNode) root;
+ if (oldNode.getUserObject() == null
+ && (path == null || DataBundles.isMissing(path)))
+ return;
+ compare(oldNode, createTree(path));
+ }
+ });
+ }
+
+ private void compare(DefaultMutableTreeNode oldNode,
+ DefaultMutableTreeNode newNode) {
+ if (oldNode.getUserObject() == null) {
+ Path newPath = (Path) newNode.getUserObject();
+ if (newPath != null) {
+ oldNode.setUserObject(newPath);
+ if (DataBundles.isList(newPath))
+ nodeStructureChanged(oldNode);
+ else
+ nodeChanged(oldNode);
+ }
+ } else if (DataBundles.isList((Path) oldNode.getUserObject())) {
+ @SuppressWarnings("unchecked")
+ Enumeration<DefaultMutableTreeNode> oldChildren = oldNode
+ .children();
+ @SuppressWarnings("unchecked")
+ Enumeration<DefaultMutableTreeNode> newChildren = newNode
+ .children();
+ int index = 0;
+ while (oldChildren.hasMoreElements()) {
+ index++;
+ compare(oldChildren.nextElement(), newChildren.nextElement());
+ }
+ int newChildNodes = newNode.getChildCount()
+ - oldNode.getChildCount();
+ if (newChildNodes != 0) {
+ List<DefaultMutableTreeNode> childrenToAdd = new ArrayList<>(
+ newChildNodes);
+ int[] childIndices = new int[newChildNodes];
+ for (int i = 0; newChildren.hasMoreElements(); i++) {
+ childrenToAdd.add(newChildren.nextElement());
+ childIndices[i] = index++;
+ }
+ for (DefaultMutableTreeNode childToAdd : childrenToAdd)
+ oldNode.add(childToAdd);
+ nodesWereInserted(oldNode, childIndices);
+ nodeChanged(oldNode);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/FilteredDataBundleTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/FilteredDataBundleTreeModel.java
new file mode 100644
index 0000000..cb23310
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/FilteredDataBundleTreeModel.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.workflow;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.workbench.views.results.SimpleFilteredTreeModel;
+import uk.org.taverna.databundle.DataBundles;
+
+@SuppressWarnings("serial")
+public class FilteredDataBundleTreeModel extends SimpleFilteredTreeModel implements
+ TreeModelListener {
+
+ public enum FilterType {
+ ALL {
+ @Override
+ public String toString() {
+ return "view values";
+ }
+ },
+ RESULTS {
+ @Override
+ public String toString() {
+ return "view results";
+ }
+ },
+ ERRORS {
+ @Override
+ public String toString() {
+ return "view errors";
+ }
+ };
+ }
+
+ private FilterType filter;
+
+ public FilteredDataBundleTreeModel(DefaultTreeModel delegate) {
+ super(delegate);
+ delegate.addTreeModelListener(this);
+ this.filter = FilterType.ALL;
+ }
+
+ public void setFilter(FilterType filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean isShown(Object o) {
+ if (!(o instanceof DefaultMutableTreeNode))
+ return false;
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
+ Object userObject = node.getUserObject();
+ if (!(userObject instanceof Path))
+ return false;
+ switch (filter) {
+ case RESULTS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e.hasMoreElements();) {
+ DefaultMutableTreeNode subNode = (DefaultMutableTreeNode) e.nextElement();
+ if ((subNode.getUserObject() != null)
+ && !DataBundles.isError((Path) subNode.getUserObject()))
+ return true;
+ }
+ return false;
+ case ERRORS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e.hasMoreElements();) {
+ DefaultMutableTreeNode subNode = (DefaultMutableTreeNode) e.nextElement();
+ if ((subNode.getUserObject() != null)
+ && DataBundles.isError((Path) subNode.getUserObject()))
+ return true;
+ }
+ return false;
+ default:
+ // ALL/null
+ return true;
+ }
+ }
+
+ @Override
+ public void treeNodesChanged(TreeModelEvent e) {
+ if (e.getChildren() == null) {
+ nodeChanged((DefaultMutableTreeNode) getRoot());
+ return;
+ }
+
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) e
+ .getTreePath().getLastPathComponent();
+ ArrayList<Integer> indices = new ArrayList<>();
+ for (Object o : e.getChildren())
+ if (isShown(o))
+ indices.add(getFilteredIndexOfChild(parent, o));
+ if (!indices.isEmpty())
+ nodesChanged(parent, listToArray(indices));
+ }
+
+ @Override
+ public void treeNodesInserted(TreeModelEvent e) {
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) e.getTreePath()
+ .getLastPathComponent();
+ ArrayList<Integer> indices = new ArrayList<>();
+ for (Object o : e.getChildren())
+ if (isShown(o))
+ indices.add(getFilteredIndexOfChild(parent, o));
+ if (!indices.isEmpty())
+ nodesWereInserted(parent, listToArray(indices));
+ }
+
+ @Override
+ public void treeNodesRemoved(TreeModelEvent e) {
+ }
+
+ @Override
+ public void treeStructureChanged(TreeModelEvent e) {
+ }
+
+ private int[] listToArray(ArrayList<Integer> list) {
+ int[] result = new int[list.size()];
+ int index = 0;
+ for (Integer i : list)
+ result[index++] = i;
+ return result;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/FilteredWorkflowResultTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/FilteredWorkflowResultTreeModel.java
new file mode 100644
index 0000000..fe71078
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/FilteredWorkflowResultTreeModel.java
@@ -0,0 +1,126 @@
+
+package net.sf.taverna.t2.workbench.views.results.workflow;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.workbench.views.results.SimpleFilteredTreeModel;
+import uk.org.taverna.databundle.DataBundles;
+
+@SuppressWarnings("serial")
+public class FilteredWorkflowResultTreeModel extends SimpleFilteredTreeModel
+ implements TreeModelListener {
+ public enum FilterType {
+ ALL {
+ @Override
+ public String toString() {
+ return "view values";
+ }
+ },
+ RESULTS {
+ @Override
+ public String toString() {
+ return "view results";
+ }
+ },
+ ERRORS {
+ @Override
+ public String toString() {
+ return "view errors";
+ }
+ };
+ }
+
+ private FilterType filter;
+
+ public FilteredWorkflowResultTreeModel(DefaultTreeModel delegate) {
+ super(delegate);
+ delegate.addTreeModelListener(this);
+ this.filter = FilterType.ALL;
+ }
+
+ public void setFilter(FilterType filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean isShown(Object o) {
+ if (!(o instanceof WorkflowResultTreeNode))
+ return false;
+ WorkflowResultTreeNode node = (WorkflowResultTreeNode) o;
+ switch (filter) {
+ case RESULTS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e
+ .hasMoreElements();) {
+ WorkflowResultTreeNode subNode = (WorkflowResultTreeNode) e
+ .nextElement();
+ if (subNode.getReference() != null
+ && !DataBundles.isError(subNode.getReference()))
+ return true;
+ }
+ return false;
+ case ERRORS:
+ for (Enumeration<?> e = node.depthFirstEnumeration(); e
+ .hasMoreElements();) {
+ WorkflowResultTreeNode subNode = (WorkflowResultTreeNode) e
+ .nextElement();
+ if (subNode.getReference() != null
+ && DataBundles.isError(subNode.getReference()))
+ return true;
+ }
+ return false;
+ default: // ALL/null
+ return true;
+ }
+ }
+
+ @Override
+ public void treeNodesChanged(TreeModelEvent e) {
+ if (e.getChildren() == null) {
+ nodeChanged((DefaultMutableTreeNode) getRoot());
+ return;
+ }
+
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) e
+ .getTreePath().getLastPathComponent();
+ ArrayList<Integer> indices = new ArrayList<>();
+ for (Object o : e.getChildren())
+ if (isShown(o))
+ indices.add(getFilteredIndexOfChild(parent, o));
+ if (!indices.isEmpty())
+ nodesChanged(parent, listToArray(indices));
+ }
+
+ @Override
+ public void treeNodesInserted(TreeModelEvent e) {
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) e
+ .getTreePath().getLastPathComponent();
+ ArrayList<Integer> indices = new ArrayList<>();
+ for (Object o : e.getChildren())
+ if (isShown(o))
+ indices.add(getFilteredIndexOfChild(parent, o));
+ if (!indices.isEmpty())
+ nodesWereInserted(parent, listToArray(indices));
+ }
+
+ @Override
+ public void treeNodesRemoved(TreeModelEvent e) {
+ }
+
+ @Override
+ public void treeStructureChanged(TreeModelEvent e) {
+ }
+
+ private int[] listToArray(ArrayList<Integer> list) {
+ int[] result = new int[list.size()];
+ int index = 0;
+ for (Integer i : list)
+ result[index++] = i;
+ return result;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/PortResultCellRenderer.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/PortResultCellRenderer.java
new file mode 100644
index 0000000..24ba0ed
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/PortResultCellRenderer.java
@@ -0,0 +1,100 @@
+package net.sf.taverna.t2.workbench.views.results.workflow;
+
+import static java.awt.Color.RED;
+
+import java.awt.Component;
+import java.nio.file.Path;
+import java.util.Enumeration;
+
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreeNode;
+
+import uk.org.taverna.databundle.DataBundles;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public class PortResultCellRenderer extends DefaultTreeCellRenderer {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ Component result = super.getTreeCellRendererComponent(tree, value,
+ selected, expanded, leaf, row, hasFocus);
+ if (value instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
+ renderPortResult(result, node, (Path) node.getUserObject());
+ }
+ return result;
+ }
+
+ private void renderPortResult(Component result,
+ DefaultMutableTreeNode node, Path path) {
+ String text = "";
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
+ if (path == null)
+ text = "Waiting for data";
+ else if (DataBundles.isList(path))
+ text = describeList(node, parent);
+ else {
+ int index = 1;
+ if (parent != null)
+ index += parent.getIndex(node);
+ text = "Value " + index;
+ }
+ ((JLabel) result).setText(text);
+ if (containsError(node))
+ result.setForeground(RED);
+ }
+
+ private String describeList(DefaultMutableTreeNode node, TreeNode parent) {
+ if (node.getChildCount() == 0)
+ return "Empty list";
+ StringBuilder builder = new StringBuilder("List");
+ if (parent != null)
+ builder.append(" ").append(parent.getIndex(node) + 1);
+ int valueCount = node.getLeafCount();
+ builder.append(" with ").append(valueCount).append(" value");
+ if (valueCount != 1)
+ builder.append("s");
+ int sublistCount = getSublistCount(node);
+ if (sublistCount > 0) {
+ builder.append(" in ").append(sublistCount).append(" sublist");
+ if (sublistCount != 1)
+ builder.append("s");
+ }
+ return builder.toString();
+ }
+
+ public int getSublistCount(TreeNode node) {
+ int result = 0;
+ Enumeration<?> children = node.children();
+ while (children.hasMoreElements()) {
+ Object nextElement = children.nextElement();
+ if (nextElement instanceof TreeNode) {
+ TreeNode childNode = (TreeNode) nextElement;
+ if (childNode.getChildCount() != 0)
+ result++;
+ }
+ }
+ return result;
+ }
+
+ private static boolean containsError(TreeNode node) {
+ boolean result = false;
+ if (node instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode rtn = (DefaultMutableTreeNode) node;
+ Path reference = (Path) rtn.getUserObject();
+ if (reference != null && DataBundles.isError(reference))
+ result = true;
+ }
+ int childCount = node.getChildCount();
+ for (int i = 0; (i < childCount) && !result; i++)
+ result |= containsError(node.getChildAt(i));
+ return result;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/PortResultsViewTab.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/PortResultsViewTab.java
new file mode 100644
index 0000000..4b7bed3
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/PortResultsViewTab.java
@@ -0,0 +1,283 @@
+/*******************************************************************************
+ * 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.workflow;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.WEST;
+import static java.awt.Font.BOLD;
+import static java.lang.Math.round;
+import static javax.swing.JSplitPane.HORIZONTAL_SPLIT;
+import static javax.swing.SwingUtilities.invokeLater;
+import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTree;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+import net.sf.taverna.t2.workbench.views.results.workflow.FilteredDataBundleTreeModel.FilterType;
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.scufl2.api.port.Port;
+
+/**
+ * A tab containing result tree for an output port and a panel with rendered result
+ * of the currently selected node in the tree.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class PortResultsViewTab extends JPanel implements Updatable {
+ private enum State {NO_DATA, SINGLE_VALUE, LIST}
+
+ private State state;
+ /** Tree model of results */
+ private DataBundleTreeModel resultModel;
+ private FilteredDataBundleTreeModel filteredTreeModel;
+ /** Rendered result component */
+ private RenderedResultComponent renderedResultComponent;
+ private JTree tree;
+ private JComboBox<FilterType> filterChoiceBox;
+ private final RendererRegistry rendererRegistry;
+ private final List<SaveIndividualResultSPI> saveActions;
+ private final Port port;
+ private Path value;
+
+ public PortResultsViewTab(Port port, Path value, RendererRegistry rendererRegistry,
+ List<SaveIndividualResultSPI> saveActions) {
+ super(new BorderLayout());
+ this.port = port;
+ this.value = value;
+ this.rendererRegistry = rendererRegistry;
+ this.saveActions = saveActions;
+
+ initComponents();
+ }
+
+ private void initComponents() {
+ removeAll();
+ if (value == null || DataBundles.isMissing(value)) {
+ state = State.NO_DATA;
+ JLabel noDataMessage = new JLabel("No data available", JLabel.CENTER);
+ Font font = noDataMessage.getFont();
+ if (font != null) {
+ font = font.deriveFont(round(font.getSize() * 1.5)).deriveFont(BOLD);
+ noDataMessage.setFont(font);
+ }
+ add(noDataMessage, CENTER);
+ } else if (DataBundles.isList(value)) {
+ state = State.LIST;
+ initListComponents();
+ } else {
+ state = State.SINGLE_VALUE;
+ // Component for rendering individual results
+ renderedResultComponent = new RenderedResultComponent(rendererRegistry, saveActions);
+ renderedResultComponent.setPath(value);
+ add(renderedResultComponent, CENTER);
+ }
+ revalidate();
+ }
+
+ private void initListComponents() {
+ // Results tree (containing DataBundle Paths to all individual results for this port)
+ resultModel = new DataBundleTreeModel(value);
+ filteredTreeModel = new FilteredDataBundleTreeModel(resultModel);
+ tree = new JTree(filteredTreeModel);
+ tree.getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
+ tree.setExpandsSelectedPaths(true);
+ tree.setRootVisible(true);
+ tree.setShowsRootHandles(true);
+ tree.setCellRenderer(new PortResultCellRenderer());
+
+ tree.addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ TreePath selectionPath = e.getNewLeadSelectionPath();
+ if (selectionPath != null) {
+ // Get the selected node
+ DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath
+ .getLastPathComponent();
+ renderedResultComponent.setPath((Path) selectedNode.getUserObject());
+ }
+ }
+ });
+
+ filteredTreeModel.addTreeModelListener(new TreeModelListener() {
+ @Override
+ public void treeNodesChanged(TreeModelEvent e) {
+ TreePath treePath = e.getTreePath();
+ tree.expandPath(treePath);
+ // If nothing is currently selected in the tree - select the first item in the result list
+ if (tree.getSelectionRows() == null
+ || tree.getSelectionRows().length == 0) {
+ DefaultMutableTreeNode firstLeaf = ((DefaultMutableTreeNode) filteredTreeModel
+ .getRoot()).getFirstLeaf();
+ tree.setSelectionPath(new TreePath(firstLeaf.getPath()));
+ }
+ }
+
+ @Override
+ public void treeNodesInserted(TreeModelEvent e) {
+ }
+
+ @Override
+ public void treeNodesRemoved(TreeModelEvent e) {
+ }
+
+ @Override
+ public void treeStructureChanged(TreeModelEvent e) {
+ }
+ });
+
+ // Component for rendering individual results
+ renderedResultComponent = new RenderedResultComponent(rendererRegistry, saveActions);
+
+ /*
+ * Split pane containing a tree with all results from an output port and
+ * rendered result component for individual result rendered currently
+ * selected from the tree
+ */
+ JSplitPane splitPanel = new JSplitPane(HORIZONTAL_SPLIT);
+
+ JPanel leftPanel = new JPanel(new BorderLayout());
+
+ JPanel treeSubPanel = new JPanel(new BorderLayout());
+ treeSubPanel.add(new JLabel("Click in tree to"), WEST);
+ filterChoiceBox = new JComboBox<>(new FilterType[] { FilterType.ALL,
+ FilterType.RESULTS, FilterType.ERRORS });
+ filterChoiceBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateTree();
+ }
+ });
+ treeSubPanel.add(filterChoiceBox);
+ leftPanel.add(treeSubPanel, NORTH);
+ leftPanel.add(new JScrollPane(tree), CENTER);
+ splitPanel.setTopComponent(leftPanel);
+ splitPanel.setBottomComponent(renderedResultComponent);
+ splitPanel.setDividerLocation(400);
+
+ // Add all to main panel
+ add(splitPanel, CENTER);
+
+ DefaultMutableTreeNode firstLeaf = ((DefaultMutableTreeNode) filteredTreeModel
+ .getRoot()).getFirstLeaf();
+ tree.setSelectionPath(new TreePath(firstLeaf.getPath()));
+ }
+
+ @Override
+ public void update() {
+ if (value == null || DataBundles.isMissing(value)) {
+ if (state != State.NO_DATA)
+ scheduleInitComponents();
+ } else if (DataBundles.isList(value)) {
+ if (state != State.LIST)
+ scheduleInitComponents();
+ else if (resultModel != null)
+ resultModel.update();
+ } else {
+ if (state != State.SINGLE_VALUE)
+ scheduleInitComponents();
+ }
+ }
+
+ private void scheduleInitComponents() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ initComponents();
+ }
+ });
+ }
+
+ public Port getPort() {
+ return port;
+ }
+
+ public FilteredDataBundleTreeModel getModel() {
+ return filteredTreeModel;
+ }
+
+ private List<TreePath> expandedPaths = new ArrayList<>();
+ private TreePath selectionPath = null;
+
+ private void rememberPaths() {
+ expandedPaths.clear();
+ for (Enumeration<TreePath> e = tree
+ .getExpandedDescendants(new TreePath(filteredTreeModel
+ .getRoot())); (e != null) && e.hasMoreElements();)
+ expandedPaths.add(e.nextElement());
+ selectionPath = tree.getSelectionPath();
+ }
+
+ private void reinstatePaths() {
+ for (TreePath path : expandedPaths)
+ if (filteredTreeModel.isShown((DefaultMutableTreeNode) path
+ .getLastPathComponent()))
+ tree.expandPath(path);
+ if (selectionPath != null) {
+ if (filteredTreeModel
+ .isShown((DefaultMutableTreeNode) selectionPath
+ .getLastPathComponent()))
+ tree.setSelectionPath(selectionPath);
+ else {
+ tree.clearSelection();
+ renderedResultComponent.clearResult();
+ }
+ }
+ }
+
+ private void updateTree() {
+ filteredTreeModel.setFilter((FilterType) filterChoiceBox
+ .getSelectedItem());
+ rememberPaths();
+ filteredTreeModel.reload();
+ tree.setModel(filteredTreeModel);
+ reinstatePaths();
+ }
+
+ public void expandTree() {
+ if (tree != null)
+ for (int row = 0; row < tree.getRowCount(); row++)
+ tree.expandRow(row);
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/RenderedResultComponent.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/RenderedResultComponent.java
new file mode 100644
index 0000000..f1a76d3
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/RenderedResultComponent.java
@@ -0,0 +1,601 @@
+/*******************************************************************************
+ * 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.workflow;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Color.GRAY;
+import static java.awt.event.ItemEvent.SELECTED;
+import static javax.swing.BoxLayout.LINE_AXIS;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.results.ResultsUtils.getMimeTypes;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.refreshIcon;
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BoxLayout;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTree;
+import javax.swing.ListCellRenderer;
+import javax.swing.text.JTextComponent;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererException;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.databundle.ErrorDocument;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import eu.medsea.mimeutil.MimeType;
+
+/**
+ * Creates a component that renders an individual result from an output port.
+ * The component can render the result according to the renderers existing for
+ * the output port's MIME type or display an error document.
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class RenderedResultComponent extends JPanel {
+ private static final Logger logger = Logger.getLogger(RenderedResultComponent.class);
+ private static final String WRAP_TEXT = "Wrap text";
+ private static final String ERROR_DOCUMENT = "Error Document";
+
+ /** Panel containing rendered result*/
+ private JPanel renderedResultPanel;
+ /** Combo box containing possible result types*/
+ private JComboBox<String> renderersComboBox;
+ /**
+ * Button to refresh (re-render) the result, especially needed for large
+ * results that are not rendered or are partially rendered and the user
+ * wished to re-render them
+ */
+ private JButton refreshButton;
+ /**
+ * Preferred result type renderers (the ones recognised to be able to handle
+ * the result's MIME type)
+ */
+ private List<Renderer> recognisedRenderersForMimeType;
+ /**
+ * All other result type renderers (the ones not recognised to be able to
+ * handle the result's MIME type) in case user wants to use them.
+ */
+ private List<Renderer> otherRenderers;
+ /** Renderers' registry */
+ private final RendererRegistry rendererRegistry;
+ /**
+ * List of all MIME strings from all available renderers to be used for
+ * {@link #renderersComboBox}. Those that come from
+ * {@link #recognisedRenderersForMimeType} are the preferred ones. Those
+ * from {@link #otherRenderers} will be greyed-out in the combobox list but
+ * could still be used.
+ */
+ private String[] mimeList;
+ /**
+ * List of all available renderers but ordered to match the corresponding
+ * MIME type strings in mimeList: first the preferred renderers from
+ * {@link #recognisedRenderersForMimeType} then the ones from
+ * {@link #otherRenderers}.
+ */
+ private ArrayList<Renderer> rendererList;
+ /**
+ * Remember the MIME type of the last used renderer. Use "
+ * <tt>text/plain</tt>" by default until user changes it - then use that one
+ * for all result items of the port (in case result contains a list). "
+ * <tt>text/plain</tt>" will always be added to the {@link #mimeList}.
+ */
+ // text renderer will always be available
+ private String lastUsedMIMEtype = "text/plain";
+ /** If result is "text/plain" - provide possibility to wrap wide text */
+ private JCheckBox wrapTextCheckBox;
+ /** Reference to the object being displayed (contained in the tree node) */
+ private Path path;
+ /**
+ * In case the node can be rendered as "<tt>text/plain</tt>", map the hash
+ * code of the node to the wrap text check box selection value for that node
+ * (that remembers if user wanted the text wrapped or not).
+ */
+ private Map<Path, Boolean> nodeToWrapSelection = new HashMap<>();
+ /** List of all output ports - needs to be passed to 'save result' actions. */
+ List<OutputWorkflowPort> dataflowOutputPorts = null;
+ /** Panel containing all 'save results' buttons */
+ JPanel saveButtonsPanel = null;
+
+ /**
+ * Creates the component.
+ */
+ public RenderedResultComponent(RendererRegistry rendererRegistry,
+ List<SaveIndividualResultSPI> saveActions) {
+ this.rendererRegistry = rendererRegistry;
+ setLayout(new BorderLayout());
+
+ // Results type combo box
+ renderersComboBox = new JComboBox<>();
+ renderersComboBox.setModel(new DefaultComboBoxModel<String>()); // initially empty
+
+ renderersComboBox.setRenderer(new ColorCellRenderer());
+ renderersComboBox.setEditable(false);
+ renderersComboBox.setEnabled(false); // initially disabled
+
+ // Set the new listener - listen for changes in the currently selected renderer
+ renderersComboBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED
+ && !ERROR_DOCUMENT.equals(e.getItem()))
+ // render the result using the newly selected renderer
+ renderResult();
+ }
+ });
+
+ JPanel resultsTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ resultsTypePanel.add(new JLabel("Value type"));
+ resultsTypePanel.add(renderersComboBox);
+
+ // Refresh (re-render) button
+ refreshButton = new JButton("Refresh", refreshIcon);
+ refreshButton.setEnabled(false);
+ refreshButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ renderResult();
+ refreshButton.getParent().requestFocusInWindow();
+ /*
+ * so that the button does not stay focused after it is clicked
+ * on and did its action
+ */
+ }
+ });
+ resultsTypePanel.add(refreshButton);
+
+ // Check box for wrapping text if result is of type "text/plain"
+ wrapTextCheckBox = new JCheckBox(WRAP_TEXT);
+ wrapTextCheckBox.setVisible(false);
+ wrapTextCheckBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ // Should have only one child component holding the rendered result
+ // Check for empty just as well
+ if (renderedResultPanel.getComponents().length == 0)
+ return;
+ Component component = renderedResultPanel.getComponent(0);
+ if (component instanceof DialogTextArea) {
+ nodeToWrapSelection.put(path,
+ e.getStateChange() == SELECTED);
+ renderResult();
+ }
+ }
+ });
+
+ resultsTypePanel.add(wrapTextCheckBox);
+
+ // 'Save result' buttons panel
+ saveButtonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ for (SaveIndividualResultSPI action : saveActions) {
+ action.setResultReference(null);
+ final JButton saveButton = new JButton(action.getAction());
+ saveButton.setEnabled(false);
+ saveButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveButton.getParent().requestFocusInWindow();
+ /*
+ * so that the button does not stay focused after it is
+ * clicked on and did its action
+ */
+ }
+ });
+ saveButtonsPanel.add(saveButton);
+ }
+
+ // Top panel contains result type combobox and various save buttons
+ JPanel topPanel = new JPanel();
+ topPanel.setLayout(new BoxLayout(topPanel, LINE_AXIS));
+ topPanel.add(resultsTypePanel);
+ topPanel.add(saveButtonsPanel);
+
+ // Rendered results panel - initially empty
+ renderedResultPanel = new JPanel(new BorderLayout());
+
+ // Add all components
+ add(topPanel, NORTH);
+ add(new JScrollPane(renderedResultPanel), CENTER);
+ }
+
+ /**
+ * Sets the path this components renders the results for, and update the
+ * rendered results panel.
+ */
+ public void setPath(final Path path) {
+ this.path = path;
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (path == null || DataBundles.isList(path))
+ clearResult();
+ else
+ updateResult();
+ }
+ });
+ }
+
+ /**
+ * Update the component based on the node selected from the
+ * ResultViewComponent tree.
+ */
+ public void updateResult() {
+ if (recognisedRenderersForMimeType == null)
+ recognisedRenderersForMimeType = new ArrayList<>();
+ if (otherRenderers == null)
+ otherRenderers = new ArrayList<>();
+
+ // Enable the combo box
+ renderersComboBox.setEnabled(true);
+
+ /*
+ * Update the 'save result' buttons appropriately as the result node had
+ * changed
+ */
+ for (int i = 0; i < saveButtonsPanel.getComponents().length; i++) {
+ JButton saveButton = (JButton) saveButtonsPanel.getComponent(i);
+ SaveIndividualResultSPI action = (SaveIndividualResultSPI) saveButton
+ .getAction();
+ // Update the action with the new result reference
+ action.setResultReference(path);
+ saveButton.setEnabled(true);
+ }
+
+ if (DataBundles.isValue(path) || DataBundles.isReference(path)) {
+ // Enable refresh button
+ refreshButton.setEnabled(true);
+
+ List<MimeType> mimeTypes = new ArrayList<>();
+ try (InputStream inputstream = getInputStream(path)) {
+ mimeTypes.addAll(getMimeTypes(inputstream));
+ } catch (IOException e) {
+ logger.warn("Error getting mimetype", e);
+ }
+
+ if (mimeTypes.isEmpty())
+ // If MIME types is empty - add "plain/text" MIME type
+ mimeTypes.add(new MimeType("text/plain"));
+ else if (mimeTypes.size() == 1
+ && mimeTypes.get(0).toString().equals("chemical/x-fasta")) {
+ /*
+ * If MIME type is recognised as "chemical/x-fasta" only then
+ * this might be an error from MIME magic (i.e., sometimes it
+ * recognises stuff that is not "chemical/x-fasta" as
+ * "chemical/x-fasta" and then Seq Vista renderer is used that
+ * causes errors) - make sure we also add the renderers for
+ * "text/plain" and "text/xml" as it is most probably just
+ * normal xml text and push the "chemical/x-fasta" to the bottom
+ * of the list.
+ */
+ mimeTypes.add(0, new MimeType("text/plain"));
+ mimeTypes.add(1, new MimeType("text/xml"));
+ }
+
+ for (MimeType mimeType : mimeTypes) {
+ List<Renderer> renderersList = rendererRegistry.getRenderersForMimeType(mimeType
+ .toString());
+ for (Renderer renderer : renderersList)
+ if (!recognisedRenderersForMimeType.contains(renderer))
+ recognisedRenderersForMimeType.add(renderer);
+ }
+ // if there are no renderers then force text/plain
+ if (recognisedRenderersForMimeType.isEmpty())
+ recognisedRenderersForMimeType = rendererRegistry
+ .getRenderersForMimeType("text/plain");
+
+ /*
+ * Add all other available renderers that are not recognised to be
+ * able to handle the MIME type of the result
+ */
+ otherRenderers = new ArrayList<>(rendererRegistry.getRenderers());
+ otherRenderers.removeAll(recognisedRenderersForMimeType);
+
+ mimeList = new String[recognisedRenderersForMimeType.size()
+ + otherRenderers.size()];
+ rendererList = new ArrayList<>();
+
+ /*
+ * First add the ones that can handle the MIME type of the result
+ * item
+ */
+ for (int i = 0; i < recognisedRenderersForMimeType.size(); i++) {
+ mimeList[i] = recognisedRenderersForMimeType.get(i).getType();
+ rendererList.add(recognisedRenderersForMimeType.get(i));
+ }
+ // Then add the other renderers just in case
+ for (int i = 0; i < otherRenderers.size(); i++) {
+ mimeList[recognisedRenderersForMimeType.size() + i] = otherRenderers.get(i)
+ .getType();
+ rendererList.add(otherRenderers.get(i));
+ }
+
+ renderersComboBox.setModel(new DefaultComboBoxModel<String>(mimeList));
+
+ if (mimeList.length > 0) {
+ int index = 0;
+
+ // Find the index of the current MIME type for this output port.
+ for (int i = 0; i < mimeList.length; i++)
+ if (mimeList[i].equals(lastUsedMIMEtype)) {
+ index = i;
+ break;
+ }
+
+ int previousindex = renderersComboBox.getSelectedIndex();
+ renderersComboBox.setSelectedIndex(index);
+ /*
+ * force rendering as setSelectedIndex will not fire an
+ * itemstatechanged event if previousindex == index and we still
+ * need render the result as we may have switched from a
+ * different result item in a result list but the renderer index
+ * stayed the same
+ */
+ if (previousindex == index)
+ renderResult(); // draw the rendered result component
+ }
+
+ } else if (DataBundles.isError(path)) {
+ // Disable refresh button
+ refreshButton.setEnabled(false);
+
+ // Hide wrap text check box - only works for actual data
+ wrapTextCheckBox.setVisible(false);
+
+ // Reset the renderers as we have an error item
+ recognisedRenderersForMimeType = null;
+ otherRenderers = null;
+
+ DefaultMutableTreeNode root = new DefaultMutableTreeNode("Error Trace");
+
+ try {
+ ErrorDocument errorDocument = DataBundles.getError(path);
+ try {
+ buildErrorDocumentTree(root, errorDocument);
+ } catch (IOException e) {
+ logger.warn("Error building error document tree", e);
+ }
+ } catch (IOException e) {
+ logger.warn("Error getting the error document", e);
+ }
+
+ JTree errorTree = new JTree(root);
+ errorTree.setCellRenderer(new DefaultTreeCellRenderer() {
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value, boolean selected, boolean expanded,
+ boolean leaf, int row, boolean hasFocus) {
+ Component renderer = null;
+ if (value instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
+ Object userObject = treeNode.getUserObject();
+ if (userObject instanceof ErrorDocument)
+ renderer = renderErrorDocument(tree, selected,
+ expanded, leaf, row, hasFocus,
+ (ErrorDocument) userObject);
+ }
+ if (renderer == null)
+ renderer = super.getTreeCellRendererComponent(tree,
+ value, selected, expanded, leaf, row, hasFocus);
+ if (renderer instanceof JLabel) {
+ JLabel label = (JLabel) renderer;
+ label.setIcon(null);
+ }
+ return renderer;
+ }
+
+ private Component renderErrorDocument(JTree tree,
+ boolean selected, boolean expanded, boolean leaf,
+ int row, boolean hasFocus, ErrorDocument errorDocument) {
+ return super.getTreeCellRendererComponent(tree, "<html>"
+ + escapeHtml(errorDocument.getMessage())
+ + "</html>", selected, expanded, leaf, row,
+ hasFocus);
+ }
+ });
+
+ renderersComboBox.setModel(new DefaultComboBoxModel<>(
+ new String[] { ERROR_DOCUMENT }));
+ renderedResultPanel.removeAll();
+ renderedResultPanel.add(errorTree, CENTER);
+ repaint();
+ }
+ }
+
+ public void buildErrorDocumentTree(DefaultMutableTreeNode node,
+ ErrorDocument errorDocument) throws IOException {
+ DefaultMutableTreeNode child = new DefaultMutableTreeNode(errorDocument);
+ String trace = errorDocument.getTrace();
+ if (trace != null && !trace.isEmpty())
+ for (String line : trace.split("\n"))
+ child.add(new DefaultMutableTreeNode(line));
+ node.add(child);
+
+ List<Path> causes = errorDocument.getCausedBy();
+ for (Path cause : causes)
+ if (DataBundles.isError(cause)) {
+ ErrorDocument causeErrorDocument = DataBundles.getError(cause);
+ if (causes.size() == 1)
+ buildErrorDocumentTree(node, causeErrorDocument);
+ else
+ buildErrorDocumentTree(child, causeErrorDocument);
+ } else if (DataBundles.isList(cause)) {
+ List<ErrorDocument> errorDocuments = getErrorDocuments(cause);
+ if (errorDocuments.size() == 1)
+ buildErrorDocumentTree(node, errorDocuments.get(0));
+ else
+ for (ErrorDocument errorDocument2 : errorDocuments)
+ buildErrorDocumentTree(child, errorDocument2);
+ }
+ }
+
+ public List<ErrorDocument> getErrorDocuments(Path reference)
+ throws IOException {
+ List<ErrorDocument> errorDocuments = new ArrayList<>();
+ if (DataBundles.isError(reference))
+ errorDocuments.add(DataBundles.getError(reference));
+ else if (DataBundles.isList(reference))
+ for (Path element : DataBundles.getList(reference))
+ errorDocuments.addAll(getErrorDocuments(element));
+ return errorDocuments;
+ }
+
+ /**
+ * Renders the result panel using the last used renderer.
+ */
+ public void renderResult() {
+ if (ERROR_DOCUMENT.equals(renderersComboBox.getSelectedItem()))
+ // skip error documents - do not (re)render
+ return;
+
+ int selectedIndex = renderersComboBox.getSelectedIndex();
+ if (mimeList != null && selectedIndex >= 0) {
+ Renderer renderer = rendererList.get(selectedIndex);
+
+ if (renderer.getType().equals("Text")) { // if the result is "text/plain"
+ /*
+ * We use node's hash code as the key in the nodeToWrapCheckBox
+ * map as node's user object may be too large
+ */
+ if (nodeToWrapSelection.get(path) == null)
+ // initially not selected
+ nodeToWrapSelection.put(path, false);
+ wrapTextCheckBox.setSelected(nodeToWrapSelection.get(path));
+ wrapTextCheckBox.setVisible(true);
+ } else
+ wrapTextCheckBox.setVisible(false);
+ /*
+ * Remember the last used renderer - use it for all result items of
+ * this port
+ */
+ // currentRendererIndex = selectedIndex;
+ lastUsedMIMEtype = mimeList[selectedIndex];
+
+ JComponent component = null;
+ try {
+ component = renderer.getComponent(path);
+ if (component instanceof DialogTextArea)
+ if (wrapTextCheckBox.isSelected())
+ ((JTextArea) component).setLineWrap(wrapTextCheckBox.isSelected());
+ if (component instanceof JTextComponent)
+ ((JTextComponent) component).setEditable(false);
+ else if (component instanceof JTree)
+ ((JTree) component).setEditable(false);
+ } catch (RendererException e1) {
+ // maybe this should be Exception
+ /*
+ * show the user that something unexpected has happened but
+ * continue
+ */
+ component = new DialogTextArea(
+ "Could not render using renderer type "
+ + renderer.getClass()
+ + "\n"
+ + "Please try with a different renderer if available and consult log for details of problem");
+ ((DialogTextArea) component).setEditable(false);
+ logger.warn("Couln not render using " + renderer.getClass(), e1);
+ }
+ renderedResultPanel.removeAll();
+ renderedResultPanel.add(component, CENTER);
+ repaint();
+ revalidate();
+ }
+ }
+
+ /**
+ * Clears the result panel.
+ */
+ public void clearResult() {
+ refreshButton.setEnabled(false);
+ wrapTextCheckBox.setVisible(false);
+ renderedResultPanel.removeAll();
+
+ // Update the 'save result' buttons appropriately
+ for (int i = 0; i < saveButtonsPanel.getComponents().length; i++) {
+ JButton saveButton = (JButton) saveButtonsPanel.getComponent(i);
+ SaveIndividualResultSPI action = (SaveIndividualResultSPI) saveButton
+ .getAction();
+ // Update the action
+ action.setResultReference(null);
+ saveButton.setEnabled(false);
+ }
+
+ renderersComboBox.setModel(new DefaultComboBoxModel<String>());
+ renderersComboBox.setEnabled(false);
+
+ revalidate();
+ repaint();
+ }
+
+ class ColorCellRenderer implements ListCellRenderer<String> {
+ protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
+
+ @Override
+ public Component getListCellRendererComponent(
+ JList<? extends String> list, String value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ JComponent renderer = (JComponent) defaultRenderer
+ .getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ if (recognisedRenderersForMimeType == null) // error occurred
+ return renderer;
+ if (value != null && index >= recognisedRenderersForMimeType.size())
+ // one of the non-preferred renderers - show it in grey
+ renderer.setForeground(GRAY);
+ return renderer;
+ }
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultTreeModel.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultTreeModel.java
new file mode 100644
index 0000000..1f933c1
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultTreeModel.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * 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.workflow;
+
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_LIST;
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_REFERENCE;
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_WAITING;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+
+import javax.swing.tree.DefaultTreeModel;
+
+import net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.platform.report.ReportListener;
+import uk.org.taverna.platform.report.State;
+
+public class WorkflowResultTreeModel extends DefaultTreeModel implements ReportListener {
+ private static final long serialVersionUID = 7154527821423588046L;
+ private static final Logger logger = Logger.getLogger(WorkflowResultTreeModel.class);
+
+ /** Name of the output port this class models results for */
+ private String portName;
+ int depthSeen = -1;
+
+ public WorkflowResultTreeModel(String portName) {
+ super(new WorkflowResultTreeNode(ResultTreeNodeState.RESULT_TOP));
+ this.portName = portName;
+ }
+
+ @Override
+ public void outputAdded(final Path path, final String portName, final int[] index) {
+ // Don't slow down workflow execution, do it in GUI thread
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ resultTokenProducedGui(path, portName, index);
+ }
+ });
+ }
+
+ @Override
+ public void stateChanged(State oldState, State newState) {
+ // TODO Auto-generated method stub
+ }
+
+ public void resultTokenProducedGui(Path path, String portName, int[] index) {
+ if (!this.portName.equals(portName))
+ return;
+ if (depthSeen == -1)
+ depthSeen = index.length;
+ else if (index.length < depthSeen)
+ return;
+
+ Path reference = path;
+ if (!DataBundles.isList(reference)) {
+ insertNewDataTokenNode(reference, index);
+ return;
+ }
+
+ try {
+ WorkflowResultTreeNode parent = (WorkflowResultTreeNode) getRoot();
+ parent = getChildAt(parent,0);
+ changeState(parent, RESULT_LIST);
+ for (int i = 0; i < index.length; i++) {
+ parent = getChildAt(parent, index[i]);
+ changeState(parent, RESULT_LIST);
+ }
+
+ List<Path> list = DataBundles.getList(reference);
+ int[] elementIndex = new int[index.length + 1];
+ for (int indexElement = 0; indexElement < index.length; indexElement++)
+ elementIndex[indexElement] = index[indexElement];
+ int c = 0;
+ for (Path id : list) {
+ elementIndex[index.length] = c;
+ resultTokenProducedGui(id, portName, elementIndex);
+ c++;
+ }
+ //if (c == 0) {
+ // parent.setUserObject("Empty list (depth=" + reference.getDepth() + ")" + reference.getLocalPart());
+ // nodeChanged(parent);
+ //}
+ } catch (NullPointerException | IOException e) {
+ logger.error("Error resolving data entity list " + reference, e);
+ }
+ }
+
+ public void insertNewDataTokenNode(Path reference, int[] index) {
+ WorkflowResultTreeNode parent = (WorkflowResultTreeNode) getRoot();
+ if (DataBundles.isError(reference)) {
+ parent = getChildAt(parent, 0);
+ for (int i = 0; i < index.length - 1; i++) {
+ parent = getChildAt(parent, index[i]);
+ parent = getChildAt(parent, 0);
+ changeState(parent, RESULT_LIST);
+ }
+ if (index.length > 0) {
+ WorkflowResultTreeNode child = getChildAt(parent,
+ index[index.length - 1]);
+ updateNodeWithData(child, reference);
+ } else
+ updateNodeWithData(parent, reference);
+ } else {
+ int depth = index.length;
+ if (depth == 0) {
+ WorkflowResultTreeNode child = getChildAt(parent, 0);
+ updateNodeWithData(child, reference);
+ } else {
+ parent = getChildAt(parent, 0);
+ changeState(parent, RESULT_LIST);
+ for (int indexElement = 0; indexElement < depth; indexElement++) {
+ WorkflowResultTreeNode child = getChildAt(parent,
+ index[indexElement]);
+ if (indexElement == depth - 1) // leaf
+ updateNodeWithData(child, reference);
+ else { // list
+ child.setState(RESULT_LIST);
+ nodeChanged(child);
+ }
+ parent = child;
+ }
+ }
+ }
+ }
+
+ private void updateNodeWithData(WorkflowResultTreeNode node, Path reference) {
+ node.setState(RESULT_REFERENCE);
+ node.setReference(reference);
+ nodeChanged(node);
+ }
+
+ private WorkflowResultTreeNode getChildAt(WorkflowResultTreeNode parent,
+ int i) {
+ int childCount = getChildCount(parent);
+ if (childCount <= i)
+ for (int x = childCount; x <= i; x++)
+ insertNodeInto(new WorkflowResultTreeNode(RESULT_WAITING),
+ parent, x);
+ return (WorkflowResultTreeNode) parent.getChildAt(i);
+ }
+
+ private void changeState(WorkflowResultTreeNode node, ResultTreeNodeState state) {
+ if (!node.isState(state)) {
+ node.setState(state);
+ nodeChanged(node);
+ }
+ }
+
+ // Normally used for past workflow runs where data is obtained from provenance
+ public void createTree(Path path, WorkflowResultTreeNode parentNode){
+ // If reference contains a list of data references
+ if (DataBundles.isList(path))
+ // insert list node
+ try {
+ List<Path> list = DataBundles.getList(path);
+ WorkflowResultTreeNode listNode = new WorkflowResultTreeNode(
+ path, RESULT_LIST);
+ insertNodeInto(listNode, parentNode, parentNode.getChildCount());
+ for (Path ref : list)
+ createTree(ref, listNode);
+ } catch (IOException e) {
+ logger.error("Error resolving data entity list " + path, e);
+ }
+ else { // reference to single data or an error
+ // insert data node
+ WorkflowResultTreeNode dataNode = new WorkflowResultTreeNode(path,
+ RESULT_REFERENCE);
+ insertNodeInto(dataNode, parentNode, parentNode.getChildCount());
+ }
+ }
+
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultTreeNode.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultTreeNode.java
new file mode 100644
index 0000000..5761793
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultTreeNode.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.workflow;
+
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_LIST;
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_REFERENCE;
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_TOP;
+import static net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultTreeNode.ResultTreeNodeState.RESULT_WAITING;
+
+import java.nio.file.Path;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkflowResultTreeNode extends DefaultMutableTreeNode {
+ public enum ResultTreeNodeState {
+ RESULT_TOP, RESULT_WAITING, RESULT_LIST, RESULT_REFERENCE
+ };
+
+ @SuppressWarnings("unused")
+ private static final Logger logger = Logger.getLogger(WorkflowResultTreeNode.class);
+
+ private Path path;
+ private ResultTreeNodeState state;
+
+ public WorkflowResultTreeNode(Path reference, ResultTreeNodeState state) {
+ this.path = reference;
+ this.state = state;
+ }
+
+ public WorkflowResultTreeNode(ResultTreeNodeState state) {
+ this.path = null;
+ this.state = state;
+ }
+
+ public boolean isState(ResultTreeNodeState state) {
+ return this.state.equals(state);
+ }
+
+ public ResultTreeNodeState getState() {
+ return state;
+ }
+
+ public void setState(ResultTreeNodeState state) {
+ this.state = state;
+ }
+
+ public Path getReference() {
+ if (!isState(RESULT_TOP))
+ return path;
+ if (getChildCount() == 0)
+ return null;
+ return ((WorkflowResultTreeNode) getChildAt(0)).getReference();
+ }
+
+ public void setReference(Path reference) {
+ this.path = reference;
+ }
+
+ @Override
+ public String toString() {
+ if (state.equals(RESULT_TOP))
+ return "Results:";
+ if (state.equals(RESULT_LIST)) {
+ if (getChildCount() == 0)
+ return "Empty list";
+ return "List...";
+ }
+ if (state.equals(RESULT_WAITING))
+ return "Waiting for data";
+ return path.toString();
+ }
+
+ public int getValueCount() {
+ int result = 0;
+ if (isState(RESULT_REFERENCE))
+ result = 1;
+ else if (isState(RESULT_LIST)) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WorkflowResultTreeNode child = (WorkflowResultTreeNode) getChildAt(i);
+ result += child.getValueCount();
+ }
+ }
+ return result;
+ }
+
+ public int getSublistCount() {
+ int result = 0;
+ if (isState(RESULT_LIST)) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WorkflowResultTreeNode child = (WorkflowResultTreeNode) getChildAt(i);
+ if (child.isState(RESULT_LIST))
+ result++;
+ }
+ }
+ return result;
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultsComponent.java b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultsComponent.java
new file mode 100644
index 0000000..a937787
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/java/net/sf/taverna/t2/workbench/views/results/workflow/WorkflowResultsComponent.java
@@ -0,0 +1,352 @@
+/*******************************************************************************
+ * 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.workflow;
+
+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.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+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.saveAllIcon;
+
+import java.awt.BorderLayout;
+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.io.IOException;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.ui.Updatable;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import net.sf.taverna.t2.workbench.views.results.InvocationView;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI;
+import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.platform.report.WorkflowReport;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.WorkflowPort;
+
+/**
+ * This component contains a tabbed pane, where each tab displays results for one of the output
+ * ports of a workflow, and a set of 'save results' buttons that save results from all ports in a
+ * certain format.
+ *
+ * @author David Withers
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class WorkflowResultsComponent extends JPanel implements UIComponentSPI, Updatable {
+ private static Logger logger = Logger.getLogger(WorkflowResultsComponent.class);
+
+ private InvocationView portValuesComponent;
+ private JPanel saveButtonsPanel;
+ /**
+ * List of all existing 'save results' actions, each one can save results in
+ * a different format
+ */
+ private List<SaveAllResultsSPI> saveActions;
+ private final RendererRegistry rendererRegistry;
+ private final List<SaveIndividualResultSPI> saveIndividualActions;
+ private final WorkflowReport workflowReport;
+ private final Workflow workflow;
+ private Path inputs, outputs;
+
+ public WorkflowResultsComponent(WorkflowReport workflowReport,
+ RendererRegistry rendererRegistry, List<SaveAllResultsSPI> saveActions,
+ List<SaveIndividualResultSPI> saveIndividualActions) {
+ super(new BorderLayout());
+ this.workflowReport = workflowReport;
+ this.rendererRegistry = rendererRegistry;
+ this.saveActions = saveActions;
+ this.saveIndividualActions = saveIndividualActions;
+
+ workflow = workflowReport.getSubject();
+ init();
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Results View Component";
+ }
+
+ @Override
+ public void onDisplay() {
+ }
+
+ @Override
+ public void onDispose() {
+ }
+
+ private void init() {
+ saveButtonsPanel = new JPanel(new BorderLayout());
+ populateSaveButtonsPanel();
+ add(saveButtonsPanel, NORTH);
+
+ portValuesComponent = new InvocationView(workflowReport
+ .getInvocations().first(), rendererRegistry,
+ saveIndividualActions);
+ add(portValuesComponent, CENTER);
+ }
+
+ private void populateSaveButtonsPanel() {
+ JButton saveButton = new JButton(new SaveAllAction("Save all values", this));
+ saveButtonsPanel.add(saveButton, EAST);
+ }
+
+ public void selectWorkflowPortTab(WorkflowPort port) {
+ portValuesComponent.selectPortTab(port);
+ }
+
+ @Override
+ public void update() {
+ portValuesComponent.update();
+ }
+
+ private class SaveAllAction extends AbstractAction {
+
+ public SaveAllAction(String name, WorkflowResultsComponent resultViewComponent) {
+ super(name);
+ putValue(SMALL_ICON, saveAllIcon);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveAll();
+ }
+ }
+
+ private static final String TITLE = "Workflow run data saver";
+ private static final String EXPLANATION = "Select the workflow input and output ports to save the associated data";
+
+ private void saveAll() {
+ 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(EXPLANATION);
+ 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, Path> checkReferences = new HashMap<>();
+ final Map<String, Path> chosenReferences = new HashMap<>();
+ final Set<Action> actionSet = new HashSet<>();
+
+ 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));
+ }
+ };
+ NavigableMap<String, Path> inputPorts = new TreeMap<>();
+ try {
+ inputPorts = DataBundles.getPorts(inputs);
+ } catch (IOException e1) {
+ logger.info("No input ports for worklow " + workflow.getName(), e1);
+ }
+ NavigableMap<String, Path> outputPorts = new TreeMap<>();
+ try {
+ outputPorts = DataBundles.getPorts(outputs);
+ } catch (IOException e1) {
+ logger.info("No output ports for worklow " + workflow.getName(), e1);
+ }
+ JPanel portsPanel = new JPanel();
+ portsPanel.setLayout(new GridBagLayout());
+ if (!workflow.getInputPorts().isEmpty())
+ createInputPortsPanel(inputChecks, checkReferences, listener,
+ inputPorts, outputPorts, portsPanel);
+ if (!workflow.getOutputPorts().isEmpty())
+ createOutputPortsPanel(outputChecks, checkReferences, listener,
+ outputPorts, portsPanel);
+ 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);
+ }
+ 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);
+ }
+
+ private void createInputPortsPanel(Map<String, JCheckBox> inputChecks,
+ Map<JCheckBox, Path> checkReferences, ItemListener listener,
+ NavigableMap<String, Path> inputPorts,
+ NavigableMap<String, Path> outputPorts, JPanel portsPanel) {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = WEST;
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(5, 10, 5, 10);
+ portsPanel.add(new JLabel("Workflow inputs:"), gbc);
+
+ TreeMap<String, JCheckBox> sortedBoxes = new TreeMap<>();
+ for (InputWorkflowPort port : workflowReport.getSubject()
+ .getInputPorts()) {
+ String portName = port.getName();
+ Path value = inputPorts.get(portName);
+ if (value != null) {
+ JCheckBox checkBox = new JCheckBox(portName);
+ checkBox.setSelected(!outputPorts.containsKey(portName));
+ checkBox.addItemListener(listener);
+ inputChecks.put(portName, checkBox);
+ sortedBoxes.put(portName, checkBox);
+ checkReferences.put(checkBox, value);
+ }
+ }
+ 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
+ }
+
+ private void createOutputPortsPanel(Map<String, JCheckBox> outputChecks,
+ Map<JCheckBox, Path> checkReferences, ItemListener listener,
+ NavigableMap<String, Path> outputPorts, JPanel portsPanel) {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ gbc.anchor = WEST;
+ gbc.fill = NONE;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(5, 10, 5, 10);
+ portsPanel.add(new JLabel("Workflow outputs:"), gbc);
+ TreeMap<String, JCheckBox> sortedBoxes = new TreeMap<>();
+ for (OutputWorkflowPort port : workflowReport.getSubject()
+ .getOutputPorts()) {
+ String portName = port.getName();
+ Path value = outputPorts.get(portName);
+ if (value != null) {
+ JCheckBox checkBox = new JCheckBox(portName);
+ checkBox.setSelected(true);
+
+ checkReferences.put(checkBox, value);
+ 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
+ }
+}
diff --git a/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..be2aa5a
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.results.workflow.WorkflowResultsComponent
\ No newline at end of file
diff --git a/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI b/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI
new file mode 100644
index 0000000..f76321e
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsAsXML
+net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsAsExcel
+net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsToFileSystem
diff --git a/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI b/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI
new file mode 100644
index 0000000..69fda7e
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResult
\ No newline at end of file
diff --git a/taverna-workbench-results-view/src/main/resources/META-INF/spring/results-view-context-osgi.xml b/taverna-workbench-results-view/src/main/resources/META-INF/spring/results-view-context-osgi.xml
new file mode 100644
index 0000000..70fdbd1
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/resources/META-INF/spring/results-view-context-osgi.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="SaveIndividualResult" interface="net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI" />
+ <service ref="SaveAllResultsAsXML" interface="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI" />
+ <service ref="SaveAllResultsAsExcel" interface="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI" />
+ <service ref="SaveAllResultsToFileSystem" interface="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI" />
+
+</beans:beans>
diff --git a/taverna-workbench-results-view/src/main/resources/META-INF/spring/results-view-context.xml b/taverna-workbench-results-view/src/main/resources/META-INF/spring/results-view-context.xml
new file mode 100644
index 0000000..dd90895
--- /dev/null
+++ b/taverna-workbench-results-view/src/main/resources/META-INF/spring/results-view-context.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="SaveIndividualResult" class="net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResult" />
+ <bean id="SaveAllResultsAsXML" class="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsAsXML" />
+ <bean id="SaveAllResultsAsExcel" class="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsAsExcel" />
+ <bean id="SaveAllResultsToFileSystem" class="net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsToFileSystem" />
+
+</beans>
diff --git a/taverna-workbench-retry-ui/pom.xml b/taverna-workbench-retry-ui/pom.xml
new file mode 100644
index 0000000..7c22ea6
--- /dev/null
+++ b/taverna-workbench-retry-ui/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-exts</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-exts</groupId>
+ <artifactId>retry-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Retry layer contextual view</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson-databind.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigurationPanel.java b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigurationPanel.java
new file mode 100644
index 0000000..280414a
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigurationPanel.java
@@ -0,0 +1,172 @@
+package net.sf.taverna.t2.workbench.retry;
+
+import java.awt.GridLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+@SuppressWarnings("serial")
+public class RetryConfigurationPanel extends JPanel {
+
+ public static final double DEFAULT_BACKOFF = 1.0;
+ public static final int DEFAULT_INITIAL_DELAY = 1000;
+ public static final int DEFAULT_MAX_DELAY = 5000;
+ public static final int DEFAULT_RETRIES = 0;
+
+ private static final double MIN_BACKOFF = 1.0;
+ private static final int MIN_DELAY = 1;
+ private static final int MIN_RETRIES = 0;
+
+ private final ObjectNode json;
+
+ private JTextField maxRetriesField = new JTextField();
+ private JTextField initialDelayField = new JTextField();
+ private JTextField maximumDelayField = new JTextField();
+ private JTextField backoffFactorField = new JTextField();
+
+ public RetryConfigurationPanel(Configuration configuration) {
+ if (configuration.getJson().has("retry")) {
+ json = (ObjectNode) configuration.getJson().get("retry").deepCopy();
+ } else {
+ json = configuration.getJsonAsObjectNode().objectNode();
+ }
+ this.setLayout(new GridLayout(4,2));
+ this.setBorder(new EmptyBorder(10,10,10,10));
+ populate();
+ }
+
+ public void populate() {
+ readConfiguration();
+ this.removeAll();
+
+ JLabel maxRetriesLabel = new JLabel("Maximum number of retries");
+ maxRetriesLabel.setBorder(new EmptyBorder(0,0,0,10)); // give some right border to this label
+ this.add(maxRetriesLabel);
+ this.add(maxRetriesField);
+
+ this.add(new JLabel("Initial delay in ms"));
+ this.add(initialDelayField);
+
+ this.add(new JLabel("Maximum delay in ms"));
+ this.add(maximumDelayField);
+
+ this.add(new JLabel("Delay increase factor"));
+ this.add(backoffFactorField);
+ }
+
+ private void readConfiguration() {
+ int maxRetries = DEFAULT_RETRIES;
+ int initialDelay = DEFAULT_INITIAL_DELAY;
+ int maxDelay = DEFAULT_MAX_DELAY;
+ double backoffFactor = DEFAULT_BACKOFF;
+
+ if (json.has("maxRetries")) {
+ maxRetries = json.get("maxRetries").asInt();
+ }
+ if (json.has("initialDelay")) {
+ initialDelay = json.get("initialDelay").asInt();
+ }
+ if (json.has("maxDelay")) {
+ maxDelay = json.get("maxDelay").asInt();
+ }
+ if (json.has("backoffFactor")) {
+ backoffFactor = json.get("backoffFactor").asDouble();;
+ }
+
+ if (maxRetries < MIN_RETRIES) {
+ maxRetries = DEFAULT_RETRIES;
+ }
+ if (initialDelay < MIN_DELAY) {
+ initialDelay = DEFAULT_INITIAL_DELAY;
+ }
+ if (maxDelay < MIN_DELAY) {
+ maxDelay = DEFAULT_MAX_DELAY;
+ }
+ if (maxDelay < initialDelay) {
+ maxDelay = initialDelay;
+ }
+ if (backoffFactor < MIN_BACKOFF) {
+ backoffFactor = DEFAULT_BACKOFF;
+ }
+
+ maxRetriesField.setText(Integer.toString(maxRetries));
+ initialDelayField.setText(Integer.toString(initialDelay));
+ maximumDelayField.setText(Integer.toString(maxDelay));
+ backoffFactorField.setText(Double.toString(backoffFactor));
+ }
+
+ public boolean validateConfig() {
+ String errorText = "";
+ int maxRetries = -1;
+ int initialDelay = -1;
+ int maxDelay = -1;
+ float backoffFactor = -1;
+
+ try {
+ maxRetries = Integer.parseInt(maxRetriesField.getText());
+ if (maxRetries < MIN_RETRIES) {
+ errorText += "The number of retries must be non-negative.\n";
+ }
+ }
+ catch (NumberFormatException e) {
+ errorText += "The maximum number of retries must be an integer.\n";
+ }
+
+ try {
+ initialDelay = Integer.parseInt(initialDelayField.getText());
+ if (initialDelay < MIN_DELAY) {
+ errorText += "The initial delay must be a positive integer.\n";
+ }
+ }
+ catch (NumberFormatException e) {
+ errorText += "The initial delay must be an integer.\n";
+ }
+
+ try {
+ maxDelay = Integer.parseInt(maximumDelayField.getText());
+ if (maxDelay < MIN_DELAY) {
+ errorText += "The maximum delay must be a positive integer.\n";
+ }
+ else if (maxDelay < initialDelay) {
+ errorText += "The maximum delay must be greater than the initial delay.\n";
+ maxDelay = Math.max(MIN_DELAY, initialDelay);
+ maximumDelayField.setText(Integer.toString(maxDelay));
+ }
+ }
+ catch (NumberFormatException e) {
+ errorText += "The maximum delay must be an integer.\n";
+ }
+
+ try {
+ backoffFactor = Float.parseFloat(backoffFactorField.getText());
+ if (backoffFactor < MIN_BACKOFF) {
+ errorText += "The backoff factor must be greater than one.\n";
+ }
+ }
+ catch (NumberFormatException e) {
+ errorText += "The backoff factor must be a number.\n";
+ }
+ if (errorText.length() > 0) {
+ JOptionPane.showMessageDialog(this, errorText, "", JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public JsonNode getJson() {
+ json.put("backoffFactor", backoffFactorField.getText());
+ json.put("initialDelay", initialDelayField.getText());
+ json.put("maxDelay", maximumDelayField.getText());
+ json.put("maxRetries", maxRetriesField.getText());
+ return json;
+ }
+
+}
diff --git a/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigureAction.java b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigureAction.java
new file mode 100644
index 0000000..f5f7c7f
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigureAction.java
@@ -0,0 +1,183 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.retry;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.ChangeJsonEdit;
+
+import org.apache.log4j.Logger;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+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;
+
+/**
+ * @author alanrw
+ *
+ */
+@SuppressWarnings("serial")
+public class RetryConfigureAction extends AbstractAction {
+
+ private static Logger logger = Logger.getLogger(RetryConfigureAction.class);
+
+ private final Frame owner;
+ private final Processor processor;
+ private final RetryContextualView retryContextualView;
+
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ private final Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ private Configuration configuration;
+
+ public RetryConfigureAction(Frame owner, RetryContextualView retryContextualView,
+ Processor processor, EditManager editManager, SelectionManager selectionManager) {
+ super("Configure");
+ this.owner = owner;
+ this.retryContextualView = retryContextualView;
+ this.processor = processor;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String title = "Retries for service " + processor.getName();
+ final JDialog dialog = new HelpEnabledDialog(owner, title, true);
+ Configuration configuration;
+ try {
+ configuration = scufl2Tools.configurationFor(processor, selectionManager.getSelectedProfile());
+ } catch (IndexOutOfBoundsException ex) {
+ configuration = new Configuration();
+ }
+ RetryConfigurationPanel retryConfigurationPanel = new RetryConfigurationPanel(configuration);
+ dialog.add(retryConfigurationPanel, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout());
+
+ JButton okButton = new JButton(new OKAction(dialog, retryConfigurationPanel));
+ buttonPanel.add(okButton);
+
+ JButton resetButton = new JButton(new ResetAction(retryConfigurationPanel));
+ buttonPanel.add(resetButton);
+
+ JButton cancelButton = new JButton(new CancelAction(dialog));
+ buttonPanel.add(cancelButton);
+
+ dialog.add(buttonPanel, BorderLayout.SOUTH);
+ dialog.pack();
+ dialog.setLocationRelativeTo(null);
+ dialog.setVisible(true);
+ }
+
+ public class ResetAction extends AbstractAction {
+
+ private final RetryConfigurationPanel retryConfigurationPanel;
+
+ public ResetAction(RetryConfigurationPanel retryConfigurationPanel) {
+ super("Reset");
+ this.retryConfigurationPanel = retryConfigurationPanel;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ retryConfigurationPanel.populate();
+ }
+
+ }
+
+ public class OKAction extends AbstractAction {
+
+ private final RetryConfigurationPanel retryConfigurationPanel;
+ private final JDialog dialog;
+
+ public OKAction(JDialog dialog, RetryConfigurationPanel retryConfigurationPanel) {
+ super("OK");
+ this.dialog = dialog;
+ this.retryConfigurationPanel = retryConfigurationPanel;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (retryConfigurationPanel.validateConfig()) {
+ try {
+ try {
+ Configuration configuration = scufl2Tools.configurationFor(processor, selectionManager.getSelectedProfile());
+ ObjectNode json = configuration.getJsonAsObjectNode().deepCopy();
+ ObjectNode parallelizeNode = null;
+ if (json.has("retry")) {
+ parallelizeNode = (ObjectNode) json.get("retry");
+ } else {
+ parallelizeNode = json.objectNode();
+ json.put("retry", parallelizeNode);
+ }
+ JsonNode newParallelizeNode = retryConfigurationPanel.getJson();
+ Iterator<Entry<String, JsonNode>> fields = newParallelizeNode.fields();
+ while (fields.hasNext()) {
+ Entry<String, JsonNode> entry = fields.next();
+ parallelizeNode.set(entry.getKey(), entry.getValue());
+ }
+ Edit<Configuration> edit = new ChangeJsonEdit(configuration, json);
+ editManager.doDataflowEdit(selectionManager.getSelectedWorkflowBundle(), edit);
+ } catch (IndexOutOfBoundsException ex) {
+ Configuration configuration = new Configuration();
+ configuration.setConfigures(processor);
+ ObjectNode json = configuration.getJsonAsObjectNode();
+ json.put("retry", retryConfigurationPanel.getJson());
+ Edit<Profile> edit = new AddChildEdit<Profile>(selectionManager.getSelectedProfile(), configuration);
+ editManager.doDataflowEdit(selectionManager.getSelectedWorkflowBundle(), edit);
+ }
+ dialog.setVisible(false);
+ if (retryContextualView != null) {
+ retryContextualView.refreshView();
+ }
+ } catch (EditException e1) {
+ logger.warn("Could not configure retries", e1);
+ JOptionPane.showMessageDialog(owner, "Could not configure retries",
+ "An error occured when configuring retries: " + e1.getMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ }
+
+ public class CancelAction extends AbstractAction {
+
+ private final JDialog dialog;
+
+ public CancelAction(JDialog dialog) {
+ super("Cancel");
+ this.dialog = dialog;
+
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(false);
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigureMenuAction.java b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigureMenuAction.java
new file mode 100644
index 0000000..c88fd1e
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryConfigureMenuAction.java
@@ -0,0 +1,77 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.retry;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+public class RetryConfigureMenuAction extends AbstractContextualMenuAction {
+
+ public static final URI configureRunningSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/configureRunning");
+
+ private static final URI RETRY_CONFIGURE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/retryConfigure");
+
+ public static URI TYPE = URI.create("http://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Retry");
+
+ private EditManager editManager;
+
+ private SelectionManager selectionManager;
+
+ public RetryConfigureMenuAction() {
+ super(configureRunningSection, 30, RETRY_CONFIGURE_URI);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Retries...") {
+ public void actionPerformed(ActionEvent e) {
+ Processor processor = (Processor) getContextualSelection().getSelection();
+ RetryConfigureAction retryConfigureAction = new RetryConfigureAction(null,
+ null, processor, editManager, selectionManager);
+ retryConfigureAction.actionPerformed(e);
+ }
+ };
+ }
+
+ public boolean isEnabled() {
+ return super.isEnabled() && (getContextualSelection().getSelection() instanceof Processor);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+}
diff --git a/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryContextualView.java b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryContextualView.java
new file mode 100644
index 0000000..9fef2e7
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryContextualView.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * 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.retry;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+/**
+ * View of a processor, including it's iteration stack, activities, etc.
+ *
+ * @author Alan R Williams
+ *
+ */
+@SuppressWarnings("serial")
+public class RetryContextualView extends ContextualView {
+
+ private final Processor processor;
+
+ private final EditManager editManager;
+
+ private final SelectionManager selectionManager;
+
+ private final Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ private JPanel panel;
+
+ public RetryContextualView(Processor processor, EditManager editManager, SelectionManager selectionManager) {
+ super();
+ this.processor = processor;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ initialise();
+ initView();
+ }
+
+ /*
+ * @Override public Action getConfigureAction(Frame owner) { return new
+ * ConfigureAction(owner); }
+ */
+ @Override
+ public void refreshView() {
+ initialise();
+ }
+
+ private void initialise() {
+ if (panel == null) {
+ panel = createPanel();
+ } else {
+ panel.removeAll();
+ }
+
+ JTextArea textArea = new ReadOnlyTextArea();
+ textArea.setEditable(false);
+ String text = "";
+ int maxRetries = RetryConfigurationPanel.DEFAULT_RETRIES;
+ int initialDelay = RetryConfigurationPanel.DEFAULT_INITIAL_DELAY;
+ int maxDelay = RetryConfigurationPanel.DEFAULT_MAX_DELAY;
+ double backoffFactor = RetryConfigurationPanel.DEFAULT_BACKOFF;
+
+ for (Configuration configuration : scufl2Tools.configurationsFor(processor, selectionManager.getSelectedProfile())) {
+ JsonNode processorConfig = configuration.getJson();
+ if (processorConfig.has("retry")) {
+ JsonNode retryConfig = processorConfig.get("retry");
+ if (retryConfig.has("maxRetries")) {
+ maxRetries = retryConfig.get("maxRetries").asInt();
+ }
+ if (retryConfig.has("initialDelay")) {
+ initialDelay = retryConfig.get("initialDelay").asInt();
+ }
+ if (retryConfig.has("maxDelay")) {
+ maxDelay = retryConfig.get("maxDelay").asInt();
+ }
+ if (retryConfig.has("backoffFactor")) {
+ backoffFactor = retryConfig.get("backoffFactor").asDouble();;
+ }
+ }
+ }
+
+ if (maxRetries < 1) {
+ text += "The service is not re-tried";
+ } else if (maxRetries == 1) {
+ text += "The service is re-tried once";
+ text += " after " + initialDelay + "ms";
+ } else {
+ text += "The service is re-tried " + maxRetries + " times. ";
+ if (backoffFactor == 1.0) {
+ text += "Each time after a delay of " + initialDelay + "ms.";
+ } else {
+ text += "The first delay is " + initialDelay + "ms";
+ int noMaxDelay = (int) (initialDelay * Math.pow(backoffFactor, maxRetries - 1));
+ if (noMaxDelay < maxDelay) {
+ maxDelay = noMaxDelay;
+ }
+ text += " with a maximum delay of " + maxDelay + "ms.";
+ text += " Each delay is increased by a factor of " + backoffFactor;
+ }
+ }
+ textArea.setText(text);
+ textArea.setBackground(panel.getBackground());
+ panel.add(textArea, BorderLayout.CENTER);
+ revalidate();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ return panel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Retries";
+ }
+
+ protected JPanel createPanel() {
+ JPanel result = new JPanel();
+ result.setLayout(new BorderLayout());
+ result.setOpaque(false);
+
+ return result;
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 400;
+ }
+
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new RetryConfigureAction(owner, this, processor, editManager, selectionManager);
+ }
+
+}
diff --git a/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryContextualViewFactory.java b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryContextualViewFactory.java
new file mode 100644
index 0000000..ab34f78
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/java/net/sf/taverna/t2/workbench/retry/RetryContextualViewFactory.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.retry;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+public class RetryContextualViewFactory implements ContextualViewFactory<Processor> {
+
+ public static URI TYPE = URI.create("http://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Retry");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public boolean canHandle(Object selection) {
+ return selection instanceof Processor;
+ }
+
+ public List<ContextualView> getViews(Processor selection) {
+ return Arrays.asList(new ContextualView[] { new RetryContextualView(selection, editManager,
+ selectionManager) });
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+
+}
diff --git a/taverna-workbench-retry-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-retry-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6a3f2a0
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.retry.RetryConfigureMenuAction
diff --git a/taverna-workbench-retry-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-retry-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..a4e53bf
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.retry.RetryContextualViewFactory
diff --git a/taverna-workbench-retry-ui/src/main/resources/META-INF/spring/retry-ui-context-osgi.xml b/taverna-workbench-retry-ui/src/main/resources/META-INF/spring/retry-ui-context-osgi.xml
new file mode 100644
index 0000000..891a096
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/resources/META-INF/spring/retry-ui-context-osgi.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="RetryConfigureMenuAction" auto-export="interfaces" />
+
+ <service ref="RetryContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-retry-ui/src/main/resources/META-INF/spring/retry-ui-context.xml b/taverna-workbench-retry-ui/src/main/resources/META-INF/spring/retry-ui-context.xml
new file mode 100644
index 0000000..1b57ef4
--- /dev/null
+++ b/taverna-workbench-retry-ui/src/main/resources/META-INF/spring/retry-ui-context.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="RetryConfigureMenuAction" class="net.sf.taverna.t2.workbench.retry.RetryConfigureMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+ <bean id="RetryContextualViewFactory" class="net.sf.taverna.t2.workbench.retry.RetryContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-run-ui/pom.xml b/taverna-workbench-run-ui/pom.xml
new file mode 100644
index 0000000..bcda0d6
--- /dev/null
+++ b/taverna-workbench-run-ui/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>run-ui</artifactId>
+ <packaging>bundle</packaging>
+ <name>Results UI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>reference-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-run-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.platform</groupId>
+ <artifactId>taverna-execution-api</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/OpenWorkflowRunAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/OpenWorkflowRunAction.java
new file mode 100644
index 0000000..9c50e31
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/OpenWorkflowRunAction.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.run.actions;
+
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.SwingWorker;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.platform.run.api.RunService;
+
+/**
+ * An action for opening a workflow run from a file.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class OpenWorkflowRunAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(OpenWorkflowRunAction.class);
+ private static final String OPEN_WORKFLOW_RUN = "Open workflow run...";
+
+ private final RunService runService;
+ private final File runStore;
+
+ public OpenWorkflowRunAction(RunService runService, File runStore) {
+ super(OPEN_WORKFLOW_RUN, WorkbenchIcons.openIcon);
+ this.runService = runService;
+ this.runStore = runStore;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ else
+ parentComponent = null;
+ openWorkflowRuns(parentComponent);
+ }
+
+ public void openWorkflowRuns(final Component parentComponent) {
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.setDialogTitle(OPEN_WORKFLOW_RUN);
+
+ fileChooser.setFileFilter(new FileFilter() {
+ @Override
+ public String getDescription() {
+ return "Workflow Run";
+ }
+
+ @Override
+ public boolean accept(File file) {
+ return file.getName().endsWith(".wfRun");
+ }
+ });
+
+ fileChooser.setCurrentDirectory(runStore);
+ fileChooser.setMultiSelectionEnabled(true);
+
+ if (fileChooser.showOpenDialog(parentComponent) == APPROVE_OPTION) {
+ final File[] selectedFiles = fileChooser.getSelectedFiles();
+ if (selectedFiles.length == 0) {
+ logger.warn("No files selected");
+ return;
+ }
+ new SwingWorker<Void, Void>() {
+ @Override
+ public Void doInBackground() {
+ for (File file : selectedFiles)
+ try {
+ runService.open(file);
+ } catch (IOException e) {
+ showErrorMessage(parentComponent, file, e);
+ }
+ return null;
+ }
+ }.execute();
+ }
+ }
+
+ /**
+ * Show an error message if a file could not be opened
+ *
+ * @param parentComponent
+ * @param file
+ * @param throwable
+ */
+ protected void showErrorMessage(final Component parentComponent,
+ final File file, Throwable throwable) {
+ Throwable cause = throwable;
+ while (cause.getCause() != null)
+ cause = cause.getCause();
+ final String message = cause.getMessage();
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ showMessageDialog(parentComponent,
+ "Failed to open workflow from " + file + ":\n"
+ + message, "Warning", WARNING_MESSAGE);
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/RunWorkflowAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/RunWorkflowAction.java
new file mode 100644
index 0000000..b87d35a
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/RunWorkflowAction.java
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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.run.actions;
+
+import static java.awt.Frame.ICONIFIED;
+import static java.awt.Frame.NORMAL;
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_R;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.reference.ui.InvalidDataflowReport.showErrorDialog;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.runIcon;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.reference.ui.CopyWorkflowInProgressDialog;
+import net.sf.taverna.t2.reference.ui.CopyWorkflowSwingWorker;
+import net.sf.taverna.t2.reference.ui.WorkflowLaunchWindow;
+import net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+import org.apache.log4j.Logger;
+import org.purl.wf4ever.robundle.Bundle;
+
+import uk.org.taverna.databundle.DataBundles;
+import uk.org.taverna.platform.execution.api.ExecutionEnvironment;
+import uk.org.taverna.platform.execution.api.InvalidExecutionIdException;
+import uk.org.taverna.platform.execution.api.InvalidWorkflowException;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunProfile;
+import uk.org.taverna.platform.run.api.RunProfileException;
+import uk.org.taverna.platform.run.api.RunService;
+import uk.org.taverna.platform.run.api.RunStateException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Run the current workflow (with workflow input dialogue if needed) and add it
+ * to the list of runs.
+ * <p>
+ * Note that running a workflow will force a clone of the WorkflowBundle, allowing further edits to
+ * the current WorkflowBundle without obstructing the run.
+ */
+@SuppressWarnings("serial")
+public class RunWorkflowAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(RunWorkflowAction.class);
+
+ /**
+ * A map of workflows and their corresponding {@link WorkflowLaunchWindow}s.
+ * We only create one window per workflow and then update its content if the
+ * workflow gets updated
+ */
+ private static HashMap<WorkflowBundle, WorkflowLaunchWindow> workflowLaunchWindowMap = new HashMap<>();
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final ReportManager reportManager;
+ private final Workbench workbench;
+ private final RunService runService;
+ private final SelectionManager selectionManager;
+
+ public RunWorkflowAction(EditManager editManager, FileManager fileManager,
+ ReportManager reportManager, Workbench workbench, RunService runService,
+ SelectionManager selectionManager) {
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManager = reportManager;
+ this.workbench = workbench;
+ this.runService = runService;
+ this.selectionManager = selectionManager;
+ putValue(SMALL_ICON, runIcon);
+ putValue(NAME, "Run workflow...");
+ putValue(SHORT_DESCRIPTION, "Run the current workflow");
+ putValue(MNEMONIC_KEY, VK_R);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_R, getDefaultToolkit().getMenuShortcutKeyMask()));
+ fileManager.addObserver(new Observer<FileManagerEvent>() {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
+ throws Exception {
+ if (message instanceof ClosedDataflowEvent)
+ workflowLaunchWindowMap
+ .remove(((ClosedDataflowEvent) message)
+ .getDataflow());
+ }
+ });
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ final WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+ final Profile profile = selectionManager.getSelectedProfile();
+ Set<ExecutionEnvironment> executionEnvironments = runService
+ .getExecutionEnvironments(profile);
+ if (executionEnvironments.isEmpty()) {
+ showErrorDialog(
+ "There are no execution environments capable of running this workflow",
+ "Can't run workflow");
+ return;
+ }
+
+ // TODO ask user to choose execution environment
+ final ExecutionEnvironment executionEnvironment = executionEnvironments.iterator().next();
+ try {
+ if (validate(workflowBundle, profile)) {
+ if (workflowBundle.getMainWorkflow().getInputPorts().isEmpty()) {
+ final Bundle bundle = DataBundles.createBundle();
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ runWorkflow(workflowBundle, profile,
+ executionEnvironment, bundle);
+ }
+ });
+ } else // workflow had inputs - show the input dialog
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ showInputDialog(workflowBundle, profile,
+ executionEnvironment);
+ }
+ });
+ }
+ } catch (Exception ex) {
+ String message = "Could not run workflow " + workflowBundle.getName();
+ logger.warn(message);
+ showErrorDialog(ex.getMessage(), message);
+ }
+ }
+
+ // TODO update to use Scufl2 validation
+ private boolean validate(WorkflowBundle workflowBundle, Profile selectedProfile) {
+ //CheckWorkflowStatus.checkWorkflow(selectedProfile, workbench, editManager,
+ // fileManager,reportManager);
+ return true;
+ }
+
+ private void runWorkflow(WorkflowBundle workflowBundle, Profile profile,
+ ExecutionEnvironment executionEnvironment, Bundle workflowInputs) {
+ try {
+ RunProfile runProfile = createRunProfile(workflowBundle, profile,
+ executionEnvironment, workflowInputs);
+ if (runProfile != null) {
+ String runId = runService.createRun(runProfile);
+ runService.start(runId);
+ }
+ } catch (InvalidWorkflowException | RunProfileException | InvalidRunIdException
+ | RunStateException | InvalidExecutionIdException e) {
+ String message = "Could not run workflow " + workflowBundle.getName();
+ logger.warn(message, e);
+ showErrorDialog(e.getMessage(), message);
+ }
+ }
+
+ private RunProfile createRunProfile(WorkflowBundle workflowBundle, Profile profile,
+ ExecutionEnvironment executionEnvironment, Bundle inputDataBundle) {
+ /*
+ * Make a copy of the workflow to run so user can still modify the
+ * original workflow
+ */
+ WorkflowBundle workflowBundleCopy = null;
+
+ /*
+ * CopyWorkflowSwingWorker will make a copy of the workflow and pop up a
+ * modal dialog that will block the GUI while CopyWorkflowSwingWorker is
+ * doing it to let the user know that something is being done. Blocking
+ * of the GUI is needed here so that the user cannot modify the original
+ * workflow while it is being copied.
+ */
+ CopyWorkflowSwingWorker copyWorkflowSwingWorker = new CopyWorkflowSwingWorker(
+ workflowBundle);
+
+ CopyWorkflowInProgressDialog dialog = new CopyWorkflowInProgressDialog();
+ copyWorkflowSwingWorker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
+ copyWorkflowSwingWorker.execute();
+
+ /*
+ * Give a chance to the SwingWorker to finish so we do not have to
+ * display the dialog if copying of the workflow is quick (so it won't
+ * flicker on the screen)
+ */
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+
+ if (!copyWorkflowSwingWorker.isDone())
+ dialog.setVisible(true); // this will block the GUI
+ // see if user cancelled the dialog
+ boolean userCancelled = dialog.hasUserCancelled();
+
+ if (userCancelled) {
+ // Stop the CopyWorkflowSwingWorker if it is still working
+ copyWorkflowSwingWorker.cancel(true);
+ return null;
+ }
+
+ // Get the workflow copy from the copyWorkflowSwingWorker
+ try {
+ workflowBundleCopy = copyWorkflowSwingWorker.get();
+ } catch (InterruptedException | ExecutionException e) {
+ logger.error("Failed to get the workflow copy", e);
+ }
+
+ if (workflowBundleCopy == null) {
+ showErrorDialog("Unable to make a copy of the workflow to run",
+ "Workflow copy failed");
+ return null;
+ }
+
+ return new RunProfile(executionEnvironment, workflowBundleCopy,
+ workflowBundleCopy.getMainWorkflow().getName(),
+ profile.getName(), inputDataBundle);
+ }
+
+ private void showInputDialog(final WorkflowBundle workflowBundle,
+ final Profile profile,
+ final ExecutionEnvironment executionEnvironment) {
+ // Get the WorkflowLauchWindow
+ WorkflowLaunchWindow launchWindow = null;
+ synchronized (workflowLaunchWindowMap) {
+ WorkflowLaunchWindow savedLaunchWindow = workflowLaunchWindowMap
+ .get(workflowBundle);
+ if (savedLaunchWindow == null) {
+ launchWindow = new WorkflowLaunchWindow(
+ workflowBundle.getMainWorkflow(), editManager,
+ fileManager, reportManager, workbench,
+ new ArrayList<ReferenceActionSPI>(), null) {
+ @Override
+ public void handleLaunch(Bundle workflowInputs) {
+ runWorkflow(workflowBundle, profile,
+ executionEnvironment, workflowInputs);
+ //TODO T2 now makes the launch window vanish
+ setState(ICONIFIED); // minimise the window
+ }
+
+ @Override
+ public void handleCancel() {
+ // Keep the window so we do not have to rebuild it again
+ setVisible(false);
+ }
+ };
+
+ /*
+ * Add this window to the map of the workflow input/launch
+ * windows
+ */
+ workflowLaunchWindowMap.put(workflowBundle, launchWindow);
+ launchWindow.setLocationRelativeTo(null);
+ } else
+ launchWindow = savedLaunchWindow;
+
+ // Display the window
+ launchWindow.setVisible(true);
+ /*
+ * On Win XP setting the window visible seems not to be enough to
+ * bring the window up if it was minimised previously so we restore
+ * it here
+ */
+ if (launchWindow.getState() == ICONIFIED)
+ launchWindow.setState(NORMAL); // restore the window
+ }
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/ValidateWorkflowAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/ValidateWorkflowAction.java
new file mode 100644
index 0000000..ce13c2a
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/actions/ValidateWorkflowAction.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.run.actions;
+
+import static java.awt.event.KeyEvent.VK_V;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.searchIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+@SuppressWarnings("serial")
+public class ValidateWorkflowAction extends AbstractAction {
+ private static final String VALIDATE_WORKFLOW = "Validate workflow";
+
+ protected Action subAction;
+
+ public ValidateWorkflowAction(EditManager editManager,
+ FileManager fileManager, ReportManager reportManager,
+ Workbench workbench) {
+ super(VALIDATE_WORKFLOW, searchIcon);
+ putValue(MNEMONIC_KEY, VK_V);
+ // subAction = new ReportOnWorkflowAction("", true, false, editManager,
+ // fileManager, reportManager, workbench);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ if (subAction != null)
+ subAction.actionPerformed(ev);
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/cleanup/WorkflowRunStatusShutdownDialog.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/cleanup/WorkflowRunStatusShutdownDialog.java
new file mode 100644
index 0000000..4718fd7
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/cleanup/WorkflowRunStatusShutdownDialog.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (C) 2010 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.run.cleanup;
+
+import static java.awt.Color.WHITE;
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.NORTHWEST;
+import static java.awt.GridBagConstraints.SOUTHEAST;
+import static java.awt.GridBagConstraints.SOUTHWEST;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+
+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 javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * Dialog that warns if there are running workflows while the workbench is being
+ * shutdown and gives the user a change to cancel.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class WorkflowRunStatusShutdownDialog extends HelpEnabledDialog {
+ private JButton abortButton;
+ private JButton cancelButton;
+ private boolean confirmShutdown = true;
+
+ public WorkflowRunStatusShutdownDialog(int runningWorkflows, int pausedWorkflows) {
+ super((Frame) null, "Workflows still running", true);
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ setLocationRelativeTo(null);
+
+ GridBagConstraints c = new GridBagConstraints();
+ setLayout(new GridBagLayout());
+
+ JLabel title = new JLabel("Running or paused workflows detected.");
+ title.setFont(title.getFont().deriveFont(BOLD, 14));
+
+ abortButton = new JButton("Shutdown now");
+ abortButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (showConfirmDialog(
+ WorkflowRunStatusShutdownDialog.this,
+ "If you close Taverna now all workflows will be cancelled.\n"
+ + "Are you sure you want to close now?",
+ "Confirm Shutdown", YES_NO_OPTION, WARNING_MESSAGE) == YES_OPTION)
+ setVisible(false);
+ }
+ });
+
+ cancelButton = new JButton("Cancel shutdown");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ confirmShutdown = false;
+ setVisible(false);
+ }
+ });
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("There ");
+ if (runningWorkflows > 0) {
+ sb.append(runningWorkflows > 1 ? "are " : "is ");
+ sb.append(runningWorkflows);
+ sb.append(" running ");
+ sb.append(runningWorkflows > 1 ? "workflows" : "workflow");
+ } else
+ sb.append(pausedWorkflows > 1 ? "are " : "is ");
+ if (pausedWorkflows > 0) {
+ if (runningWorkflows > 0)
+ sb.append(" and ");
+ sb.append(pausedWorkflows);
+ sb.append(" paused ");
+ sb.append(pausedWorkflows > 1 ? "workflows" : "workflow");
+ }
+ JLabel message = new JLabel(sb.toString());
+
+ JPanel topPanel = new JPanel(new GridBagLayout());
+ topPanel.setBackground(WHITE);
+
+ c.anchor = NORTHWEST;
+ c.insets = new Insets(20, 30, 20, 30);
+ c.weightx = 1d;
+ c.weighty = 0d;
+ topPanel.add(title, c);
+
+ c.insets = new Insets(0, 0, 0, 0);
+ c.fill = HORIZONTAL;
+ c.gridwidth = 2;
+ c.gridx = 0;
+ add(topPanel, c);
+
+ c.insets = new Insets(20, 20, 20, 20);
+ c.weighty = 1d;
+ c.weighty = 1d;
+ add(message, c);
+
+ c.fill = NONE;
+ c.anchor = SOUTHWEST;
+ c.insets = new Insets(10, 20, 10, 20);
+ c.weightx = 0.5;
+ c.weighty = 0d;
+ c.gridx = 0;
+ c.gridwidth = 1;
+ add(cancelButton, c);
+
+ c.anchor = SOUTHEAST;
+ c.gridx = 1;
+ add(abortButton, c);
+
+ setSize(400, 230);
+ }
+
+ /**
+ * @return <code>true</code> if it's OK to proceed with the shutdown
+ */
+ public boolean confirmShutdown() {
+ return confirmShutdown;
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/cleanup/WorkflowRunStatusShutdownHook.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/cleanup/WorkflowRunStatusShutdownHook.java
new file mode 100644
index 0000000..717b2c1
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/cleanup/WorkflowRunStatusShutdownHook.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (C) 2010 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.run.cleanup;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+import uk.org.taverna.platform.execution.api.InvalidExecutionIdException;
+import uk.org.taverna.platform.run.api.InvalidRunIdException;
+import uk.org.taverna.platform.run.api.RunService;
+import uk.org.taverna.platform.run.api.RunStateException;
+
+/**
+ * Shutdown hook that detects running and paused workflows.
+ *
+ * @author David Withers
+ */
+public class WorkflowRunStatusShutdownHook implements ShutdownSPI {
+ private static final String RUN_STORE_DIRECTORY = "workflow-runs";
+
+ private RunService runService;
+ private ApplicationConfiguration applicationConfiguration;
+
+ @Override
+ public int positionHint() {
+ return 40;
+ }
+
+ @Override
+ public boolean shutdown() {
+ boolean shutdown = true;
+ List<String> workflowRuns = runService.getRuns();
+ List<String> runningWorkflows = new ArrayList<>();
+ List<String> pausedWorkflows = new ArrayList<>();
+ for (String workflowRun : workflowRuns)
+ try {
+ switch (runService.getState(workflowRun)) {
+ case PAUSED:
+ case RUNNING:
+ pausedWorkflows.add(workflowRun);
+ default:
+ break;
+ }
+ } catch (InvalidRunIdException e) {
+ }
+ if (runningWorkflows.size() + pausedWorkflows.size() > 0) {
+ WorkflowRunStatusShutdownDialog dialog = new WorkflowRunStatusShutdownDialog(
+ runningWorkflows.size(), pausedWorkflows.size());
+ dialog.setVisible(true);
+ shutdown = dialog.confirmShutdown();
+ }
+ if (shutdown) {
+ for (String workflowRun : pausedWorkflows)
+ try {
+ runService.cancel(workflowRun);
+ } catch (InvalidRunIdException | RunStateException
+ | InvalidExecutionIdException e) {
+ }
+ for (String workflowRun : runningWorkflows)
+ try {
+ runService.cancel(workflowRun);
+ } catch (InvalidRunIdException | RunStateException
+ | InvalidExecutionIdException e) {
+ }
+ for (String workflowRun : workflowRuns) {
+ File runStore = new File(
+ applicationConfiguration.getApplicationHomeDir(),
+ RUN_STORE_DIRECTORY);
+ try {
+ File file = new File(runStore,
+ runService.getRunName(workflowRun) + ".wfRun");
+ if (!file.exists())
+ runService.save(workflowRun, file);
+ } catch (InvalidRunIdException | IOException e) {
+ }
+ }
+ }
+ return shutdown;
+ }
+
+ public void setRunService(RunService runService) {
+ this.runService = runService;
+ }
+
+ public void setApplicationConfiguration(ApplicationConfiguration appConfig) {
+ this.applicationConfiguration = appConfig;
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileOpenRunMenuAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileOpenRunMenuAction.java
new file mode 100644
index 0000000..6914ac7
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileOpenRunMenuAction.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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.run.menu;
+
+import static net.sf.taverna.t2.workbench.run.menu.FileRunMenuSection.FILE_RUN_SECTION_URI;
+
+import java.io.File;
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+import uk.org.taverna.platform.run.api.RunService;
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.run.actions.OpenWorkflowRunAction;
+
+public class FileOpenRunMenuAction extends AbstractMenuAction {
+ private static final String RUN_STORE_DIRECTORY = "workflow-runs";
+ private static final URI FILE_OPEN_RUN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenRun");
+
+ private RunService runService;
+ private ApplicationConfiguration applicationConfiguration;
+
+ public FileOpenRunMenuAction() {
+ super(FILE_RUN_SECTION_URI, 20, FILE_OPEN_RUN_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ File runStore = new File(
+ applicationConfiguration.getApplicationHomeDir(),
+ RUN_STORE_DIRECTORY);
+ return new OpenWorkflowRunAction(runService, runStore);
+ }
+
+ public void setRunService(RunService runService) {
+ this.runService = runService;
+ }
+
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileRunMenuAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileRunMenuAction.java
new file mode 100644
index 0000000..d1a73c1
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileRunMenuAction.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.run.menu;
+
+import static net.sf.taverna.t2.workbench.run.menu.FileRunMenuSection.FILE_RUN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.run.actions.RunWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import uk.org.taverna.platform.run.api.RunService;
+
+public class FileRunMenuAction extends AbstractMenuAction {
+ private static final URI FILE_RUN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileRun");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ReportManager reportManager;
+ private Workbench workbench;
+ private RunService runService;
+ private SelectionManager selectionManager;
+
+ public FileRunMenuAction() {
+ super(FILE_RUN_SECTION_URI, 10, FILE_RUN_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new RunWorkflowAction(editManager, fileManager, reportManager, workbench,
+ runService, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+ public void setRunService(RunService runService) {
+ this.runService = runService;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileRunMenuSection.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileRunMenuSection.java
new file mode 100644
index 0000000..6af6eb9
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileRunMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.run.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileRunMenuSection extends AbstractMenuSection {
+ public static final URI FILE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ public static final URI FILE_RUN_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileRunSection");
+
+ public FileRunMenuSection() {
+ super(FILE_URI, 40, FILE_RUN_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileValidateMenuAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileValidateMenuAction.java
new file mode 100644
index 0000000..3558c41
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/menu/FileValidateMenuAction.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.run.menu;
+
+import static net.sf.taverna.t2.workbench.run.menu.FileRunMenuSection.FILE_RUN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.run.actions.ValidateWorkflowAction;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+public class FileValidateMenuAction extends AbstractMenuAction {
+ private static final URI FILE_VALIDATE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileValidate");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ReportManager reportManager;
+ private Workbench workbench;
+
+ public FileValidateMenuAction() {
+ super(FILE_RUN_SECTION_URI, 5, FILE_VALIDATE_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new ValidateWorkflowAction(editManager, fileManager,
+ reportManager, workbench);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/toolbar/RunToolbarAction.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/toolbar/RunToolbarAction.java
new file mode 100644
index 0000000..5e84653
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/toolbar/RunToolbarAction.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.run.toolbar;
+
+import static net.sf.taverna.t2.workbench.run.toolbar.RunToolbarSection.RUN_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.run.actions.RunWorkflowAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import uk.org.taverna.platform.run.api.RunService;
+
+public class RunToolbarAction extends AbstractMenuAction {
+ private static final URI RUN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#runToolbarRun");
+
+ private EditManager editManager;
+ private FileManager fileManager;
+ private ReportManager reportManager;
+ private Workbench workbench;
+ private RunService runService;
+ private SelectionManager selectionManager;
+
+ public RunToolbarAction() {
+ super(RUN_TOOLBAR_SECTION, 10, RUN_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new RunWorkflowAction(editManager, fileManager, reportManager,
+ workbench, runService, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+
+ public void setRunService(RunService runService) {
+ this.runService = runService;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/toolbar/RunToolbarSection.java b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/toolbar/RunToolbarSection.java
new file mode 100644
index 0000000..b023f16
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/java/net/sf/taverna/t2/workbench/run/toolbar/RunToolbarSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.run.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class RunToolbarSection extends AbstractMenuSection {
+ public static final URI RUN_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#runToolbarSection");
+
+ public RunToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 15, RUN_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6605bee
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,6 @@
+net.sf.taverna.t2.workbench.run.menu.FileRunMenuAction
+net.sf.taverna.t2.workbench.run.menu.FileValidateMenuAction
+net.sf.taverna.t2.workbench.run.menu.FileRunMenuSection
+
+net.sf.taverna.t2.workbench.run.toolbar.RunToolbarSection
+net.sf.taverna.t2.workbench.run.toolbar.RunToolbarAction
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..a948c94
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1,5 @@
+net.sf.taverna.t2.workbench.run.cleanup.ReferenceDatabaseCleanUpShutdownHook
+net.sf.taverna.t2.workbench.run.cleanup.ReferenceServiceShutdownHook
+net.sf.taverna.t2.workbench.run.cleanup.RemoveDataflowRunsShutdownHook
+net.sf.taverna.t2.workbench.run.cleanup.StoreRunIdsToDeleteLaterShutdownHook
+net.sf.taverna.t2.workbench.run.cleanup.WorkflowRunStatusShutdownHook
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..0b9a575
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.run.cleanup.LoadRunIdsToDeleteStartupHook
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..01e8c64
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.run.ResultsPerspectiveComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..9ce6b24
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.run.ResultsPerspectiveComponent
\ No newline at end of file
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/spring/run-ui-context-osgi.xml b/taverna-workbench-run-ui/src/main/resources/META-INF/spring/run-ui-context-osgi.xml
new file mode 100644
index 0000000..ab1510e
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/spring/run-ui-context-osgi.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="FileRunMenuSection" auto-export="interfaces" />
+ <service ref="RunToolbarSection" auto-export="interfaces" />
+
+ <service ref="FileOpenRunMenuAction" auto-export="interfaces" />
+ <service ref="FileRunMenuAction" auto-export="interfaces" />
+ <service ref="FileValidateMenuAction" auto-export="interfaces" />
+ <service ref="RunToolbarAction" auto-export="interfaces" />
+
+ <service ref="WorkflowRunStatusShutdownHook" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="reportManager" interface="net.sf.taverna.t2.workbench.report.ReportManager" cardinality="0..1"/>
+ <reference id="workbench" interface="net.sf.taverna.t2.workbench.ui.Workbench" cardinality="0..1"/>
+ <reference id="runService" interface="uk.org.taverna.platform.run.api.RunService" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+</beans:beans>
diff --git a/taverna-workbench-run-ui/src/main/resources/META-INF/spring/run-ui-context.xml b/taverna-workbench-run-ui/src/main/resources/META-INF/spring/run-ui-context.xml
new file mode 100644
index 0000000..808556d
--- /dev/null
+++ b/taverna-workbench-run-ui/src/main/resources/META-INF/spring/run-ui-context.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="FileRunMenuSection" class="net.sf.taverna.t2.workbench.run.menu.FileRunMenuSection" />
+ <bean id="RunToolbarSection" class="net.sf.taverna.t2.workbench.run.toolbar.RunToolbarSection" />
+
+ <bean id="FileRunMenuAction" class="net.sf.taverna.t2.workbench.run.menu.FileRunMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="workbench" ref="workbench" />
+ <property name="runService" ref="runService" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="FileOpenRunMenuAction" class="net.sf.taverna.t2.workbench.run.menu.FileOpenRunMenuAction">
+ <property name="runService" ref="runService" />
+ <property name="applicationConfiguration" ref="applicationConfiguration" />
+ </bean>
+ <bean id="FileValidateMenuAction" class="net.sf.taverna.t2.workbench.run.menu.FileValidateMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="workbench" ref="workbench" />
+ </bean>
+ <bean id="RunToolbarAction" class="net.sf.taverna.t2.workbench.run.toolbar.RunToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="workbench" ref="workbench" />
+ <property name="runService" ref="runService" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+ <bean id="WorkflowRunStatusShutdownHook" class="net.sf.taverna.t2.workbench.run.cleanup.WorkflowRunStatusShutdownHook">
+ <property name="runService" ref="runService" />
+ <property name="applicationConfiguration" ref="applicationConfiguration" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-selection-api/pom.xml b/taverna-workbench-selection-api/pom.xml
new file mode 100644
index 0000000..3925201
--- /dev/null
+++ b/taverna-workbench-selection-api/pom.xml
@@ -0,0 +1,53 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Selection API</name>
+ <description>API for handling selection in the workbench.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <releases />
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>mygrid-repository</id>
+ <name>myGrid Repository</name>
+ <url>http://www.mygrid.org.uk/maven/repository
+ </url>
+ </repository>
+ <repository>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots />
+ <id>mygrid-snapshot-repository</id>
+ <name>myGrid Snapshot Repository</name>
+ <url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+ </repository>
+ </repositories>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/DataflowSelectionModel.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/DataflowSelectionModel.java
new file mode 100644
index 0000000..dd3b0b9
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/DataflowSelectionModel.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.selection;
+
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+
+/**
+ * The current state of the selection of dataflow objects.
+ *
+ * @author David Withers
+ */
+public interface DataflowSelectionModel extends
+ Observable<DataflowSelectionMessage> {
+ /**
+ * Adds an element to the current selection.
+ *
+ * If the element is not in the selection the {@link Observer}s are
+ * notified. If <code>element</code> is null, this method has no effect.
+ *
+ * @param element
+ * the element to add
+ */
+ void addSelection(Object element);
+
+ /**
+ * Removes an element from the current selection.
+ *
+ * If the element is in the selection the {@link Observer}s are notified. If
+ * <code>element</code> is null, this method has no effect.
+ *
+ * @param element
+ * the element to remove
+ */
+ void removeSelection(Object element);
+
+ /**
+ * Sets the current selection.
+ *
+ * If this changes the selection the {@link Observer}s are notified. If
+ * <code>elements</code> is null, this has the same effect as invoking
+ * <code>clearSelection</code>.
+ *
+ * @param elements
+ * the current selection
+ */
+ void setSelection(Set<Object> elements);
+
+ /**
+ * Returns the current selection.
+ *
+ * Returns an empty set if nothing is currently selected.
+ *
+ * @return the current selection
+ */
+ Set<Object> getSelection();
+
+ /**
+ * Clears the current selection.
+ *
+ * If this changes the selection the {@link Observer}s are notified.
+ */
+ void clearSelection();
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/SelectionManager.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/SelectionManager.java
new file mode 100644
index 0000000..edccc70
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/SelectionManager.java
@@ -0,0 +1,113 @@
+package net.sf.taverna.t2.workbench.selection;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Manages workflowBundles, workflows, profiles and perspectives selected on the
+ * Workbench.
+ *
+ * @author David Withers
+ */
+public interface SelectionManager extends Observable<SelectionManagerEvent> {
+ /**
+ * Returns the <code>DataflowSelectionModel</code> for the WorkflowBundle.
+ *
+ * @param workflowBundle
+ * the WorkflowBundle to return the current selection model for
+ * @return the <code>DataflowSelectionModel</code> for the WorkflowBundle
+ */
+ DataflowSelectionModel getDataflowSelectionModel(
+ WorkflowBundle workflowBundle);
+
+ /**
+ * Returns the currently selected WorkflowBundle.
+ *
+ * @return the currently selected WorkflowBundle
+ */
+ WorkflowBundle getSelectedWorkflowBundle();
+
+ /**
+ * Sets the currently selected WorkflowBundle.
+ *
+ * @param workflowBundle
+ * the WorkflowBundle to set as currently selected
+ */
+ void setSelectedWorkflowBundle(WorkflowBundle workflowBundle);
+
+ /**
+ * Returns the currently selected Workflow.
+ *
+ * @return the currently selected Workflow
+ */
+ Workflow getSelectedWorkflow();
+
+ /**
+ * Sets the currently selected Workflow.
+ *
+ * @param workflow
+ * the Workflow to set as currently selected
+ */
+ void setSelectedWorkflow(Workflow workflow);
+
+ /**
+ * Returns the currently selected Profile.
+ *
+ * @return the currently selected Profile
+ */
+ Profile getSelectedProfile();
+
+ /**
+ * Sets the currently selected Profile.
+ *
+ * @param profile
+ * the Profile to set as currently selected
+ */
+ void setSelectedProfile(Profile profile);
+
+ /**
+ * Returns the currently selected workflow run.
+ *
+ * @return the currently selected workflow run. If there are no workflow
+ * runs <code>null</code> is returned.
+ */
+ String getSelectedWorkflowRun();
+
+ /**
+ * Sets the currently selected workflow run.
+ *
+ * @param workflowRun
+ * the workflow run to set as currently selected. May be
+ * <code>null</code> if there are no workflow runs .
+ */
+ void setSelectedWorkflowRun(String workflowRun);
+
+ /**
+ * Returns the <code>DataflowSelectionModel</code> for the workflow run.
+ *
+ * @param workflowRun
+ * the workflow run to return the current selection model for
+ * @return the <code>DataflowSelectionModel</code> for the workflow run
+ */
+ DataflowSelectionModel getWorkflowRunSelectionModel(String workflowRun);
+
+ /**
+ * Returns the currently selected Perspective.
+ *
+ * @return the currently selected Perspective
+ */
+ PerspectiveSPI getSelectedPerspective();
+
+ /**
+ * Sets the currently selected Perspective.
+ *
+ * @param perspective
+ * the Perspective to set as currently selected
+ */
+ void setSelectedPerspective(PerspectiveSPI perspective);
+}
\ No newline at end of file
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/DataflowSelectionMessage.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/DataflowSelectionMessage.java
new file mode 100644
index 0000000..d15337d
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/DataflowSelectionMessage.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * 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.selection.events;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * A message about the selection of a {@linkplain Workflow dataflow} object.
+ *
+ * @author David Withers
+ */
+public class DataflowSelectionMessage {
+ public enum Type {
+ ADDED, REMOVED
+ }
+
+ private Type type;
+ private Object element;
+
+ /**
+ * Constructs a new instance of DataflowSelectionMessage.
+ *
+ * @param type
+ * @param element
+ */
+ public DataflowSelectionMessage(Type type, Object element) {
+ this.type = type;
+ this.element = element;
+ }
+
+ /**
+ * Returns the type of the message.
+ *
+ * @return the type of the message
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Returns the subject of the message.
+ *
+ * @return the of the message
+ */
+ public Object getElement() {
+ return element;
+ }
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/PerspectiveSelectionEvent.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/PerspectiveSelectionEvent.java
new file mode 100644
index 0000000..30dd8ff
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/PerspectiveSelectionEvent.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.selection.events;
+
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+/**
+ * {@link SelectionManagerEvent} for changes to the selected
+ * {@linkplain PerspectiveSPI perspective}.
+ *
+ * @author David Withers
+ */
+public class PerspectiveSelectionEvent implements SelectionManagerEvent {
+ private PerspectiveSPI previouslySelectedPerspective;
+ private PerspectiveSPI selectedPerspective;
+
+ public PerspectiveSelectionEvent(
+ PerspectiveSPI previouslySelectedPerspective,
+ PerspectiveSPI selectedPerspective) {
+ this.previouslySelectedPerspective = previouslySelectedPerspective;
+ this.selectedPerspective = selectedPerspective;
+ }
+
+ /**
+ * Returns the previously selected Perspective.
+ *
+ * @return the previously selected Perspective
+ */
+ public PerspectiveSPI getPreviouslySelectedPerspective() {
+ return previouslySelectedPerspective;
+ }
+
+ /**
+ * Returns the currently selected Perspective.
+ *
+ * @return the currently selected Perspective
+ */
+ public PerspectiveSPI getSelectedPerspective() {
+ return selectedPerspective;
+ }
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/ProfileSelectionEvent.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/ProfileSelectionEvent.java
new file mode 100644
index 0000000..a63b186
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/ProfileSelectionEvent.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.selection.events;
+
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * {@link SelectionManagerEvent} for changes to the selected {@link Profile}.
+ *
+ * @author David Withers
+ */
+public class ProfileSelectionEvent implements SelectionManagerEvent {
+ private Profile previouslySelectedProfile;
+ private Profile selectedProfile;
+
+ public ProfileSelectionEvent(Profile previouslySelectedProfile,
+ Profile selectedProfile) {
+ this.previouslySelectedProfile = previouslySelectedProfile;
+ this.selectedProfile = selectedProfile;
+ }
+
+ /**
+ * Returns the previously selected Profile.
+ *
+ * @return the previously selected Profile
+ */
+ public Profile getPreviouslySelectedProfile() {
+ return previouslySelectedProfile;
+ }
+
+ /**
+ * Returns the currently selected Profile.
+ *
+ * @return the currently selected Profile
+ */
+ public Profile getSelectedProfile() {
+ return selectedProfile;
+ }
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/SelectionManagerEvent.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/SelectionManagerEvent.java
new file mode 100644
index 0000000..af15581
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/SelectionManagerEvent.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.selection.events;
+
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+/**
+ * An event given to {@link SelectionManager} observers registered using
+ * {@link SelectionManager#addObserver(Observer)} .
+ *
+ * @author David Withers
+ */
+public interface SelectionManagerEvent {
+
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowBundleSelectionEvent.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowBundleSelectionEvent.java
new file mode 100644
index 0000000..078fad8
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowBundleSelectionEvent.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.selection.events;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * {@link SelectionManagerEvent} for changes to the selected
+ * {@link WorkflowBundle}.
+ *
+ * @author David Withers
+ */
+public class WorkflowBundleSelectionEvent implements SelectionManagerEvent {
+ private WorkflowBundle previouslySelectedWorkflowBundle;
+ private WorkflowBundle selectedWorkflowBundle;
+
+ public WorkflowBundleSelectionEvent(
+ WorkflowBundle previouslySelectedWorkflowBundle,
+ WorkflowBundle selectedWorkflowBundle) {
+ this.previouslySelectedWorkflowBundle = previouslySelectedWorkflowBundle;
+ this.selectedWorkflowBundle = selectedWorkflowBundle;
+ }
+
+ /**
+ * Returns the previously selected WorkflowBundle.
+ *
+ * @return the previously selected WorkflowBundle
+ */
+ public WorkflowBundle getPreviouslySelectedWorkflowBundle() {
+ return previouslySelectedWorkflowBundle;
+ }
+
+ /**
+ * Returns the currently selected WorkflowBundle.
+ *
+ * @return the currently selected WorkflowBundle
+ */
+ public WorkflowBundle getSelectedWorkflowBundle() {
+ return selectedWorkflowBundle;
+ }
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowRunSelectionEvent.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowRunSelectionEvent.java
new file mode 100644
index 0000000..3d8f839
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowRunSelectionEvent.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.selection.events;
+
+/**
+ * {@link SelectionManagerEvent} for changes to the selected workflow run.
+ *
+ * @author David Withers
+ */
+public class WorkflowRunSelectionEvent implements SelectionManagerEvent {
+ private String previouslySelectedWorkflowRun;
+ private String selectedWorkflowRun;
+
+ public WorkflowRunSelectionEvent(String previouslySelectedWorkflowRun,
+ String selectedWorkflowRun) {
+ this.previouslySelectedWorkflowRun = previouslySelectedWorkflowRun;
+ this.selectedWorkflowRun = selectedWorkflowRun;
+ }
+
+ /**
+ * @return the ID of the previously selected workflow run
+ */
+ public String getPreviouslySelectedWorkflowRun() {
+ return previouslySelectedWorkflowRun;
+ }
+
+ /**
+ * @return the ID of the currently selected workflow run
+ */
+ public String getSelectedWorkflowRun() {
+ return selectedWorkflowRun;
+ }
+}
diff --git a/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowSelectionEvent.java b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowSelectionEvent.java
new file mode 100644
index 0000000..cc75b34
--- /dev/null
+++ b/taverna-workbench-selection-api/src/main/java/net/sf/taverna/t2/workbench/selection/events/WorkflowSelectionEvent.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.selection.events;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * {@link SelectionManagerEvent} for changes to the selected {@link Workflow}.
+ *
+ * @author David Withers
+ */
+public class WorkflowSelectionEvent implements SelectionManagerEvent {
+ private Workflow previouslySelectedWorkflow;
+ private Workflow selectedWorkflow;
+
+ public WorkflowSelectionEvent(Workflow previouslySelectedWorkflow,
+ Workflow selectedWorkflow) {
+ this.previouslySelectedWorkflow = previouslySelectedWorkflow;
+ this.selectedWorkflow = selectedWorkflow;
+ }
+
+ /**
+ * Returns the previously selected Workflow.
+ *
+ * @return the previously selected Workflow
+ */
+ public Workflow getPreviouslySelectedWorkflow() {
+ return previouslySelectedWorkflow;
+ }
+
+ /**
+ * Returns the currently selected Workflow.
+ *
+ * @return the currently selected Workflow
+ */
+ public Workflow getSelectedWorkflow() {
+ return selectedWorkflow;
+ }
+}
diff --git a/taverna-workbench-selection-impl/pom.xml b/taverna-workbench-selection-impl/pom.xml
new file mode 100644
index 0000000..cb6cbcb
--- /dev/null
+++ b/taverna-workbench-selection-impl/pom.xml
@@ -0,0 +1,35 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>selection-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Selection Implementation</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>${osgi.core.version}</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java
new file mode 100644
index 0000000..09d6f71
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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.selection.impl;
+
+import static net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage.Type.ADDED;
+import static net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage.Type.REMOVED;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+
+/**
+ * Default implementation of a <code>DataflowSelectionModel</code>.
+ *
+ * @author David Withers
+ */
+public class DataflowSelectionModelImpl implements DataflowSelectionModel {
+ private MultiCaster<DataflowSelectionMessage> multiCaster;
+ private Set<Object> selection = new TreeSet<>(new Comparator<Object>() {
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2)
+ return 0;
+ return o1.hashCode() - o2.hashCode();
+ }
+ });
+
+ /**
+ * Constructs a new instance of DataflowSelectionModelImpl.
+ */
+ public DataflowSelectionModelImpl() {
+ multiCaster = new MultiCaster<>(this);
+ }
+
+ @Override
+ public void addSelection(Object element) {
+ if (element != null) {
+ if (!selection.contains(element)) {
+ clearSelection();
+ selection.add(element);
+ multiCaster.notify(new DataflowSelectionMessage(ADDED, element));
+ }
+ }
+ }
+
+ @Override
+ public void clearSelection() {
+ for (Object element : new HashSet<>(selection))
+ removeSelection(element);
+ }
+
+ @Override
+ public Set<Object> getSelection() {
+ return new HashSet<>(selection);
+ }
+
+ @Override
+ public void removeSelection(Object element) {
+ if (element != null && selection.remove(element))
+ multiCaster.notify(new DataflowSelectionMessage(REMOVED, element));
+ }
+
+ @Override
+ public void setSelection(Set<Object> elements) {
+ if (elements == null) {
+ clearSelection();
+ return;
+ }
+ Set<Object> newSelection = new HashSet<>(elements);
+ for (Object element : new HashSet<>(selection))
+ if (!newSelection.remove(element))
+ removeSelection(element);
+ for (Object element : newSelection)
+ addSelection(element);
+ }
+
+ @Override
+ public void addObserver(Observer<DataflowSelectionMessage> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ @Override
+ public List<Observer<DataflowSelectionMessage>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ @Override
+ public void removeObserver(Observer<DataflowSelectionMessage> observer) {
+ multiCaster.removeObserver(observer);
+ }
+}
diff --git a/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java
new file mode 100644
index 0000000..2e86f1a
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * 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.selection.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.ProfileSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Implementation of the {@link SelectionManager}.
+ *
+ * @author David Withers
+ */
+public class SelectionManagerImpl implements SelectionManager {
+ private static final String RESULTS_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective";
+
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+ private static final Logger logger = Logger.getLogger(SelectionManagerImpl.class);
+
+ private WorkflowBundle selectedWorkflowBundle;
+ private Map<WorkflowBundle, DataflowSelectionModel> workflowSelectionModels = new IdentityHashMap<>();
+ private Map<WorkflowBundle, Workflow> selectedWorkflows = new IdentityHashMap<>();
+ private Map<WorkflowBundle, Profile> selectedProfiles = new IdentityHashMap<>();
+ private String selectedWorkflowRun;
+ private Map<String, DataflowSelectionModel> workflowRunSelectionModels = new HashMap<>();
+ private PerspectiveSPI selectedPerspective;
+ private MultiCaster<SelectionManagerEvent> observers = new MultiCaster<>(this);
+ private FileManager fileManager;
+ private List<PerspectiveSPI> perspectives;
+
+ @Override
+ public DataflowSelectionModel getDataflowSelectionModel(WorkflowBundle dataflow) {
+ DataflowSelectionModel selectionModel;
+ synchronized (workflowSelectionModels) {
+ selectionModel = workflowSelectionModels.get(dataflow);
+ if (selectionModel == null) {
+ selectionModel = new DataflowSelectionModelImpl();
+ workflowSelectionModels.put(dataflow, selectionModel);
+ }
+ }
+ return selectionModel;
+ }
+
+ @Override
+ public WorkflowBundle getSelectedWorkflowBundle() {
+ return selectedWorkflowBundle;
+ }
+
+ @Override
+ public void setSelectedWorkflowBundle(WorkflowBundle workflowBundle) {
+ setSelectedWorkflowBundle(workflowBundle, true);
+ }
+
+ private void setSelectedWorkflowBundle(WorkflowBundle workflowBundle, boolean notifyFileManager) {
+ if (workflowBundle == null || workflowBundle == selectedWorkflowBundle)
+ return;
+ if (notifyFileManager) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ return;
+ }
+ if (selectedWorkflows.get(workflowBundle) == null)
+ selectedWorkflows.put(workflowBundle,
+ workflowBundle.getMainWorkflow());
+ if (selectedProfiles.get(workflowBundle) == null)
+ selectedProfiles.put(workflowBundle,
+ workflowBundle.getMainProfile());
+ SelectionManagerEvent selectionManagerEvent = new WorkflowBundleSelectionEvent(
+ selectedWorkflowBundle, workflowBundle);
+ selectedWorkflowBundle = workflowBundle;
+ notify(selectionManagerEvent);
+ selectDesignPerspective();
+ }
+
+ private void removeWorkflowBundle(WorkflowBundle dataflow) {
+ synchronized (workflowSelectionModels) {
+ DataflowSelectionModel selectionModel = workflowSelectionModels.remove(dataflow);
+ if (selectionModel != null)
+ for (Observer<DataflowSelectionMessage> observer : selectionModel.getObservers())
+ selectionModel.removeObserver(observer);
+ }
+ synchronized (selectedWorkflows) {
+ selectedWorkflows.remove(dataflow);
+ }
+ synchronized (selectedProfiles) {
+ selectedProfiles.remove(dataflow);
+ }
+ }
+
+ @Override
+ public Workflow getSelectedWorkflow() {
+ return selectedWorkflows.get(selectedWorkflowBundle);
+ }
+
+ @Override
+ public void setSelectedWorkflow(Workflow workflow) {
+ if (workflow != null) {
+ Workflow selectedWorkflow = selectedWorkflows.get(workflow
+ .getParent());
+ if (selectedWorkflow != workflow) {
+ SelectionManagerEvent selectionManagerEvent = new WorkflowSelectionEvent(
+ selectedWorkflow, workflow);
+ selectedWorkflows.put(workflow.getParent(), workflow);
+ notify(selectionManagerEvent);
+ }
+ }
+ }
+
+ @Override
+ public Profile getSelectedProfile() {
+ return selectedProfiles.get(selectedWorkflowBundle);
+ }
+
+ @Override
+ public void setSelectedProfile(Profile profile) {
+ if (profile != null) {
+ Profile selectedProfile = selectedProfiles.get(profile.getParent());
+ if (selectedProfile != profile) {
+ SelectionManagerEvent selectionManagerEvent = new ProfileSelectionEvent(
+ selectedProfile, profile);
+ selectedProfiles.put(profile.getParent(), profile);
+ notify(selectionManagerEvent);
+ }
+ }
+ }
+
+ @Override
+ public String getSelectedWorkflowRun() {
+ return selectedWorkflowRun;
+ }
+
+ @Override
+ public void setSelectedWorkflowRun(String workflowRun) {
+ if ((selectedWorkflowRun == null && workflowRun != null)
+ || !selectedWorkflowRun.equals(workflowRun)) {
+ SelectionManagerEvent selectionManagerEvent = new WorkflowRunSelectionEvent(
+ selectedWorkflowRun, workflowRun);
+ selectedWorkflowRun = workflowRun;
+ notify(selectionManagerEvent);
+ selectResultsPerspective();
+ }
+ }
+
+ @Override
+ public DataflowSelectionModel getWorkflowRunSelectionModel(String workflowRun) {
+ DataflowSelectionModel selectionModel;
+ synchronized (workflowRunSelectionModels) {
+ selectionModel = workflowRunSelectionModels.get(workflowRun);
+ if (selectionModel == null) {
+ selectionModel = new DataflowSelectionModelImpl();
+ workflowRunSelectionModels.put(workflowRun, selectionModel);
+ }
+ }
+ return selectionModel;
+ }
+
+ @SuppressWarnings("unused")
+ private void removeWorkflowRun(String workflowRun) {
+ synchronized (workflowRunSelectionModels) {
+ DataflowSelectionModel selectionModel = workflowRunSelectionModels
+ .remove(workflowRun);
+ if (selectionModel != null)
+ for (Observer<DataflowSelectionMessage> observer : selectionModel
+ .getObservers())
+ selectionModel.removeObserver(observer);
+ }
+ }
+
+ @Override
+ public PerspectiveSPI getSelectedPerspective() {
+ return selectedPerspective;
+ }
+
+ @Override
+ public void setSelectedPerspective(PerspectiveSPI perspective) {
+ if (selectedPerspective != perspective) {
+ SelectionManagerEvent selectionManagerEvent = new PerspectiveSelectionEvent(
+ selectedPerspective, perspective);
+ selectedPerspective = perspective;
+ notify(selectionManagerEvent);
+ }
+ }
+
+ private void selectDesignPerspective() {
+ for (PerspectiveSPI perspective : perspectives)
+ if (DESIGN_PERSPECTIVE_ID.equals(perspective.getID())) {
+ setSelectedPerspective(perspective);
+ break;
+ }
+ }
+
+ private void selectResultsPerspective() {
+ for (PerspectiveSPI perspective : perspectives)
+ if (RESULTS_PERSPECTIVE_ID.equals(perspective.getID())) {
+ setSelectedPerspective(perspective);
+ break;
+ }
+ }
+
+ @Override
+ public void addObserver(Observer<SelectionManagerEvent> observer) {
+ synchronized (observers) {
+ WorkflowBundle selectedWorkflowBundle = getSelectedWorkflowBundle();
+ Workflow selectedWorkflow = getSelectedWorkflow();
+ Profile selectedProfile = getSelectedProfile();
+ String selectedWorkflowRun = getSelectedWorkflowRun();
+ PerspectiveSPI selectedPerspective = getSelectedPerspective();
+ try {
+ if (selectedWorkflowBundle != null)
+ observer.notify(this, new WorkflowBundleSelectionEvent(
+ null, selectedWorkflowBundle));
+ if (selectedWorkflow != null)
+ observer.notify(this, new WorkflowSelectionEvent(null,
+ selectedWorkflow));
+ if (selectedProfile != null)
+ observer.notify(this, new ProfileSelectionEvent(null,
+ selectedProfile));
+ if (selectedWorkflowRun != null)
+ observer.notify(this, new WorkflowRunSelectionEvent(null,
+ selectedWorkflowRun));
+ if (selectedPerspective != null)
+ observer.notify(this, new PerspectiveSelectionEvent(null,
+ selectedPerspective));
+ } catch (Exception e) {
+ logger.warn("Could not notify " + observer, e);
+ }
+ observers.addObserver(observer);
+ }
+ }
+
+ @Override
+ public void removeObserver(Observer<SelectionManagerEvent> observer) {
+ observers.removeObserver(observer);
+ }
+
+ @Override
+ public List<Observer<SelectionManagerEvent>> getObservers() {
+ return observers.getObservers();
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ setSelectedWorkflowBundle(fileManager.getCurrentDataflow());
+ fileManager.addObserver(new FileManagerObserver());
+ }
+
+ public void setEditManager(EditManager editManager) {
+ editManager.addObserver(new EditManagerObserver());
+ }
+
+ public void setPerspectives(List<PerspectiveSPI> perspectives) {
+ this.perspectives = perspectives;
+ }
+
+ public class FileManagerObserver implements Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof ClosedDataflowEvent) {
+ WorkflowBundle workflowBundle = ((ClosedDataflowEvent) message).getDataflow();
+ removeWorkflowBundle(workflowBundle);
+ } else if (message instanceof OpenedDataflowEvent) {
+ WorkflowBundle workflowBundle = ((OpenedDataflowEvent) message).getDataflow();
+ setSelectedWorkflowBundle(workflowBundle, false);
+ } else if (message instanceof SetCurrentDataflowEvent) {
+ WorkflowBundle workflowBundle = ((SetCurrentDataflowEvent) message).getDataflow();
+ setSelectedWorkflowBundle(workflowBundle, false);
+ }
+ }
+ }
+
+ private class EditManagerObserver implements Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+ throws Exception {
+ Edit<?> edit = message.getEdit();
+ considerEdit(edit, message instanceof DataFlowUndoEvent);
+ }
+
+ private void considerEdit(Edit<?> edit, boolean undoing) {
+ if (edit instanceof CompoundEdit) {
+ CompoundEdit compound = (CompoundEdit) edit;
+ for (Edit<?> e : compound.getChildEdits())
+ considerEdit(e, undoing);
+ } else if (edit instanceof AddChildEdit
+ && edit.getSubject() instanceof Workflow) {
+ Workflow subject = (Workflow) edit.getSubject();
+ DataflowSelectionModel selectionModel = getDataflowSelectionModel(subject
+ .getParent());
+ Object child = ((AddChildEdit<?>) edit).getChild();
+ if (child instanceof Processor
+ || child instanceof InputWorkflowPort
+ || child instanceof OutputWorkflowPort) {
+ if (undoing
+ && selectionModel.getSelection().contains(child))
+ selectionModel.clearSelection();
+ else {
+ Set<Object> selection = new HashSet<>();
+ selection.add(child);
+ selectionModel.setSelection(selection);
+ }
+ }
+ }
+ }
+ }
+
+ private void notify(SelectionManagerEvent event) {
+ synchronized (observers) {
+ observers.notify(event);
+ }
+ }
+}
diff --git a/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml
new file mode 100644
index 0000000..19faa00
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <!-- <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" /> -->
+
+ <list id="perspectives" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml
new file mode 100644
index 0000000..84bbff1
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="selectionManager" class="net.sf.taverna.t2.workbench.selection.impl.SelectionManagerImpl" >
+ <property name="fileManager" ref="fileManager"/>
+ <property name="editManager" ref="editManager"/>
+ <property name="perspectives" ref="perspectives"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-update-manager/pom.xml b/taverna-workbench-update-manager/pom.xml
new file mode 100644
index 0000000..c2f2003
--- /dev/null
+++ b/taverna-workbench-update-manager/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>update-manager</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>Taverna Workbench Update Manager</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-update-api</artifactId>
+ <version>${taverna.commons.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java
new file mode 100644
index 0000000..6b70101
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.update.impl;
+
+import java.awt.GridBagLayout;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.commons.update.UpdateManager;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings({ "serial", "unused" })
+public class UpdateManagerView extends JPanel {
+
+ private UpdateManager updateManager;
+
+ public UpdateManagerView(UpdateManager updateManager) {
+ this.updateManager = updateManager;
+ initialize();
+ }
+
+ private void initialize() {
+ setLayout(new GridBagLayout());
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java
new file mode 100644
index 0000000..2c1459b
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.update.impl.menu;
+
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.update.UpdateException;
+import uk.org.taverna.commons.update.UpdateManager;
+
+public class UpdateMenuAction extends AbstractMenuAction {
+ private static final Logger logger = Logger.getLogger(UpdateMenuAction.class);
+ private static final URI ADVANCED_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+ private UpdateManager updateManager;
+
+ public UpdateMenuAction() {
+ super(ADVANCED_MENU_URI, 1000);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Check for updates") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ findUpdates();
+ }
+ };
+ }
+
+ public void setUpdateManager(UpdateManager updateManager) {
+ this.updateManager = updateManager;
+ }
+
+ private void findUpdates() {
+ Component parent = null;
+ try {
+ if (!areUpdatesAvailable()) {
+ showMessageDialog(null, "No update available");
+ return;
+ }
+ if (showConfirmDialog(parent, "Update available. Update Now?") != YES_OPTION)
+ return;
+ applyUpdates();
+ showMessageDialog(parent,
+ "Update complete. Restart Taverna to apply update.");
+ } catch (UpdateException ex) {
+ showMessageDialog(parent, "Update failed: " + ex.getMessage());
+ logger.warn("Update failed", ex);
+ }
+ }
+
+ protected boolean areUpdatesAvailable() throws UpdateException {
+ return updateManager.checkForUpdates();
+ }
+
+ protected void applyUpdates() throws UpdateException {
+ updateManager.update();
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java
new file mode 100644
index 0000000..e6b4695
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static java.awt.event.KeyEvent.VK_F12;
+import static javax.swing.KeyStroke.getKeyStroke;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+public class PluginMenuAction extends AbstractMenuAction {
+ private static final String UPDATES_AND_PLUGINS = "Updates and plugins";
+
+ @SuppressWarnings("serial")
+ protected class SoftwareUpdates extends AbstractAction {
+ public SoftwareUpdates() {
+ super(UPDATES_AND_PLUGINS, null/*UpdatesAvailableIcon.updateRecommendedIcon*/);
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F12, 0));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ @SuppressWarnings("unused")
+ Component parent = null;
+ if (e.getSource() instanceof Component) {
+ parent = (Component) e.getSource();
+ }
+ //FIXME this does nothing!
+ //final PluginManagerFrame pluginManagerUI = new PluginManagerFrame(
+ // PluginManager.getInstance());
+ //if (parent != null) {
+ // pluginManagerUI.setLocationRelativeTo(parent);
+ //}
+ //pluginManagerUI.setVisible(true);
+ }
+ }
+
+ public PluginMenuAction() {
+ super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#advanced"),
+ 100);
+ }
+
+ @Override
+ protected Action createAction() {
+ //return new SoftwareUpdates();
+ return null;
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java
new file mode 100644
index 0000000..e2611be
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static net.sf.taverna.t2.workbench.updatemanager.UpdatesToolbarSection.UPDATES_SECTION;
+
+import java.awt.Component;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+
+public class UpdatesAvailableMenuAction extends AbstractMenuCustom {
+ public UpdatesAvailableMenuAction() {
+ super(UPDATES_SECTION, 10);
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ //return new UpdatesAvailableIcon();
+ return null;
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java
new file mode 100644
index 0000000..b16d614
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java
@@ -0,0 +1,16 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class UpdatesToolbarSection extends AbstractMenuSection {
+ public static final URI UPDATES_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/toolbar#updatesSection");
+
+ public UpdatesToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 10000, UPDATES_SECTION);
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml
new file mode 100644
index 0000000..ea637dd
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UpdateMenuAction" auto-export="interfaces" />
+
+ <reference id="updateManager" interface="uk.org.taverna.commons.update.UpdateManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml
new file mode 100644
index 0000000..c3adf1f
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UpdateMenuAction"
+ class="net.sf.taverna.t2.workbench.update.impl.menu.UpdateMenuAction">
+ <property name="updateManager" ref="updateManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-workbench-api/pom.xml b/taverna-workbench-workbench-api/pom.xml
new file mode 100644
index 0000000..4d9a9ad
--- /dev/null
+++ b/taverna-workbench-workbench-api/pom.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-api</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <packaging>bundle</packaging>
+ <name>Workbench API</name>
+ <description>The main workbench ui</description>
+</project>
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/MainWindow.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/MainWindow.java
new file mode 100644
index 0000000..6b2de88
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/MainWindow.java
@@ -0,0 +1,16 @@
+package net.sf.taverna.t2.workbench;
+
+import javax.swing.JFrame;
+
+public class MainWindow {
+
+ private static JFrame mainWindow;
+
+ public static JFrame getMainWindow() {
+ return mainWindow;
+ }
+
+ public static void setMainWindow(JFrame window) {
+ mainWindow = window;
+ }
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ModelMapConstants.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ModelMapConstants.java
new file mode 100644
index 0000000..ff4b267
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ModelMapConstants.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * 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;
+
+public class ModelMapConstants {
+ public static final String CURRENT_DATAFLOW = "currentDataflow";
+ public static final String CURRENT_PERSPECTIVE = "currentPerspective";
+
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ShutdownSPI.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ShutdownSPI.java
new file mode 100644
index 0000000..30a06e2
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ShutdownSPI.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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;
+
+import java.util.Comparator;
+
+/**
+ * SPI for components that want to be notified when the workbench is about to be
+ * shutdown.
+ * <p>
+ * Components should implement this if they need to delay the shutdown while
+ * they finish a task, e.g. writing out cached data to disk.
+ * <p>
+ * <b>NB</b> There is no guarantee that the workbench will actually shut down as
+ * the user may decide to abort the shutdown.
+ *
+ * @see ShutdownSPI
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public interface ShutdownSPI {
+
+ /**
+ * Called when the workbench is about to shutdown. Implementations should
+ * block until they are ready for the shutdown process to proceed. If the
+ * shutdown for a component will take more than a few seconds a dialog
+ * should inform the user, possibly allowing the user to cancel the shutdown
+ * task for the component and/or the workbench shutdown process.
+ * <p>
+ * When the shutdown process has finished (or the user has canceled it) this
+ * method should return with a vale of <code>true</code>.
+ * <p>
+ * Only return <code>false</code> if the user has chosen to abort the
+ * workbench shutdown process.
+ *
+ * @return
+ */
+ public boolean shutdown();
+
+ /**
+ * Provides a hint for the position in the shutdown sequence that shutdown
+ * should be called. The lower the number the earlier shutdown will be
+ * called.
+ * <p>
+ * Custom plugins are recommended to start with a value < 100.
+ */
+ public int positionHint();
+
+ public class ShutdownComparator implements Comparator<ShutdownSPI> {
+ public int compare(ShutdownSPI o1, ShutdownSPI o2) {
+ return o1.positionHint() - o2.positionHint();
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/StartupSPI.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/StartupSPI.java
new file mode 100644
index 0000000..d43b7e8
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/StartupSPI.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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;
+
+import java.util.Comparator;
+
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+/**
+ * SPI for components/plugins that want to be able to perform some configuration
+ * or similar initialization on Workbench startup.
+ *
+ * @see ShutdownSPI
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ */
+public interface StartupSPI {
+
+ /**
+ * Called when the Workbench is starting up for implementations of this
+ * interface to perform any configuration on start up.
+ * <p>
+ * When the configuration process has finished this method should return
+ * <code>true</code>.
+ * <p>
+ * Return <code>false</code> if and only if failure in this method will
+ * cause Workbench not to function at all.
+ *
+ */
+ public boolean startup();
+
+ /**
+ * Provides a hint for the order in which the startup hooks (that implement
+ * this interface) will be called. The lower the number, the earlier will
+ * the startup hook be invoked.
+ * <p>
+ * Custom plugins are recommended to start with a value > 100.
+ */
+ public int positionHint();
+
+ public class StartupComparator implements Comparator<StartupSPI> {
+ public int compare(StartupSPI o1, StartupSPI o2) {
+ return o1.positionHint() - o2.positionHint();
+ }
+ }
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/icons/WorkbenchIcons.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/icons/WorkbenchIcons.java
new file mode 100644
index 0000000..5318572
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/icons/WorkbenchIcons.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * 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.icons;
+
+import javax.swing.ImageIcon;
+
+/**
+ * A container for common icons used by the workbench
+ *
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchIcons {
+
+ public static ImageIcon resultsPerspectiveIcon;
+ public static ImageIcon closeIcon;
+ public static ImageIcon closeAllIcon;
+ public static ImageIcon deleteIcon;
+ public static ImageIcon trashIcon; // not in use - using delete icon instead
+ public static ImageIcon zoomIcon;
+ public static ImageIcon zoomInIcon;
+ public static ImageIcon zoomOutIcon;
+ public static ImageIcon webIcon;
+ public static ImageIcon openIcon;
+ public static ImageIcon runIcon;
+ public static ImageIcon refreshIcon;
+ public static ImageIcon editIcon;
+ public static ImageIcon findIcon;
+ public static ImageIcon folderOpenIcon;
+ public static ImageIcon folderClosedIcon;
+ public static ImageIcon newInputIcon;
+ public static ImageIcon newIcon;
+ public static ImageIcon newListIcon;
+ public static ImageIcon inputValueIcon;
+ public static ImageIcon xmlNodeIcon;
+ public static ImageIcon saveIcon;
+ public static ImageIcon saveAllIcon;
+ public static ImageIcon saveAsIcon;
+ public static ImageIcon leafIcon;
+ public static ImageIcon saveMenuIcon;
+ public static ImageIcon savePNGIcon;
+ public static ImageIcon importIcon;
+ public static ImageIcon importFileIcon;
+ public static ImageIcon importUrlIcon;
+ public static ImageIcon openurlIcon;
+ public static ImageIcon openMenuIcon;
+ public static ImageIcon pauseIcon;
+ public static ImageIcon playIcon;
+ public static ImageIcon stopIcon;
+ public static ImageIcon breakIcon;
+ public static ImageIcon rbreakIcon;
+ public static ImageIcon tickIcon;
+ public static ImageIcon untickIcon;
+ public static ImageIcon greentickIcon;
+ public static ImageIcon renameIcon;
+ public static ImageIcon databaseIcon;
+ public static ImageIcon nullIcon;
+ public static ImageIcon uninstallIcon;
+ public static ImageIcon workingIcon;
+ public static ImageIcon workingStoppedIcon;
+ public static ImageIcon updateRecommendedIcon;
+ public static ImageIcon updateIcon;
+ public static ImageIcon searchIcon;
+ public static ImageIcon pasteIcon;
+ public static ImageIcon copyIcon;
+ public static ImageIcon cutIcon;
+ public static ImageIcon datalinkIcon;
+ public static ImageIcon controlLinkIcon;
+ public static ImageIcon mergeIcon;
+ public static ImageIcon inputIcon;
+ public static ImageIcon inputPortIcon;
+ public static ImageIcon outputIcon;
+ public static ImageIcon outputPortIcon;
+ public static ImageIcon verticalIcon;
+ public static ImageIcon horizontalIcon;
+ public static ImageIcon noportIcon;
+ public static ImageIcon allportIcon;
+ public static ImageIcon blobIcon;
+ public static ImageIcon expandNestedIcon;
+ public static ImageIcon workflowExplorerIcon;
+ public static ImageIcon configureIcon;
+ public static ImageIcon plusIcon;
+ public static ImageIcon minusIcon;
+ public static ImageIcon undoIcon;
+ public static ImageIcon redoIcon;
+ public static ImageIcon upArrowIcon;
+ public static ImageIcon downArrowIcon;
+ public static ImageIcon tavernaCogs64x64Icon;
+ public static ImageIcon tavernaCogs32x32Icon;
+ public static ImageIcon opmIcon;
+ public static ImageIcon janusIcon;
+ public static ImageIcon workflowResultsIcon;
+ public static ImageIcon normalizeIcon;
+ public static ImageIcon errorMessageIcon;
+ public static ImageIcon infoMessageIcon;
+ public static ImageIcon questionMessageIcon;
+ public static ImageIcon warningMessageIcon;
+
+ static {
+ // Load the image files found in this package into the class.
+ Class<?> c = WorkbenchIcons.class;
+
+ resultsPerspectiveIcon = new ImageIcon(c.getResource("generic/results-perspective.png"));
+ closeIcon = new ImageIcon(c.getResource("generic/close.gif"));
+ closeAllIcon = new ImageIcon(c.getResource("generic/closeAll.gif"));
+ deleteIcon = new ImageIcon(c.getResource("generic/bin.png"));
+ trashIcon = new ImageIcon(c.getResource("graph/trash.png"));
+ zoomIcon = new ImageIcon(c.getResource("generic/zoom.gif"));
+ zoomInIcon = new ImageIcon(c.getResource("generic/zoomin.png"));
+ zoomOutIcon = new ImageIcon(c.getResource("generic/zoomout.png"));
+ webIcon = new ImageIcon(c.getResource("generic/web.gif"));
+ openIcon = new ImageIcon(c.getResource("generic/open.gif"));
+ runIcon = new ImageIcon(c.getResource("generic/run.gif"));
+ refreshIcon = new ImageIcon(c.getResource("generic/refresh.gif"));
+ editIcon = new ImageIcon(c.getResource("generic/edit.gif"));
+ findIcon = new ImageIcon(c.getResource("generic/find.gif"));
+ folderOpenIcon = new ImageIcon(c.getResource("generic/folder-open.png"));
+ folderClosedIcon = new ImageIcon(c.getResource("generic/folder-closed.png"));
+ newInputIcon = new ImageIcon(c.getResource("generic/newinput.gif"));
+ newIcon = new ImageIcon(c.getResource("generic/newinput.gif"));
+ newListIcon = new ImageIcon(c.getResource("generic/newlist.gif"));
+ inputValueIcon = new ImageIcon(c.getResource("generic/inputValue.gif"));
+
+ xmlNodeIcon = new ImageIcon(c.getResource("generic/xml_node.gif"));
+ leafIcon = new ImageIcon(c.getResource("generic/leaf.gif"));
+ saveIcon = new ImageIcon(c.getResource("generic/save.png"));
+ saveAllIcon = new ImageIcon(c.getResource("generic/saveAll.png"));
+ saveAsIcon = new ImageIcon(c.getResource("generic/saveAs.png"));
+ saveMenuIcon = new ImageIcon(c.getResource("generic/savemenu.gif"));
+ savePNGIcon = new ImageIcon(c.getResource("generic/savepng.gif"));
+ importIcon = new ImageIcon(c.getResource("generic/import.gif"));
+ importFileIcon = new ImageIcon(c.getResource("generic/fileimport.png"));
+ importUrlIcon = new ImageIcon(c.getResource("generic/urlimport.png"));
+ openurlIcon = new ImageIcon(c.getResource("generic/openurl.gif"));
+ openIcon = new ImageIcon(c.getResource("generic/open.gif"));
+ openMenuIcon = new ImageIcon(c.getResource("generic/openmenu.gif"));
+ pauseIcon = new ImageIcon(c.getResource("generic/pause.png"));
+ playIcon = new ImageIcon(c.getResource("generic/play.png"));
+ stopIcon = new ImageIcon(c.getResource("generic/stop.gif"));
+ breakIcon = new ImageIcon(c.getResource("generic/break.gif"));
+ rbreakIcon = new ImageIcon(c.getResource("generic/rbreak.gif"));
+ tickIcon = new ImageIcon(c.getResource("generic/tick.png"));
+ untickIcon = new ImageIcon(c.getResource("generic/untick.png"));
+ greentickIcon = new ImageIcon(c.getResource("generic/greentick.png"));
+ renameIcon = new ImageIcon(c.getResource("generic/rename.png"));
+ databaseIcon = new ImageIcon(c.getResource("generic/database.gif"));
+ nullIcon = new ImageIcon(new java.awt.image.BufferedImage(1, 1,
+ java.awt.image.BufferedImage.TYPE_INT_RGB));
+ copyIcon = new ImageIcon(c.getResource("generic/copy.png"));
+ cutIcon = new ImageIcon(c.getResource("generic/cut.png"));
+ pasteIcon = new ImageIcon(c.getResource("generic/paste.png"));
+ searchIcon = new ImageIcon(c.getResource("generic/search.png"));
+ updateIcon = new ImageIcon(c.getResource("generic/update.png"));
+ updateRecommendedIcon = new ImageIcon(c.getResource("generic/updateRecommended.png"));
+ uninstallIcon = new ImageIcon(c.getResource("generic/uninstall.png"));
+ workingIcon = new ImageIcon(c.getResource("generic/working.gif"));
+ workingStoppedIcon = new ImageIcon(c.getResource("generic/workingStopped.png"));
+ datalinkIcon = new ImageIcon(c.getResource("explorer/datalink.gif"));
+ controlLinkIcon = new ImageIcon(c.getResource("explorer/constraint.gif"));
+ mergeIcon = new ImageIcon(c.getResource("explorer/merge.png"));
+ inputIcon = new ImageIcon(c.getResource("explorer/input.png"));
+ inputPortIcon = new ImageIcon(c.getResource("explorer/inputport.png"));
+ outputIcon = new ImageIcon(c.getResource("explorer/output.png"));
+ outputPortIcon = new ImageIcon(c.getResource("explorer/outputport.png"));
+ verticalIcon = new ImageIcon(c.getResource("graph/vertical.png"));
+ horizontalIcon = new ImageIcon(c.getResource("graph/horizontal.png"));
+ noportIcon = new ImageIcon(c.getResource("graph/noport.png"));
+ allportIcon = new ImageIcon(c.getResource("graph/allport.png"));
+ blobIcon = new ImageIcon(c.getResource("graph/blob.png"));
+ expandNestedIcon = new ImageIcon(c.getResource("graph/expandnested.png"));
+ workflowExplorerIcon = new ImageIcon(c.getResource("explorer/workflow-explorer.png"));
+ configureIcon = new ImageIcon(c.getResource("generic/configure.png"));
+ plusIcon = new ImageIcon(c.getResource("generic/plus.png"));
+ minusIcon = new ImageIcon(c.getResource("generic/minus.png"));
+ undoIcon = new ImageIcon(c.getResource("generic/undo.png"));
+ redoIcon = new ImageIcon(c.getResource("generic/redo.png"));
+ upArrowIcon = new ImageIcon(c.getResource("generic/up-arrow.png"));
+ downArrowIcon = new ImageIcon(c.getResource("generic/down-arrow.png"));
+ tavernaCogs64x64Icon = new ImageIcon(c.getResource("generic/taverna_cogs_64x64.png"));
+ tavernaCogs32x32Icon = new ImageIcon(c.getResource("generic/taverna_cogs_32x32.png"));
+ opmIcon = new ImageIcon(c.getResource("generic/opmIcon.png"));
+ janusIcon = new ImageIcon(c.getResource("generic/janus.png"));
+ workflowResultsIcon = new ImageIcon(c.getResource("generic/workflowResults.png"));
+ normalizeIcon = new ImageIcon(c.getResource("generic/normalize.png"));
+
+ errorMessageIcon = new ImageIcon(
+ c.getResource("generic/taverna-wheel-message-dialog-error-icon.png"));
+ infoMessageIcon = new ImageIcon(
+ c.getResource("generic/taverna-wheel-message-dialog-info-icon.png"));
+ questionMessageIcon = new ImageIcon(
+ c.getResource("generic/taverna-wheel-message-dialog-question-icon.png"));
+ warningMessageIcon = new ImageIcon(
+ c.getResource("generic/taverna-wheel-message-dialog-warning-icon.png"));
+
+ }
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/SwingWorkerCompletionWaiter.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/SwingWorkerCompletionWaiter.java
new file mode 100644
index 0000000..536918f
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/SwingWorkerCompletionWaiter.java
@@ -0,0 +1,33 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JDialog;
+import javax.swing.SwingWorker;
+
+/**
+ * @author alanrw
+ *
+ */
+public class SwingWorkerCompletionWaiter implements PropertyChangeListener {
+
+ private final JDialog dialog;
+
+ public SwingWorkerCompletionWaiter(JDialog dialog) {
+ this.dialog = dialog;
+ }
+
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("state".equals(evt.getPropertyName())
+ && SwingWorker.StateValue.DONE == evt.getNewValue()) {
+ dialog.setVisible(false);
+ dialog.dispose();
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Updatable.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Updatable.java
new file mode 100644
index 0000000..561ca2e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Updatable.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (C) 2013 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.ui;
+
+/**
+ * Something that can be updated.
+ *
+ * @author David Withers
+ */
+public interface Updatable {
+
+ public void update();
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Utils.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Utils.java
new file mode 100644
index 0000000..3b25551
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Utils.java
@@ -0,0 +1,20 @@
+package net.sf.taverna.t2.workbench.ui;
+
+import java.awt.Component;
+import java.awt.Frame;
+
+import net.sf.taverna.t2.workbench.MainWindow;
+
+public class Utils {
+ public static Frame getParentFrame(Component container) {
+ while (container != null && !(container instanceof Frame)) {
+ container = container.getParent();
+ }
+ if (container == null) {
+ return MainWindow.getMainWindow();
+ }
+ // Must be a Frame
+ return (Frame) container;
+ }
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Workbench.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Workbench.java
new file mode 100644
index 0000000..4b0f071
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/Workbench.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (C) 2011 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.ui;
+
+import java.io.IOException;
+
+public interface Workbench {
+
+ public void makeNamedComponentVisible(String componentName);
+
+ public void exit();
+
+ public void storeSizeAndLocationPrefs() throws IOException;
+
+}
\ No newline at end of file
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/PerspectiveSPI.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/PerspectiveSPI.java
new file mode 100644
index 0000000..912de63
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/PerspectiveSPI.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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.ui.zaria;
+
+import java.util.Comparator;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+
+/**
+ * SPI representing UI perspectives.
+ *
+ * @author Stuart Owen
+ */
+public interface PerspectiveSPI {
+
+ /**
+ * Returns an identifier that uniquely identifies the perspective.
+ *
+ * @return an identifier that uniquely identifies the perspective
+ */
+ public String getID();
+
+ /**
+ * Returns the component containing the perspective.
+ *
+ * @return the component containing the perspective
+ */
+ public JComponent getPanel();
+
+ /**
+ * Returns the icon image for the toolbar button
+ *
+ * @return the icon image for the toolbar button
+ */
+ public ImageIcon getButtonIcon();
+
+ /**
+ *
+ * @return the text for the perspective
+ */
+ public String getText();
+
+ /**
+ * Provides a hint for the position of perspective in the toolbar and menu.
+ * The lower the value the earlier it will appear in the list.
+ *
+ * Custom plugins are recommended to start with a value > 100 (allowing for a whopping 100 built in plugins!)
+ */
+ public int positionHint();
+
+ public class PerspectiveComparator implements Comparator<PerspectiveSPI> {
+ public int compare(PerspectiveSPI o1, PerspectiveSPI o2) {
+ return new Integer(o1.positionHint()).compareTo(new Integer(o2
+ .positionHint()));
+ }
+ }
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/UIComponentFactorySPI.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/UIComponentFactorySPI.java
new file mode 100644
index 0000000..ae79a3a
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/UIComponentFactorySPI.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.ui.zaria;
+
+import javax.swing.ImageIcon;
+
+/**
+ * Implementations can construct UI components to be inserted into containers
+ * within the workbench. These components may or may not bind to any given model
+ * object, the previous approach of forcing all UI components to be model
+ * listeners wasn't really very bright.
+ * <p>
+ * This class is intended to allow minimal information for building menus and
+ * the like without having to construct potentially heavy swing objects every
+ * time.
+ *
+ * @author Tom Oinn
+ */
+public interface UIComponentFactorySPI {
+
+ /**
+ * Get the preferred name of this component, for titles in windows etc.
+ */
+ public String getName();
+
+ /**
+ * Get an icon to be used in window decorations for this component.
+ */
+ public ImageIcon getIcon();
+
+ /**
+ * Construct a JComponent from this factory, cast as a UIComponent but must
+ * also implement JComponent (if anyone knows how to define this sensibly
+ * I'm all ears...)
+ */
+ public UIComponentSPI getComponent();
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/UIComponentSPI.java b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/UIComponentSPI.java
new file mode 100644
index 0000000..cbfb29c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/java/net/sf/taverna/t2/workbench/ui/zaria/UIComponentSPI.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+/**
+ * This file is a component of the Taverna project,
+ * and is licensed under the GNU LGPL.
+ * Copyright Tom Oinn, EMBL-EBI
+ */
+package net.sf.taverna.t2.workbench.ui.zaria;
+
+import javax.swing.ImageIcon;
+
+/**
+ * Interface for any UI component to be used within the workbench
+ * @author Tom Oinn
+ */
+public interface UIComponentSPI {
+
+ /**
+ * Get the preferred name of this component, for titles in windows etc.
+ */
+ public String getName();
+
+ /**
+ * Get an icon to be used in window decorations for this component.
+ */
+ public ImageIcon getIcon();
+
+ /**
+ * Called when the component is displayed in the UI
+ */
+ public void onDisplay();
+
+ /**
+ * Called after the component has been removed from the UI
+ */
+ public void onDispose();
+
+}
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/biomoby.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/biomoby.png
new file mode 100644
index 0000000..af33f02
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/biomoby.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/constraint.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/constraint.gif
new file mode 100644
index 0000000..2f6b8ab
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/constraint.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/dataflow.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/dataflow.png
new file mode 100644
index 0000000..71b188c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/dataflow.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/datalink.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/datalink.gif
new file mode 100644
index 0000000..8e305a0
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/datalink.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/input.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/input.png
new file mode 100644
index 0000000..404a14b
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/input.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/inputport.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/inputport.png
new file mode 100644
index 0000000..02161e8
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/inputport.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/localworker.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/localworker.png
new file mode 100644
index 0000000..758a457
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/localworker.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/merge.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/merge.png
new file mode 100644
index 0000000..7b33c7f
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/merge.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/output.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/output.png
new file mode 100644
index 0000000..27d1870
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/output.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/outputport.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/outputport.png
new file mode 100644
index 0000000..2f1ea12
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/outputport.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/rserv.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/rserv.png
new file mode 100644
index 0000000..5772daf
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/rserv.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/seqhound.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/seqhound.png
new file mode 100644
index 0000000..a0440d7
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/seqhound.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/soaplab.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/soaplab.png
new file mode 100644
index 0000000..b86d848
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/soaplab.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/stringconstant.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/stringconstant.png
new file mode 100644
index 0000000..0810c97
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/stringconstant.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/talisman.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/talisman.png
new file mode 100644
index 0000000..e099664
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/talisman.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/unknownprocessor.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/unknownprocessor.png
new file mode 100644
index 0000000..eb2c5b9
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/unknownprocessor.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow-explorer-old.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow-explorer-old.png
new file mode 100644
index 0000000..1d2212e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow-explorer-old.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow-explorer.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow-explorer.png
new file mode 100644
index 0000000..0743208
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow-explorer.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow.png
new file mode 100644
index 0000000..dd140ae
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflow.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflowInputPort.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflowInputPort.png
new file mode 100644
index 0000000..1103241
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflowInputPort.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflowOutputPort.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflowOutputPort.png
new file mode 100644
index 0000000..5ddc0ae
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/workflowOutputPort.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/wsdl.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/wsdl.png
new file mode 100644
index 0000000..f9fdae8
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/explorer/wsdl.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/bin.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/bin.png
new file mode 100644
index 0000000..09cb4dd
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/bin.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/break.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/break.gif
new file mode 100644
index 0000000..9b7a76e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/break.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/close.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/close.gif
new file mode 100644
index 0000000..b6922ac
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/close.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/closeAll.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/closeAll.gif
new file mode 100644
index 0000000..e94ea20
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/closeAll.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/configure.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/configure.png
new file mode 100644
index 0000000..5c8213f
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/configure.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/copy.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/copy.png
new file mode 100755
index 0000000..8a227f8
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/copy.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/cut.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/cut.png
new file mode 100644
index 0000000..cf1a93b
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/cut.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/database.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/database.gif
new file mode 100644
index 0000000..fda86b5
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/database.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/delete.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/delete.png
new file mode 100644
index 0000000..0d2a7c3
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/delete.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/down-arrow.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/down-arrow.png
new file mode 100644
index 0000000..d352f72
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/down-arrow.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/edit.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/edit.gif
new file mode 100644
index 0000000..eb40160
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/edit.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/fileimport.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/fileimport.png
new file mode 100644
index 0000000..32baf9c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/fileimport.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/find.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/find.gif
new file mode 100644
index 0000000..b991e07
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/find.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/folder-closed.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/folder-closed.png
new file mode 100644
index 0000000..e758fdf
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/folder-closed.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/folder-open.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/folder-open.png
new file mode 100644
index 0000000..918d3e3
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/folder-open.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/greentick.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/greentick.png
new file mode 100644
index 0000000..bcf7c41
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/greentick.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/import.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/import.gif
new file mode 100644
index 0000000..141d638
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/import.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/inputValue.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/inputValue.gif
new file mode 100644
index 0000000..061161a
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/inputValue.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/janus.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/janus.png
new file mode 100644
index 0000000..2edb3a1
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/janus.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/leaf.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/leaf.gif
new file mode 100644
index 0000000..a9af5d5
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/leaf.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/minus.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/minus.png
new file mode 100644
index 0000000..e24a868
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/minus.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/newinput.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/newinput.gif
new file mode 100644
index 0000000..dd15d9c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/newinput.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/newlist.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/newlist.gif
new file mode 100644
index 0000000..1b0575b
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/newlist.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/normalize.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/normalize.png
new file mode 100644
index 0000000..973a981
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/normalize.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/open.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/open.gif
new file mode 100644
index 0000000..ab602f2
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/open.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/openmenu.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/openmenu.gif
new file mode 100644
index 0000000..abb1e35
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/openmenu.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/openurl.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/openurl.gif
new file mode 100644
index 0000000..ec6cca4
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/openurl.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/opmIcon.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/opmIcon.png
new file mode 100644
index 0000000..cb34785
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/opmIcon.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/paste.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/paste.png
new file mode 100755
index 0000000..06ea9df
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/paste.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/pause.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/pause.png
new file mode 100644
index 0000000..6f1b25d
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/pause.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/play.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/play.png
new file mode 100644
index 0000000..7d92ba1
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/play.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/plus.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/plus.png
new file mode 100644
index 0000000..2bb5ad9
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/plus.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/rbreak.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/rbreak.gif
new file mode 100644
index 0000000..a523a04
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/rbreak.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/redo.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/redo.png
new file mode 100644
index 0000000..fa304bf
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/redo.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/refresh.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/refresh.gif
new file mode 100644
index 0000000..1b724a6
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/refresh.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/rename.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/rename.png
new file mode 100644
index 0000000..8998054
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/rename.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/results-perspective.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/results-perspective.png
new file mode 100644
index 0000000..32db54c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/results-perspective.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/run.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/run.gif
new file mode 100644
index 0000000..b88fb72
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/run.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/save.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/save.gif
new file mode 100644
index 0000000..499dd0c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/save.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/save.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/save.png
new file mode 100644
index 0000000..18021d6
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/save.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/saveAll.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/saveAll.png
new file mode 100644
index 0000000..148f2c9
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/saveAll.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/saveAs.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/saveAs.png
new file mode 100644
index 0000000..6debf8b
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/saveAs.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/savemenu.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/savemenu.gif
new file mode 100644
index 0000000..6b0f6a7
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/savemenu.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/savepng.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/savepng.gif
new file mode 100644
index 0000000..573390e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/savepng.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/search.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/search.png
new file mode 100755
index 0000000..593a566
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/search.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/stop.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/stop.gif
new file mode 100644
index 0000000..31ad6a1
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/stop.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-error-icon.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-error-icon.png
new file mode 100644
index 0000000..0880bfe
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-error-icon.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-info-icon.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-info-icon.png
new file mode 100644
index 0000000..5541b5a
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-info-icon.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-question-icon.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-question-icon.png
new file mode 100644
index 0000000..e910be2
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-question-icon.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-warning-icon.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-warning-icon.png
new file mode 100644
index 0000000..b5d0a35
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna-wheel-message-dialog-warning-icon.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna_cogs_32x32.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna_cogs_32x32.png
new file mode 100644
index 0000000..1cf4a2c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna_cogs_32x32.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna_cogs_64x64.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna_cogs_64x64.png
new file mode 100644
index 0000000..61b4db7
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/taverna_cogs_64x64.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/tick.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/tick.png
new file mode 100644
index 0000000..a9925a0
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/tick.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/undo.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/undo.png
new file mode 100644
index 0000000..eb899e9
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/undo.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/uninstall.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/uninstall.png
new file mode 100755
index 0000000..c4488ad
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/uninstall.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/untick.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/untick.png
new file mode 100644
index 0000000..9414371
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/untick.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/up-arrow.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/up-arrow.png
new file mode 100644
index 0000000..ddf4d5b
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/up-arrow.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/update.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/update.png
new file mode 100755
index 0000000..e3695cb
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/update.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/updateRecommended.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/updateRecommended.png
new file mode 100755
index 0000000..5adc4b1
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/updateRecommended.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/urlimport.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/urlimport.png
new file mode 100644
index 0000000..a655ce3
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/urlimport.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/web.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/web.gif
new file mode 100644
index 0000000..ec6cca4
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/web.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/workflowResults.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/workflowResults.png
new file mode 100644
index 0000000..0723805
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/workflowResults.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/working.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/working.gif
new file mode 100644
index 0000000..d0bce15
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/working.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/workingStopped.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/workingStopped.png
new file mode 100644
index 0000000..bcfc3d7
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/workingStopped.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/xml_node.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/xml_node.gif
new file mode 100644
index 0000000..21fad22
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/xml_node.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoom.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoom.gif
new file mode 100644
index 0000000..a4548c5
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoom.gif
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomin.gif b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomin.gif
new file mode 100644
index 0000000..dc8b42e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomin.gif
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2440"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ width="16"
+ height="16"
+ version="1.0"
+ sodipodi:docname="zoomin.gif"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata2445">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs2443">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective2447" />
+ </defs>
+ <sodipodi:namedview
+ inkscape:window-height="759"
+ inkscape:window-width="1233"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="31.125"
+ inkscape:cx="3.1004016"
+ inkscape:cy="8"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:current-layer="svg2440" />
+ <image
+ xlink:href="zoom.gif"
+ sodipodi:absref="/Users/davidwithers/Documents/workspace/t2workbench/common-icons/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoom.gif"
+ width="16"
+ height="16"
+ id="image2449"
+ x="0"
+ y="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:9px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="2.3775103"
+ y="8.8353415"
+ id="text2451"><tspan
+ sodipodi:role="line"
+ id="tspan2453"
+ x="2.3775103"
+ y="8.8353415">+</tspan></text>
+</svg>
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomin.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomin.png
new file mode 100644
index 0000000..3045724
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomin.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomout.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomout.png
new file mode 100644
index 0000000..a24e6db
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/generic/zoomout.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.png
new file mode 100644
index 0000000..72c3c96
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.svg b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.svg
new file mode 100644
index 0000000..b4b2b21
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.svg
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg7810"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="allport.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/davidwithers/Documents/workspace/t2workbench/common-icons/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/allport.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs7812">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 8 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="16 : 8 : 1"
+ inkscape:persp3d-origin="8 : 5.3333333 : 1"
+ id="perspective7818" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="2.3462871"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1193"
+ inkscape:window-height="701"
+ inkscape:window-x="65"
+ inkscape:window-y="68" />
+ <metadata
+ id="metadata7815">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#b0c4de;fill-opacity:1;stroke:#000000;stroke-width:0.94117647;stroke-opacity:1"
+ id="rect7820"
+ width="15.058824"
+ height="15.058824"
+ x="0.47058824"
+ y="0.47058821" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 0.45049505,5.6386137 L 15.45198,5.6386137 L 15.632178,5.6386137"
+ id="path7832" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 0.45049505,10.774256 L 15.49703,10.774256"
+ id="path7834" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8,0.41287115 C 8,5.4133662 8,5.4133662 8,5.4133662"
+ id="path7838" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.5px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ x="3.5911865"
+ y="9.8732672"
+ id="text7840"><tspan
+ sodipodi:role="line"
+ id="tspan7842"
+ x="3.5911865"
+ y="9.8732672">abc</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ x="2.9282181"
+ y="4.0168314"
+ id="text7844"><tspan
+ sodipodi:role="line"
+ id="tspan7846"
+ x="2.9282181"
+ y="4.0168314">x</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ x="10.721782"
+ y="4.0168314"
+ id="text7848"><tspan
+ sodipodi:role="line"
+ id="tspan7850"
+ x="10.721782"
+ y="4.0168314">y</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ x="4.203125"
+ y="14.558415"
+ id="text7856"><tspan
+ sodipodi:role="line"
+ id="tspan7858"
+ x="4.203125"
+ y="14.558415">out</tspan></text>
+ </g>
+</svg>
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/blob.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/blob.png
new file mode 100644
index 0000000..6f002a0
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/blob.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/blob.svg b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/blob.svg
new file mode 100644
index 0000000..8016b8c
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/blob.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg7880"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="blob.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs7882">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 8 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="16 : 8 : 1"
+ inkscape:persp3d-origin="8 : 5.3333333 : 1"
+ id="perspective7888" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1103"
+ inkscape:window-height="701"
+ inkscape:window-x="85"
+ inkscape:window-y="88" />
+ <metadata
+ id="metadata7885">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="fill:#b0c4de;fill-opacity:1;stroke:#000000;stroke-opacity:1"
+ id="path7890"
+ sodipodi:cx="2.3876238"
+ sodipodi:cy="3.070792"
+ sodipodi:rx="5.6762376"
+ sodipodi:ry="5.6762376"
+ d="M 8.0638614,3.070792 A 5.6762376,5.6762376 0 1 1 -3.2886138,3.070792 A 5.6762376,5.6762376 0 1 1 8.0638614,3.070792 z"
+ transform="matrix(1.0524206,0,0,1.0524207,5.4872152,4.7682347)" />
+ </g>
+</svg>
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/expandnested.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/expandnested.png
new file mode 100644
index 0000000..66f479b
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/expandnested.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/expandnested.svg b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/expandnested.svg
new file mode 100644
index 0000000..5e26659
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/expandnested.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg7954"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="expandnested.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs7956">
+ <marker
+ inkscape:stockid="TriangleOutS"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="TriangleOutS"
+ style="overflow:visible">
+ <path
+ id="path3717"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 8 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="16 : 8 : 1"
+ inkscape:persp3d-origin="8 : 5.3333333 : 1"
+ id="perspective7962" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="5.3763582"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1221"
+ inkscape:window-height="701"
+ inkscape:window-x="40"
+ inkscape:window-y="40" />
+ <metadata
+ id="metadata7959">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#ffc0cb;fill-opacity:1;stroke:#000000;stroke-width:0.82900399000000002;stroke-opacity:1"
+ id="rect7964"
+ width="6.1709962"
+ height="3.170996"
+ x="0.41450199"
+ y="12.414503" />
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#848484;stroke-width:1.11509478;stroke-opacity:1"
+ id="rect7966"
+ width="10.830945"
+ height="9.7408457"
+ x="4.6115079"
+ y="0.55754775" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#424242;stroke-width:0.89528197;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.89528193, 0.89528193;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 0.53328454,12.538498 L 4.5122601,0.45061113"
+ id="path8477" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#424242;stroke-width:0.96703959;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.96703956, 0.96703956;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 6.5607476,15.611034 L 15.738757,10.127084"
+ id="path8479" />
+ <rect
+ style="fill:#d15fee;fill-opacity:1;stroke:#000000;stroke-width:0.9063822;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8483"
+ width="4.6436181"
+ height="2.0307465"
+ x="7.7467556"
+ y="1.8977455"
+ ry="0.65274"
+ rx="0" />
+ <rect
+ style="fill:#ffb90f;fill-opacity:1;stroke:#000000;stroke-width:0.98945916;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8994"
+ width="3.3442042"
+ height="1.9927191"
+ x="6.0313625"
+ y="6.8947301" />
+ <rect
+ style="fill:#b0c4de;fill-opacity:1;stroke:#000000;stroke-width:1.04274833;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8998"
+ width="3.1557665"
+ height="1.9844794"
+ x="10.833254"
+ y="6.8763247" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline"
+ d="M 9.1450496,3.7465345 L 7.5232674,6.8099009"
+ id="path9008"
+ inkscape:connector-type="polyline" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-start:none;marker-mid:none;marker-end:none;stroke-opacity:1;display:inline"
+ d="M 11.127228,3.791584 L 12.568812,6.7648514"
+ id="path9010"
+ inkscape:connector-type="polyline" />
+ </g>
+</svg>
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.png
new file mode 100644
index 0000000..a27de9e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.svg b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.svg
new file mode 100644
index 0000000..b703f0f
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.svg
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2763"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="horizontal.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/davidwithers/Documents/workspace/t2workbench/common-icons/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/horizontal.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs2765">
+ <marker
+ inkscape:stockid="TriangleOutS"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="TriangleOutS"
+ style="overflow:visible">
+ <path
+ id="path3717"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2)" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleOutM"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="TriangleOutM"
+ style="overflow:visible">
+ <path
+ id="path3714"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3583"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3595"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path3571"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 8 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="16 : 8 : 1"
+ inkscape:persp3d-origin="8 : 5.3333333 : 1"
+ id="perspective2771" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="6.9863861"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1212"
+ inkscape:window-height="743"
+ inkscape:window-x="84"
+ inkscape:window-y="75" />
+ <metadata
+ id="metadata2768">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#b0c4de;fill-opacity:1;stroke:#000000;stroke-width:0.83990079;stroke-opacity:1"
+ id="rect2773"
+ width="8.9906569"
+ height="4.1302953"
+ x="-12.44273"
+ y="0.42721564"
+ transform="matrix(0,-1,1,0,0,0)" />
+ <rect
+ style="fill:#deb887;fill-opacity:1;stroke:#000000;stroke-width:0.82784092;stroke-opacity:1"
+ id="rect3553"
+ width="9.0011196"
+ height="4.0078716"
+ x="-12.402912"
+ y="11.654533"
+ transform="matrix(0,-1,1,0,0,0)" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.09248292px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#TriangleOutS);stroke-opacity:1;display:inline"
+ d="M 4.9774613,7.9435346 L 9.7306932,7.9361385"
+ id="path3561"
+ inkscape:connector-type="polyline"
+ inkscape:connection-start="#rect2773" />
+ </g>
+</svg>
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.png
new file mode 100644
index 0000000..07aa96e
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.svg b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.svg
new file mode 100644
index 0000000..70f5e76
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg7774"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="noport.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/davidwithers/Documents/workspace/t2workbench/common-icons/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/noport.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs7776">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 8 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="16 : 8 : 1"
+ inkscape:persp3d-origin="8 : 5.3333333 : 1"
+ id="perspective7782" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8.5548188"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1181"
+ inkscape:window-height="701"
+ inkscape:window-x="54"
+ inkscape:window-y="67" />
+ <metadata
+ id="metadata7779">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#b0c4de;fill-opacity:1;stroke:#000000;stroke-width:0.91163695;stroke-opacity:1"
+ id="rect7784"
+ width="15.088363"
+ height="9.0883627"
+ x="0.45581847"
+ y="3.4558189" />
+ <text
+ xml:space="preserve"
+ style="font-size:6px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ x="2.1215818"
+ y="10.236816"
+ id="text7786"><tspan
+ sodipodi:role="line"
+ id="tspan7788"
+ x="2.1215818"
+ y="10.236816">abc</tspan></text>
+ </g>
+</svg>
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsDOT.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsDOT.png
new file mode 100644
index 0000000..63fac44
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsDOT.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPNG.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPNG.png
new file mode 100644
index 0000000..2106b5d
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPNG.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPS.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPS.png
new file mode 100644
index 0000000..4459aaf
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPS.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPS2.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPS2.png
new file mode 100644
index 0000000..8cc42ec
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsPS2.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsSVG.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsSVG.png
new file mode 100644
index 0000000..8f033d0
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/saveAsSVG.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/trash.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/trash.png
new file mode 100644
index 0000000..b15bbb4
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/trash.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.png b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.png
new file mode 100644
index 0000000..0be9893
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.png
Binary files differ
diff --git a/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.svg b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.svg
new file mode 100644
index 0000000..24b5e32
--- /dev/null
+++ b/taverna-workbench-workbench-api/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2763"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="vertical.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/davidwithers/Documents/workspace/t2workbench/common-icons/src/main/resources/net/sf/taverna/t2/workbench/icons/graph/vertical.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs2765">
+ <marker
+ inkscape:stockid="TriangleOutS"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="TriangleOutS"
+ style="overflow:visible">
+ <path
+ id="path3717"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2)" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleOutM"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="TriangleOutM"
+ style="overflow:visible">
+ <path
+ id="path3714"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.4)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3583"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3595"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path3571"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 8 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="16 : 8 : 1"
+ inkscape:persp3d-origin="8 : 5.3333333 : 1"
+ id="perspective2771" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8.1970659"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1212"
+ inkscape:window-height="743"
+ inkscape:window-x="20"
+ inkscape:window-y="20" />
+ <metadata
+ id="metadata2768">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#b0c4de;fill-opacity:1;stroke:#000000;stroke-width:0.95980698;stroke-opacity:1"
+ id="rect2773"
+ width="11.843163"
+ height="4.0946488"
+ x="2.0071313"
+ y="0.52792311" />
+ <rect
+ style="fill:#deb887;fill-opacity:1;stroke:#030303;stroke-width:0.94602543;stroke-opacity:1"
+ id="rect3553"
+ width="11.856944"
+ height="3.9732819"
+ x="2.0002406"
+ y="11.580687" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.06261837px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#TriangleOutS);stroke-opacity:1;display:inline"
+ d="M 7.9287127,5.1024754 L 7.9287129,9.6480197"
+ id="path3561"
+ inkscape:connector-type="polyline"
+ inkscape:connection-start="#rect2773" />
+ </g>
+</svg>
diff --git a/taverna-workbench-workbench-impl/pom.xml b/taverna-workbench-workbench-impl/pom.xml
new file mode 100644
index 0000000..5b52d3a
--- /dev/null
+++ b/taverna-workbench-workbench-impl/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>workbench-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Workbench UI implementation</name>
+ <description>The main workbench ui</description>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Embed-Dependency>osxapplication</Embed-Dependency>
+ <Import-Package>com.apple.eawt;resolution:=optional,*</Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-api</artifactId>
+ <version>${taverna.commons.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.simplericity.macify</groupId>
+ <artifactId>macify</artifactId>
+ <version>1.6</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java
new file mode 100644
index 0000000..4c493d1
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java
@@ -0,0 +1,93 @@
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static uk.org.taverna.scufl2.api.container.WorkflowBundle.generateIdentifier;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.UpdateDataflowInternalIdentifierEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Listens out for any edits on a dataflow and changes its internal id (or back
+ * to the old one in the case of redo/undo). Is first created when the workbench
+ * is initialised.
+ *
+ * @author Ian Dunlop
+ */
+public class DataflowEditsListener implements Observer<EditManagerEvent> {
+ private static Logger logger = Logger
+ .getLogger(DataflowEditsListener.class);
+
+ private Map<Edit<?>, URI> dataflowEditMap;
+
+ public DataflowEditsListener() {
+ super();
+ dataflowEditMap = new HashMap<>();
+ }
+
+ /**
+ * Receives {@link EditManagerEvent}s from the {@link EditManager} and
+ * changes the id of the {@link Dataflow} to a new one or back to its old
+ * one depending on whether it is a do/undo/redo event. Stores the actual
+ * edit and the pre-edit dataflow id in a Map and changes the id when it
+ * gets further actions against this same edit
+ */
+ @Override
+ public void notify(Observable<EditManagerEvent> observable,
+ EditManagerEvent event) throws Exception {
+ Edit<?> edit = event.getEdit();
+ WorkflowBundle dataFlow = ((AbstractDataflowEditEvent) event)
+ .getDataFlow();
+
+ if (event instanceof DataflowEditEvent) {
+ /*
+ * the dataflow has been edited in some way so change its internal
+ * id and store the old one against the edit that is changing
+ * 'something'
+ */
+ URI internalIdentifier = dataFlow.getGlobalBaseURI();
+ dataflowEditMap.put(edit, internalIdentifier);
+ URI newIdentifier = generateIdentifier();
+ new UpdateDataflowInternalIdentifierEdit(dataFlow, newIdentifier)
+ .doEdit();
+ logger.debug("Workflow edit, id changed from: "
+ + internalIdentifier + " to " + newIdentifier);
+ } else if (event instanceof DataFlowRedoEvent) {
+ /*
+ * change the id back to the old one and store the new one in case
+ * we want to change it back
+ */
+ URI newId = dataFlow.getGlobalBaseURI();
+ URI oldId = dataflowEditMap.get(edit);
+ dataflowEditMap.put(edit, newId);
+ new UpdateDataflowInternalIdentifierEdit(dataFlow, oldId).doEdit();
+ logger.debug("Workflow edit, id changed from: " + newId + " to "
+ + oldId);
+ } else if (event instanceof DataFlowUndoEvent) {
+ /*
+ * change the id back to the old one and store the new one in case
+ * we want to change it back
+ */
+ URI newId = dataFlow.getGlobalBaseURI();
+ URI oldId = dataflowEditMap.get(edit);
+ dataflowEditMap.put(edit, newId);
+ new UpdateDataflowInternalIdentifierEdit(dataFlow, oldId).doEdit();
+ logger.debug("Workflow edit, id changed from: " + newId + " to "
+ + oldId);
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java
new file mode 100644
index 0000000..fe13d4d
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java
@@ -0,0 +1,136 @@
+/***************************************
+ * *
+ * JBoss: The OpenSource J2EE WebOS *
+ * *
+ * Distributable under LGPL license. *
+ * See terms of license at gnu.org. *
+ * *
+ * Modified by Stian Soiland-Reyes *
+ * *
+ ***************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.Level;
+
+/**
+ * A subclass of PrintStream that redirects its output to a log4j Logger.
+ *
+ * <p>This class is used to map PrintStream/PrintWriter oriented logging onto
+ * the log4j Categories. Examples include capturing System.out/System.err
+ *
+ * @version <tt>$Revision: 1.1.1.1 $</tt>
+ * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
+ * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
+ */
+//FIXME Replace this class entirely
+class LoggerStream extends PrintStream {
+ /**
+ * Default flag to enable/disable tracing println calls. from the system
+ * property <tt>net.sf.taverna.t2.workbench.ui.impl.LoggerStream.trace</tt>
+ * or if not set defaults to <tt>false</tt>.
+ */
+ public static final boolean TRACE =
+ getBoolean(LoggerStream.class.getName() + ".trace", false);
+
+ /**
+ * Helper to get boolean value from system property or use default if not
+ * set.
+ */
+ private static boolean getBoolean(String name, boolean defaultValue) {
+ String value = System.getProperty(name, null);
+ if (value == null)
+ return defaultValue;
+ return new Boolean(value).booleanValue();
+ }
+
+ private Logger logger;
+ private Level level;
+ private boolean issuedWarning;
+
+ /**
+ * Redirect logging to the indicated logger using Level.INFO
+ */
+ public LoggerStream(Logger logger) {
+ this(logger, Level.INFO, System.out);
+ }
+
+ /**
+ * Redirect logging to the indicated logger using the given level. The ps is
+ * simply passed to super but is not used.
+ */
+ public LoggerStream(Logger logger, Level level, PrintStream ps) {
+ super(ps);
+ this.logger = logger;
+ this.level = level;
+ }
+
+ @Override
+ public void println(String msg) {
+ if (msg == null)
+ msg = "null";
+ byte[] bytes = msg.getBytes();
+ write(bytes, 0, bytes.length);
+ }
+
+ @Override
+ public void println(Object msg) {
+ if (msg == null)
+ msg = "null";
+ byte[] bytes = msg.toString().getBytes();
+ write(bytes, 0, bytes.length);
+ }
+
+ public void write(byte b) {
+ byte[] bytes = { b };
+ write(bytes, 0, 1);
+ }
+
+ private ThreadLocal<Boolean> recursiveCheck = new ThreadLocal<>();
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ Boolean recursed = recursiveCheck.get();
+ if (recursed != null && recursed) {
+ /*
+ * There is a configuration error that is causing looping. Most
+ * likely there are two console appenders so just return to prevent
+ * spinning.
+ */
+ if (issuedWarning == false) {
+ String msg = "ERROR: invalid log settings detected, console capturing is looping";
+ // out.write(msg.getBytes());
+ new Exception(msg).printStackTrace((PrintStream) out);
+ issuedWarning = true;
+ }
+ try {
+ out.write(b, off, len);
+ } catch (IOException e) {
+ }
+ return;
+ }
+
+ // Remove the end of line chars
+ while (len > 0 && (b[len - 1] == '\n' || b[len - 1] == '\r')
+ && len > off)
+ len--;
+
+ /*
+ * HACK, something is logging exceptions line by line (including
+ * blanks), but I can't seem to find it, so for now just ignore empty
+ * lines... they aren't very useful.
+ */
+ if (len != 0) {
+ String msg = new String(b, off, len);
+ recursiveCheck.set(true);
+ if (TRACE)
+ logger.log(level, msg, new Throwable());
+ else
+ logger.log(level, msg);
+ recursiveCheck.set(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java
new file mode 100644
index 0000000..a8bdfdd
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static org.apache.log4j.Level.ERROR;
+import static org.apache.log4j.Level.WARN;
+
+import java.io.PrintStream;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+
+public class SetConsoleLoggerStartup implements StartupSPI {
+ private static final PrintStream originalErr = System.err;
+ private static final PrintStream originalOut = System.out;
+
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ public SetConsoleLoggerStartup(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ @Override
+ public int positionHint() {
+ /*
+ * Must be <b>after</b> PrepareLoggerStarup in file-translator --
+ * otherwise Taverna 1 libraries will cause double logging
+ */
+ return 10;
+ }
+
+ @Override
+ public boolean startup() {
+ setSystemOutCapture();
+ return true;
+ }
+
+ public void setSystemOutCapture() {
+ if (!workbenchConfiguration.getCaptureConsole()) {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ return;
+ }
+ Logger systemOutLogger = Logger.getLogger("System.out");
+ Logger systemErrLogger = Logger.getLogger("System.err");
+
+ try {
+ /*
+ * This logger stream not loop with log4j > 1.2.13, which has
+ * getFollow method
+ */
+ ConsoleAppender.class.getMethod("getFollow");
+ System.setOut(new LoggerStream(systemOutLogger, WARN, originalOut));
+ } catch (SecurityException e) {
+ } catch (NoSuchMethodException e) {
+ System.err.println("Not capturing System.out, use log4j >= 1.2.13");
+ }
+
+ System.setErr(new LoggerStream(systemErrLogger, ERROR, originalErr));
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java
new file mode 100644
index 0000000..2537f0b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.ui.impl;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+/**
+ * Store Workbench window size and perspectives, so that settings can be used on
+ * next startup.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class StoreWindowStateOnShutdown implements ShutdownSPI {
+ private static Logger logger = Logger
+ .getLogger(StoreWindowStateOnShutdown.class);
+
+ private Workbench workbench;
+
+ @Override
+ public int positionHint() {
+ return 1000;
+ }
+
+ @Override
+ public boolean shutdown() {
+ try {
+ workbench.storeSizeAndLocationPrefs();
+ } catch (Exception ex) {
+ logger.error("Error saving the Workbench size and position", ex);
+ }
+ return true;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java
new file mode 100644
index 0000000..3e32f7b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * 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.ui.impl;
+
+public class UserRegistrationData {
+ private String tavernaVersion = "";
+ private String firstName = "";
+ private String lastName = "";
+ private String emailAddress = "";
+ private String institutionOrCompanyName = "";
+ private String industry = "";
+ private String field = "";
+ private String purposeOfUsingTaverna = "";
+ private boolean keepMeInformed = false;
+
+ public void setTavernaVersion(String tavernaVersion) {
+ this.tavernaVersion = tavernaVersion;
+ }
+
+ public String getTavernaVersion() {
+ return tavernaVersion;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setEmailAddress(String emailAddress) {
+ this.emailAddress = emailAddress;
+ }
+
+ public String getEmailAddress() {
+ return emailAddress;
+ }
+
+ public void setInstitutionOrCompanyName(String institutionOrCompanyName) {
+ this.institutionOrCompanyName = institutionOrCompanyName;
+ }
+
+ public String getInstitutionOrCompanyName() {
+ return institutionOrCompanyName;
+ }
+
+ public void setIndustry(String industry) {
+ this.industry = industry;
+ }
+
+ public String getIndustry() {
+ return industry;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public void setPurposeOfUsingTaverna(String purposeOfUsingTaverna) {
+ this.purposeOfUsingTaverna = purposeOfUsingTaverna;
+ }
+
+ public String getPurposeOfUsingTaverna() {
+ return purposeOfUsingTaverna;
+ }
+
+ public void setKeepMeInformed(boolean keepMeInformed) {
+ this.keepMeInformed = keepMeInformed;
+ }
+
+ public boolean getKeepMeInformed() {
+ return keepMeInformed;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java
new file mode 100644
index 0000000..97f831f
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java
@@ -0,0 +1,995 @@
+/*******************************************************************************
+ * 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.ui.impl;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Color.LIGHT_GRAY;
+import static java.awt.Color.WHITE;
+import static java.awt.FlowLayout.LEFT;
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.FIRST_LINE_START;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.awt.event.KeyEvent.VK_ENTER;
+import static java.awt.event.KeyEvent.VK_TAB;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingConstants.BOTTOM;
+import static javax.swing.SwingConstants.TOP;
+import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tavernaCogs32x32Icon;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Properties;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.Border;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+/**
+ * User registration form.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class UserRegistrationForm extends HelpEnabledDialog {
+ private static final String FAILED = "User registration failed: ";
+
+ private static final String REGISTRATION_FAILED_MSG = "User registration failed. Please try again later.";
+
+ private static final String REGISTRATION_URL = "http://www.mygrid.org.uk/taverna/registration/";
+
+ public static final String TAVERNA_VERSION_PROPERTY_NAME = "Taverna version";
+ public static final String FIRST_NAME_PROPERTY_NAME = "First name";
+ public static final String LAST_NAME_PROPERTY_NAME = "Last name";
+ public static final String EMAIL_ADDRESS_PROPERTY_NAME = "Email address";
+ public static final String INSTITUTION_OR_COMPANY_PROPERTY_NAME = "Institution or company name";
+ public static final String INDUSTRY_PROPERTY_NAME = "Industry";
+ public static final String FIELD_PROPERTY_NAME = "Field of investigation";
+ public static final String PURPOSE_PROPERTY_NAME = "Purpose of using Taverna";
+ public static final String KEEP_ME_INFORMED_PROPERTY_NAME = "Keep me informed by email";
+
+ public static final String TAVERNA_REGISTRATION_POST_PARAMETER_NAME = "taverna_registration";
+ public static final String TAVERNA_VERSION_POST_PARAMETER_NAME = "taverna_version";
+ public static final String FIRST_NAME_POST_PARAMETER_NAME = "first_name";
+ public static final String LAST_NAME_POST_PARAMETER_NAME = "last_name";
+ public static final String EMAIL_ADDRESS_POST_PARAMETER_NAME = "email";
+ public static final String INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME = "institution_or_company";
+ public static final String INDUSTRY_TYPE_POST_PARAMETER_NAME = "industry_type";
+ public static final String FIELD_POST_PARAMETER_NAME = "field";
+ public static final String PURPOSE_POST_PARAMETER_NAME = "purpose";
+ public static final String KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME = "keep_me_informed";
+
+ private static String TRUE = Boolean.TRUE.toString();
+ private static String FALSE = Boolean.FALSE.toString();
+
+ private static final String WELCOME = "Welcome to the Taverna User Registration Form";
+ private static final String PLEASE_FILL_IN_THIS_REGISTRATION_FORM = "Please fill in this registration form to let us know that you are using Taverna";
+
+ private static final String WE_DO = "Note that by registering:\n"
+ + " \u25CF We do not have access to your data\n"
+ + " \u25CF We do not have access to your service usage\n"
+ + " \u25CF You will not be monitored\n"
+ + " \u25CF We do record the information you provide\n"
+ + " at registration time";
+
+ private static final String WHY_REGISTER = "By registering you will:\n"
+ + " \u25CF Allow us to support you better; future plans will be\n"
+ + " directed towards solutions Taverna users require\n"
+ + " \u25CF Help sustain Taverna development; our continued\n"
+ + " funding relies on us showing usage\n"
+ + " \u25CF (Optionally) Hear about news and product updates";
+
+ private static final String FIRST_NAME = "*First name:";
+ private static final String LAST_NAME = "*Last name:";
+ private static final String EMAIL_ADDRESS = "*Email address:";
+ private static final String KEEP_ME_INFORMED = "Keep me informed of news and product updates via email";
+ private static final String INSTITUTION_COMPANY_NAME = "*Institution/Company name:";
+ private static final String FIELD_OF_INVESTIGATION = " Field of investigation:\n"
+ + " (e.g. bioinformatics)";
+ private static final String WHY_YOU_INTEND_TO_USE_TAVERNA = " A brief description of how you intend\n"
+ + " to use Taverna: (e.g. genome analysis\n"
+ + " for bacterial strain identification)";
+
+ private static String[] industryTypes = { "", "Academia - Life Sciences",
+ "Academia - Social Sciences", "Academia - Physical Sciences",
+ "Academia - Environmental Sciences", "Academia - Other",
+ "Industry - Biotechnology", "Industry - Pharmaceutical",
+ "Industry - Engineering", "Industry - Other",
+ "Healthcare Services", "Goverment and Public Sector", "Other" };
+
+ private static final String I_AGREE_TO_THE_TERMS_AND_CONDITIONS = "I agree to the terms and conditions of registration at";
+ private static final String TERMS_AND_CONDITIONS_URL = "http://www.taverna.org.uk/legal/terms";
+
+ private Logger logger = Logger.getLogger(UserRegistrationForm.class);
+ private UserRegistrationData previousRegistrationData;
+ private JTextField firstNameTextField;
+ private JTextField lastNameTextField;
+ private JTextField emailTextField;
+ private JCheckBox keepMeInformedCheckBox;
+ private JTextField institutionOrCompanyTextField;
+ private JComboBox<String> industryTypeTextField;
+ private JTextField fieldTextField;
+ private JTextArea purposeTextArea;
+ private JCheckBox termsAndConditionsCheckBox;
+
+ private final File registrationDataFile;
+ private final File remindMeLaterFile;
+ private final File doNotRegisterMeFile;
+ private final String appName;
+
+ public UserRegistrationForm(String appName, File registrationDataFile,
+ File doNotRegisterMeFile, File remindMeLaterFile) {
+ super((Frame) null, "Taverna User Registration", true);
+ this.appName = appName;
+ this.registrationDataFile = registrationDataFile;
+ this.doNotRegisterMeFile = doNotRegisterMeFile;
+ this.remindMeLaterFile = remindMeLaterFile;
+ initComponents();
+ }
+
+ public UserRegistrationForm(String appName,
+ File previousRegistrationDataFile, File registrationDataFile,
+ File doNotRegisterMeFile, File remindMeLaterFile) {
+ super((Frame) null, "Taverna User Registration", true);
+ this.appName = appName;
+ this.registrationDataFile = registrationDataFile;
+ this.doNotRegisterMeFile = doNotRegisterMeFile;
+ this.remindMeLaterFile = remindMeLaterFile;
+ previousRegistrationData = loadUserRegistrationData(previousRegistrationDataFile);
+ initComponents();
+ }
+
+ // For testing only
+ // public static void main(String[] args) throws ClassNotFoundException,
+ // InstantiationException, IllegalAccessException,
+ // UnsupportedLookAndFeelException{
+ // WorkbenchImpl.setLookAndFeel();
+ // UserRegistrationForm form = new UserRegistrationForm();
+ // form.setVisible(true);
+ // }
+
+ private void initComponents() {
+ JPanel mainPanel = new JPanel(new GridBagLayout());
+
+ // Base font for all components on the form
+ Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+
+ // Title panel
+ JPanel titlePanel = new JPanel(new FlowLayout(LEFT));
+ titlePanel.setBackground(WHITE);
+ // titlePanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+ JLabel titleLabel = new JLabel(WELCOME);
+ titleLabel.setFont(baseFont.deriveFont(BOLD, 13.5f));
+ // titleLabel.setBorder(new EmptyBorder(10, 10, 0, 10));
+ JLabel titleIcon = new JLabel(tavernaCogs32x32Icon);
+ // titleIcon.setBorder(new EmptyBorder(10, 10, 10, 10));
+ DialogTextArea titleMessage = new DialogTextArea(
+ PLEASE_FILL_IN_THIS_REGISTRATION_FORM);
+ titleMessage.setMargin(new Insets(0, 20, 0, 10));
+ titleMessage.setFont(baseFont);
+ titleMessage.setEditable(false);
+ titleMessage.setFocusable(false);
+ // titlePanel.setBorder( new EmptyBorder(10, 10, 0, 10));
+ JPanel messagePanel = new JPanel(new BorderLayout());
+ messagePanel.add(titleLabel, NORTH);
+ messagePanel.add(titleMessage, CENTER);
+ messagePanel.setBackground(WHITE);
+ titlePanel.add(titleIcon);
+ titlePanel.add(messagePanel);
+ addDivider(titlePanel, BOTTOM, true);
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ // gbc.insets = new Insets(5, 10, 0, 0);
+ mainPanel.add(titlePanel, gbc);
+
+ // Registration messages
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ // gbc.insets = new Insets(5, 0, 0, 10);
+ DialogTextArea registrationMessage1 = new DialogTextArea(WHY_REGISTER);
+ registrationMessage1.setMargin(new Insets(0, 10, 0, 0));
+ registrationMessage1.setFont(baseFont);
+ registrationMessage1.setEditable(false);
+ registrationMessage1.setFocusable(false);
+ registrationMessage1.setBackground(getBackground());
+
+ DialogTextArea registrationMessage2 = new DialogTextArea(WE_DO);
+ registrationMessage2.setMargin(new Insets(0, 10, 0, 10));
+ registrationMessage2.setFont(baseFont);
+ registrationMessage2.setEditable(false);
+ registrationMessage2.setFocusable(false);
+ registrationMessage2.setBackground(getBackground());
+ JPanel registrationMessagePanel = new JPanel(new FlowLayout(
+ FlowLayout.CENTER));
+ registrationMessagePanel.add(registrationMessage1);
+ registrationMessagePanel.add(registrationMessage2);
+ addDivider(registrationMessagePanel, BOTTOM, true);
+ mainPanel.add(registrationMessagePanel, gbc);
+
+ // Mandatory label
+ // JLabel mandatoryLabel = new JLabel("* Mandatory fields");
+ // mandatoryLabel.setFont(baseFont);
+ // gbc.weightx = 0.0;
+ // gbc.weighty = 0.0;
+ // gbc.gridx = 0;
+ // gbc.gridy = 3;
+ // gbc.fill = NONE;
+ // gbc.anchor = GridBagConstraints.EAST;
+ // gbc.gridwidth = 2;
+ // gbc.insets = new Insets(0, 10, 0, 20);
+ // mainPanel.add(mandatoryLabel, gbc);
+
+ // First name
+ JLabel firstNameLabel = new JLabel(FIRST_NAME);
+ firstNameLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 4;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(0, 10, 0, 10);
+ mainPanel.add(firstNameLabel, gbc);
+
+ firstNameTextField = new JTextField();
+ firstNameTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ firstNameTextField.setText(previousRegistrationData.getFirstName());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 4;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(firstNameTextField, gbc);
+
+ // Last name
+ JLabel lastNameLabel = new JLabel(LAST_NAME);
+ lastNameLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 5;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(0, 10, 0, 10);
+ mainPanel.add(lastNameLabel, gbc);
+
+ lastNameTextField = new JTextField();
+ lastNameTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ lastNameTextField.setText(previousRegistrationData.getLastName());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 5;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(lastNameTextField, gbc);
+
+ // Email address
+ JLabel emailLabel = new JLabel(EMAIL_ADDRESS);
+ emailLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 6;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(emailLabel, gbc);
+
+ emailTextField = new JTextField();
+ emailTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ emailTextField.setText(previousRegistrationData.getEmailAddress());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 6;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(emailTextField, gbc);
+
+ // Keep me informed
+ keepMeInformedCheckBox = new JCheckBox(KEEP_ME_INFORMED);
+ keepMeInformedCheckBox.setFont(baseFont);
+ if (previousRegistrationData != null)
+ keepMeInformedCheckBox.setSelected(previousRegistrationData
+ .getKeepMeInformed());
+ keepMeInformedCheckBox.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ keepMeInformedCheckBox.setSelected(!keepMeInformedCheckBox
+ .isSelected());
+ }
+ }
+ });
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 7;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(keepMeInformedCheckBox, gbc);
+
+ // Institution name
+ JLabel institutionLabel = new JLabel(INSTITUTION_COMPANY_NAME);
+ institutionLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 8;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(institutionLabel, gbc);
+
+ institutionOrCompanyTextField = new JTextField();
+ institutionOrCompanyTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ institutionOrCompanyTextField.setText(previousRegistrationData
+ .getInstitutionOrCompanyName());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 8;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(institutionOrCompanyTextField, gbc);
+
+ // Industry type
+ JLabel industryLabel = new JLabel(" Industry type:");
+ industryLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 9;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(industryLabel, gbc);
+
+ industryTypeTextField = new JComboBox<>(industryTypes);
+ industryTypeTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ industryTypeTextField.setSelectedItem(previousRegistrationData
+ .getIndustry());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 9;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(industryTypeTextField, gbc);
+
+ // Field of investigation
+ JTextArea fieldLabel = new JTextArea(FIELD_OF_INVESTIGATION);
+ fieldLabel.setFont(baseFont);
+ fieldLabel.setEditable(false);
+ fieldLabel.setFocusable(false);
+ fieldLabel.setBackground(getBackground());
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 10;
+ gbc.fill = NONE;
+ gbc.anchor = LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(fieldLabel, gbc);
+
+ fieldTextField = new JTextField();
+ fieldTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ fieldTextField.setText(previousRegistrationData.getField());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 10;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = FIRST_LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(fieldTextField, gbc);
+
+ // Purpose of using Taverna
+ JTextArea purposeLabel = new JTextArea(WHY_YOU_INTEND_TO_USE_TAVERNA);
+ purposeLabel.setFont(baseFont);
+ purposeLabel.setEditable(false);
+ purposeLabel.setFocusable(false);
+ purposeLabel.setBackground(getBackground());
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 11;
+ gbc.fill = NONE;
+ gbc.anchor = LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(purposeLabel, gbc);
+
+ purposeTextArea = new JTextArea(4, 30);
+ purposeTextArea.setFont(baseFont);
+ purposeTextArea.setLineWrap(true);
+ purposeTextArea.setAutoscrolls(true);
+ if (previousRegistrationData != null)
+ purposeTextArea.setText(previousRegistrationData
+ .getPurposeOfUsingTaverna());
+ purposeTextArea.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_TAB) {
+ if (evt.getModifiers() > 0)
+ purposeTextArea.transferFocusBackward();
+ else
+ purposeTextArea.transferFocus();
+ evt.consume();
+ }
+ }
+ });
+ JScrollPane purposeScrollPane = new JScrollPane(purposeTextArea);
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 11;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = FIRST_LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(purposeScrollPane, gbc);
+
+ // Terms and conditions
+ termsAndConditionsCheckBox = new JCheckBox(
+ I_AGREE_TO_THE_TERMS_AND_CONDITIONS);
+ termsAndConditionsCheckBox.setFont(baseFont);
+ termsAndConditionsCheckBox.setBorder(null);
+ termsAndConditionsCheckBox.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ termsAndConditionsCheckBox
+ .setSelected(!termsAndConditionsCheckBox
+ .isSelected());
+ }
+ }
+ });
+ // gbc.weightx = 0.0;
+ // gbc.weighty = 0.0;
+ // gbc.gridx = 0;
+ // gbc.gridy = 12;
+ // gbc.fill = NONE;
+ // gbc.anchor = WEST;
+ // gbc.gridwidth = 2;
+ // gbc.insets = new Insets(10, 10, 0, 0);
+ // mainPanel.add(termsAndConditionsCheckBox, gbc);
+
+ // Terms and conditions link
+ JEditorPane termsAndConditionsURL = new JEditorPane();
+ termsAndConditionsURL.setEditable(false);
+ termsAndConditionsURL.setBackground(getBackground());
+ termsAndConditionsURL.setFocusable(false);
+ HTMLEditorKit kit = new HTMLEditorKit();
+ termsAndConditionsURL.setEditorKit(kit);
+ StyleSheet styleSheet = kit.getStyleSheet();
+ // styleSheet.addRule("body {font-family:"+baseFont.getFamily()+"; font-size:"+baseFont.getSize()+";}");
+ // // base font looks bigger when rendered as HTML
+ styleSheet.addRule("body {font-family:" + baseFont.getFamily()
+ + "; font-size:9px;}");
+ Document doc = kit.createDefaultDocument();
+ termsAndConditionsURL.setDocument(doc);
+ termsAndConditionsURL.setText("<html><body><a href=\""
+ + TERMS_AND_CONDITIONS_URL + "\">" + TERMS_AND_CONDITIONS_URL
+ + "</a></body></html>");
+ termsAndConditionsURL.addHyperlinkListener(new HyperlinkListener() {
+ @Override
+ public void hyperlinkUpdate(HyperlinkEvent he) {
+ if (he.getEventType() == ACTIVATED)
+ followHyperlinkToTandCs();
+ }
+ });
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 13;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ JPanel termsAndConditionsPanel = new JPanel(new FlowLayout(LEFT));
+ termsAndConditionsPanel.add(termsAndConditionsCheckBox);
+ termsAndConditionsPanel.add(termsAndConditionsURL);
+ mainPanel.add(termsAndConditionsPanel, gbc);
+
+ // Button panel
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ JButton registerButton = new JButton("Register");
+ registerButton.setFont(baseFont);
+ registerButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ register();
+ }
+ }
+ });
+ registerButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ register();
+ }
+ });
+ JButton doNotRegisterButton = new JButton("Do not ask me again");
+ doNotRegisterButton.setFont(baseFont);
+ doNotRegisterButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ doNotRegister();
+ }
+ }
+ });
+ doNotRegisterButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ doNotRegister();
+ }
+ });
+ JButton remindMeLaterButton = new JButton("Remind me later"); // in 2 weeks
+ remindMeLaterButton.setFont(baseFont);
+ remindMeLaterButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ remindMeLater();
+ }
+ }
+ });
+ remindMeLaterButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ remindMeLater();
+ }
+ });
+ buttonPanel.add(registerButton);
+ buttonPanel.add(remindMeLaterButton);
+ buttonPanel.add(doNotRegisterButton);
+ addDivider(buttonPanel, TOP, true);
+ gbc.gridx = 0;
+ gbc.gridy = 14;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = GridBagConstraints.CENTER;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ gbc.gridwidth = 2;
+ mainPanel.add(buttonPanel, gbc);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(mainPanel, CENTER);
+
+ pack();
+ setResizable(false);
+ // Center the dialog on the screen (we do not have the parent)
+ Dimension dimension = getToolkit().getScreenSize();
+ Rectangle abounds = getBounds();
+ setLocation((dimension.width - abounds.width) / 2,
+ (dimension.height - abounds.height) / 2);
+ setSize(getPreferredSize());
+ }
+
+ protected void remindMeLater() {
+ try {
+ FileUtils.touch(remindMeLaterFile);
+ } catch (IOException ioex) {
+ logger.error(
+ "Failed to touch the 'Remind me later' file at user registration.",
+ ioex);
+ }
+ closeDialog();
+ }
+
+ protected void doNotRegister() {
+ try {
+ FileUtils.touch(doNotRegisterMeFile);
+ if (remindMeLaterFile.exists())
+ remindMeLaterFile.delete();
+ } catch (IOException ioex) {
+ logger.error(
+ "Failed to touch the 'Do not register me' file at user registration.",
+ ioex);
+ }
+ closeDialog();
+ }
+
+ private void register() {
+ if (!validateForm())
+ return;
+ UserRegistrationData regData = new UserRegistrationData();
+ regData.setTavernaVersion(appName);
+ regData.setFirstName(firstNameTextField.getText());
+ regData.setLastName(lastNameTextField.getText());
+ regData.setEmailAddress(emailTextField.getText());
+ regData.setKeepMeInformed(keepMeInformedCheckBox.isSelected());
+ regData.setInstitutionOrCompanyName(institutionOrCompanyTextField
+ .getText());
+ regData.setIndustry(industryTypeTextField.getSelectedItem().toString());
+ regData.setField(fieldTextField.getText());
+ regData.setPurposeOfUsingTaverna(purposeTextArea.getText());
+
+ if (postUserRegistrationDataToServer(regData)) {
+ saveUserRegistrationData(regData, registrationDataFile);
+ if (remindMeLaterFile.exists())
+ remindMeLaterFile.delete();
+ closeDialog();
+ }
+ }
+
+ private boolean validateForm() {
+ String errorMessage = "";
+ if (firstNameTextField.getText().isEmpty())
+ errorMessage += "Please provide your first name.<br>";
+ if (lastNameTextField.getText().isEmpty())
+ errorMessage += "Please provide your last name.<br>";
+ if (emailTextField.getText().isEmpty())
+ errorMessage += "Please provide your email address.<br>";
+ if (institutionOrCompanyTextField.getText().isEmpty())
+ errorMessage += "Please provide your institution or company name.";
+ if (!errorMessage.isEmpty()) {
+ showMessageDialog(this, new JLabel("<html><body>"
+ + errorMessage + "</body></html>"), "Error in form",
+ ERROR_MESSAGE);
+ return false;
+ }
+ if (!termsAndConditionsCheckBox.isSelected()) {
+ showMessageDialog(this, new JLabel(
+ "You must agree to the terms and conditions."),
+ "Error in form", ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public UserRegistrationData loadUserRegistrationData(File propertiesFile) {
+ UserRegistrationData regData = new UserRegistrationData();
+ Properties props = new Properties();
+
+ // Try to retrieve data from file
+ try {
+ props.load(new FileInputStream(propertiesFile));
+ } catch (IOException e) {
+ logger.error("Failed to load old user registration data from "
+ + propertiesFile.getAbsolutePath(), e);
+ return null;
+ }
+ regData.setTavernaVersion(props
+ .getProperty(TAVERNA_VERSION_PROPERTY_NAME));
+ regData.setFirstName(props.getProperty(FIRST_NAME_PROPERTY_NAME));
+ regData.setLastName(props.getProperty(LAST_NAME_PROPERTY_NAME));
+ regData.setEmailAddress(props.getProperty(EMAIL_ADDRESS_PROPERTY_NAME));
+ regData.setKeepMeInformed(props.getProperty(
+ KEEP_ME_INFORMED_PROPERTY_NAME).equals(TRUE));
+ regData.setInstitutionOrCompanyName(props
+ .getProperty(INSTITUTION_OR_COMPANY_PROPERTY_NAME));
+ regData.setIndustry(props.getProperty(INDUSTRY_PROPERTY_NAME));
+ regData.setField(props.getProperty(FIELD_PROPERTY_NAME));
+ regData.setPurposeOfUsingTaverna(props
+ .getProperty(PURPOSE_PROPERTY_NAME));
+ return regData;
+ }
+
+ private void enc(StringBuilder buffer, String name, Object value)
+ throws UnsupportedEncodingException {
+ if (buffer.length() != 0)
+ buffer.append('&');
+ buffer.append(URLEncoder.encode(name, "UTF-8"));
+ buffer.append('=');
+ buffer.append(URLEncoder.encode(value.toString(), "UTF-8"));
+ }
+
+ /**
+ * Post registration data to our server.
+ */
+ private boolean postUserRegistrationDataToServer(
+ UserRegistrationData regData) {
+ StringBuilder parameters = new StringBuilder();
+
+ /*
+ * The 'submit' parameter - to let the server-side script know we are
+ * submitting the user's registration form - all other requests will be
+ * silently ignored
+ */
+ try {
+ // value does not matter
+ enc(parameters, TAVERNA_REGISTRATION_POST_PARAMETER_NAME, "submit");
+
+ enc(parameters, TAVERNA_VERSION_POST_PARAMETER_NAME,
+ regData.getTavernaVersion());
+ enc(parameters, FIRST_NAME_POST_PARAMETER_NAME,
+ regData.getFirstName());
+ enc(parameters, LAST_NAME_POST_PARAMETER_NAME,
+ regData.getLastName());
+ enc(parameters, EMAIL_ADDRESS_POST_PARAMETER_NAME,
+ regData.getEmailAddress());
+ enc(parameters, KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME,
+ regData.getKeepMeInformed());
+ enc(parameters, INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME,
+ regData.getInstitutionOrCompanyName());
+ enc(parameters, INDUSTRY_TYPE_POST_PARAMETER_NAME,
+ regData.getIndustry());
+ enc(parameters, FIELD_POST_PARAMETER_NAME, regData.getField());
+ enc(parameters, PURPOSE_POST_PARAMETER_NAME,
+ regData.getPurposeOfUsingTaverna());
+ } catch (UnsupportedEncodingException ueex) {
+ logger.error(FAILED + "Could not url encode post parameters", ueex);
+ showMessageDialog(null, REGISTRATION_FAILED_MSG,
+ "Error encoding registration data", ERROR_MESSAGE);
+ return false;
+ }
+ String server = REGISTRATION_URL;
+ logger.info("Posting user registartion to " + server
+ + " with parameters: " + parameters);
+ String response = "";
+ String failure;
+ try {
+ URL url = new URL(server);
+ URLConnection conn = url.openConnection();
+ /*
+ * Set timeout to e.g. 7 seconds, otherwise we might hang too long
+ * if server is not responding and it will block Taverna
+ */
+ conn.setConnectTimeout(7000);
+ // Set connection parameters
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ // Make server believe we are HTML form data...
+ conn.setRequestProperty("Content-Type",
+ "application/x-www-form-urlencoded");
+ // Write out the bytes of the content string to the stream.
+ try (DataOutputStream out = new DataOutputStream(
+ conn.getOutputStream())) {
+ out.writeBytes(parameters.toString());
+ out.flush();
+ }
+ // Read response from the input stream.
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(
+ conn.getInputStream()))) {
+ String temp;
+ while ((temp = in.readLine()) != null)
+ response += temp + "\n";
+ // Remove the last \n character
+ if (!response.isEmpty())
+ response = response.substring(0, response.length() - 1);
+ }
+ if (response.equals("Registration successful!"))
+ return true;
+ logger.error(FAILED + "Response form server was: " + response);
+ failure = "Error saving registration data on the server";
+ } catch (ConnectException ceex) {
+ /*
+ * the connection was refused remotely (e.g. no process is listening
+ * on the remote address/port).
+ */
+ logger.error(
+ FAILED
+ + "Registration server is not listening of the specified url.",
+ ceex);
+ failure = "Registration server is not listening at the specified url";
+ } catch (SocketTimeoutException stex) {
+ // timeout has occurred on a socket read or accept.
+ logger.error(FAILED + "Socket timeout occurred.", stex);
+ failure = "Registration server timeout";
+ } catch (MalformedURLException muex) {
+ logger.error(FAILED + "Registartion server's url is malformed.",
+ muex);
+ failure = "Error with registration server's url";
+ } catch (IOException ioex) {
+ logger.error(
+ FAILED
+ + "Failed to open url connection to registration server or writing/reading to/from it.",
+ ioex);
+ failure = "Error opening connection to the registration server";
+ }
+ showMessageDialog(null, REGISTRATION_FAILED_MSG, failure, ERROR_MESSAGE);
+ return false;
+ }
+
+ private void saveUserRegistrationData(UserRegistrationData regData,
+ File propertiesFile) {
+ Properties props = new Properties();
+ props.setProperty(TAVERNA_VERSION_PROPERTY_NAME,
+ regData.getTavernaVersion());
+ props.setProperty(FIRST_NAME_PROPERTY_NAME, regData.getFirstName());
+ props.setProperty(LAST_NAME_PROPERTY_NAME, regData.getLastName());
+ props.setProperty(EMAIL_ADDRESS_PROPERTY_NAME,
+ regData.getEmailAddress());
+ props.setProperty(KEEP_ME_INFORMED_PROPERTY_NAME,
+ regData.getKeepMeInformed() ? TRUE : FALSE);
+ props.setProperty(INSTITUTION_OR_COMPANY_PROPERTY_NAME,
+ regData.getInstitutionOrCompanyName());
+ props.setProperty(INDUSTRY_PROPERTY_NAME, regData.getIndustry());
+ props.setProperty(FIELD_PROPERTY_NAME,
+ regData.getPurposeOfUsingTaverna());
+ props.setProperty(PURPOSE_PROPERTY_NAME,
+ regData.getPurposeOfUsingTaverna());
+
+ // Write the properties file.
+ try {
+ props.store(new FileOutputStream(propertiesFile), null);
+ } catch (Exception e) {
+ logger.error("Failed to save user registration data locally on disk.");
+ }
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+ /**
+ * Adds a light gray or etched border to the top or bottom of a JComponent.
+ *
+ * @author David Withers
+ * @param component
+ */
+ protected void addDivider(JComponent component, final int position,
+ final boolean etched) {
+ component.setBorder(new Border() {
+ private final Color borderColor = new Color(.6f, .6f, .6f);
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ if (position == TOP)
+ return new Insets(5, 0, 0, 0);
+ else
+ return new Insets(0, 0, 5, 0);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ if (position == TOP) {
+ if (etched) {
+ g.setColor(borderColor);
+ g.drawLine(x, y, x + width, y);
+ g.setColor(WHITE);
+ g.drawLine(x, y + 1, x + width, y + 1);
+ } else {
+ g.setColor(LIGHT_GRAY);
+ g.drawLine(x, y, x + width, y);
+ }
+ } else {
+ if (etched) {
+ g.setColor(borderColor);
+ g.drawLine(x, y + height - 2, x + width, y + height - 2);
+ g.setColor(WHITE);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ } else {
+ g.setColor(LIGHT_GRAY);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+ }
+ }
+ });
+ }
+
+ private void followHyperlinkToTandCs() {
+ // Open a Web browser
+ try {
+ Desktop.getDesktop().browse(new URI(TERMS_AND_CONDITIONS_URL));
+ } catch (Exception ex) {
+ logger.error("User registration: Failed to launch browser to show terms and conditions at "
+ + TERMS_AND_CONDITIONS_URL);
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java
new file mode 100644
index 0000000..257c3f3
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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.ui.impl;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class UserRegistrationHook implements StartupSPI {
+ /** Delay between when we ask the user about registration, in milliseconds */
+ private static final int TWO_WEEKS = 14 * 24 * 3600 * 1000;
+ public static final String REGISTRATION_DIRECTORY_NAME = "registration";
+ public static final String REGISTRATION_DATA_FILE_NAME = "registration_data.properties";
+ public static final String REMIND_ME_LATER_FILE_NAME = "remind_me_later";
+ public static final String DO_NOT_REGISTER_ME_FILE_NAME = "do_not_register_me";
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ @Override
+ public int positionHint() {
+ return 50;
+ }
+
+ @Override
+ public boolean startup() {
+ File registrationDirectory = getRegistrationDirectory();
+ File registrationDataFile = new File(registrationDirectory,
+ REGISTRATION_DATA_FILE_NAME);
+ File doNotRegisterMeFile = new File(registrationDirectory,
+ DO_NOT_REGISTER_ME_FILE_NAME);
+ File remindMeLaterFile = new File(registrationDirectory,
+ REMIND_ME_LATER_FILE_NAME);
+
+ // if we are running headlessly just return
+ if (isHeadless())
+ return true;
+ // For Taverna snapshots - do not ask user to register
+ if (applicationConfiguration.getName().toLowerCase().contains("snapshot"))
+ return true;
+
+ // If there is already user's registration data present - exit.
+ if (registrationDataFile.exists())
+ return true;
+
+ // If user did not want to register - exit.
+ if (doNotRegisterMeFile.exists())
+ return true;
+
+ /*
+ * If user said to remind them - check if more than 2 weeks passed since
+ * we asked previously.
+ */
+ if (remindMeLaterFile.exists()) {
+ long lastModified = remindMeLaterFile.lastModified();
+ long now = new Date().getTime();
+ if (now - lastModified < TWO_WEEKS)
+ // 2 weeks have not passed since we last asked
+ return true;
+
+ // Ask user again if they want to register
+ UserRegistrationForm form = new UserRegistrationForm(
+ applicationConfiguration.getName(), registrationDataFile,
+ doNotRegisterMeFile, remindMeLaterFile);
+ form.setVisible(true);
+ return true;
+ }
+
+ /*
+ * Check if there are previous Taverna versions installed and find the
+ * latest one that contains user registration data, if any. Ask user if
+ * they want to upload that previous data.
+ */
+ final File appHomeDirectory = applicationConfiguration.getApplicationHomeDir();
+ File parentDirectory = appHomeDirectory.getParentFile();
+ FileFilter fileFilter = new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !(file.getName().equals(appHomeDirectory.getName())
+ // Exclude Taverna home directory for this app
+ && file.isDirectory()
+ && file.getName().toLowerCase().startsWith("taverna-")
+ // exclude snapshots
+ && !file.getName().toLowerCase().contains("snapshot")
+ // exclude command line tool
+ && !file.getName().toLowerCase().contains("cmd")
+ // exclude dataviewer
+ && !file.getName().toLowerCase().contains("dataviewer"));
+ }
+ };
+ File[] tavernaDirectories = parentDirectory.listFiles(fileFilter);
+ // Find the latest previous registration data file, if any
+ File previousRegistrationDataFile = null;
+ for (File tavernaDirectory : tavernaDirectories) {
+ File regFile = new File(tavernaDirectory, REGISTRATION_DIRECTORY_NAME
+ + System.getProperty("file.separator") + REGISTRATION_DATA_FILE_NAME);
+ if (!regFile.exists())
+ continue;
+ if (previousRegistrationDataFile == null)
+ previousRegistrationDataFile = regFile;
+ else if (previousRegistrationDataFile.lastModified() < regFile
+ .lastModified())
+ previousRegistrationDataFile = regFile;
+ }
+
+ UserRegistrationForm form;
+ if (previousRegistrationDataFile == null)
+ // No previous registration file - ask user to register
+ form = new UserRegistrationForm(applicationConfiguration.getName(),
+ registrationDataFile, doNotRegisterMeFile,
+ remindMeLaterFile);
+ else
+ /*
+ * Fill in user's old registration data in the form and ask them to
+ * register
+ */
+ form = new UserRegistrationForm(applicationConfiguration.getName(),
+ previousRegistrationDataFile, registrationDataFile,
+ doNotRegisterMeFile, remindMeLaterFile);
+ form.setVisible(true);
+ return true;
+ }
+
+ /**
+ * Gets the registration directory where info about registration will be
+ * saved to.
+ */
+ public File getRegistrationDirectory() {
+ File home = applicationConfiguration.getApplicationHomeDir();
+
+ File registrationDirectory = new File(home, REGISTRATION_DIRECTORY_NAME);
+ if (!registrationDirectory.exists())
+ registrationDirectory.mkdir();
+ return registrationDirectory;
+ }
+
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java
new file mode 100644
index 0000000..16f189e
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java
@@ -0,0 +1,538 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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.ui.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.lang.Thread.setDefaultUncaughtExceptionHandler;
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+import static javax.swing.UIManager.getCrossPlatformLookAndFeelClassName;
+import static javax.swing.UIManager.getLookAndFeel;
+import static javax.swing.UIManager.getLookAndFeelDefaults;
+import static javax.swing.UIManager.getSystemLookAndFeelClassName;
+import static net.sf.taverna.t2.workbench.MainWindow.setMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.errorMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.infoMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.questionMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.warningMessageIcon;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.CardLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+import javax.swing.UIManager;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.ui.menu.MenuManager.MenuManagerEvent;
+import net.sf.taverna.t2.ui.menu.MenuManager.UpdatedMenuManagerEvent;
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.helper.Helper;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+import org.simplericity.macify.eawt.Application;
+import org.simplericity.macify.eawt.ApplicationAdapter;
+import org.simplericity.macify.eawt.ApplicationEvent;
+import org.simplericity.macify.eawt.ApplicationListener;
+import org.simplericity.macify.eawt.DefaultApplication;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * The main workbench frame.
+ *
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchImpl extends JFrame implements Workbench {
+ private static final String NIMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+ private static final String LAUNCHER_LOGO_PNG = "/launcher_logo.png";
+ private static final long serialVersionUID = 1L;
+ private static Logger logger = getLogger(WorkbenchImpl.class);
+ private static Preferences userPreferences = userNodeForPackage(WorkbenchImpl.class);
+
+ private Application osxApp = new DefaultApplication();
+ private ApplicationListener osxAppListener = new OSXAppListener();
+ private MenuManager menuManager;
+ private FileManager fileManager;
+ @SuppressWarnings("unused")
+ private EditManager editManager;
+ private PluginManager pluginManager;
+ private SelectionManager selectionManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private ApplicationConfiguration applicationConfiguration;
+ private WorkbenchPerspectives workbenchPerspectives;
+ private T2ConfigurationFrame t2ConfigurationFrame;
+ private JToolBar perspectiveToolBar;
+ private List<StartupSPI> startupHooks;
+ private List<ShutdownSPI> shutdownHooks;
+ private final List<PerspectiveSPI> perspectives;
+ private JMenuBar menuBar;
+ private JToolBar toolBar;
+ private MenuManagerObserver menuManagerObserver;
+
+ public WorkbenchImpl(List<StartupSPI> startupHooks,
+ List<ShutdownSPI> shutdownHooks, List<PerspectiveSPI> perspectives) {
+ this.perspectives = perspectives;
+ this.startupHooks = startupHooks;
+ this.shutdownHooks = shutdownHooks;
+ setMainWindow(this);
+ }
+
+ protected void initialize() {
+ setExceptionHandler();
+ setLookAndFeel();
+
+ // Set icons for Error, Information, Question and Warning messages
+ UIManager.put("OptionPane.errorIcon", errorMessageIcon);
+ UIManager.put("OptionPane.informationIcon", infoMessageIcon);
+ UIManager.put("OptionPane.questionIcon", questionMessageIcon);
+ UIManager.put("OptionPane.warningIcon", warningMessageIcon);
+
+ // Call the startup hooks
+ if (!callStartupHooks()) {
+ System.exit(0);
+ }
+
+ makeGUI();
+ fileManager.newDataflow();
+ try {
+ pluginManager.loadPlugins();
+ } catch (PluginException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ /*
+ * the DataflowEditsListener changes the WorkflowBundle ID for every
+ * workflow edit and changes the URI so port definitions can't find the
+ * port they refer to
+ */
+ // TODO check if it's OK to not update the WorkflowBundle ID
+ //editManager.addObserver(new DataflowEditsListener());
+
+ closeTheSplashScreen();
+ setVisible(true);
+ }
+
+ private void closeTheSplashScreen() {
+// SplashScreen splash = SplashScreen.getSplashScreen();
+// if (splash != null) {
+// splash.setClosable();
+// splash.requestClose();
+// }
+ }
+
+ private void showAboutDialog() {
+ // TODO implement this!
+ }
+
+ private void makeGUI() {
+ setLayout(new GridBagLayout());
+
+ addWindowListener(new WindowClosingListener());
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ Helper.setKeyCatcher(this);
+
+ URL launcherLogo = getClass().getResource(LAUNCHER_LOGO_PNG);
+ if (launcherLogo != null) {
+ ImageIcon imageIcon = new ImageIcon(launcherLogo);
+ setIconImage(imageIcon.getImage());
+ }
+ setTitle(applicationConfiguration.getTitle());
+
+ osxApp.setEnabledPreferencesMenu(true);
+ osxApp.setEnabledAboutMenu(true);
+ osxApp.addApplicationListener(osxAppListener);
+
+ /*
+ * Set the size and position of the Workbench to the last saved values
+ * or use the default ones the first time it is launched
+ */
+ loadSizeAndLocationPrefs();
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.weightx = 0.1;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = LINE_START;
+
+ add(makeToolbarPanel(), gbc);
+
+ gbc.anchor = CENTER;
+ gbc.fill = BOTH;
+ gbc.gridy = 1;
+ gbc.weightx = 0.1;
+ gbc.weighty = 0.1;
+
+ add(makePerspectivePanel(), gbc);
+
+ menuBar = menuManager.createMenuBar();
+ setJMenuBar(menuBar);
+ }
+
+ protected JPanel makeToolbarPanel() {
+ JPanel toolbarPanel = new JPanel(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.anchor = LINE_START;
+
+ toolBar = menuManager.createToolBar();
+ toolBar.setFloatable(false);
+ toolbarPanel.add(toolBar, gbc);
+
+ perspectiveToolBar = new JToolBar("Perspectives");
+ perspectiveToolBar.setFloatable(false);
+ gbc.gridy = 1;
+ gbc.weightx = 0.1;
+ gbc.fill = HORIZONTAL;
+ toolbarPanel.add(perspectiveToolBar, gbc);
+
+ return toolbarPanel;
+ }
+
+ private JPanel makePerspectivePanel() {
+ CardLayout perspectiveLayout = new CardLayout();
+ JPanel perspectivePanel = new JPanel(perspectiveLayout);
+ workbenchPerspectives = new WorkbenchPerspectives(perspectiveToolBar,
+ perspectivePanel, perspectiveLayout, selectionManager);
+ workbenchPerspectives.setPerspectives(perspectives);
+ return perspectivePanel;
+ }
+
+ @Override
+ public void makeNamedComponentVisible(String componentName) {
+ // basePane.makeNamedComponentVisible(componentName);
+ }
+
+ protected void setExceptionHandler() {
+ setDefaultUncaughtExceptionHandler(new ExceptionHandler());
+ }
+
+ public void setStartupHooks(List<StartupSPI> startupHooks) {
+ this.startupHooks = startupHooks;
+ }
+
+ /**
+ * Calls the startup methods on all the {@link StartupSPI}s. If any startup
+ * method returns <code>false</code> (meaning that the Workbench will not
+ * function at all) then this method returns <code>false</code>.
+ */
+ private boolean callStartupHooks() {
+ for (StartupSPI startupSPI : startupHooks)
+ if (!startupSPI.startup())
+ return false;
+ return true;
+ }
+
+ @Override
+ public void exit() {
+ if (callShutdownHooks())
+ System.exit(0);
+ }
+
+ public void setShutdownHooks(List<ShutdownSPI> shutdownHooks) {
+ this.shutdownHooks = shutdownHooks;
+ }
+
+ /**
+ * Calls all the shutdown on all the {@link ShutdownSPI}s. If a shutdown
+ * returns <code>false</code> (meaning that the shutdown process should be
+ * aborted) then this method returns with a value of <code>false</code>
+ * immediately.
+ *
+ * @return <code>true</code> if all the <code>ShutdownSPIs</code> return
+ * <code>true</code> and the workbench shutdown should proceed
+ */
+ private boolean callShutdownHooks() {
+ for (ShutdownSPI shutdownSPI : shutdownHooks)
+ if (!shutdownSPI.shutdown())
+ return false;
+ return true;
+ }
+
+ /**
+ * Store current Workbench position and size.
+ */
+ @Override
+ public void storeSizeAndLocationPrefs() throws IOException {
+ userPreferences.putInt("width", getWidth());
+ userPreferences.putInt("height", getHeight());
+ userPreferences.putInt("x", getX());
+ userPreferences.putInt("y", getY());
+ }
+
+ /**
+ * Loads last saved Workbench position and size.
+ */
+ private void loadSizeAndLocationPrefs() {
+ Dimension screen = getToolkit().getScreenSize();
+
+ int width = userPreferences.getInt("width", (int) (screen.getWidth() * 0.75));
+ int height = userPreferences.getInt("height", (int) (screen.getHeight() * 0.75));
+ int x = userPreferences.getInt("x", 0);
+ int y = userPreferences.getInt("y", 0);
+
+ // Make sure our window is not too big
+ width = Math.min((int) screen.getWidth(), width);
+ height = Math.min((int) screen.getHeight(), height);
+
+ // Move to upper left corner if we are too far off
+ if (x > (screen.getWidth() - 50) || x < 0)
+ x = 0;
+ if (y > (screen.getHeight() - 50) || y < 0)
+ y = 0;
+
+ this.setSize(width, height);
+ this.setLocation(x, y);
+ }
+
+ public static void setLookAndFeel() {
+ String defaultLaf = System.getProperty("swing.defaultlaf");
+ try {
+ if (defaultLaf != null) {
+ UIManager.setLookAndFeel(defaultLaf);
+ return;
+ }
+ } catch (Exception e) {
+ logger.info("Can't set requested look and feel -Dswing.defaultlaf="
+ + defaultLaf, e);
+ }
+ String os = System.getProperty("os.name");
+ if (os.contains("Mac") || os.contains("Windows")) {
+ // For OSX and Windows use the system look and feel
+ String systemLF = getSystemLookAndFeelClassName();
+ try {
+ UIManager.setLookAndFeel(systemLF);
+ getLookAndFeelDefaults().put("ClassLoader",
+ WorkbenchImpl.class.getClassLoader());
+ logger.info("Using system L&F " + systemLF);
+ return;
+ } catch (Exception ex2) {
+ logger.error("Unable to load system look and feel " + systemLF,
+ ex2);
+ }
+ }
+ /*
+ * The system look and feel on *NIX
+ * (com.sun.java.swing.plaf.gtk.GTKLookAndFeel) looks like Windows 3.1..
+ * try to use Nimbus (Java 6e10 and later)
+ */
+ try {
+ UIManager.setLookAndFeel(NIMBUS);
+ logger.info("Using Nimbus look and feel");
+ return;
+ } catch (Exception e) {
+ }
+
+ // Metal should be better than GTK still
+ try {
+ String crossPlatform = getCrossPlatformLookAndFeelClassName();
+ UIManager.setLookAndFeel(crossPlatform);
+ logger.info("Using cross platform Look and Feel " + crossPlatform);
+ return;
+ } catch (Exception e){
+ }
+
+ // Final fallback
+ try {
+ String systemLF = getSystemLookAndFeelClassName();
+ UIManager.setLookAndFeel(systemLF);
+ logger.info("Using system platform Look and Feel " + systemLF);
+ } catch (Exception e){
+ logger.info("Using default Look and Feel " + getLookAndFeel());
+ }
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ if (this.menuManager != null && menuManagerObserver != null)
+ this.menuManager.removeObserver(menuManagerObserver);
+ this.menuManager = menuManager;
+ menuManagerObserver = new MenuManagerObserver();
+ menuManager.addObserver(menuManagerObserver);
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void refreshPerspectives(Object service, Map<?,?> properties) {
+ workbenchPerspectives.refreshPerspectives();
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+
+ public void setT2ConfigurationFrame(T2ConfigurationFrame t2ConfigurationFrame) {
+ this.t2ConfigurationFrame = t2ConfigurationFrame;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setPluginManager(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ }
+
+ private final class MenuManagerObserver extends
+ SwingAwareObserver<MenuManagerEvent> {
+ @Override
+ public void notifySwing(Observable<MenuManagerEvent> sender,
+ MenuManagerEvent message) {
+ if (message instanceof UpdatedMenuManagerEvent
+ && WorkbenchImpl.this.isVisible())
+ refreshMenus();
+ }
+ }
+
+ private void refreshMenus() {
+ if (menuBar != null) {
+ menuBar.revalidate();
+ menuBar.repaint();
+ }
+ if (toolBar != null) {
+ toolBar.revalidate();
+ toolBar.repaint();
+ }
+ }
+
+ private final class ExceptionHandler implements UncaughtExceptionHandler {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ logger.error("Uncaught exception in " + t, e);
+ if (e instanceof Exception
+ && !workbenchConfiguration.getWarnInternalErrors()) {
+ /*
+ * User preference disables warnings - but we'll show it anyway
+ * if it is an Error (which is more serious)
+ */
+ return;
+ }
+ final String message;
+ final String title;
+ final int style;
+ if (t.getClass().getName().equals("java.awt.EventDispatchThread")) {
+ message = "The user action could not be completed due to an unexpected error:\n"
+ + e;
+ title = "Could not complete user action";
+ style = ERROR_MESSAGE;
+ } else {
+ message = "An unexpected internal error occured in \n" + t + ":\n" + e;
+ title = "Unexpected internal error";
+ style = WARNING_MESSAGE;
+ }
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ showMessageDialog(WorkbenchImpl.this, message, title, style);
+ }
+ });
+ }
+ }
+
+ private class WindowClosingListener extends WindowAdapter {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ exit();
+ }
+ }
+
+ private class OSXAppListener extends ApplicationAdapter {
+ @Override
+ public void handleAbout(ApplicationEvent e) {
+ showAboutDialog();
+ e.setHandled(true);
+ }
+
+ @Override
+ public void handleQuit(ApplicationEvent e) {
+ e.setHandled(true);
+ exit();
+ }
+
+ @Override
+ public void handlePreferences(ApplicationEvent e) {
+ e.setHandled(true);
+ t2ConfigurationFrame.showFrame();
+ }
+
+ @Override
+ public void handleOpenFile(ApplicationEvent e) {
+ try {
+ if (e.getFilename() != null) {
+ fileManager.openDataflow(null, new File(e.getFilename()));
+ e.setHandled(true);
+ }
+ } catch (OpenException | IllegalStateException ex) {
+ logger.warn("Could not open file " + e.getFilename(), ex);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java
new file mode 100644
index 0000000..921260a
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * 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.ui.impl;
+
+import static java.awt.Image.SCALE_SMOOTH;
+import static javax.swing.Action.NAME;
+import static javax.swing.Action.SMALL_ICON;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkbenchPerspectives {
+ private static Logger logger = Logger
+ .getLogger(WorkbenchPerspectives.class);
+
+ private PerspectiveSPI currentPerspective;
+ private ButtonGroup perspectiveButtonGroup = new ButtonGroup();
+ private Map<String, JToggleButton> perspectiveButtonMap = new HashMap<>();
+ private JToolBar toolBar;
+ private JPanel panel;
+ private CardLayout cardLayout;
+ private List<PerspectiveSPI> perspectives = new ArrayList<>();
+ private boolean refreshing;
+ private final SelectionManager selectionManager;
+
+ public WorkbenchPerspectives(JToolBar toolBar, JPanel panel,
+ CardLayout cardLayout, SelectionManager selectionManager) {
+ this.panel = panel;
+ this.toolBar = toolBar;
+ this.cardLayout = cardLayout;
+ this.selectionManager = selectionManager;
+ refreshing = true;
+ selectionManager.addObserver(new SelectionManagerObserver());
+ refreshing = false;
+ }
+
+ public List<PerspectiveSPI> getPerspectives() {
+ return this.perspectives;
+ }
+
+ public void setPerspectives(List<PerspectiveSPI> perspectives) {
+ this.perspectives = perspectives;
+ initialisePerspectives();
+ }
+
+ private void initialisePerspectives() {
+ for (final PerspectiveSPI perspective : perspectives)
+ addPerspective(perspective, false);
+ selectFirstPerspective();
+ }
+
+ private void setPerspective(PerspectiveSPI perspective) {
+ if (perspective != currentPerspective) {
+ if (!perspectiveButtonMap.containsKey(perspective.getID()))
+ addPerspective(perspective, true);
+ if (!(perspective instanceof BlankPerspective))
+ perspectiveButtonMap.get(perspective.getID()).setSelected(true);
+ cardLayout.show(panel, perspective.getID());
+ currentPerspective = perspective;
+ }
+ }
+
+ private void addPerspective(final PerspectiveSPI perspective,
+ boolean makeActive) {
+ // ensure icon image is always 16x16
+ ImageIcon buttonIcon = null;
+ if (perspective.getButtonIcon() != null) {
+ Image buttonImage = perspective.getButtonIcon().getImage();
+ buttonIcon = new ImageIcon(buttonImage.getScaledInstance(16, 16,
+ SCALE_SMOOTH));
+ }
+
+ final JToggleButton toolbarButton = new JToggleButton(
+ perspective.getText(), buttonIcon);
+ toolbarButton.setToolTipText(perspective.getText() + " perspective");
+ Action action = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ selectionManager.setSelectedPerspective(perspective);
+ }
+ };
+ action.putValue(NAME, perspective.getText());
+ action.putValue(SMALL_ICON, buttonIcon);
+
+ toolbarButton.setAction(action);
+ toolBar.add(toolbarButton);
+ perspectiveButtonGroup.add(toolbarButton);
+ perspectiveButtonMap.put(perspective.getID(), toolbarButton);
+
+ panel.add(perspective.getPanel(), perspective.getID());
+ if (makeActive)
+ toolbarButton.doClick();
+ }
+
+ /**
+ * Recreates the toolbar buttons. Useful if a perspective has been removed.
+ */
+ public void refreshPerspectives() {
+ invokeLater(new RefreshRunner());
+ }
+
+ /** selects the first visible perspective by clicking on the toolbar button */
+ private void selectFirstPerspective() {
+ boolean set = false;
+ for (Component c : toolBar.getComponents())
+ if (c instanceof AbstractButton && c.isVisible()) {
+ ((AbstractButton) c).doClick();
+ set = true;
+ break;
+ }
+
+ if (!set) {
+ // no visible perspectives were found
+ logger.info("No visible perspectives.");
+ selectionManager.setSelectedPerspective(new BlankPerspective());
+ }
+ }
+
+ private final class RefreshRunner implements Runnable {
+ @Override
+ public void run() {
+ synchronized (WorkbenchPerspectives.this) {
+ if (refreshing)
+ // We only need one run
+ return;
+ refreshing = true;
+ }
+ try {
+ toolBar.removeAll();
+ toolBar.repaint();
+ initialisePerspectives();
+ } finally {
+ synchronized (WorkbenchPerspectives.this) {
+ refreshing = false;
+ }
+ }
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof PerspectiveSelectionEvent) {
+ PerspectiveSPI selectedPerspective = ((PerspectiveSelectionEvent) message)
+ .getSelectedPerspective();
+ setPerspective(selectedPerspective);
+ }
+ }
+ }
+
+ /**
+ * A dummy blank perspective for when there are no visible perspectives
+ * available
+ *
+ * @author Stuart Owen
+ */
+ private class BlankPerspective implements PerspectiveSPI {
+ @Override
+ public String getID() {
+ return BlankPerspective.class.getName();
+ }
+
+ @Override
+ public JPanel getPanel() {
+ return new JPanel();
+ }
+
+ @Override
+ public ImageIcon getButtonIcon() {
+ return null;
+ }
+
+ @Override
+ public String getText() {
+ return null;
+ }
+
+ @Override
+ public int positionHint() {
+ return 0;
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java
new file mode 100644
index 0000000..d5573be
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * 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.ui.impl.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+/**
+ * Exit the workbench
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class ExitAction extends AbstractMenuAction {
+ private static final String EXIT_LABEL = "Exit";
+ private static final String MAC_OS_X = "Mac OS X";
+ private Workbench workbench;
+
+ public ExitAction() {
+ super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#file"),
+ 10000);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction(EXIT_LABEL) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ workbench.exit();
+ }
+ };
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return !MAC_OS_X.equalsIgnoreCase(System.getProperty("os.name"));
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
new file mode 100644
index 0000000..a9fa855
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.WorkbenchLauncher
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..0c43dec
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,18 @@
+
+net.sf.taverna.t2.workbench.ui.impl.menu.FileMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.ExitAction
+
+net.sf.taverna.t2.workbench.ui.impl.menu.EditMenu
+
+net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.DisplayPerspectivesMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.EditPerspectivesMenu
+
+net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.OnlineHelpMenuAction
+net.sf.taverna.t2.workbench.ui.impl.menu.FeedbackMenuAction
+
+#net.sf.taverna.t2.workbench.ui.impl.menu.ViewShowMenuSection
+#net.sf.taverna.t2.workbench.ui.impl.menu.ChangePerspectiveMenuAction
+
+net.sf.taverna.t2.workbench.ui.impl.menu.ShowLogsAndDataMenuAction
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..c022389
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.StoreWindowStateOnShutdown
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..86349aa
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.impl.UserRegistrationHook
+net.sf.taverna.t2.workbench.ui.impl.SetConsoleLoggerStartup
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml
new file mode 100644
index 0000000..0dc7efc
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UserRegistrationHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+ <!-- <service ref="SetConsoleLoggerStartup" interface="net.sf.taverna.t2.workbench.StartupSPI" /> -->
+
+ <service ref="StoreWindowStateOnShutdown" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+ <service ref="ExitAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.exit" />
+ </service-properties>
+ </service>
+
+ <service ref="Workbench" interface="net.sf.taverna.t2.workbench.ui.Workbench" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+ <reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+ <reference id="t2ConfigurationFrame" interface="net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame" />
+
+ <list id="perspectives" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" cardinality="0..N" comparator-ref="PerspectiveComparator" greedy-proxying="true">
+ <listener ref="Workbench" bind-method="refreshPerspectives" unbind-method="refreshPerspectives" />
+ </list>
+
+ <list id="startupHooks" interface="net.sf.taverna.t2.workbench.StartupSPI" cardinality="0..N" comparator-ref="StartupComparator"/>
+ <list id="shutdownHooks" interface="net.sf.taverna.t2.workbench.ShutdownSPI" cardinality="0..N" comparator-ref="ShutdownComparator"/>
+
+</beans:beans>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml
new file mode 100644
index 0000000..78c4eb2
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UserRegistrationHook" class="net.sf.taverna.t2.workbench.ui.impl.UserRegistrationHook">
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ </bean>
+ <bean id="SetConsoleLoggerStartup" class="net.sf.taverna.t2.workbench.ui.impl.SetConsoleLoggerStartup">
+ <constructor-arg ref="workbenchConfiguration" />
+ </bean>
+
+ <bean id="StoreWindowStateOnShutdown" class="net.sf.taverna.t2.workbench.ui.impl.StoreWindowStateOnShutdown">
+ <property name="workbench">
+ <ref local="Workbench"/>
+ </property>
+ </bean>
+
+ <bean id="ExitAction" class="net.sf.taverna.t2.workbench.ui.impl.menu.ExitAction">
+ <property name="workbench">
+ <ref local ="Workbench"/>
+ </property>
+ </bean>
+
+ <bean id="Workbench" class="net.sf.taverna.t2.workbench.ui.impl.WorkbenchImpl" init-method="initialize">
+ <constructor-arg ref="startupHooks"/>
+ <constructor-arg ref="shutdownHooks"/>
+ <constructor-arg ref="perspectives"/>
+ <property name="editManager" ref="editManager"/>
+ <property name="fileManager" ref="fileManager"/>
+ <property name="menuManager" ref="menuManager"/>
+ <property name="t2ConfigurationFrame" ref="t2ConfigurationFrame"/>
+ <property name="workbenchConfiguration" ref="workbenchConfiguration"/>
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ <property name="selectionManager" ref="selectionManager"/>
+ <property name="pluginManager" ref="pluginManager"/>
+ </bean>
+
+ <bean id="PerspectiveComparator" class="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI$PerspectiveComparator" />
+ <bean id="StartupComparator" class="net.sf.taverna.t2.workbench.StartupSPI$StartupComparator" />
+ <bean id="ShutdownComparator" class="net.sf.taverna.t2.workbench.ShutdownSPI$ShutdownComparator" />
+
+</beans>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/Map.jhm b/taverna-workbench-workbench-impl/src/main/resources/Map.jhm
new file mode 100644
index 0000000..ab5f560
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/Map.jhm
@@ -0,0 +1,28 @@
+<?xml version='1.0' encoding='ISO-8859-1' ?>
+<!DOCTYPE map
+ PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN"
+ "http://java.sun.com/javase/technologies/desktop/javahelp/map_1_0.dtd">
+
+<map version="1.0">
+ <mapID target="toplevelfolder" url="images/toplevel.gif" />
+ <mapID target="top" url="help/welcome.html" />
+
+ <mapID target="intro" url="help/welcome.html" />
+ <mapID target="start" url="help/start.html" />
+ <mapID target="overview" url="help/welcome.html" />
+ <mapID target="one" url="help/start.html" />
+ <mapID target="two" url="help/start.html" />
+
+ <mapID target="bean.story" url="help/welcome.html" />
+ <mapID target="bean.story" url="help/start.html" />
+ <mapID target="bean.story" url="help/welcome.html" />
+
+ <mapID target="http://taverna.sf.net/2008/t2workbench/menu#help"
+ url="http://www.google.com" />
+
+ <mapID target="http://taverna.sf.net/2008/t2workbench/menu#fileOpen"
+ url="http://www.google.com" />
+
+
+
+</map>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml b/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml
new file mode 100644
index 0000000..5d84369
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<registration_data>
+ <taverna_version>taverna-2.2.0</taverna_version>
+ <first_name>John</first_name>
+ <last_name>Doe</last_name>
+ <email_address>john.doe@jd-consulting.com</email_address>
+ <keep_me_informed>false</keep_me_informed>
+ <institution_or_company_name>JD Consulting</institution_or_company_name>
+ <industry_type>Industry - Pharmaceutical</industry_type>
+ <field_of_interest>bioinformatics</field_of_interest>
+ <purpose_of_using_taverna>pharmacogenomics</purpose_of_using_taverna>
+</registration_data>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd b/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd
new file mode 100644
index 0000000..776f8e5
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:element name="registration_data">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="taverna_version"/>
+ <xs:element ref="first_name"/>
+ <xs:element ref="last_name"/>
+ <xs:element ref="email_address"/>
+ <xs:element ref="keep_me_informed"/>
+ <xs:element ref="institution_or_company_name"/>
+ <xs:element ref="industry_type"/>
+ <xs:element ref="field_of_interest"/>
+ <xs:element ref="purpose_of_using_taverna"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="taverna_version" type="xs:string"/>
+ <xs:element name="first_name" type="xs:string"/>
+ <xs:element name="last_name" type="xs:string"/>
+ <xs:element name="email_address" type="xs:string"/>
+ <xs:element name="keep_me_informed" type="xs:boolean"/>
+ <xs:element name="institution_or_company_name" type="xs:string"/>
+ <xs:element name="industry_type" type="xs:string"/>
+ <xs:element name="field_of_interest" type="xs:string"/>
+ <xs:element name="purpose_of_using_taverna" type="xs:string"/>
+</xs:schema>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/registration.php b/taverna-workbench-workbench-impl/src/main/resources/registration.php
new file mode 100644
index 0000000..902200b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/registration.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * A folder where to write the registrations
+ */
+
+$registrations_folder = "/var/taverna-registration";
+
+
+
+function xmlescape($string){
+ $res = str_replace("&", "&",$string);
+ $res = str_replace("<", "<",$res);
+ $res = str_replace(">", ">",$res);
+ return $res;
+}
+
+
+
+/* From http://php.net/manual/en/function.uiqid.php
+ * Requires yum install uuid-php
+ and in .htaccess / php.ini:
+ php_value allow_call_time_pass_reference true
+ */
+
+class uuid {
+ /**
+ * This class enables you to get real uuids using the OSSP library.
+ * Note you need php-uuid installed.
+ * On my system 1000 UUIDs are created in 0.0064 seconds.
+ *
+ * @author Marius Karthaus
+ *
+ */
+
+ protected $uuidobject;
+
+ /**
+ * On long running deamons i've seen a lost resource. This checks the resource and creates it if needed.
+ *
+ */
+ protected function create() {
+ if (! is_resource ( $this->uuidobject )) {
+ uuid_create ( &$this->uuidobject );
+ }
+ }
+
+ /**
+ * Return a type 1 (MAC address and time based) uuid
+ *
+ * @return String
+ */
+ public function v1() {
+ $this->create ();
+ uuid_make ( $this->uuidobject, UUID_MAKE_V1 );
+ uuid_export ( $this->uuidobject, UUID_FMT_STR, &$uuidstring );
+ return trim ( $uuidstring );
+ }
+
+ /**
+ * Return a type 4 (random) uuid
+ *
+ * @return String
+ */
+ public function v4() {
+ $this->create ();
+ uuid_make ( $this->uuidobject, UUID_MAKE_V4 );
+ uuid_export ( $this->uuidobject, UUID_FMT_STR, &$uuidstring );
+ return trim ( $uuidstring );
+ }
+
+ /**
+ * Return a type 5 (SHA-1 hash) uuid
+ *
+ * @return String
+ */
+ public function v5() {
+ $this->create ();
+ uuid_make ( $this->uuidobject, UUID_MAKE_V5 );
+ uuid_export ( $this->uuidobject, UUID_FMT_STR, $uuidstring );
+ return trim ( $uuidstring );
+ }
+}
+
+ if(isset($_POST['taverna_registration'])){
+
+ $taverna_version = $_POST['taverna_version'];
+ $first_name = $_POST['first_name'];
+ $last_name = $_POST['last_name'];
+ $email = $_POST['email'];
+ $keep_me_informed = $_POST['keep_me_informed'];
+ $institution_or_company = $_POST['institution_or_company'];
+ $industry = $_POST['industry_type'];
+ $field = $_POST['field'];
+ $purpose = $_POST['purpose'];
+
+ $uuid=new uuid();
+
+
+ // Generate user registration data file name with a random identifier
+ $random_id = $uuid->v4();
+ $user_registration_file_name = $registrations_folder . "/user_registration_" . $random_id . ".xml";
+ $user_registration_file = fopen($user_registration_file_name,'w') or die ("Could not open file ". $user_registration_file_name . " for writing." );
+
+ // Save this to a file
+ /*
+ $registration_data = "";
+ $registration_data .= "Taverna version=" . $taverna_version . "\n";
+ $registration_data .= "First name=" . $first_name . "\n";
+ $registration_data .= "Last name=" . $last_name . "\n";
+ $registration_data .= "Email address=" . $email . "\n";
+ $registration_data .= "Keep me informed by email=" . $keep_me_informed . "\n";
+ $registration_data .= "Institution or company=" . $institution_or_company . "\n";
+ $registration_data .= "Industry=" . $industry_type . "\n";
+ $registration_data .= "Field of interest=" . $field . "\n";
+ $registration_data .= "Purpose of using Taverna=" . $purpose;
+ */
+
+ $registration_data = "";
+ $registration_data .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ $registration_data .= "<registration_data>\n";
+ $registration_data .= "\t<taverna_version>".xmlescape($taverna_version)."</taverna_version>\n";
+ $registration_data .= "\t<first_name>".xmlescape($first_name)."</first_name>\n";
+ $registration_data .= "\t<last_name>".xmlescape($last_name)."</last_name>\n";
+ $registration_data .= "\t<email_address>".xmlescape($email)."</email_address>\n";
+ $registration_data .= "\t<keep_me_informed>".xmlescape($keep_me_informed)."</keep_me_informed>\n";
+ $registration_data .= "\t<institution_or_company_name>".xmlescape($institution_or_company)."</institution_or_company_name>\n";
+ $registration_data .= "\t<industry_type>".xmlescape($industry)."</industry_type>\n";
+ $registration_data .= "\t<field_of_interest>".xmlescape($field)."</field_of_interest>\n";
+ $registration_data .= "\t<purpose_of_using_taverna>".xmlescape($purpose)."</purpose_of_using_taverna>\n";
+ $registration_data .= "</registration_data>\n";
+
+ fwrite($user_registration_file, $registration_data) or die ("Could not write to file ". $user_registration_file_name );
+ fclose($user_registration_file);
+ echo "Registration successful!";
+ }
+?>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/sample.hs b/taverna-workbench-workbench-impl/src/main/resources/sample.hs
new file mode 100644
index 0000000..9b43dca
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/sample.hs
@@ -0,0 +1,64 @@
+<?xml version='1.0' encoding='ISO-8859-1' ?>
+<!DOCTYPE helpset
+ PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN"
+ "../dtd/helpset_2_0.dtd">
+
+<helpset version="1.0">
+
+ <!-- title -->
+ <title>My Sample Help - Online</title>
+
+ <!-- maps -->
+ <maps>
+ <homeID>top</homeID>
+ <mapref location="Map.jhm"/>
+ </maps>
+
+ <!-- views -->
+ <view>
+ <name>TOC</name>
+ <label>Table Of Contents</label>
+ <type>javax.help.TOCView</type>
+ <data>SampleTOC.xml</data>
+ </view>
+
+ <view>
+ <name>Index</name>
+ <label>Index</label>
+ <type>javax.help.IndexView</type>
+ <data>SampleIndex.xml</data>
+ </view>
+
+ <view>
+ <name>Search</name>
+ <label>Search</label>
+ <type>javax.help.SearchView</type>
+ <data engine="com.sun.java.help.search.DefaultSearchEngine">
+ JavaHelpSearch
+ </data>
+ </view>
+
+ <presentation default="true" displayviewimages="false">
+ <name>main window</name>
+ <size width="700" height="400" />
+ <location x="200" y="200" />
+ <title>My Sample Help - Online</title>
+ <image>toplevelfolder</image>
+ <toolbar>
+ <helpaction>javax.help.BackAction</helpaction>
+ <helpaction>javax.help.ForwardAction</helpaction>
+ <helpaction>javax.help.SeparatorAction</helpaction>
+ <helpaction>javax.help.HomeAction</helpaction>
+ <helpaction>javax.help.ReloadAction</helpaction>
+ <helpaction>javax.help.SeparatorAction</helpaction>
+ <helpaction>javax.help.PrintAction</helpaction>
+ <helpaction>javax.help.PrintSetupAction</helpaction>
+ </toolbar>
+ </presentation>
+ <presentation>
+ <name>main</name>
+ <size width="400" height="400" />
+ <location x="200" y="200" />
+ <title>My Sample Help - Online</title>
+ </presentation>
+</helpset>
diff --git a/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java b/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java
new file mode 100644
index 0000000..4c64aa3
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * 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.ui.impl;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import static org.junit.Assert.*;
+
+import net.sf.taverna.t2.workbench.ui.impl.UserRegistrationForm;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class UserRegistrationTest {
+
+ @Ignore
+ @Test
+ public void postUserRegistrationDataToServer() {
+
+ String parameters = "";
+
+ // The 'submit' parameter - to let the server-side script know we are
+ // submitting
+ // the user's registration form - all other requests will be silently
+ // ignored
+ try {
+ parameters = URLEncoder
+ .encode(
+ UserRegistrationForm.TAVERNA_REGISTRATION_POST_PARAMETER_NAME,
+ "UTF-8")
+ + "=" + URLEncoder.encode("submit", "UTF-8"); // value does
+ // not
+ // matter
+
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.TAVERNA_VERSION_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("snapshot", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.FIRST_NAME_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Alex", "UTF-8");
+ parameters += "&"
+ + URLEncoder.encode(
+ UserRegistrationForm.LAST_NAME_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Nenadic", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.EMAIL_ADDRESS_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("alex@alex.com", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("true", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Uni of Manchester", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.INDUSTRY_TYPE_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Academia", "UTF-8");
+ parameters += "&"
+ + URLEncoder.encode(
+ UserRegistrationForm.FIELD_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Research", "UTF-8");
+ parameters += "&"
+ + URLEncoder.encode(
+ UserRegistrationForm.PURPOSE_POST_PARAMETER_NAME,
+ "UTF-8") + "=" + URLEncoder.encode("None", "UTF-8");
+ } catch (UnsupportedEncodingException ueex) {
+ System.out
+ .println("Failed to url encode post parameters when sending user registration data.");
+ }
+ String server = "http://cactus.cs.man.ac.uk/~alex/taverna_registration/registration.php";
+ server = "http://localhost/~alex/taverna_registration/registration.php";
+ // server = "https://somehost.co.uk";
+
+ System.out.println("Posting user registartion to " + server
+ + " with parameters: " + parameters);
+ String response = "";
+ try {
+ URL url = new URL(server);
+ URLConnection conn = url.openConnection();
+ System.out.println("Opened a connection");
+ // Set timeout for connection, otherwise we might hang too long
+ // if server is not responding and it will block Taverna
+ conn.setConnectTimeout(7000);
+ // Set connection parameters
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ // Make server believe we are HTML form data...
+ conn.setRequestProperty("Content-Type",
+ "application/x-www-form-urlencoded");
+ System.out
+ .println("Trying to get an output stream from the connection");
+ DataOutputStream out = new DataOutputStream(conn.getOutputStream());
+ // Write out the bytes of the content string to the stream.
+ out.writeBytes(parameters);
+ out.flush();
+ out.close();
+ // Read response from the input stream.
+ BufferedReader in = new BufferedReader(new InputStreamReader(conn
+ .getInputStream()));
+ String temp;
+ while ((temp = in.readLine()) != null) {
+ response += temp + "\n";
+ }
+ // Remove the last \n character
+ if (!response.equals("")) {
+ response = response.substring(0, response.length() - 1);
+ }
+ in.close();
+ System.out.println(response);
+ if (!response.equals("Registration successful!")) {
+ System.out
+ .println("Registration failed. Response form server was: "
+ + response);
+ }
+ assertTrue(response.equals("Registration successful!"));
+ }
+ // Catch some runtime exceptions
+ catch (ConnectException ceex) { // the connection was refused remotely
+ // (e.g. no process is listening on the
+ // remote address/port).
+ System.out
+ .println("User registration failed: Registration server is not listening of the specified url.");
+ ceex.printStackTrace();
+ }
+ // Catch some runtime exceptions
+ catch (SocketTimeoutException stex) { // timeout has occurred on a
+ // socket read or accept.
+ System.out
+ .println("User registration failed: Socket timeout occurred.");
+ stex.printStackTrace();
+ } catch (MalformedURLException muex) {
+ System.out
+ .println("User registration failed: Registartion server's url is malformed.");
+ muex.printStackTrace();
+ } catch (IOException ioex) {
+ System.out
+ .println("User registration failed: Failed to open url connection to registration server or writing to it or reading from it.");
+ ioex.printStackTrace();
+ }
+ }
+
+}
diff --git a/taverna-workbench-workbench-impl/src/test/resources/log4j.properties b/taverna-workbench-workbench-impl/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fa27070
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/test/resources/log4j.properties
@@ -0,0 +1,10 @@
+log4j.rootLogger=WARN, CONSOLE
+log4j.logger.net.sf.taverna.t2.workbench=INFO
+log4j.logger.net.sf.taverna.t2.ui=DEBUG
+
+#log4j.logger.org.apache.commons.httpclient=ERROR
+
+# Default output to console
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%-5p %d{ISO8601} (%c:%L) - %m%n
\ No newline at end of file
diff --git a/taverna-workbench-workflow-explorer/pom.xml b/taverna-workbench-workflow-explorer/pom.xml
new file mode 100644
index 0000000..8076b99
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-explorer</artifactId>
+ <packaging>bundle</packaging>
+ <name>Workflow Explorer</name>
+ <description>Workflow Explorer</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>menu-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-icons-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>design-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>helper</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>workbench-impl</artifactId>
+ <version>${t2.ui.impl.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>${commons.beanutils.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorer.java b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorer.java
new file mode 100644
index 0000000..df768b7
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorer.java
@@ -0,0 +1,824 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.workflowexplorer;
+
+import static java.awt.BorderLayout.CENTER;
+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 net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.inputIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.minusIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.outputIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.plusIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorerTreeModel.INPUTS;
+import static net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorerTreeModel.OUTPUTS;
+import static net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorerTreeModel.PROCESSORS;
+import static net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorerTreeModel.getPathForObject;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+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.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowInputAction;
+import net.sf.taverna.t2.workbench.design.actions.AddDataflowOutputAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.dndhandler.ServiceTransferHandler;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Workflow Explorer provides a context sensitive tree view of a workflow (showing its inputs,
+ * outputs, processors, datalinks, etc.). Selection of a node in the Model Explorer tree and a
+ * right-click leads to context sensitive options appearing in a pop-up menu.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class WorkflowExplorer extends JPanel implements UIComponentSPI {
+ /** Purple colour for shaded label on pop up menus */
+ public static final Color PURPLISH = new Color(0x8070ff);
+ /** Manager of all opened workflows */
+ private SelectionManager selectionManager;
+ private MenuManager menuManager;
+ /** Currently selected workflow (to be displayed in the Workflow Explorer). */
+ private Workflow workflow;
+ /** Map of trees for all opened workflows. */
+ private Map<Workflow, JTree> openedWorkflowsTrees = new HashMap<>();
+ /** Tree representation of the currently selected workflow. */
+ private JTree wfTree;
+ /**
+ * Current workflow's selection model event observer - telling us what is
+ * the currently selected object in the current workflow.
+ */
+ private Observer<DataflowSelectionMessage> workflowSelectionListener = new DataflowSelectionListener();
+ /** Scroll pane containing the workflow tree. */
+ private JScrollPane scrollPane;
+ protected FileManager fileManager;
+ protected FileManagerObserver fileManagerObserver = new FileManagerObserver();
+ protected EditManager editManager;
+ protected EditManagerObserver editManagerObserver = new EditManagerObserver();
+
+ private final ReportManager reportManager;
+ private final ActivityIconManager activityIconManager;
+ private final ServiceRegistry serviceRegistry;
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Workflow Explorer";
+ }
+
+ @Override
+ public void onDisplay() {
+ }
+
+ @Override
+ public void onDispose() {
+ }
+
+ /**
+ * Constructs the Workflow Explorer.
+ */
+ public WorkflowExplorer(EditManager editManager, FileManager fileManager,
+ MenuManager menuManager, ReportManager reportManager,
+ SelectionManager selectionManager,
+ ActivityIconManager activityIconManager, ServiceRegistry serviceRegistry) {
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.menuManager = menuManager;
+ this.reportManager = reportManager;
+ this.selectionManager = selectionManager;
+ this.activityIconManager = activityIconManager;
+ this.serviceRegistry = serviceRegistry;
+ this.setTransferHandler(new ServiceTransferHandler(editManager, menuManager,
+ selectionManager, serviceRegistry));
+
+ /*
+ * Create a tree that will represent a view over the current workflow.
+ * Initially, there is no workflow opened, so we create an empty tree,
+ * but immediately after all visual components of the Workbench are
+ * created (including Workflow Explorer) a new empty workflow is
+ * created, which is represented with a NON-empty JTree with four nodes
+ * (Inputs, Outputs, Processors, and Data links) that themselves have no
+ * children.
+ */
+ assignWfTree(new JTree(new DefaultMutableTreeNode("No workflow open")));
+
+ // Start observing workflow switching or closing events on File Manager
+ fileManager.addObserver(fileManagerObserver);
+ selectionManager.addObserver(new SelectionManagerObserver());
+
+ /*
+ * Start observing events on Edit Manager when current workflow is
+ * edited (e.g. a node added, deleted or updated)
+ */
+ editManager.addObserver(editManagerObserver);
+
+ // Draw visual components
+ initComponents();
+ }
+
+ private void assignWfTree(JTree tree) {
+ wfTree = tree;
+ wfTree.setTransferHandler(new ServiceTransferHandler(editManager,
+ menuManager, selectionManager, serviceRegistry));
+ }
+
+ /**
+ * Lays out the swing components.
+ */
+ public void initComponents() {
+ setLayout(new BorderLayout());
+
+ // Workflow tree scroll pane
+ scrollPane = new JScrollPane(wfTree, VERTICAL_SCROLLBAR_AS_NEEDED,
+ HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ scrollPane.setBorder(new EtchedBorder());
+
+ /*
+ * Title - not needed as it is now located on a tab labelled 'Workflow
+ * Explorer'
+ */
+ // JLabel wfExplorerLabel = new JLabel("Workflow Explorer");
+ // wfExplorerLabel.setMinimumSize(new Dimension(0, 0)); // so that it
+ // can shrink completely
+ // wfExplorerLabel.setBorder(new EmptyBorder(0, 0, 5, 0));
+
+ // add(wfExplorerLabel, BorderLayout.NORTH);
+ add(scrollPane, CENTER);
+ }
+
+ /**
+ * Gets called when a workflow is opened or a new (empty) one created.
+ */
+ public void createWorkflowTree(Workflow df) {
+ // Set the current workflow
+ workflow = df;
+
+ // Create a new tree and populate it with the workflow's data
+ assignWfTree(createTreeFromWorkflow(workflow));
+
+ // Add the new tree to the list of opened workflow trees
+ openedWorkflowsTrees.put(workflow, wfTree);
+
+ // Expand the tree
+ expandAll(wfTree);
+
+ Runnable expandWorkflowTreeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Repaint the scroll pane containing the tree
+ scrollPane.setViewportView(wfTree);
+ scrollPane.revalidate();
+ scrollPane.repaint();
+ }
+ };
+
+ if (isEventDispatchThread()) {
+ expandWorkflowTreeRunnable.run();
+ } else {
+ invokeLater(expandWorkflowTreeRunnable);
+ }
+ }
+
+ /**
+ * Switch the current workflow to a previously opened workflow.
+ */
+ private void switchWorkflowTree(Workflow workflow) {
+ // Set the current workflow to the one we have switched to
+ this.workflow = workflow;
+ // Select the node(s) that should be selected (do this after
+ // assigning the tree to the scroll pane)
+ setSelectedNodes(wfTree, workflow);
+ // Set the tree for the current workflow
+ wfTree = openedWorkflowsTrees.get(workflow);
+
+ // Repaint the scroll pane containing the tree
+ scrollPane.setViewportView(wfTree);
+
+ // Repaint the scroll pane containing the tree
+ scrollPane.revalidate();
+ scrollPane.repaint();
+ }
+
+ /**
+ * Gets called when the current workflow is edited, or when a parent
+ * workflow of a nested workflow is edited due to saved changes in the
+ * nested workflow (which is the current workflow).
+ */
+ public void updateWorkflowTree(Workflow df) {
+ // Create the new tree from the updated workflow
+ JTree newTree = createTreeFromWorkflow(df);
+
+ // Get the old workflow tree
+ JTree oldTree = openedWorkflowsTrees.get(df);
+
+ // Update the tree in the list of opened workflow trees
+ openedWorkflowsTrees.put(df, newTree);
+
+ /*
+ * Update the new tree's expansion state based on the old tree i.e. all
+ * nodes in the old tree that have been expanded/collapsed should also
+ * be expanded/collapsed in the new tree (unless an expanded node has
+ * been removed)
+ */
+ copyExpansionState(oldTree, (DefaultMutableTreeNode) oldTree.getModel()
+ .getRoot(), newTree, (DefaultMutableTreeNode) newTree
+ .getModel().getRoot());
+
+ /*
+ * Get the current workflow from FileManager.
+ *
+ * If current workflow is different from the workflow df passed through
+ * this method then this means that the current workflow is the nested
+ * workflow (whose parent is workflow df) and that the nested workflow
+ * has been previously edited and then saved which triggered the update
+ * on the parent workflow df.
+ *
+ * In this case, we should just update the parent workflow tree but keep
+ * the nested workflow as the current workflow. On the other hand, if
+ * the current workflow is the same as workflow df then this is just an
+ * update to the current workflow so we have to update and redraw the
+ * workflow tree.
+ */
+ if (df.equals(selectionManager.getSelectedWorkflow())) {
+ // this was an update on the current workflow
+
+ // Update the current workflow
+ workflow = df; // although they are the same anyway
+
+ // Set the current tree to the new tree
+ assignWfTree(newTree);
+
+ // Repaint the scroll pane containing the tree
+ scrollPane.setViewportView(wfTree);
+
+ // Select the node(s) that should be selected (do this after
+ // assigning the tree to the scroll pane)
+ setSelectedNodes(wfTree, workflow);
+
+ scrollPane.revalidate();
+ scrollPane.repaint();
+ } else {
+ /*
+ * just update the parent tree (already done above) but do not
+ * switch the trees. Do not revalidate/repaint as we are not
+ * switching to the new tree but keep showing the nested wf that has
+ * not changed.
+ */
+ }
+ }
+
+ /**
+ * Copies the expansion state of the old tree starting from the given node
+ * in the old tree to the new tree starting from the new node. We normally
+ * use it starting from the root nodes of both trees when an update has
+ * happened to the tree and we want to preserve the expansion state in the
+ * updated tree.
+ */
+ @SuppressWarnings("unchecked")
+ private void copyExpansionState(JTree oldTree,
+ DefaultMutableTreeNode oldNode, JTree newTree,
+ DefaultMutableTreeNode newNode) {
+ boolean expandParentNode = false;
+
+ /*
+ * Do the children on the node first (so we can set the node's children
+ * to be expanded even if the node itself is collapsed)
+ */
+ Enumeration<DefaultMutableTreeNode> children = newNode.children();
+ while (children.hasMoreElements()) {
+ DefaultMutableTreeNode newChild = children.nextElement();
+ // Find the corresponding node in the old tree, if any
+ DefaultMutableTreeNode oldChild = findChildWithUserObject(oldNode,
+ newChild.getUserObject());
+
+ if (oldChild != null) // corresponding node found in the old tree
+ // Recursively do the same for each child
+ copyExpansionState(oldTree, oldChild, newTree, newChild);
+ else
+ /*
+ * corresponding node not found in the old tree - a new node has
+ * been added or a node had been edited in the new tree so make
+ * that node visible now by expanding the parent node
+ */
+ expandParentNode = true;
+ }
+
+ // Now do the node
+ if (expandParentNode) {
+ /*
+ * Order matters - we first check if a new child was inserted to
+ * this node (that means that the old node might have been a leaf
+ * before)
+ */
+ int row = newTree.getRowForPath(new TreePath(newNode.getPath()));
+ newTree.expandRow(row);
+ } else if (oldNode.isLeaf()) {
+ /*
+ * if it is a leaf - expand/collapse does not work, so use
+ * isVisible/makeVisible
+ */
+ if (oldTree.isVisible(new TreePath(oldNode.getPath())))
+ newTree.makeVisible(new TreePath(newNode.getPath()));
+ } else if (oldTree.isExpanded(new TreePath(oldNode.getPath()))) {
+ int row = newTree.getRowForPath(new TreePath(newNode.getPath()));
+ newTree.expandRow(row);
+ } else { // node was collapsed
+ int row = newTree.getRowForPath(new TreePath(newNode.getPath()));
+ newTree.collapseRow(row);
+ }
+ }
+
+ /**
+ * Returns a child of a given node that contains the same user object as the
+ * one passed to the method.
+ */
+ @SuppressWarnings("unchecked")
+ private DefaultMutableTreeNode findChildWithUserObject(
+ DefaultMutableTreeNode node, Object userObject) {
+ Enumeration<DefaultMutableTreeNode> children = node.children();
+ while (children.hasMoreElements()) {
+ DefaultMutableTreeNode child = children.nextElement();
+ if (child.getUserObject().equals(userObject))
+ return child;
+ }
+ return null;
+ }
+
+ private JTree createTreeFromWorkflow(final Workflow workflow) {
+ // Create a new tree and populate it with the workflow's data
+ final JTree tree = new JTree(new WorkflowExplorerTreeModel(workflow));
+ tree.setRowHeight(18);
+ tree.setLargeModel(true);
+ tree.setEditable(false);
+ tree.setExpandsSelectedPaths(true);
+ tree.setDragEnabled(false);
+ tree.setScrollsOnExpand(false);
+ tree.setCellRenderer(new WorkflowExplorerTreeCellRenderer(workflow,
+ reportManager, activityIconManager));
+ // tree.setSelectionModel(new WorkflowExplorerTreeSelectionModel());
+ tree.addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ TreePath selectionPath = e.getNewLeadSelectionPath();
+ if (selectionPath != null) {
+ final DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath
+ .getLastPathComponent();
+
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflow.getParent());
+
+ /*
+ * If the node that was clicked on was inputs, outputs,
+ * services, data links, control links or merges in the main
+ * workflow then just make it selected and clear the
+ * selection model (as these are just containers for the
+ * 'real' workflow components).
+ */
+ if ((selectedNode.getUserObject() instanceof String)
+ && (selectionPath.getPathCount() == 2)) {
+ selectionModel.clearSelection();
+ tree.getSelectionModel().setSelectionPath(selectionPath);
+ } else {
+ /*
+ * a 'real' workflow component or the 'whole' workflow
+ * (i.e. the tree root) was clicked on
+ */
+
+ /*
+ * We want to disable selection of any nested workflow
+ * components (apart from input and output ports in the
+ * wrapping DataflowActivity)
+ */
+ TreePath path = getPathForObject(selectedNode
+ .getUserObject(), (DefaultMutableTreeNode) tree
+ .getModel().getRoot());
+
+ /*
+ * The getPathForObject() method will return null in a
+ * node is inside a nested workflow and should not be
+ * selected
+ */
+ if (path == null)
+ // Just return
+ return;
+
+ /*
+ * Add it to selection model so it is also selected
+ * on the graph as well that listens to the
+ * selection model
+ */
+ selectionModel.addSelection(selectedNode.getUserObject());
+ }
+ }
+ }
+ });
+
+ tree.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent evt) {
+ handleMouseEvent(evt);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent evt) {
+ handleMouseEvent(evt);
+ }
+
+ private void handleMouseEvent(MouseEvent evt) {
+ if (!evt.isPopupTrigger())
+ return;
+ // Discover the tree row that was clicked on
+ int selRow = tree.getRowForLocation(evt.getX(), evt.getY());
+ if (selRow == -1)
+ return;
+
+ // Get the selection path for the row
+ TreePath selectionPath = tree.getPathForLocation(evt.getX(),
+ evt.getY());
+ if (selectionPath == null)
+ return;
+
+ // Get the selected node
+ final DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath
+ .getLastPathComponent();
+
+ /*
+ * For both left and right click - add the workflow object to
+ * selection model. This will cause the node to become selected
+ * (from the selection listener's code)
+ */
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflow.getParent());
+
+ /*
+ * If the node that was clicked on was inputs, outputs,
+ * services, data links, control links or merges in the main
+ * workflow then just make it selected and clear the selection
+ * model (as these are just containers for the 'real' workflow
+ * components).
+ */
+ if ((selectedNode.getUserObject() instanceof String)
+ && selectionPath.getPathCount() == 2) {
+ selectionModel.clearSelection();
+ tree.getSelectionModel().setSelectionPath(selectionPath);
+
+ Object userObject = selectedNode.getUserObject();
+ if (userObject.equals(PROCESSORS)) {
+ JPopupMenu menu = new JPopupMenu();
+ menu.add(new ShadedLabel("Tree", PURPLISH));
+ menu.add(new JMenuItem(new AbstractAction("Expand",
+ plusIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ expandAscendants(tree, selectedNode);
+ }
+ }));
+ menu.add(new JMenuItem(new AbstractAction("Collapse",
+ minusIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ collapseAscendants(tree, selectedNode);
+ }
+ }));
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ } else if (userObject.equals(INPUTS)) {
+ JPopupMenu menu = new JPopupMenu();
+ menu.add(new ShadedLabel("Workflow input ports", GREEN));
+ menu.add(new JMenuItem(new AbstractAction(
+ "Add workflow input port", inputIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ new AddDataflowInputAction(
+ (Workflow) ((DefaultMutableTreeNode) tree
+ .getModel().getRoot())
+ .getUserObject(), wfTree
+ .getParent(), editManager,
+ selectionManager).actionPerformed(evt);
+ }
+ }));
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ } else if (userObject.equals(OUTPUTS)) {
+ JPopupMenu menu = new JPopupMenu();
+ menu.add(new ShadedLabel("Workflow output ports", GREEN));
+ menu.add(new JMenuItem(new AbstractAction(
+ "Add workflow output port", outputIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ new AddDataflowOutputAction(
+ (Workflow) ((DefaultMutableTreeNode) tree
+ .getModel().getRoot())
+ .getUserObject(), wfTree
+ .getParent(), editManager,
+ selectionManager).actionPerformed(evt);
+ }
+ }));
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ }
+ } else {
+ /*
+ * a 'real' workflow component or the 'whole' workflow (i.e.
+ * the tree root) was clicked on
+ */
+
+ /*
+ * We want to disable selection of any nested workflow
+ * components (apart from input and output ports in the
+ * wrapping DataflowActivity)
+ */
+ TreePath path = getPathForObject(
+ selectedNode.getUserObject(),
+ (DefaultMutableTreeNode) tree.getModel().getRoot());
+
+ /*
+ * The getPathForObject() method will return null in a node
+ * is inside a nested workflow and should not be selected
+ */
+ if (path == null)
+ // Just return
+ return;
+
+ /*
+ * Add it to selection model so it is also selected on the
+ * graph as well that listens to the selection model
+ */
+ selectionModel.addSelection(selectedNode.getUserObject());
+
+ // Show a contextual pop-up menu
+ JPopupMenu menu = menuManager.createContextMenu(workflow,
+ selectedNode.getUserObject(), wfTree.getParent());
+ if (menu == null)
+ menu = new JPopupMenu();
+
+ if (selectedNode.getUserObject() instanceof Workflow) {
+ menu.add(new ShadedLabel("Tree", PURPLISH));
+ // Action to expand the whole tree
+ menu.add(new JMenuItem(new AbstractAction("Expand all",
+ plusIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ expandAll(tree);
+ }
+ }));
+ // Action to collapse the whole tree
+ menu.add(new JMenuItem(new AbstractAction(
+ "Collapse all", minusIcon) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ collapseAll(tree);
+ }
+ }));
+ }
+
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ }
+ }
+ });
+
+ return tree;
+ }
+
+ /**
+ * Sets the currently selected node(s) based on the workflow selection
+ * model, i.e. the node(s) currently selected in the workflow graph view
+ * also become selected in the tree view.
+ */
+ private void setSelectedNodes(JTree tree, Workflow wf) {
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(wf.getParent());
+
+ // List of all selected objects in the graph view
+ Set<Object> selection = selectionModel.getSelection();
+ if (selection.isEmpty())
+ return;
+
+ // Selection path(s) - can be multiple if more objects are selected
+ int i = selection.size();
+ TreePath[] paths = new TreePath[i];
+
+ for (Object selected : selection) {
+ TreePath path = WorkflowExplorerTreeModel.getPathForObject(
+ selected, (DefaultMutableTreeNode) tree.getModel()
+ .getRoot());
+ paths[--i] = path;
+ }
+ tree.setSelectionPaths(paths);
+ tree.scrollPathToVisible(paths[0]);
+ }
+
+ /**
+ * Expands all nodes in the tree that have children.
+ */
+ private void expandAll(JTree tree) {
+ int row = 0;
+ while (row < tree.getRowCount()) {
+ tree.expandRow(row);
+ row++;
+ }
+ }
+
+ /**
+ * Collapses all but the root node in the tree that have children.
+ */
+ private void collapseAll(JTree tree) {
+ int row = 1;
+ while (row < tree.getRowCount()) {
+ tree.collapseRow(row);
+ row++;
+ }
+ }
+
+ /**
+ * Expands all ascendants of a node in the tree.
+ */
+ private void expandAscendants(JTree tree, DefaultMutableTreeNode node) {
+ @SuppressWarnings("unchecked")
+ Enumeration<DefaultMutableTreeNode> children = node.children();
+ while (children.hasMoreElements()) {
+ DefaultMutableTreeNode child = children.nextElement();
+ if (child.isLeaf())
+ tree.makeVisible(new TreePath(child.getPath()));
+ else
+ expandAscendants(tree, child);
+ }
+ }
+
+ /**
+ * Collapses all direct ascendants of a node in the tree.
+ */
+ private void collapseAscendants(JTree tree, DefaultMutableTreeNode node) {
+ @SuppressWarnings("unchecked")
+ Enumeration<DefaultMutableTreeNode> children = node.children();
+ while (children.hasMoreElements()) {
+ DefaultMutableTreeNode child = children.nextElement();
+ int row = tree.getRowForPath(new TreePath(child.getPath()));
+ tree.collapseRow(row);
+ }
+ }
+
+ /**
+ * Update workflow explorer when current dataflow changes or closes.
+ */
+ public class FileManagerObserver extends
+ SwingAwareObserver<FileManagerEvent> {
+ @Override
+ public void notifySwing(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) {
+ if (message instanceof ClosedDataflowEvent) {
+ /*
+ * Remove the closed workflow tree from the map of opened
+ * workflow trees
+ */
+ openedWorkflowsTrees.remove(((ClosedDataflowEvent) message)
+ .getDataflow());
+ }
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent) {
+ WorkflowBundleSelectionEvent workflowBundleSelectionEvent = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle oldWorkflowBundle = workflowBundleSelectionEvent
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newWorkflowBundle = workflowBundleSelectionEvent
+ .getSelectedWorkflowBundle();
+ Workflow selectedWorkflow = selectionManager
+ .getSelectedWorkflow();
+
+ /*
+ * Remove the workflow selection model listener from the
+ * previous (if any) and add to the new workflow (if any)
+ */
+ if (oldWorkflowBundle != null)
+ selectionManager.getDataflowSelectionModel(
+ oldWorkflowBundle).removeObserver(
+ workflowSelectionListener);
+ if (newWorkflowBundle != null)
+ selectionManager.getDataflowSelectionModel(
+ newWorkflowBundle).addObserver(
+ workflowSelectionListener);
+
+ // If the workflow tree has already been created switch to it
+ if (openedWorkflowsTrees.containsKey(selectedWorkflow))
+ switchWorkflowTree(selectedWorkflow);
+ else // otherwise create a new tree for the workflow
+ createWorkflowTree(selectedWorkflow);
+ } else if (message instanceof WorkflowSelectionEvent) {
+ WorkflowSelectionEvent workflowSelectionEvent = (WorkflowSelectionEvent) message;
+ Workflow newWorkflow = workflowSelectionEvent.getSelectedWorkflow();
+
+ // If the workflow tree has already been created switch to it
+ if (openedWorkflowsTrees.containsKey(newWorkflow))
+ switchWorkflowTree(newWorkflow);
+ else // otherwise create a new tree for the workflow
+ createWorkflowTree(newWorkflow);
+ }
+ }
+ }
+
+ /**
+ * Update workflow tree on edits to the workflow. Gets called when either
+ * current workflow is edited or when current workflow is a nested workflow
+ * that had been edited and then saved which will trigger update to the
+ * parent workflow which is not the current workflow.
+ */
+ public class EditManagerObserver extends
+ SwingAwareObserver<EditManagerEvent> {
+ @Override
+ public void notifySwing(Observable<EditManagerEvent> sender,
+ final EditManagerEvent message) {
+ if (message instanceof AbstractDataflowEditEvent) {
+ WorkflowBundle workflowBundle = ((AbstractDataflowEditEvent) message)
+ .getDataFlow();
+ // Update the workflow trees to reflect the changes
+ for (Workflow workflow : workflowBundle.getWorkflows())
+ if (openedWorkflowsTrees.containsKey(workflow))
+ updateWorkflowTree(workflow);
+ }
+ }
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow node
+ * is selected in the graph view.
+ */
+ private final class DataflowSelectionListener extends
+ SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ setSelectedNodes(wfTree, workflow);
+ scrollPane.revalidate();
+ scrollPane.repaint();
+ }
+ }
+}
diff --git a/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerFactory.java b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerFactory.java
new file mode 100644
index 0000000..255c83c
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerFactory.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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.ui.workflowexplorer;
+
+import javax.swing.ImageIcon;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+/**
+ * Workflow Explorer factory.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class WorkflowExplorerFactory implements UIComponentFactorySPI {
+ private EditManager editManager;
+ private FileManager fileManager;
+ private MenuManager menuManager;
+ private ReportManager reportManager;
+ private SelectionManager selectionManager;
+ private ActivityIconManager activityIconManager;
+ private ServiceRegistry serviceRegistry;
+
+ @Override
+ public UIComponentSPI getComponent() {
+ return new WorkflowExplorer(editManager, fileManager, menuManager,
+ reportManager, selectionManager, activityIconManager,
+ serviceRegistry);
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Workflow Explorer";
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setReportManager(ReportManager reportManager) {
+ this.reportManager = reportManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setActivityIconManager(ActivityIconManager activityIconManager) {
+ this.activityIconManager = activityIconManager;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeCellRenderer.java b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeCellRenderer.java
new file mode 100644
index 0000000..a212a69
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeCellRenderer.java
@@ -0,0 +1,216 @@
+/*******************************************************************************
+ * 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.ui.workflowexplorer;
+
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.controlLinkIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.datalinkIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.folderClosedIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.folderOpenIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.inputIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.inputPortIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.outputIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.outputPortIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workflowExplorerIcon;
+
+import java.awt.Component;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.t2.lang.ui.icons.Icons;
+//import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+
+import org.apache.commons.beanutils.BeanUtils;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.Port;
+import uk.org.taverna.scufl2.api.port.ProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+
+/**
+ * Cell renderer for Workflow Explorer tree.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class WorkflowExplorerTreeCellRenderer extends DefaultTreeCellRenderer {
+ // FIXME This enum is just a workaround
+ enum Status {
+ OK, WARNING, SEVERE
+ }
+
+ private static final long serialVersionUID = -1326663036193567147L;
+ private static final String RUNS_AFTER = " runs after ";
+
+ private final ActivityIconManager activityIconManager;
+
+ @SuppressWarnings("unused")
+ private Workflow workflow = null;
+ @SuppressWarnings("unused")
+ private final ReportManager reportManager;
+
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public WorkflowExplorerTreeCellRenderer(Workflow workflow, ReportManager reportManager,
+ ActivityIconManager activityIconManager) {
+ super();
+ this.workflow = workflow;
+ this.reportManager = reportManager;
+ this.activityIconManager = activityIconManager;
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
+ boolean expanded, boolean leaf, int row, boolean hasFocus) {
+ Component result = super.getTreeCellRendererComponent(tree, value, sel,
+ expanded, leaf, row, hasFocus);
+
+ Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
+ // TODO rewrite report manager to use scufl2 validation
+ // Status status = reportManager.getStatus(workflow, userObject);
+ Status status = Status.OK;
+ WorkflowExplorerTreeCellRenderer renderer = (WorkflowExplorerTreeCellRenderer) result;
+
+ if (userObject instanceof Workflow) { // the root node
+ if (!hasGrandChildren((DefaultMutableTreeNode) value))
+ renderer.setIcon(workflowExplorerIcon);
+ else
+ renderer.setIcon(chooseIcon(workflowExplorerIcon, status));
+ renderer.setText(((Workflow) userObject).getName());
+ } else if (userObject instanceof InputWorkflowPort) {
+ renderer.setIcon(chooseIcon(inputIcon, status));
+ renderer.setText(((InputWorkflowPort) userObject).getName());
+ } else if (userObject instanceof OutputWorkflowPort) {
+ renderer.setIcon(chooseIcon(outputIcon, status));
+ renderer.setText(((OutputWorkflowPort) userObject).getName());
+ } else if (userObject instanceof Processor) {
+ Processor p = (Processor) userObject;
+ /*
+ * Get the activity associated with the processor - currently only
+ * the first one in the list gets displayed
+ */
+ List<ProcessorBinding> processorbindings = scufl2Tools
+ .processorBindingsForProcessor(p, p.getParent().getParent()
+ .getMainProfile());
+ String text = p.getName();
+ if (!processorbindings.isEmpty()) {
+ Activity activity = processorbindings.get(0).getBoundActivity();
+ Icon basicIcon = activityIconManager.iconForActivity(activity);
+ renderer.setIcon(chooseIcon(basicIcon, status));
+
+ try {
+ String extraDescription = BeanUtils.getProperty(activity,
+ "extraDescription");
+ text += " - " + extraDescription;
+ } catch (IllegalAccessException | InvocationTargetException
+ | NoSuchMethodException e) {
+ // no problem
+ }
+ }
+ renderer.setText(text);
+ }
+ // Processor's child input port
+ else if (userObject instanceof InputProcessorPort) {
+ renderer.setIcon(chooseIcon(inputPortIcon, status));
+ renderer.setText(((InputProcessorPort) userObject).getName());
+ }
+ // Processor's child output port
+ else if (userObject instanceof OutputProcessorPort) {
+ renderer.setIcon(chooseIcon(outputPortIcon, status));
+ renderer.setText(((OutputProcessorPort) userObject).getName());
+ } else if (userObject instanceof DataLink) {
+ renderer.setIcon(chooseIcon(datalinkIcon, status));
+ SenderPort source = ((DataLink) userObject).getReceivesFrom();
+ String sourceName = findName(source);
+ ReceiverPort sink = ((DataLink) userObject).getSendsTo();
+ String sinkName = findName(sink);
+ renderer.setText(sourceName + " -> " + sinkName);
+ } else if (userObject instanceof BlockingControlLink) {
+ renderer.setIcon(chooseIcon(controlLinkIcon, status));
+ String htmlText = "<html><head></head><body>"
+ + ((BlockingControlLink) userObject).getBlock().getName()
+ + " "
+ + RUNS_AFTER
+ + " "
+ + ((BlockingControlLink) userObject).getUntilFinished()
+ .getName() + "</body></html>";
+ renderer.setText(htmlText);
+
+ } else {
+ /*
+ * It one of the main container nodes (inputs, outputs, processors,
+ * datalinks) or a nested workflow node
+ */
+ if (expanded)
+ renderer.setIcon(folderOpenIcon);
+ else
+ renderer.setIcon(folderClosedIcon);
+ }
+
+ return result;
+ }
+
+ private static Icon chooseIcon(final Icon basicIcon, Status status) {
+ if (status == null)
+ return basicIcon;
+ if (status == Status.OK)
+ return basicIcon;
+ else if (status == Status.WARNING)
+ return Icons.warningIcon;
+ else if (status == Status.SEVERE)
+ return Icons.severeIcon;
+ return basicIcon;
+ }
+
+ private static boolean hasGrandChildren(DefaultMutableTreeNode node) {
+ int childCount = node.getChildCount();
+ for (int i = 0; i < childCount; i++)
+ if (node.getChildAt(i).getChildCount() > 0)
+ return true;
+ return false;
+ }
+
+ private String findName(Port port) {
+ if (port instanceof ProcessorPort) {
+ String sourceProcessorName = ((ProcessorPort) port).getParent()
+ .getName();
+ return sourceProcessorName + ":" + port.getName();
+ }
+ return port.getName();
+ }
+}
diff --git a/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeModel.java b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeModel.java
new file mode 100644
index 0000000..05255a4
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeModel.java
@@ -0,0 +1,404 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2009 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.ui.workflowexplorer;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * Workflow Explorer tree model. The tree root has four children nodes,
+ * representing the workflow inputs, outputs, services (processors), dataLinks,
+ * controlLinks.
+ * <p>
+ * A service node can contain a nested workflow if it contains an activity of of
+ * type DataflowActivity. In this case, the service node will have 3 children:
+ * the input and output of the DataflowActivity and the workflow node itself
+ * (containing the nested workflow being wrapped inside the DataflowActivity).
+ * The structure of the workflow node sub-tree (the tree whose root is the
+ * workflow node) is the same as that of the main workflow and it gets named
+ * after the nested workflow. Alternatively, a service (processor) can be simple
+ * and have only the processor's input and output ports as children.
+ * <p>
+ * Input, output, data link and control link nodes are leaves.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ * @author David Withers
+ */
+public class WorkflowExplorerTreeModel extends DefaultTreeModel{
+ private static final long serialVersionUID = -2327461863858923772L;
+ public static final String INPUTS = "Workflow input ports";
+ public static final String OUTPUTS = "Workflow output ports";
+ public static final String PROCESSORS = "Services";
+ public static final String DATALINKS = "Data links";
+ public static final String CONTROLLINKS = "Control links";
+ public static final String MERGES = "Merges";
+
+ @SuppressWarnings("unused")
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ /* Root of the tree. */
+ private DefaultMutableTreeNode rootNode;
+
+ public WorkflowExplorerTreeModel(Workflow df) {
+ super(new DefaultMutableTreeNode(df)); // root node contains the whole workflow
+ rootNode = (DefaultMutableTreeNode) this.getRoot();
+ createTree(df, rootNode);
+ }
+
+ /**
+ * Creates the tree model from a given workflow, for a given tree root.
+ */
+ private void createTree(Workflow df, DefaultMutableTreeNode root) {
+ // Create the four main node groups - inputs, outputs,
+ // services, data links, control links and merges.
+ DefaultMutableTreeNode inputs = new DefaultMutableTreeNode(INPUTS);
+ DefaultMutableTreeNode outputs = new DefaultMutableTreeNode(OUTPUTS);
+ DefaultMutableTreeNode services = new DefaultMutableTreeNode(PROCESSORS);
+ DefaultMutableTreeNode datalinks = new DefaultMutableTreeNode(DATALINKS);
+ DefaultMutableTreeNode controllinks = new DefaultMutableTreeNode(CONTROLLINKS);
+
+ // Attach them to the root of the tree
+ root.add(inputs);
+ root.add(outputs);
+ root.add(services);
+ root.add(datalinks);
+ root.add(controllinks);
+
+ // Populate the workflow's inputs.
+ for (InputWorkflowPort dataflowInput : df.getInputPorts())
+ inputs.add(new DefaultMutableTreeNode(dataflowInput));
+
+ // Populate the workflow's outputs.
+ for (OutputWorkflowPort dataflowOutput : df.getOutputPorts())
+ outputs.add(new DefaultMutableTreeNode(dataflowOutput));
+
+ /*
+ * Populate the workflow's processors (which in turn can contain a
+ * nested workflow).
+ */
+ NamedSet<Processor> processorsList = df.getProcessors();
+ for (Processor processor : processorsList) {
+ DefaultMutableTreeNode processorNode = new DefaultMutableTreeNode(
+ processor);
+ services.add(processorNode);
+
+ // A processor node can have children (input and output ports).
+ for (InputProcessorPort inputPort : processor.getInputPorts())
+ processorNode.add(new DefaultMutableTreeNode(inputPort));
+ for (OutputProcessorPort outputPort : processor.getOutputPorts())
+ processorNode.add(new DefaultMutableTreeNode(outputPort));
+ }
+
+ // Populate the workflow's data links.
+ for (DataLink datalink: df.getDataLinks())
+ datalinks.add(new DefaultMutableTreeNode(datalink));
+
+ // Populate the workflow's control links.
+ for (ControlLink controlLink : df.getControlLinks())
+ controllinks.add(new DefaultMutableTreeNode(controlLink));
+ }
+
+ private static final int INPUT_IDX = 0;
+ private static final int OUTPUT_IDX = 1;
+ private static final int PROCESSOR_IDX = 2;
+ private static final int DATA_IDX = 3;
+ private static final int CONTROL_IDX = 4;
+
+ /**
+ * Returns a path from the root to the node containing the object. For a
+ * nested workflow, only a path for the DataflowActivity and its input and
+ * output ports is returned - for all other nested workflow objects we
+ * return null as we do not want them to be selection in the tree.
+ */
+ public static TreePath getPathForObject(Object userObject,
+ DefaultMutableTreeNode root) {
+ if (userObject instanceof Workflow) { // node contains a Dataflow object
+ if (root.getUserObject().equals(userObject)) // is it the root of the tree?
+ return new TreePath(root.getPath());
+ } else if (userObject instanceof InputWorkflowPort) {
+ // Get the root inputs node
+ DefaultMutableTreeNode inputs = (DefaultMutableTreeNode) root
+ .getChildAt(INPUT_IDX);
+ for (int i = 0; i < inputs.getChildCount(); i++) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) inputs
+ .getChildAt(i);
+ if (node.getUserObject().equals(userObject))
+ return new TreePath(node.getPath());
+ }
+ /*
+ * The node we are looking for must be under some nested workflow
+ * then - but we do not want to let the user select a node under a
+ * nested workflow so return here
+ */
+ return null;
+
+ /*DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root.getChildAt(2);
+ for (int i = 0; i < processors.getChildCount(); i++){
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors.getChildAt(i);
+ // If this is a nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ }
+ }*/
+ } else if (userObject instanceof OutputWorkflowPort) {
+ // Get the root outputs node
+ DefaultMutableTreeNode outputs = (DefaultMutableTreeNode) root
+ .getChildAt(OUTPUT_IDX);
+ for (int i = 0; i< outputs.getChildCount(); i++) { // loop through the outputs
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) outputs
+ .getChildAt(i);
+ if (node.getUserObject().equals(userObject))
+ return new TreePath(node.getPath());
+ }
+ /*
+ * The node we are looking for must be under some nested workflow
+ * then - but we do not want to let the user select a node under a
+ * nested workflow so return here
+ */
+ return null;
+
+ /*DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root.getChildAt(2);
+ for (int i = 0; i < processors.getChildCount(); i++){
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors.getChildAt(i);
+ // If this is a nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ }
+ }*/
+ } else if (userObject instanceof Processor) {
+ // Get the root services (processors) node
+ DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root
+ .getChildAt(PROCESSOR_IDX);
+ for (int i = 0; i < processors.getChildCount(); i++) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) processors
+ .getChildAt(i);
+ if (node.getUserObject().equals(userObject))
+ return new TreePath(node.getPath());
+ }
+ /*
+ * The node we are looking for must be under some nested workflow
+ * then - but we do not want to let the user select a node under a
+ * nested workflow so return here
+ */
+ return null;
+
+ /*for (int i = 0; i < processors.getChildCount(); i++){
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors.getChildAt(i);
+ // If this is a nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ }
+ }*/
+ } else if (userObject instanceof InputProcessorPort) {
+ // This is an input port of a processor
+ // Get the root processors node
+ DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root
+ .getChildAt(PROCESSOR_IDX);
+ for (int i = processors.getChildCount() - 1; i >= 0; i--) {
+ // Looping backwards so that nested workflows are checked last
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors
+ .getChildAt(i);
+
+ /*
+ * We actually do not want to check nested workflows as we do
+ * not want the user to be able to select a component of a
+ * nested workflow
+ */
+
+ /*
+ // If this is nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Check the associated DataflowActivity's input ports first
+ // Do not check the last child as it is the nested workflow node
+ for (int j = 0; j < processor.getChildCount()-1; j++){
+ DefaultMutableTreeNode port_node = (DefaultMutableTreeNode) processor.getChildAt(j);
+ if ((port_node.getUserObject() instanceof ActivityInputPort) &&
+ (((ActivityInputPort) port_node.getUserObject()).equals(userObject)))
+ return new TreePath(port_node.getPath());
+ }
+
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ } else */
+ /*
+ * This is not a nested workflow, so loop thought the
+ * processor's input and output ports, and see if there is a
+ * matching input port
+ */
+ for (int j = 0; j < processor.getChildCount(); j++) {
+ DefaultMutableTreeNode port_node = (DefaultMutableTreeNode) processor
+ .getChildAt(j);
+ if ((port_node.getUserObject() instanceof InputProcessorPort)
+ && (((InputProcessorPort) port_node
+ .getUserObject()).equals(userObject)))
+ return new TreePath(port_node.getPath());
+ }
+ }
+ return null; // The node is inside a nested workflow so just return here
+ } else if (userObject instanceof OutputProcessorPort) {
+ // This is an output port of a processor (i.e. of its associated activity)
+ // Get the root processors node
+ DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root
+ .getChildAt(PROCESSOR_IDX);
+ for (int i = processors.getChildCount() - 1; i >= 0 ; i--){
+ // Looping backwards so that nested workflows are checked last
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors
+ .getChildAt(i);
+
+ /*
+ * We actually do not want to check nested workflows as we do
+ * not want the user to be able to select a component of a
+ * nested workflow
+ */
+
+ /*
+ // If this is nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Check the associated DataflowActivity's output ports first
+ // Do not check the last child as it is the nested workflow node
+ for (int j = 0; j < processor.getChildCount()-1; j++){
+ DefaultMutableTreeNode port_node = (DefaultMutableTreeNode) processor.getChildAt(j);
+ if ((port_node.getUserObject() instanceof ActivityOutputPortImpl) &&
+ (((ActivityOutputPortImpl) port_node.getUserObject()).equals(userObject)))
+ return new TreePath(port_node.getPath());
+ }
+
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ } else */
+ {
+ /*
+ * This is not a nested workflow, so loop thought the
+ * processor's input and output ports, and see if there is a
+ * matching output port
+ */
+ for (int j = 0; j < processor.getChildCount(); j++) {
+ DefaultMutableTreeNode port_node = (DefaultMutableTreeNode) processor
+ .getChildAt(j);
+ if ((port_node.getUserObject() instanceof OutputProcessorPort)
+ && (((OutputProcessorPort) port_node
+ .getUserObject()).equals(userObject)))
+ return new TreePath(port_node.getPath());
+ }
+ }
+ }
+ return null; // The node is inside a nested workflow so just return here
+ } else if (userObject instanceof DataLink) {
+ // Get the root data links node
+ DefaultMutableTreeNode datalinks = (DefaultMutableTreeNode) root
+ .getChildAt(DATA_IDX);
+ for (int i = 0; i < datalinks.getChildCount(); i++) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) datalinks
+ .getChildAt(i);
+ if (node.getUserObject().equals(userObject))
+ return new TreePath(node.getPath());
+ }
+ /*
+ * The node we are looking for must be under some nested workflow
+ * then - but we do not want to let the user select a node under a
+ * nested workflow so return here
+ */
+ return null;
+
+ /*DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root.getChildAt(2);
+ for (int i = 0; i < processors.getChildCount(); i++){
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors.getChildAt(i);
+ // If this is a nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ }
+ }*/
+ } else if (userObject instanceof ControlLink) {
+ // Get the root control links node
+ DefaultMutableTreeNode controllinks = (DefaultMutableTreeNode) root
+ .getChildAt(CONTROL_IDX);
+ for (int i = 0; i < controllinks.getChildCount(); i++) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) controllinks
+ .getChildAt(i);
+ if (node.getUserObject().equals(userObject))
+ return new TreePath(node.getPath());
+ }
+ /*
+ * The node we are looking for must be under some nested workflow
+ * then - but we do not want to let the user select a node under a
+ * nested workflow so return here
+ */
+ return null;
+
+ /*DefaultMutableTreeNode processors = (DefaultMutableTreeNode) root.getChildAt(2);
+ for (int i = 0; i < processors.getChildCount(); i++){
+ DefaultMutableTreeNode processor = (DefaultMutableTreeNode) processors.getChildAt(i);
+ // If this is a nested workflow - descend into it
+ if (Tools.containsNestedWorkflow((Processor) processor.getUserObject())){
+ // Get the nested workflow node - it is always the last child of the
+ // wrapping processor's node
+ DefaultMutableTreeNode nestedWorkflowNode = (DefaultMutableTreeNode) processor.getLastChild();
+ TreePath tp = getPathForObject(userObject, nestedWorkflowNode);
+ if (tp != null)
+ return tp;
+ }
+ }*/
+ }
+
+ return null;
+ }
+}
diff --git a/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeSelectionModel.java b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeSelectionModel.java
new file mode 100644
index 0000000..f9c9455
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/java/net/sf/taverna/t2/workbench/ui/workflowexplorer/WorkflowExplorerTreeSelectionModel.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.ui.workflowexplorer;
+
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreePath;
+
+public class WorkflowExplorerTreeSelectionModel extends
+ DefaultTreeSelectionModel {
+ private static final long serialVersionUID = 3127644524735089630L;
+
+ public WorkflowExplorerTreeSelectionModel() {
+ super();
+ }
+
+ @Override
+ public void setSelectionPath(TreePath path) {
+ /*
+ * Nothing happens here - only calls to mySetSelectionPath() will have
+ * the effect of a node being selected
+ */
+ }
+
+ public void mySetSelectionPath(TreePath path) {
+ super.setSelectionPath(path);
+ }
+}
diff --git a/taverna-workbench-workflow-explorer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..919b1c6
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorerFactory
\ No newline at end of file
diff --git a/taverna-workbench-workflow-explorer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
new file mode 100644
index 0000000..6ec4a78
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorer
\ No newline at end of file
diff --git a/taverna-workbench-workflow-explorer/src/main/resources/META-INF/spring/workflow-explorer-context-osgi.xml b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/spring/workflow-explorer-context-osgi.xml
new file mode 100644
index 0000000..28c78b1
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/spring/workflow-explorer-context-osgi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="WorkflowExplorerFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="reportManager" interface="net.sf.taverna.t2.workbench.report.ReportManager" cardinality="0..1"/>
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="activityIconManager" interface="net.sf.taverna.t2.workbench.activityicons.ActivityIconManager" />
+ <reference id="serviceRegistry" interface="uk.org.taverna.commons.services.ServiceRegistry" />
+
+</beans:beans>
diff --git a/taverna-workbench-workflow-explorer/src/main/resources/META-INF/spring/workflow-explorer-context.xml b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/spring/workflow-explorer-context.xml
new file mode 100644
index 0000000..0fc6454
--- /dev/null
+++ b/taverna-workbench-workflow-explorer/src/main/resources/META-INF/spring/workflow-explorer-context.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="WorkflowExplorerFactory"
+ class="net.sf.taverna.t2.workbench.ui.workflowexplorer.WorkflowExplorerFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="fileManager" ref="fileManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="reportManager" ref="reportManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="activityIconManager" ref="activityIconManager" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-workflow-view/pom.xml b/taverna-workbench-workflow-view/pom.xml
new file mode 100644
index 0000000..b2bd0ef
--- /dev/null
+++ b/taverna-workbench-workflow-view/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-components</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>workflow-view</artifactId>
+ <packaging>bundle</packaging>
+ <name>Workflow View</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>io</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-tools</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-components</groupId>
+ <artifactId>design-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-services-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CopyGraphComponentAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CopyGraphComponentAction.java
new file mode 100644
index 0000000..ae2defa
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CopyGraphComponentAction.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * 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.ui.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_C;
+import static java.awt.event.KeyEvent.VK_Y;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.copyIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.copyProcessor;
+
+import java.awt.event.ActionEvent;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+/**
+ * An action that copies the selected graph component.
+ *
+ * @author Alan R Williams
+ *
+ */
+@SuppressWarnings("serial")
+public class CopyGraphComponentAction extends AbstractAction implements
+ DesignOnlyAction {
+ /** Current workflow's selection model event observer. */
+ private Observer<DataflowSelectionMessage> workflowSelectionObserver = new DataflowSelectionObserver();
+ private final SelectionManager selectionManager;
+
+ public CopyGraphComponentAction(SelectionManager selectionManager) {
+ super("Copy", copyIcon);
+ this.selectionManager = selectionManager;
+ putValue(SHORT_DESCRIPTION, "Copy selected component");
+ putValue(MNEMONIC_KEY, VK_Y);
+
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_C, getDefaultToolkit().getMenuShortcutKeyMask()));
+ setEnabled(false);
+
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus() {
+ WorkflowBundle workflowBundle = selectionManager
+ .getSelectedWorkflowBundle();
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+
+ // List of all selected objects in the graph view
+ Set<Object> selection = selectionModel.getSelection();
+
+ if (selection.isEmpty())
+ setEnabled(false);
+ else {
+ /*
+ * Take the first selected item - we only support single selections
+ * anyway
+ */
+ Object selected = selection.toArray()[0];
+ setEnabled (selected instanceof Processor);
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ copyProcessor(selectionManager);
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow node
+ * is selected in the graph view, and enables/disables this action
+ * accordingly.
+ */
+ private final class DataflowSelectionObserver extends
+ SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ updateStatus();
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent) {
+ WorkflowBundleSelectionEvent event = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle oldFlow = event
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newFlow = event.getSelectedWorkflowBundle();
+ // Update the buttons status as current dataflow has changed
+ updateStatus();
+
+ /*
+ * Remove the workflow selection model listener from the
+ * previous (if any) and add to the new workflow (if any)
+ */
+ if (oldFlow != null)
+ selectionManager.getDataflowSelectionModel(oldFlow)
+ .removeObserver(workflowSelectionObserver);
+ if (newFlow != null)
+ selectionManager.getDataflowSelectionModel(newFlow)
+ .addObserver(workflowSelectionObserver);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CopyProcessorAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CopyProcessorAction.java
new file mode 100644
index 0000000..b262b76
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CopyProcessorAction.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * 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.ui.actions;
+
+import static java.awt.event.KeyEvent.VK_Y;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.copyIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.copyProcessor;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+
+/**
+ * Action for copying a processor.
+ *
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class CopyProcessorAction extends AbstractAction {
+ private Processor processor;
+
+ public CopyProcessorAction(Processor processor) {
+ this.processor = processor;
+ putValue(SMALL_ICON, copyIcon);
+ putValue(NAME, "Copy");
+ putValue(MNEMONIC_KEY, VK_Y);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ copyProcessor(processor);
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CutGraphComponentAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CutGraphComponentAction.java
new file mode 100644
index 0000000..c798c5e
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CutGraphComponentAction.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * 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.ui.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_T;
+import static java.awt.event.KeyEvent.VK_X;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.cutIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.cutProcessor;
+
+import java.awt.event.ActionEvent;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+/**
+ * An action that copies the selected graph component.
+ *
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class CutGraphComponentAction extends AbstractAction implements DesignOnlyAction {
+ /** Current workflow's selection model event observer. */
+ private Observer<DataflowSelectionMessage> workflowSelectionObserver = new DataflowSelectionObserver();
+
+ private Processor processor;
+
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public CutGraphComponentAction(EditManager editManager, SelectionManager selectionManager) {
+ super("Cut", cutIcon);
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ putValue(SHORT_DESCRIPTION, "Cut selected component");
+ putValue(MNEMONIC_KEY, VK_T);
+
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_X, getDefaultToolkit().getMenuShortcutKeyMask()));
+ setEnabled(false);
+
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus() {
+ WorkflowBundle workflowBundle = selectionManager
+ .getSelectedWorkflowBundle();
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+
+ // List of all selected objects in the graph view
+ Set<Object> selection = selectionModel.getSelection();
+
+ if (selection.isEmpty())
+ setEnabled(false);
+ else {
+ /*
+ * Take the first selected item - we only support single selections
+ * anyway
+ */
+ Object selected = selection.toArray()[0];
+ if (selected instanceof Processor) {
+ processor = (Processor) selected;
+ setEnabled(true);
+ } else
+ setEnabled(false);
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ cutProcessor(processor.getParent(), processor, null, editManager,
+ selectionManager);
+ }
+
+ /**
+ * Observes events on workflow Selection Manager, i.e. when a workflow node
+ * is selected in the graph view, and enables/disables this action
+ * accordingly.
+ */
+ private final class DataflowSelectionObserver extends
+ SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ updateStatus();
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent) {
+ WorkflowBundleSelectionEvent workflowBundleSelectionEvent = (WorkflowBundleSelectionEvent) message;
+ WorkflowBundle oldFlow = workflowBundleSelectionEvent
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newFlow = workflowBundleSelectionEvent
+ .getSelectedWorkflowBundle();
+ // Update the buttons status as current dataflow has changed
+ updateStatus();
+
+ /*
+ * Remove the workflow selection model listener from the
+ * previous (if any) and add to the new workflow (if any)
+ */
+ if (oldFlow != null)
+ selectionManager.getDataflowSelectionModel(oldFlow)
+ .removeObserver(workflowSelectionObserver);
+ if (newFlow != null)
+ selectionManager.getDataflowSelectionModel(newFlow)
+ .addObserver(workflowSelectionObserver);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CutProcessorAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CutProcessorAction.java
new file mode 100644
index 0000000..96d0aa5
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/CutProcessorAction.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.ui.actions;
+
+import static java.awt.event.KeyEvent.VK_C;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.cutIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.cutProcessor;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * Action for copying a processor.
+ *
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class CutProcessorAction extends AbstractAction {
+ private Processor processor;
+ private Workflow dataflow;
+ private Component component;
+
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public CutProcessorAction(Workflow dataflow, Processor processor,
+ Component component, EditManager editManager,
+ SelectionManager selectionManager) {
+ this.dataflow = dataflow;
+ this.processor = processor;
+ this.component = component;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ putValue(SMALL_ICON, cutIcon);
+ putValue(NAME, "Cut");
+ putValue(MNEMONIC_KEY, VK_C);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ cutProcessor(dataflow, processor, component, editManager,
+ selectionManager);
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/PasteGraphComponentAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/PasteGraphComponentAction.java
new file mode 100644
index 0000000..0c88c79
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/actions/PasteGraphComponentAction.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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.ui.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_P;
+import static java.awt.event.KeyEvent.VK_V;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.pasteIcon;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.pasteTransferable;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+/**
+ * An action that pastes a graph component
+ *
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+//TODO this class appears to be non-OSGi-fied
+public class PasteGraphComponentAction extends AbstractAction {
+ private static PasteGraphComponentAction instance = null;
+
+ private static boolean enabled = false;
+
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ private PasteGraphComponentAction(EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager,
+ ServiceRegistry serviceRegistry) {
+ super();
+ this.editManager = editManager;
+ this.menuManager = menuManager;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+ putValue(SMALL_ICON, pasteIcon);
+ putValue(NAME, "Paste");
+ putValue(SHORT_DESCRIPTION, "Paste");
+ putValue(MNEMONIC_KEY, VK_P);
+
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_V, getDefaultToolkit().getMenuShortcutKeyMask()));
+ setEnabled(enabled);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ pasteTransferable(editManager, menuManager, selectionManager,
+ serviceRegistry);
+ }
+
+ public static Action getInstance(EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager,
+ ServiceRegistry serviceRegistry) {
+ if (instance == null)
+ instance = new PasteGraphComponentAction(editManager, menuManager,
+ selectionManager, serviceRegistry);
+ return instance;
+ }
+
+ public static void setEnabledStatic(boolean enabled) {
+ if (instance == null) {
+ PasteGraphComponentAction.enabled = enabled;
+ } else {
+ instance.setEnabled(enabled);
+ }
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/dndhandler/ServiceTransferHandler.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/dndhandler/ServiceTransferHandler.java
new file mode 100644
index 0000000..ef5a1cd
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/dndhandler/ServiceTransferHandler.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * 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.ui.dndhandler;
+
+import static java.awt.datatransfer.DataFlavor.javaJVMLocalObjectMimeType;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.copyProcessor;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.cutProcessor;
+import static net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView.pasteTransferable;
+
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+
+import javax.swing.JComponent;
+import javax.swing.TransferHandler;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+/**
+ * TransferHandler for accepting ActivityAndBeanWrapper object dropped on the
+ * GraphView. On a successful drop a Processor is created from the Activity and
+ * configured with the ConfigurationBean.
+ *
+ * @author David Withers
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class ServiceTransferHandler extends TransferHandler {
+ private static Logger logger = Logger.getLogger(ServiceTransferHandler.class);
+
+ private DataFlavor serviceDescriptionDataFlavor;
+
+ private final EditManager editManager;
+ private final MenuManager menuManager;
+ private final SelectionManager selectionManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public ServiceTransferHandler(EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager,
+ ServiceRegistry serviceRegistry) {
+ this.editManager = editManager;
+ this.menuManager = menuManager;
+ this.selectionManager = selectionManager;
+ this.serviceRegistry = serviceRegistry;
+
+ try {
+ serviceDescriptionDataFlavor = new DataFlavor(
+ javaJVMLocalObjectMimeType + ";class="
+ + ServiceDescription.class.getCanonicalName(),
+ "ServiceDescription", getClass().getClassLoader());
+ } catch (ClassNotFoundException e) {
+ logger.warn("Could not find the class "
+ + ServiceDescription.class);
+ }
+ }
+
+ @Override
+ public boolean canImport(JComponent component, DataFlavor[] dataFlavors) {
+ logger.debug("Trying to import something");
+ for (DataFlavor dataFlavor : dataFlavors)
+ if (dataFlavor.equals(serviceDescriptionDataFlavor))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean importData(JComponent component, Transferable transferable) {
+ logger.info("Importing a transferable");
+ logger.debug(component.getClass().getCanonicalName());
+ pasteTransferable(transferable, editManager, menuManager,
+ selectionManager, serviceRegistry);
+ return true;
+ }
+
+ @Override
+ protected Transferable createTransferable(JComponent c) {
+ return null;
+ }
+
+ @Override
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ @Override
+ public void exportToClipboard(JComponent comp, Clipboard clip, int action)
+ throws IllegalStateException {
+ super.exportToClipboard(comp, clip, action);
+ if (action == COPY)
+ copyProcessor(selectionManager);
+ else if (action == MOVE)
+ cutProcessor(editManager, selectionManager);
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CopyGraphComponentMenuAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CopyGraphComponentMenuAction.java
new file mode 100644
index 0000000..9d32058
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CopyGraphComponentMenuAction.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.CopyGraphComponentAction;
+
+/**
+ * @author Alan R Williams
+ */
+public class CopyGraphComponentMenuAction extends AbstractMenuAction {
+ private static final URI COPY_GRAPH_COMPONENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuCopyGraphComponent");
+
+ private SelectionManager selectionManager;
+
+ public CopyGraphComponentMenuAction() {
+ super(
+ URI.create("http://taverna.sf.net/2008/t2workbench/menu#graphCopyMenuSection"),
+ 12, COPY_GRAPH_COMPONENT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CopyGraphComponentAction(selectionManager);
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CopyProcessorMenuAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CopyProcessorMenuAction.java
new file mode 100644
index 0000000..2af61b0
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CopyProcessorMenuAction.java
@@ -0,0 +1,56 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu;
+
+import java.awt.Component;
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.ui.actions.CopyProcessorAction;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class CopyProcessorMenuAction extends AbstractContextualMenuAction {
+ public static final URI editSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/edit");
+
+ public CopyProcessorMenuAction() {
+ super(editSection, 20);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Processor;
+ }
+
+ @Override
+ @SuppressWarnings("unused")
+ protected Action createAction() {
+ Workflow dataflow = (Workflow) getContextualSelection().getParent();
+ Processor processor = (Processor) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new CopyProcessorAction(processor);
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CutGraphComponentMenuAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CutGraphComponentMenuAction.java
new file mode 100644
index 0000000..3e088fe
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CutGraphComponentMenuAction.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.CutGraphComponentAction;
+
+/**
+ * @author Alan R Williams
+ */
+public class CutGraphComponentMenuAction extends AbstractMenuAction {
+ private static final URI CUT_GRAPH_COMPONENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuCutGraphComponent");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public CutGraphComponentMenuAction() {
+ super(
+ URI.create("http://taverna.sf.net/2008/t2workbench/menu#graphCopyMenuSection"),
+ 11, CUT_GRAPH_COMPONENT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CutGraphComponentAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CutProcessorMenuAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CutProcessorMenuAction.java
new file mode 100644
index 0000000..5c68fd6
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/CutProcessorMenuAction.java
@@ -0,0 +1,69 @@
+/**********************************************************************
+ * Copyright (C) 2007-2009 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.ui.menu;
+
+import java.awt.Component;
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.CutProcessorAction;
+
+public class CutProcessorMenuAction extends AbstractContextualMenuAction {
+ public static final URI editSection = URI
+ .create("http://taverna.sf.net/2009/contextMenu/edit");
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public CutProcessorMenuAction() {
+ super(editSection, 10);
+ }
+
+ @Override
+ protected Action createAction() {
+ Workflow dataflow = (Workflow) getContextualSelection().getParent();
+ Processor processor = (Processor) getContextualSelection()
+ .getSelection();
+ Component component = getContextualSelection().getRelativeToComponent();
+ return new CutProcessorAction(dataflow, processor, component,
+ editManager, selectionManager);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled()
+ && getContextualSelection().getSelection() instanceof Processor;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/PasteGraphComponentMenuAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/PasteGraphComponentMenuAction.java
new file mode 100644
index 0000000..25fa3a9
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/menu/PasteGraphComponentMenuAction.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * 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.ui.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.PasteGraphComponentAction;
+
+/**
+ * @author Alan R Williams
+ */
+public class PasteGraphComponentMenuAction extends AbstractMenuAction {
+ private static final URI PASTE_GRAPH_COMPONENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#graphMenuPasteGraphComponent");
+ private EditManager editManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ServiceRegistry serviceRegistry;
+
+ public PasteGraphComponentMenuAction() {
+ super(
+ URI.create("http://taverna.sf.net/2008/t2workbench/menu#graphCopyMenuSection"),
+ 13, PASTE_GRAPH_COMPONENT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return PasteGraphComponentAction.getInstance(editManager, menuManager,
+ selectionManager, serviceRegistry);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/CopyToolbarAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/CopyToolbarAction.java
new file mode 100644
index 0000000..2ae53bc
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/CopyToolbarAction.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.ui.toolbar;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.CopyGraphComponentAction;
+
+public class CopyToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+ private static final URI EDIT_TOOLBAR_COPY_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarCopy");
+
+ private SelectionManager selectionManager;
+
+ public CopyToolbarAction() {
+ super(EDIT_TOOLBAR_SECTION, 40, EDIT_TOOLBAR_COPY_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CopyGraphComponentAction(selectionManager);
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/CutToolbarAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/CutToolbarAction.java
new file mode 100644
index 0000000..e792fc1
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/CutToolbarAction.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.ui.toolbar;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.CutGraphComponentAction;
+
+public class CutToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+ private static final URI EDIT_TOOLBAR_CUT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarCut");
+
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public CutToolbarAction() {
+ super(EDIT_TOOLBAR_SECTION, 30, EDIT_TOOLBAR_CUT_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CutGraphComponentAction(editManager, selectionManager);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/PasteToolbarAction.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/PasteToolbarAction.java
new file mode 100644
index 0000000..c28c522
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/toolbar/PasteToolbarAction.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.ui.toolbar;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.PasteGraphComponentAction;
+
+public class PasteToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+ private static final URI EDIT_TOOLBAR_PASTE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarPaste");
+ private EditManager editManager;
+ private MenuManager menuManager;
+ private SelectionManager selectionManager;
+ private ServiceRegistry serviceRegistry;
+
+ public PasteToolbarAction() {
+ super(EDIT_TOOLBAR_SECTION, 50, EDIT_TOOLBAR_PASTE_URI);
+ }
+
+ @Override
+ protected Action createAction() {
+ return PasteGraphComponentAction.getInstance(editManager, menuManager,
+ selectionManager, serviceRegistry);
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ this.menuManager = menuManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/workflowview/ShowExceptionRunnable.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/workflowview/ShowExceptionRunnable.java
new file mode 100644
index 0000000..05403bc
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/workflowview/ShowExceptionRunnable.java
@@ -0,0 +1,26 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.workflowview;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+/**
+ * @author alanrw
+ */
+public class ShowExceptionRunnable implements Runnable {
+ Exception e;
+ private final String message;
+
+ public ShowExceptionRunnable(String message, Exception e) {
+ this.message = message;
+ this.e = e;
+ }
+
+ @Override
+ public void run() {
+ showMessageDialog(null, message + ": " + e.getMessage(),
+ "Service addition problem", ERROR_MESSAGE);
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/workflowview/WorkflowView.java b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/workflowview/WorkflowView.java
new file mode 100644
index 0000000..0be5fd0
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/java/net/sf/taverna/t2/workbench/ui/workflowview/WorkflowView.java
@@ -0,0 +1,414 @@
+package net.sf.taverna.t2.workbench.ui.workflowview;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.datatransfer.DataFlavor.javaJVMLocalObjectMimeType;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.Component;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.design.actions.RemoveProcessorAction;
+import net.sf.taverna.t2.workbench.design.actions.RenameProcessorAction;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.apache.log4j.Logger;
+//import org.jdom.Element;
+
+
+
+import uk.org.taverna.commons.services.ActivityTypeNotFoundException;
+import uk.org.taverna.commons.services.InvalidConfigurationException;
+import uk.org.taverna.commons.services.ServiceRegistry;
+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.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.iterationstrategy.CrossProduct;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.profiles.ProcessorBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Super class for all UIComponentSPIs that display a Workflow
+ *
+ * @author alanrw
+ */
+public class WorkflowView {
+ //public static Element copyRepresentation = null;
+
+ private static Logger logger = Logger.getLogger(WorkflowView.class);
+
+ private static DataFlavor processorFlavor = null;
+ private static DataFlavor serviceDescriptionDataFlavor = null;
+ //private static HashMap<String, Element> requiredSubworkflows = new HashMap<>();
+
+ private static String UNABLE_TO_ADD_SERVICE = "Unable to add service";
+ @SuppressWarnings("unused")
+ private static String UNABLE_TO_COPY_SERVICE = "Unable to copy service";
+
+ @SuppressWarnings("unused")
+ private static Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ static {
+ try {
+ if (serviceDescriptionDataFlavor == null) {
+ serviceDescriptionDataFlavor = new DataFlavor(
+ javaJVMLocalObjectMimeType + ";class="
+ + ServiceDescription.class.getCanonicalName(),
+ "ServiceDescription",
+ ServiceDescription.class.getClassLoader());
+ if (processorFlavor == null)
+ processorFlavor = new DataFlavor(javaJVMLocalObjectMimeType
+ + ";class=" + Processor.class.getCanonicalName(),
+ "Processor", Processor.class.getClassLoader());
+ }
+ } catch (ClassNotFoundException e) {
+ logger.error(e);
+ }
+ }
+
+ public final static Processor importServiceDescription(
+ ServiceDescription sd, boolean rename, EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager,
+ ServiceRegistry serviceRegistry) {
+ Workflow workflow = selectionManager.getSelectedWorkflow();
+ Profile profile = selectionManager.getSelectedProfile();
+
+ Processor processor = new Processor();
+ processor.setName(sd.getName());
+
+ CrossProduct crossProduct = new CrossProduct();
+ crossProduct.setParent(processor.getIterationStrategyStack());
+
+ URI activityType = sd.getActivityType();
+
+ Activity activity = new Activity();
+ activity.setType(activityType);
+ Configuration configuration = sd.getActivityConfiguration();
+ configuration.setConfigures(activity);
+
+ ProcessorBinding processorBinding = new ProcessorBinding();
+ processorBinding.setBoundProcessor(processor);
+ processorBinding.setBoundActivity(activity);
+
+ try {
+ for (InputActivityPort activityPort : serviceRegistry
+ .getActivityInputPorts(activityType,
+ configuration.getJson())) {
+ // add port to activity
+ activityPort.setParent(activity);
+ // create processor port
+ InputProcessorPort processorPort = new InputProcessorPort(
+ processor, activityPort.getName());
+ processorPort.setDepth(activityPort.getDepth());
+ // add a new port binding
+ new ProcessorInputPortBinding(processorBinding, processorPort,
+ activityPort);
+ }
+ for (OutputActivityPort activityPort : serviceRegistry
+ .getActivityOutputPorts(activityType,
+ configuration.getJson())) {
+ // add port to activity
+ activityPort.setParent(activity);
+ // create processor port
+ OutputProcessorPort processorPort = new OutputProcessorPort(
+ processor, activityPort.getName());
+ processorPort.setDepth(activityPort.getDepth());
+ processorPort.setGranularDepth(activityPort.getGranularDepth());
+ // add a new port binding
+ new ProcessorOutputPortBinding(processorBinding, activityPort,
+ processorPort);
+ }
+ } catch (InvalidConfigurationException | ActivityTypeNotFoundException e) {
+ logger.warn("Unable to get activity ports for configuration", e);
+ }
+
+ List<Edit<?>> editList = new ArrayList<>();
+ editList.add(new AddChildEdit<>(profile, activity));
+ editList.add(new AddChildEdit<>(profile, configuration));
+ editList.add(new AddChildEdit<>(profile, processorBinding));
+ editList.add(new AddProcessorEdit(workflow, processor));
+ Edit<?> insertionEdit = sd.getInsertionEdit(workflow, processor, activity);
+ if (insertionEdit != null)
+ editList.add(insertionEdit);
+ try {
+ editManager.doDataflowEdit(workflow.getParent(), new CompoundEdit(
+ editList));
+ } catch (EditException e) {
+ showException(UNABLE_TO_ADD_SERVICE, e);
+ logger.warn("Could not add processor : edit error", e);
+ processor = null;
+ }
+
+ if (processor != null && rename) {
+ RenameProcessorAction rpa = new RenameProcessorAction(workflow,
+ processor, null, editManager, selectionManager);
+ rpa.actionPerformed(new ActionEvent(sd, 0, ""));
+ }
+
+ if (processor != null && sd.isTemplateService()) {
+ Action action = getConfigureAction(processor, menuManager);
+ if (action != null)
+ action.actionPerformed(new ActionEvent(sd, 0, ""));
+ }
+ return processor;
+ }
+
+ public static Action getConfigureAction(Processor p, MenuManager menuManager) {
+ Action result = null;
+ JPopupMenu dummyMenu = menuManager.createContextMenu(null, p, null);
+ for (Component c : dummyMenu.getComponents()) {
+ logger.debug(c.getClass().getCanonicalName());
+ if (c instanceof JMenuItem) {
+ JMenuItem menuItem = (JMenuItem) c;
+ Action action = menuItem.getAction();
+ if (action != null
+ && action instanceof ActivityConfigurationAction
+ && action.isEnabled()) {
+ if (result != null) {
+ // do not return anything if there are two matches
+ // logger.info("Multiple actions " +
+ // action.getClass().getCanonicalName() + " " +
+ // result.getClass().getCanonicalName());
+ return null;
+ }
+ result = action;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static void pasteTransferable(Transferable t,
+ EditManager editManager, MenuManager menuManager,
+ SelectionManager selectionManager, ServiceRegistry serviceRegistry) {
+ if (t.isDataFlavorSupported(processorFlavor))
+ pasteProcessor(t, editManager);
+ else if (t.isDataFlavorSupported(serviceDescriptionDataFlavor))
+ try {
+ ServiceDescription data = (ServiceDescription) t
+ .getTransferData(serviceDescriptionDataFlavor);
+ importServiceDescription(data, false, editManager, menuManager,
+ selectionManager, serviceRegistry);
+ } catch (UnsupportedFlavorException | IOException e) {
+ showException(UNABLE_TO_ADD_SERVICE, e);
+ logger.error(e);
+ }
+ }
+
+ public static void pasteTransferable(EditManager editManager,
+ MenuManager menuManager, SelectionManager selectionManager,
+ ServiceRegistry serviceRegistry) {
+ pasteTransferable(
+ getDefaultToolkit().getSystemClipboard().getContents(null),
+ editManager, menuManager, selectionManager, serviceRegistry);
+ }
+
+ public static void pasteProcessor(Transferable t, EditManager editManager) {
+ //FIXME
+// try {
+// Element e = (Element) t.getTransferData(processorFlavor);
+// WorkflowBundle currentDataflow = (WorkflowBundle) ModelMap.getInstance().getModel(
+// ModelMapConstants.CURRENT_DATAFLOW);
+// Processor p = ProcessorXMLDeserializer.getInstance().deserializeProcessor(e,
+// requiredSubworkflows);
+// if (p == null) {
+// return;
+// }
+// String newName = Tools.uniqueProcessorName(p.getName(), currentDataflow);
+// List<Edit<?>> editList = new ArrayList<Edit<?>>();
+//
+// Edits edits = editManager.getEdits();
+// if (!newName.equals(p.getName())) {
+// Edit renameEdit = edits.getRenameProcessorEdit(p, newName);
+// editList.add(renameEdit);
+// }
+//
+// Activity activity = null;
+// if (p.getActivityList().size() > 0) {
+// activity = p.getActivityList().get(0);
+// }
+//
+// List<InputProcessorPort> processorInputPorts = new ArrayList<InputProcessorPort>();
+// processorInputPorts.addAll(p.getInputPorts());
+// for (InputProcessorPort port : processorInputPorts) {
+// Edit removePortEdit = edits.getRemoveProcessorInputPortEdit(p, port);
+// editList.add(removePortEdit);
+// if (activity != null) {
+// Edit removePortMapEdit = edits.getRemoveActivityInputPortMappingEdit(activity,
+// port);
+// editList.add(removePortMapEdit);
+// }
+// }
+// List<ProcessorOutputPort> processorOutputPorts = new ArrayList<ProcessorOutputPort>();
+// processorOutputPorts.addAll(p.getOutputPorts());
+// for (ProcessorOutputPort port : processorOutputPorts) {
+// Edit removePortEdit = edits.getRemoveProcessorOutputPortEdit(p, port);
+// editList.add(removePortEdit);
+// if (activity != null) {
+// Edit removePortMapEdit = edits.getRemoveActivityOutputPortMappingEdit(activity,
+// port.getName());
+// editList.add(removePortMapEdit);
+// }
+// }
+// Edit edit = edits.getAddProcessorEdit(currentDataflow, p);
+// editList.add(edit);
+// editManager.doDataflowEdit(currentDataflow, new CompoundEdit(editList));
+// } catch (ActivityConfigurationException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (EditException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (ClassNotFoundException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (InstantiationException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (IllegalAccessException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (DeserializationException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (UnsupportedFlavorException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// } catch (IOException e) {
+// showException(UNABLE_TO_ADD_SERVICE, e);
+// logger.error(e);
+// }
+ }
+
+ public static void copyProcessor(SelectionManager selectionManager) {
+ WorkflowBundle currentDataflow = selectionManager.getSelectedWorkflowBundle();
+ DataflowSelectionModel dataFlowSelectionModel = selectionManager
+ .getDataflowSelectionModel(currentDataflow);
+ // Get all selected components
+ Set<Object> selectedWFComponents = dataFlowSelectionModel.getSelection();
+ Processor p = null;
+ for (Object selectedWFComponent : selectedWFComponents)
+ if (selectedWFComponent instanceof Processor) {
+ p = (Processor) selectedWFComponent;
+ break;
+ }
+ if (p != null)
+ copyProcessor(p);
+ }
+
+ public static void copyProcessor(Processor p) {
+// try {
+// final Element e = ProcessorXMLSerializer.getInstance().processorToXML(p);
+// requiredSubworkflows = new HashMap<String, Element>();
+// rememberSubworkflows(p, profile);
+// Transferable t = new Transferable() {
+//
+// public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException,
+// IOException {
+// return e;
+// }
+//
+// public DataFlavor[] getTransferDataFlavors() {
+// DataFlavor[] result = new DataFlavor[1];
+// result[0] = processorFlavor;
+// return result;
+// }
+//
+// public boolean isDataFlavorSupported(DataFlavor flavor) {
+// return flavor.equals(processorFlavor);
+// }
+//
+// };
+// Toolkit.getDefaultToolkit().getSystemClipboard().setContents(t, null);
+// PasteGraphComponentAction.setEnabledStatic(true);
+// } catch (IOException e1) {
+// showException(UNABLE_TO_COPY_SERVICE, e1);
+// logger.error(e1);
+// } catch (JDOMException e1) {
+// logger.error(UNABLE_TO_COPY_SERVICE, e1);
+// } catch (SerializationException e) {
+// logger.error(UNABLE_TO_COPY_SERVICE, e);
+// }
+ }
+
+// private static void rememberSubworkflows(Processor p, Profile profile) throws SerializationException {
+//
+// for (ProcessorBinding processorBinding : scufl2Tools.processorBindingsForProcessor(p, profile)) {
+// Activity a = processorBinding.getBoundActivity();
+// if (a.getConfigurableType().equals(URI.create("http://ns.taverna.org.uk/2010/activity/nested-workflow"))) {
+// NestedDataflow da = (NestedDataflow) a;
+// Dataflow df = da.getNestedDataflow();
+// if (!requiredSubworkflows.containsKey(df.getIdentifier())) {
+// requiredSubworkflows.put(df.getIdentifier(), DataflowXMLSerializer
+// .getInstance().serializeDataflow(df));
+// for (Processor sp : df.getProcessors()) {
+// rememberSubworkflows(sp);
+// }
+// }
+// }
+// }
+// }
+
+ public static void cutProcessor(EditManager editManager, SelectionManager selectionManager) {
+// WorkflowBundle currentDataflow = (WorkflowBundle) ModelMap.getInstance().getModel(
+// ModelMapConstants.CURRENT_DATAFLOW);
+// DataflowSelectionModel dataFlowSelectionModel = selectionManager
+// .getDataflowSelectionModel(currentDataflow);
+// // Get all selected components
+// Set<Object> selectedWFComponents = dataFlowSelectionModel.getSelection();
+// Processor p = null;
+// for (Object selectedWFComponent : selectedWFComponents) {
+// if (selectedWFComponent instanceof Processor) {
+// p = (Processor) selectedWFComponent;
+// break;
+// }
+// }
+// if (p != null) {
+// cutProcessor(p.getParent(), p, null, editManager, selectionManager);
+// }
+ }
+
+ public static void cutProcessor(Workflow dataflow, Processor processor,
+ Component component, EditManager editManager,
+ SelectionManager selectionManager) {
+ copyProcessor(processor);
+ new RemoveProcessorAction(dataflow, processor, component, editManager,
+ selectionManager).actionPerformed(null);
+ }
+
+ private static void showException(String message, Exception e) {
+ if (!isHeadless())
+ invokeLater(new ShowExceptionRunnable(message, e));
+ }
+}
diff --git a/taverna-workbench-workflow-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-workflow-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..d3b9734
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,10 @@
+net.sf.taverna.t2.workbench.ui.menu.CopyProcessorMenuAction
+
+net.sf.taverna.t2.workbench.ui.menu.CopyGraphComponentMenuAction
+net.sf.taverna.t2.workbench.ui.menu.PasteGraphComponentMenuAction
+net.sf.taverna.t2.workbench.ui.menu.CutProcessorMenuAction
+net.sf.taverna.t2.workbench.ui.menu.CutGraphComponentMenuAction
+
+net.sf.taverna.t2.workbench.ui.toolbar.CutToolbarAction
+net.sf.taverna.t2.workbench.ui.toolbar.CopyToolbarAction
+net.sf.taverna.t2.workbench.ui.toolbar.PasteToolbarAction
diff --git a/taverna-workbench-workflow-view/src/main/resources/META-INF/spring/workflow-view-context-osgi.xml b/taverna-workbench-workflow-view/src/main/resources/META-INF/spring/workflow-view-context-osgi.xml
new file mode 100644
index 0000000..194a9e3
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/resources/META-INF/spring/workflow-view-context-osgi.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="CopyProcessorMenuAction" auto-export="interfaces" />
+ <service ref="CopyGraphComponentMenuAction" auto-export="interfaces" />
+ <service ref="PasteGraphComponentMenuAction" auto-export="interfaces" />
+ <service ref="CutProcessorMenuAction" auto-export="interfaces" />
+ <service ref="CutGraphComponentMenuAction" auto-export="interfaces" />
+ <service ref="CutToolbarAction" auto-export="interfaces" />
+ <service ref="CopyToolbarAction" auto-export="interfaces" />
+ <service ref="PasteToolbarAction" auto-export="interfaces" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="serviceRegistry" interface="uk.org.taverna.commons.services.ServiceRegistry" />
+
+</beans:beans>
diff --git a/taverna-workbench-workflow-view/src/main/resources/META-INF/spring/workflow-view-context.xml b/taverna-workbench-workflow-view/src/main/resources/META-INF/spring/workflow-view-context.xml
new file mode 100644
index 0000000..7f051a4
--- /dev/null
+++ b/taverna-workbench-workflow-view/src/main/resources/META-INF/spring/workflow-view-context.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="CopyProcessorMenuAction" class="net.sf.taverna.t2.workbench.ui.menu.CopyProcessorMenuAction" />
+ <bean id="CopyGraphComponentMenuAction" class="net.sf.taverna.t2.workbench.ui.menu.CopyGraphComponentMenuAction">
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="PasteGraphComponentMenuAction" class="net.sf.taverna.t2.workbench.ui.menu.PasteGraphComponentMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+ <bean id="CutProcessorMenuAction" class="net.sf.taverna.t2.workbench.ui.menu.CutProcessorMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="CutGraphComponentMenuAction" class="net.sf.taverna.t2.workbench.ui.menu.CutGraphComponentMenuAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="CutToolbarAction" class="net.sf.taverna.t2.workbench.ui.toolbar.CutToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="CopyToolbarAction" class="net.sf.taverna.t2.workbench.ui.toolbar.CopyToolbarAction">
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="PasteToolbarAction" class="net.sf.taverna.t2.workbench.ui.toolbar.PasteToolbarAction">
+ <property name="editManager" ref="editManager" />
+ <property name="menuManager" ref="menuManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="serviceRegistry" ref="serviceRegistry" />
+ </bean>
+
+</beans>