From https://github.com/taverna/taverna-ui-exts master
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-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-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("&nbsp;<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>&nbsp;<b>Namespace: </b>" + StringEscapeUtils.escapeHtml(this.tagNamespace));

+    }

+    tooltip.append("<br>&nbsp;<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 "&nbsp;", "&gt;", 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>&nbsp;Tips for creating search queries:<br>" +

+        "&nbsp;1) Use wildcards to make more flexible queries. Asterisk (<b>*</b>) matches any zero or more<br>" +

+        "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;characters (e.g. <b><i>Seq*</i></b> would match <b><i>Sequence</i></b>), question mark (<b>?</b>) matches any single<br>" +

+        "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;character (e.g. <b><i>Bla?t</i></b> would match <b><i>Blast</i></b>).<br>" +

+        "&nbsp;2) Enclose the <b><i>\"search query\"</i></b> in double quotes to make exact phrase matching, otherwise<br>" +

+        "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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\">&nbsp;("+f.getCount().intValue()+")</span></html>" : "</html>"),*/

+                               (ontology != null ? "<span color=\"#3090C7\"> &lt;"+ ontology +"&gt;</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-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>"

+			+ "&nbsp;<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>"

+			+ "&nbsp;<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>&nbsp;&nbsp;&nbsp;&nbsp;characters (e.g. "

+		+ "<b><i>Seq*</i></b> would match <b><i>Sequence</i></b>), question mark (<b>?</b>) matches any single<br>"

+		+ "&nbsp;&nbsp;&nbsp;&nbsp;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>"

+		+ "&nbsp;&nbsp;&nbsp;&nbsp;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("&nbsp;&nbsp;&nbsp;");

+		}

+

+		content.append("<br>");

+		content.append("</div>");

+		content.append("</div>");

+	  } else {

+		content.append("<br>");

+		content.append("<span style='color: gray; font-weight: italic;'>&nbsp;&nbsp;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("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");

+	  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>"

+			+ "&nbsp;<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 &nbsp; 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 "&nbsp;", "&gt;", 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-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-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-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>