From https://github.com/taverna/taverna-ui-impl master
diff --git a/taverna-workbench-activity-palette-impl/pom.xml b/taverna-workbench-activity-palette-impl/pom.xml
new file mode 100644
index 0000000..4926a94
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>activity-palette-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Activity Palette Impl</name>
+	<description>Activity Palette Implementation</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>activity-palette-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.core</groupId>
+			<artifactId>workflowmodel-api</artifactId>
+			<version>${t2.core.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>observer</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-beanutils</groupId>
+			<artifactId>commons-beanutils</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jdom</groupId>
+			<artifactId>com.springsource.org.jdom</artifactId>
+		</dependency>
+
+		<!-- TODO Remove non-test impl dependency -->
+		<dependency>
+			<groupId>net.sf.taverna.t2.core</groupId>
+			<artifactId>workflowmodel-impl</artifactId>
+			<version>${t2.core.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-impl</artifactId>
+			<version>${taverna.configuration.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-impl</artifactId>
+			<version>${taverna.configuration.version}</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java
new file mode 100644
index 0000000..c5221be
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java
@@ -0,0 +1,10 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+public interface ServiceDescriptionConstants {
+	String SERVICE_PANEL_CONFIGURATION = "servicePanelConfiguration";
+	String PROVIDERS = "providers";
+	String IGNORED = "ignored";
+	String PROVIDER_ID = "providerID";
+	String CONFIGURATION = "configuration";
+	String TYPE = "type";
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java
new file mode 100644
index 0000000..0a40bda
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java
@@ -0,0 +1,167 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.IGNORED;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDERS;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDER_ID;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.TYPE;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class ServiceDescriptionDeserializer {
+	private List<ServiceDescriptionProvider> serviceDescriptionProviders;
+
+	ServiceDescriptionDeserializer(
+			List<ServiceDescriptionProvider> serviceDescriptionProviders) {
+		this.serviceDescriptionProviders = serviceDescriptionProviders;
+	}
+
+	public void deserialize(ServiceDescriptionRegistry registry,
+			File serviceDescriptionsFile) throws DeserializationException {
+		try (FileInputStream serviceDescriptionFileStream = new FileInputStream(
+				serviceDescriptionsFile)) {
+			deserialize(registry, serviceDescriptionFileStream);
+		} catch (FileNotFoundException ex) {
+			throw new DeserializationException("Could not locate file "
+					+ serviceDescriptionsFile.getAbsolutePath()
+					+ " containing service descriptions.");
+		} catch (IOException ex) {
+			throw new DeserializationException(
+					"Could not read stream containing service descriptions from "
+							+ serviceDescriptionsFile.getAbsolutePath(), ex);
+		}
+	}
+
+	public void deserialize(ServiceDescriptionRegistry registry,
+			URL serviceDescriptionsURL) throws DeserializationException {
+		try (InputStream serviceDescriptionInputStream = serviceDescriptionsURL
+				.openStream()) {
+			deserialize(registry, serviceDescriptionInputStream);
+		} catch (FileNotFoundException ex) {
+			throw new DeserializationException("Could not open URL "
+					+ serviceDescriptionsURL
+					+ " containing service descriptions.");
+		} catch (IOException ex) {
+			throw new DeserializationException(
+					"Could not read stream containing service descriptions from "
+							+ serviceDescriptionsURL, ex);
+		}
+	}
+
+	private static final JsonFactory factory = new JsonFactory();
+
+	private void deserialize(ServiceDescriptionRegistry registry,
+			InputStream serviceDescriptionsInputStream) throws IOException,
+			DeserializationException {
+		ObjectNode node = (ObjectNode) new ObjectMapper(factory)
+				.readTree(serviceDescriptionsInputStream);
+		List<ServiceDescriptionProvider> providers = deserializeProviders(node,
+				true);
+		for (ServiceDescriptionProvider provider : providers)
+			registry.addServiceDescriptionProvider(provider);
+	}
+
+	public Collection<? extends ServiceDescriptionProvider> deserializeDefaults(
+			ServiceDescriptionRegistry registry,
+			File defaultConfigurableServiceProvidersFile)
+			throws DeserializationException {
+		ObjectNode node;
+		try (FileInputStream serviceDescriptionStream = new FileInputStream(
+				defaultConfigurableServiceProvidersFile)) {
+			node = (ObjectNode) new ObjectMapper(factory)
+					.readTree(serviceDescriptionStream);
+		} catch (IOException e) {
+			throw new DeserializationException("Can't read "
+					+ defaultConfigurableServiceProvidersFile);
+		}
+		return deserializeProviders(node, false);
+	}
+
+	private List<ServiceDescriptionProvider> deserializeProviders(
+			ObjectNode rootNode, boolean obeyIgnored)
+			throws DeserializationException {
+		List<ServiceDescriptionProvider> providers = new ArrayList<>();
+
+		ArrayNode providersNode = (ArrayNode) rootNode.get(PROVIDERS);
+		if (providersNode != null)
+			for (JsonNode provider : providersNode)
+				providers.add(deserializeProvider((ObjectNode) provider));
+
+		if (obeyIgnored) {
+			ArrayNode ignoredNode = (ArrayNode) rootNode.get(IGNORED);
+			if (ignoredNode != null)
+				for (JsonNode provider : ignoredNode)
+					providers
+							.remove(deserializeProvider((ObjectNode) provider));
+		}
+
+		return providers;
+	}
+
+	private ServiceDescriptionProvider deserializeProvider(
+			ObjectNode providerNode) throws DeserializationException {
+		String providerId = providerNode.get(PROVIDER_ID).asText().trim();
+		ServiceDescriptionProvider provider = null;
+		for (ServiceDescriptionProvider serviceProvider : serviceDescriptionProviders)
+			if (serviceProvider.getId().equals(providerId)) {
+				provider = serviceProvider;
+				break;
+			}
+		if (provider == null)
+			throw new DeserializationException(
+					"Could not find provider with id " + providerId);
+
+		/*
+		 * So we know the service provider now, but we need a separate instance
+		 * of that provider for each providerElem. E.g. we can have 2 or more
+		 * WSDL provider elements and need to return a separate provider
+		 * instance for each as they will have different configurations.
+		 */
+		ServiceDescriptionProvider instance = provider.newInstance();
+
+		if (instance instanceof ConfigurableServiceProvider)
+			try {
+				Configuration config = new Configuration();
+				config.setType(URI.create(providerNode.get(TYPE).textValue()));
+				config.setJson(providerNode.get(CONFIGURATION));
+				if (config != null)
+					((ConfigurableServiceProvider) instance).configure(config);
+			} catch (Exception e) {
+				throw new DeserializationException(
+						"Could not configure provider " + providerId
+								+ " using bean " + providerNode, e);
+			}
+		return instance;
+	}
+
+	@SuppressWarnings("serial")
+	static class DeserializationException extends Exception {
+		public DeserializationException(String string) {
+			super(string);
+		}
+
+		public DeserializationException(String string, Exception ex) {
+			super(string, ex);
+		}
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java
new file mode 100644
index 0000000..9dc3f00
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java
@@ -0,0 +1,652 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static java.lang.System.currentTimeMillis;
+import static java.lang.Thread.MIN_PRIORITY;
+import static java.lang.Thread.currentThread;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider.FindServiceDescriptionsCallBack;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.events.AddedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.PartialServiceDescriptionsNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderErrorNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderStatusNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderUpdatingNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderWarningNotification;
+import net.sf.taverna.t2.servicedescriptions.events.RemovedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionProvidedEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionRegistryEvent;
+import net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionDeserializer.DeserializationException;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class ServiceDescriptionRegistryImpl implements ServiceDescriptionRegistry {
+	/**
+	 * If a writable property of this name on a provider exists (ie. the provider has a method
+	 * setServiceDescriptionRegistry(ServiceDescriptionRegistry registry) - then this property will
+	 * be set to the current registry.
+	 */
+	public static final String SERVICE_DESCRIPTION_REGISTRY = "serviceDescriptionRegistry";
+	public static Logger logger = Logger.getLogger(ServiceDescriptionRegistryImpl.class);
+	public static final ThreadGroup threadGroup = new ThreadGroup("Service description providers");
+	/**
+	 * Total maximum timeout while waiting for description threads to finish
+	 */
+	private static final long DESCRIPTION_THREAD_TIMEOUT_MS = 3000;
+	protected static final String CONF_DIR = "conf";
+	protected static final String SERVICE_PROVIDERS_FILENAME = "service_providers.xml";
+	private static final String DEFAULT_CONFIGURABLE_SERVICE_PROVIDERS_FILENAME = "default_service_providers.xml";
+
+	private ServiceDescriptionsConfiguration serviceDescriptionsConfig;
+	private ApplicationConfiguration applicationConfiguration;
+	/**
+	 * <code>false</code> until first call to {@link #loadServiceProviders()} - which is done by
+	 * first call to {@link #getServiceDescriptionProviders()}.
+	 */
+	private boolean hasLoadedProviders = false;
+	/**
+	 * <code>true</code> while {@link #loadServiceProviders(File)},
+	 * {@link #loadServiceProviders(URL)} or {@link #loadServiceProviders()} is in progress, avoids
+	 * triggering {@link #saveServiceDescriptions()} on
+	 * {@link #addServiceDescriptionProvider(ServiceDescriptionProvider)} calls.
+	 */
+	private boolean loading = false;
+	private MultiCaster<ServiceDescriptionRegistryEvent> observers = new MultiCaster<>(this);
+	private List<ServiceDescriptionProvider> serviceDescriptionProviders;
+	private Set<ServiceDescriptionProvider> allServiceProviders;
+	private Map<ServiceDescriptionProvider, Set<ServiceDescription>> providerDescriptions = new HashMap<>();
+	private Map<ServiceDescriptionProvider, Thread> serviceDescriptionThreads = new HashMap<>();
+	/**
+	 * Service providers added by the user, should be saved
+	 */
+	private Set<ServiceDescriptionProvider> userAddedProviders = new HashSet<>();
+	private Set<ServiceDescriptionProvider> userRemovedProviders = new HashSet<>();
+	private Set<ServiceDescriptionProvider> defaultServiceDescriptionProviders;
+	/**
+	 * File containing a list of configured ConfigurableServiceProviders which is used to get the
+	 * default set of service descriptions together with those provided by AbstractTemplateServiceS.
+	 * This file is located in the conf directory of the Taverna startup directory.
+	 */
+	private File defaultConfigurableServiceProvidersFile;
+	private boolean defaultSystemConfigurableProvidersLoaded = false;
+
+	static {
+		threadGroup.setMaxPriority(MIN_PRIORITY);
+	}
+
+	public ServiceDescriptionRegistryImpl(
+			ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+		defaultConfigurableServiceProvidersFile = new File(
+				getTavernaStartupConfigurationDirectory(),
+				DEFAULT_CONFIGURABLE_SERVICE_PROVIDERS_FILENAME);
+	}
+
+	/**
+	 * Get the Taverna distribution (startup) configuration directory.
+	 */
+	private File getTavernaStartupConfigurationDirectory() {
+		File distroHome = null;
+		File configDirectory = null;
+		distroHome = applicationConfiguration.getStartupDir();
+		configDirectory = new File(distroHome, "conf");
+		if (!configDirectory.exists())
+			configDirectory.mkdir();
+		return configDirectory;
+	}
+
+	private static void joinThreads(Collection<? extends Thread> threads,
+			long descriptionThreadTimeoutMs) {
+		long finishJoinBy = currentTimeMillis() + descriptionThreadTimeoutMs;
+		for (Thread thread : threads) {
+			// No shorter timeout than 1 ms (thread.join(0) waits forever!)
+			long timeout = Math.max(1, finishJoinBy - currentTimeMillis());
+			try {
+				thread.join(timeout);
+			} catch (InterruptedException e) {
+				currentThread().interrupt();
+				return;
+			}
+			if (thread.isAlive())
+				logger.debug("Thread did not finish " + thread);
+		}
+	}
+
+
+	@Override
+	public void addObserver(Observer<ServiceDescriptionRegistryEvent> observer) {
+		observers.addObserver(observer);
+	}
+
+	@Override
+	public void addServiceDescriptionProvider(ServiceDescriptionProvider provider) {
+		synchronized (this) {
+			userRemovedProviders.remove(provider);
+			if (!getDefaultServiceDescriptionProviders().contains(provider))
+				userAddedProviders.add(provider);
+			allServiceProviders.add(provider);
+		}
+
+		// Spring-like auto-config
+		try {
+			// BeanUtils should ignore this if provider does not have that property
+			BeanUtils.setProperty(provider, SERVICE_DESCRIPTION_REGISTRY, this);
+		} catch (IllegalAccessException | InvocationTargetException e) {
+			logger.warn("Could not set serviceDescriptionRegistry on "
+					+ provider, e);
+		}
+
+		if (!loading)
+			saveServiceDescriptions();
+		observers.notify(new AddedProviderEvent(provider));
+		updateServiceDescriptions(false, false);
+	}
+
+	private File findServiceDescriptionsFile() {
+		File confDir = new File(
+				applicationConfiguration.getApplicationHomeDir(), CONF_DIR);
+		confDir.mkdirs();
+		if (!confDir.isDirectory())
+			throw new RuntimeException("Invalid directory: " + confDir);
+		File serviceDescriptionsFile = new File(confDir,
+				SERVICE_PROVIDERS_FILENAME);
+		return serviceDescriptionsFile;
+	}
+
+	@Override
+	public List<Observer<ServiceDescriptionRegistryEvent>> getObservers() {
+		return observers.getObservers();
+	}
+
+	// Fallback to this method that uses hardcoded default services if you cannot read them from
+	// the file.
+//	@SuppressWarnings("unchecked")
+//	public synchronized Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProvidersFallback() {
+//		/*if (defaultServiceDescriptionProviders != null) {
+//	 return defaultServiceDescriptionProviders;
+//	 }
+//	 defaultServiceDescriptionProviders = new HashSet<ServiceDescriptionProvider>();
+//		 */
+//		for (ServiceDescriptionProvider provider : serviceDescriptionProviders) {
+//
+//			/* We do not need these - already loaded them from getDefaultServiceDescriptionProviders()
+//	 if (!(provider instanceof ConfigurableServiceProvider)) {
+//	 defaultServiceDescriptionProviders.add(provider);
+//	 continue;
+//	 }*/
+//
+//			// Just load the hard coded default configurable service providers
+//			if (provider instanceof ConfigurableServiceProvider){
+//				ConfigurableServiceProvider<Object> template = ((ConfigurableServiceProvider<Object>)
+//						provider);
+//				// Get configurations
+//				List<Object> configurables = template.getDefaultConfigurations();
+//				for (Object config : configurables) {
+//					// Make a copy that we can configure
+//					ConfigurableServiceProvider<Object> configurableProvider = template.clone();
+//					try {
+//						configurableProvider.configure(config);
+//					} catch (ConfigurationException e) {
+//						logger.warn("Can't configure provider "
+//								+ configurableProvider + " with " + config);
+//						continue;
+//					}
+//					defaultServiceDescriptionProviders.add(configurableProvider);
+//				}
+//			}
+//		}
+//		return defaultServiceDescriptionProviders;
+//	}
+
+	// Get the default services.
+	@Override
+	public synchronized Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProviders() {
+		if (defaultServiceDescriptionProviders != null)
+			return defaultServiceDescriptionProviders;
+		defaultServiceDescriptionProviders = new HashSet<>();
+
+		/*
+		 * Add default configurable service description providers from the
+		 * default_service_providers.xml file
+		 */
+		if (defaultConfigurableServiceProvidersFile.exists()) {
+			try {
+				ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+						serviceDescriptionProviders);
+				defaultServiceDescriptionProviders.addAll(deserializer
+						.deserializeDefaults(this,
+								defaultConfigurableServiceProvidersFile));
+				/*
+				 * We have successfully loaded the defaults for system
+				 * configurable providers. Note that there are still defaults
+				 * for third party configurable providers, which will be loaded
+				 * below using getDefaultConfigurations().
+				 */
+				defaultSystemConfigurableProvidersLoaded = true;
+			} catch (Exception e) {
+				logger.error("Could not load default service providers from "
+						+ defaultConfigurableServiceProvidersFile.getAbsolutePath(), e);
+
+				/*
+				 * Fallback on the old hardcoded method of loading default
+				 * system configurable service providers using
+				 * getDefaultConfigurations().
+				 */
+				defaultSystemConfigurableProvidersLoaded = false;
+			}
+		} else {
+			logger.warn("Could not find the file "
+					+ defaultConfigurableServiceProvidersFile.getAbsolutePath()
+					+ " containing default system service providers. "
+					+ "Using the hardcoded list of default system providers.");
+
+			/*
+			 * Fallback on the old hardcoded method of loading default system
+			 * configurable service providers using getDefaultConfigurations().
+			 */
+			defaultSystemConfigurableProvidersLoaded = false;
+		}
+
+		/*
+		 * Load other default service description providers - template, local
+		 * workers and third party configurable service providers
+		 */
+		for (ServiceDescriptionProvider provider : serviceDescriptionProviders) {
+			/*
+			 * Template service providers (beanshell, string constant, etc. )
+			 * and providers of local workers.
+			 */
+			if (!(provider instanceof ConfigurableServiceProvider)) {
+				defaultServiceDescriptionProviders.add(provider);
+				continue;
+			}
+
+			/*
+			 * Default system or third party configurable service description
+			 * provider. System ones are read from the
+			 * default_service_providers.xml file so getDefaultConfigurations()
+			 * on them will not have much effect here unless
+			 * defaultSystemConfigurableProvidersLoaded is set to false.
+			 */
+			//FIXME needs to be designed to work using Configuration instances
+			//FIXME needs to get configurations via OSGi discovery
+			/*
+			ConfigurableServiceProvider template = (ConfigurableServiceProvider) provider;
+			// Get configurations
+			for (ObjectNode config : template.getDefaultConfigurations()) {
+				// Make a copy that we can configure
+				ConfigurableServiceProvider configurableProvider = template.clone();
+				try {
+					configurableProvider.configure(config);
+				} catch (ConfigurationException e) {
+					logger.warn("Can't configure provider "
+							+ configurableProvider + " with " + config);
+					continue;
+				}
+				defaultServiceDescriptionProviders.add(configurableProvider);
+			}
+			*/
+		}
+
+		return defaultServiceDescriptionProviders;
+	}
+
+	@Override
+	public synchronized Set<ServiceDescriptionProvider> getServiceDescriptionProviders() {
+		if (allServiceProviders != null)
+			return new HashSet<>(allServiceProviders);
+		allServiceProviders = new HashSet<>(userAddedProviders);
+		synchronized (this) {
+			if (!hasLoadedProviders)
+				try {
+					loadServiceProviders();
+				} catch (Exception e) {
+					logger.error("Could not load service providers", e);
+				} finally {
+					hasLoadedProviders = true;
+				}
+		}
+		for (ServiceDescriptionProvider provider : getDefaultServiceDescriptionProviders()) {
+			if (userRemovedProviders.contains(provider))
+				continue;
+			if (provider instanceof ConfigurableServiceProvider
+					&& !serviceDescriptionsConfig.isIncludeDefaults())
+				// We'll skip the default configurable service provders
+				continue;
+			allServiceProviders.add(provider);
+		}
+		return new HashSet<>(allServiceProviders);
+	}
+
+	@Override
+	public Set<ServiceDescriptionProvider> getServiceDescriptionProviders(
+			ServiceDescription sd) {
+		Set<ServiceDescriptionProvider> result = new HashSet<>();
+		for (ServiceDescriptionProvider sdp : providerDescriptions.keySet())
+			if (providerDescriptions.get(sdp).contains(sd))
+				result.add(sdp);
+		return result;
+	}
+
+	@Override
+	public Set<ServiceDescription> getServiceDescriptions() {
+		updateServiceDescriptions(false, true);
+		Set<ServiceDescription> serviceDescriptions = new HashSet<>();
+		synchronized (providerDescriptions) {
+			for (Set<ServiceDescription> providerDesc : providerDescriptions
+					.values())
+				serviceDescriptions.addAll(providerDesc);
+		}
+		return serviceDescriptions;
+	}
+
+	@Override
+	public ServiceDescription getServiceDescription(URI serviceType) {
+		for (ServiceDescription serviceDescription : getServiceDescriptions())
+			if (serviceDescription.getActivityType().equals(serviceType))
+				return serviceDescription;
+		return null;
+	}
+
+	@Override
+	public List<ConfigurableServiceProvider> getUnconfiguredServiceProviders() {
+		List<ConfigurableServiceProvider> providers = new ArrayList<>();
+		for (ServiceDescriptionProvider provider : serviceDescriptionProviders)
+			if (provider instanceof ConfigurableServiceProvider)
+				providers.add((ConfigurableServiceProvider) provider);
+		return providers;
+	}
+
+	@Override
+	public Set<ServiceDescriptionProvider> getUserAddedServiceProviders() {
+		return new HashSet<>(userAddedProviders);
+	}
+
+	@Override
+	public Set<ServiceDescriptionProvider> getUserRemovedServiceProviders() {
+		return new HashSet<>(userRemovedProviders);
+	}
+
+	@Override
+	public void loadServiceProviders() {
+		File serviceProviderFile = findServiceDescriptionsFile();
+		if (serviceProviderFile.isFile())
+			loadServiceProviders(serviceProviderFile);
+		hasLoadedProviders = true;
+	}
+
+	@Override
+	public void loadServiceProviders(File serviceProvidersFile) {
+		ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+				serviceDescriptionProviders);
+		loading = true;
+		try {
+			deserializer.deserialize(this, serviceProvidersFile);
+		} catch (DeserializationException e) {
+			logger.error("failed to deserialize configuration", e);
+		}
+		loading = false;
+	}
+
+	@Override
+	public void loadServiceProviders(URL serviceProvidersURL) {
+		ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+				serviceDescriptionProviders);
+		loading = true;
+		try {
+			deserializer.deserialize(this, serviceProvidersURL);
+		} catch (DeserializationException e) {
+			logger.error("failed to deserialize configuration", e);
+		}
+		loading = false;
+	}
+
+	@Override
+	public void refresh() {
+		updateServiceDescriptions(true, false);
+	}
+
+	@Override
+	public void removeObserver(Observer<ServiceDescriptionRegistryEvent> observer) {
+		observers.removeObserver(observer);
+	}
+
+	@Override
+	public synchronized void removeServiceDescriptionProvider(
+			ServiceDescriptionProvider provider) {
+		if (!userAddedProviders.remove(provider))
+			// Not previously added - must be a default one.. but should we remove it?
+			if (loading || serviceDescriptionsConfig.isRemovePermanently()
+					&& serviceDescriptionsConfig.isIncludeDefaults())
+				userRemovedProviders.add(provider);
+		if (allServiceProviders.remove(provider)) {
+			synchronized (providerDescriptions) {
+				Thread thread = serviceDescriptionThreads.remove(provider);
+				if (thread != null)
+					thread.interrupt();
+				providerDescriptions.remove(provider);
+			}
+			observers.notify(new RemovedProviderEvent(provider));
+		}
+		if (!loading)
+			saveServiceDescriptions();
+	}
+
+	@Override
+	public void saveServiceDescriptions() {
+		File serviceDescriptionsFile = findServiceDescriptionsFile();
+		saveServiceDescriptions(serviceDescriptionsFile);
+	}
+
+	@Override
+	public void saveServiceDescriptions(File serviceDescriptionsFile) {
+		ServiceDescriptionSerializer serializer = new ServiceDescriptionSerializer();
+		try {
+			serializer.serializeRegistry(this, serviceDescriptionsFile);
+		} catch (IOException e) {
+			throw new RuntimeException("Can't save service descriptions to "
+					+ serviceDescriptionsFile);
+		}
+	}
+
+	/**
+	 * Exports all configurable service providers (that give service
+	 * descriptions) currently found in the Service Registry (apart from service
+	 * templates and local services) regardless of who added them (user or
+	 * default system providers).
+	 * <p>
+	 * Unlike {@link #saveServiceDescriptions}, this export does not have the
+	 * "ignored providers" section as this is just a plain export of everything
+	 * in the Service Registry.
+	 * 
+	 * @param serviceDescriptionsFile
+	 */
+	@Override
+	public void exportCurrentServiceDescriptions(File serviceDescriptionsFile) {
+		ServiceDescriptionSerializer serializer = new ServiceDescriptionSerializer();
+		try {
+			serializer.serializeFullRegistry(this, serviceDescriptionsFile);
+		} catch (IOException e) {
+			throw new RuntimeException("Could not save service descriptions to "
+					+ serviceDescriptionsFile);
+		}
+	}
+
+	public void setServiceDescriptionProvidersList(
+			List<ServiceDescriptionProvider> serviceDescriptionProviders) {
+		this.serviceDescriptionProviders = serviceDescriptionProviders;
+	}
+
+	private void updateServiceDescriptions(boolean refreshAll, boolean waitFor) {
+		List<Thread> threads = new ArrayList<>();
+		for (ServiceDescriptionProvider provider : getServiceDescriptionProviders()) {
+			synchronized (providerDescriptions) {
+				if (providerDescriptions.containsKey(provider) && !refreshAll)
+					// We'll used the cached values
+					continue;
+				Thread oldThread = serviceDescriptionThreads.get(provider);
+				if (oldThread != null && oldThread.isAlive()) {
+					if (refreshAll)
+						// New thread will override the old thread
+						oldThread.interrupt();
+					else {
+						// observers.notify(new ProviderStatusNotification(provider, "Waiting for provider"));
+						continue;
+					}
+				}
+				// Not run yet - we'll start a new tread
+				Thread thread = new FindServiceDescriptionsThread(provider);
+				threads.add(thread);
+				serviceDescriptionThreads.put(provider, thread);
+				thread.start();
+			}
+		}
+		if (waitFor)
+			joinThreads(threads, DESCRIPTION_THREAD_TIMEOUT_MS);
+	}
+
+	@Override
+	public boolean isDefaultSystemConfigurableProvidersLoaded() {
+		return defaultSystemConfigurableProvidersLoaded;
+	}
+
+	/**
+	 * Sets the serviceDescriptionsConfig.
+	 * 
+	 * @param serviceDescriptionsConfig
+	 *            the new value of serviceDescriptionsConfig
+	 */
+	public void setServiceDescriptionsConfig(
+			ServiceDescriptionsConfiguration serviceDescriptionsConfig) {
+		this.serviceDescriptionsConfig = serviceDescriptionsConfig;
+	}
+
+	class FindServiceDescriptionsThread extends Thread implements
+			UncaughtExceptionHandler, FindServiceDescriptionsCallBack {
+		private final ServiceDescriptionProvider provider;
+		private boolean aborting = false;
+		private final Set<ServiceDescription> providerDescs = new HashSet<>();
+
+		FindServiceDescriptionsThread(ServiceDescriptionProvider provider) {
+			super(threadGroup, "Find service descriptions from " + provider);
+			this.provider = provider;
+			setUncaughtExceptionHandler(this);
+			setDaemon(true);
+		}
+
+		@Override
+		public void fail(String message, Throwable ex) {
+			logger.warn("Provider " + getProvider() + ": " + message, ex);
+			if (aborting)
+				return;
+			observers.notify(new ProviderErrorNotification(getProvider(),
+					message, ex));
+		}
+
+		@Override
+		public void finished() {
+			if (aborting)
+				return;
+			synchronized (providerDescriptions) {
+				providerDescriptions.put(getProvider(), providerDescs);
+			}
+			observers.notify(new ServiceDescriptionProvidedEvent(getProvider(),
+					providerDescs));
+		}
+
+		@Override
+		public void partialResults(
+				Collection<? extends ServiceDescription> serviceDescriptions) {
+			if (aborting)
+				return;
+			providerDescs.addAll(serviceDescriptions);
+			synchronized (providerDescriptions) {
+				providerDescriptions.put(getProvider(), providerDescs);
+			}
+			observers.notify(new PartialServiceDescriptionsNotification(
+					getProvider(), serviceDescriptions));
+		}
+
+		@Override
+		public void status(String message) {
+			logger.debug("Provider " + getProvider() + ": " + message);
+			if (aborting)
+				return;
+			observers.notify(new ProviderStatusNotification(getProvider(),
+					message));
+		}
+
+		@Override
+		public void warning(String message) {
+			logger.warn("Provider " + getProvider() + ": " + message);
+			if (aborting)
+				return;
+			observers.notify(new ProviderWarningNotification(getProvider(),
+					message));
+		}
+
+		public ServiceDescriptionProvider getProvider() {
+			return provider;
+		}
+
+		@Override
+		public void interrupt() {
+			aborting = true;
+			super.interrupt();
+		}
+
+		@Override
+		public void run() {
+			observers.notify(new ProviderUpdatingNotification(provider));
+			getProvider().findServiceDescriptionsAsync(this);
+		}
+
+		@Override
+		public void uncaughtException(Thread t, Throwable ex) {
+			logger.error("Uncaught exception in " + t, ex);
+			fail("Uncaught exception", ex);
+		}
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java
new file mode 100644
index 0000000..8a047a3
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java
@@ -0,0 +1,102 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.IGNORED;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDERS;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDER_ID;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.SERVICE_PANEL_CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.TYPE;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Set;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import org.apache.log4j.Logger;
+import org.jdom.JDOMException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class ServiceDescriptionSerializer {
+	private static Logger logger = Logger
+			.getLogger(ServiceDescriptionSerializer.class);
+
+	public void serializeRegistry(ServiceDescriptionRegistry registry, File file)
+			throws IOException {
+		Set<ServiceDescriptionProvider> ignoreProviders = registry
+				.getUserRemovedServiceProviders();
+		JsonNode registryElement = serializeRegistry(registry, ignoreProviders);
+		try (BufferedOutputStream bufferedOutStream = new BufferedOutputStream(
+				new FileOutputStream(file))) {
+			bufferedOutStream.write(registryElement.toString()
+					.getBytes("UTF-8"));
+		}
+	}
+
+	/**
+	 * Export the whole service registry to an xml file, regardless of who added
+	 * the service provider (user or system default). In this case there will be
+	 * no "ignored providers" in the saved file.
+	 */
+	public void serializeFullRegistry(ServiceDescriptionRegistry registry,
+			File file) throws IOException {
+		JsonNode registryElement = serializeRegistry(registry, ALL_PROVIDERS);
+		try (BufferedOutputStream bufferedOutStream = new BufferedOutputStream(
+				new FileOutputStream(file))) {
+			bufferedOutStream.write(registryElement.toString()
+					.getBytes("UTF-8"));
+		}
+	}
+
+	private static final JsonNodeFactory factory = JsonNodeFactory.instance;
+	private static final Set<ServiceDescriptionProvider> ALL_PROVIDERS = null;
+
+	private JsonNode serializeRegistry(ServiceDescriptionRegistry registry,
+			Set<ServiceDescriptionProvider> ignoreProviders) {
+		ObjectNode overallConfiguration = factory.objectNode();
+		overallConfiguration.put(SERVICE_PANEL_CONFIGURATION,
+				ignoreProviders != ALL_PROVIDERS ? "full" : "defaults only");
+		ArrayNode providers = overallConfiguration.putArray(PROVIDERS);
+
+		for (ServiceDescriptionProvider provider : registry
+				.getUserAddedServiceProviders())
+			try {
+				providers.add(serializeProvider(provider));
+			} catch (JDOMException | IOException e) {
+				logger.warn("Could not serialize " + provider, e);
+			}
+
+		if (ignoreProviders != ALL_PROVIDERS) {
+			ArrayNode ignored = overallConfiguration.putArray(IGNORED);
+			for (ServiceDescriptionProvider provider : ignoreProviders)
+				try {
+					ignored.add(serializeProvider(provider));
+				} catch (JDOMException | IOException e) {
+					logger.warn("Could not serialize " + provider, e);
+				}
+		}
+
+		return overallConfiguration;
+	}
+
+	private JsonNode serializeProvider(ServiceDescriptionProvider provider)
+			throws JDOMException, IOException {
+		ObjectNode node = factory.objectNode();
+		node.put(PROVIDER_ID, provider.getId());
+
+		if (provider instanceof ConfigurableServiceProvider) {
+			ConfigurableServiceProvider configurable = (ConfigurableServiceProvider) provider;
+			node.put(TYPE, configurable.getConfiguration().getType().toString());
+			node.put(CONFIGURATION, configurable.getConfiguration().getJson());
+		}
+		return node;
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java
new file mode 100644
index 0000000..ee180a7
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java
@@ -0,0 +1,15 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import org.jdom.Namespace;
+
+public interface ServiceDescriptionXMLConstants {
+
+	public static final Namespace SERVICE_DESCRIPTION_NS = Namespace
+			.getNamespace("http://taverna.sf.net/2009/xml/servicedescription");
+	public static final String PROVIDER = "provider";
+	public static final String PROVIDERS = "providers";
+	public static final String SERVICE_DESCRIPTIONS = "serviceDescriptions";
+	public static final String IGNORED_PROVIDERS = "ignoredProviders";
+	public static final String PROVIDER_IDENTIFIER = "providerId";
+
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java
new file mode 100644
index 0000000..ef0295c
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class ServiceDescriptionsConfigurationImpl extends AbstractConfigurable
+		implements ServiceDescriptionsConfiguration {
+	private static final String INCLUDE_DEFAULTS = "includeDefaults";
+	private static final String SERVICE_PALETTE = "Service providers";
+	private static final String SERVICE_PALETTE_PREFIX = "ServiceProviders";
+	private static final String CATEGORY = "Services";
+	private static final String UUID = "f0d1ef24-9337-412f-b2c3-220a01e2efd0";
+	private static final String REMOVE_PERMANENTLY = "removePermanently";
+
+	public ServiceDescriptionsConfigurationImpl(
+			ConfigurationManager configurationManager) {
+		super(configurationManager);
+	}
+
+	@Override
+	public String getCategory() {
+		return CATEGORY;
+	}
+
+	@Override
+	public Map<String, String> getDefaultPropertyMap() {
+		Map<String, String> defaults = new HashMap<String, String>();
+		defaults.put(INCLUDE_DEFAULTS, "true");
+		defaults.put(REMOVE_PERMANENTLY, "true");
+		return defaults;
+	}
+
+	@Override
+	public String getDisplayName() {
+		return SERVICE_PALETTE;
+	}
+
+	@Override
+	public String getFilePrefix() {
+		return SERVICE_PALETTE_PREFIX;
+	}
+
+	@Override
+	public String getUUID() {
+		return UUID;
+	}
+
+	@Override
+	public boolean isIncludeDefaults() {
+		return Boolean.parseBoolean(getProperty(INCLUDE_DEFAULTS));
+	}
+
+	@Override
+	public void setIncludeDefaults(boolean includeDefaults) {
+		setProperty(INCLUDE_DEFAULTS, Boolean.toString(includeDefaults));
+	}
+
+	@Override
+	public boolean isRemovePermanently() {
+		return Boolean.parseBoolean(getProperty(REMOVE_PERMANENTLY));
+	}
+
+	@Override
+	public void setRemovePermanently(boolean removePermanently) {
+		setProperty(REMOVE_PERMANENTLY, Boolean.toString(removePermanently));
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java
new file mode 100644
index 0000000..46f82c4
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class ActivityPaletteConfiguration extends AbstractConfigurable {
+	private Map<String,String> defaultPropertyMap;
+
+	public ActivityPaletteConfiguration(ConfigurationManager configurationManager) {
+		super(configurationManager);
+	}
+
+	@Override
+	public String getCategory() {
+		return "Services";
+	}
+
+	@Override
+	public Map<String, String> getDefaultPropertyMap() {
+		if (defaultPropertyMap == null) {
+			defaultPropertyMap = new HashMap<>();
+
+			// //wsdl
+			//defaultPropertyMap.put("taverna.defaultwsdl", "http://www.ebi.ac.uk/xembl/XEMBL.wsdl,"+
+			//                    "http://soap.genome.jp/KEGG.wsdl,"+
+			//                    "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/soap/eutils.wsdl,"+
+			//                    "http://soap.bind.ca/wsdl/bind.wsdl,"+
+			//                    "http://www.ebi.ac.uk/ws/services/urn:Dbfetch?wsdl");
+
+			// //soaplab
+			//defaultPropertyMap.put("taverna.defaultsoaplab", "http://www.ebi.ac.uk/soaplab/services/");
+
+			// //biomart
+			//defaultPropertyMap.put("taverna.defaultmartregistry","http://www.biomart.org/biomart");
+
+			//add property names
+			//defaultPropertyMap.put("name.taverna.defaultwsdl", "WSDL");
+			//defaultPropertyMap.put("name.taverna.defaultsoaplab","Soaplab");
+			//defaultPropertyMap.put("name.taverna.defaultmartregistry", "Biomart");
+		}
+		return defaultPropertyMap;
+	}
+
+	@Override
+	public String getDisplayName() {
+		return "Activity Palette";
+	}
+
+	@Override
+	public String getFilePrefix() {
+		return "ActivityPalette";
+	}
+
+	@Override
+	public String getUUID() {
+		return "ad9f3a60-5967-11dd-ae16-0800200c9a66";
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java
new file mode 100644
index 0000000..6166e60
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java
@@ -0,0 +1,284 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.LEFT;
+import static java.awt.FlowLayout.RIGHT;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.border.BevelBorder.LOWERED;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BoxLayout;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.border.BevelBorder;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ActivityPaletteConfigurationPanel extends JPanel {
+	private static Logger logger = Logger
+			.getLogger(ActivityPaletteConfigurationPanel.class);
+
+	private Map<String,List<String>> values = new HashMap<>();
+	private Map<String,String> names = new HashMap<>();
+	private DefaultComboBoxModel<String> model;
+	private DefaultListModel<String> listModel;
+	private JList<String> propertyListItems;
+	private String selectedKey;
+	private JButton deleteTypeButton;
+	private final ActivityPaletteConfiguration config;
+
+	public ActivityPaletteConfigurationPanel(ActivityPaletteConfiguration config) {
+		super(new BorderLayout());
+		this.config = config;
+
+		model = new DefaultComboBoxModel<>();
+		for (String key : config.getInternalPropertyMap().keySet()) {
+			if (key.startsWith("taverna.")
+					&& config.getPropertyStringList(key) != null) {
+				model.addElement(key);
+				values.put(key,
+						new ArrayList<>(config.getPropertyStringList(key)));
+			}
+			if (key.startsWith("name.taverna."))
+				names.put(key, config.getProperty(key).toString());
+		}
+		deleteTypeButton = new JButton("Delete");
+
+		final JButton addTypeButton = new JButton("Add");
+		final JComboBox<String> comboBox = new JComboBox<>(model);
+		comboBox.setRenderer(new DefaultListCellRenderer() {
+			@Override
+			public Component getListCellRendererComponent(JList<?> list,
+					Object value, int index, boolean isSelected,
+					boolean cellHasFocus) {
+				if (value != null && value instanceof String) {
+					String name = names.get("name." + value);
+					if (name != null)
+						value = name;
+				}
+				return super.getListCellRendererComponent(list, value, index,
+						isSelected, cellHasFocus);
+			}
+		});
+
+		deleteTypeButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				String displayText = names.get("name." + selectedKey);
+				if (displayText == null)
+					displayText = selectedKey;
+				if (confirm("Confirm removal",
+						"Are you sure you wish to remove the type "
+								+ displayText + "?")) {
+					names.remove("name." + selectedKey);
+					values.remove(selectedKey);
+					model.removeElement(selectedKey);
+					comboBox.setSelectedIndex(0);
+				}
+			}
+		});
+
+		addTypeButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				String key = input("New key", "Provide the new key.");
+				if (key == null)
+					return;
+				String name = input("Name for the key",
+						"Provide the name for the key: " + key);
+				if (name == null)
+					return;
+
+				values.put(key, new ArrayList<String>());
+				names.put("name." + key, name);
+				model.addElement(key);
+				comboBox.setSelectedItem(key);
+			}
+		});
+
+		comboBox.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				if (comboBox.getSelectedItem() != null
+						&& comboBox.getSelectedItem() instanceof String) {
+					selectedKey = (String) comboBox.getSelectedItem();
+					List<String> selectedList = values.get(selectedKey);
+					populateList(selectedList);
+					deleteTypeButton.setEnabled(selectedList.size() == 0);
+				}
+			}
+		});
+
+		JPanel propertySelectionPanel = new JPanel(new FlowLayout(LEFT));
+		propertySelectionPanel.add(new JLabel("Activity type:"));
+		propertySelectionPanel.add(comboBox);
+		propertySelectionPanel.add(addTypeButton);
+		propertySelectionPanel.add(deleteTypeButton);
+		add(propertySelectionPanel, NORTH);
+
+		JPanel listPanel = new JPanel(new BorderLayout());
+		listModel = new DefaultListModel<>();
+		propertyListItems = new JList<>(listModel);
+		propertyListItems.setBorder(new BevelBorder(LOWERED));
+
+		listPanel.add(propertyListItems, CENTER);
+		listPanel.add(listButtons(), EAST);
+
+		add(listPanel, CENTER);
+
+		add(applyButtonPanel(), SOUTH);
+
+		if (model.getSize() > 0)
+			comboBox.setSelectedItem(model.getElementAt(0));
+	}
+
+	private void populateList(List<String> selectedList) {
+		listModel.removeAllElements();
+		for (String item : selectedList)
+			listModel.addElement(item);
+	}
+
+	private JPanel applyButtonPanel() {
+		JPanel applyPanel = new JPanel(new FlowLayout(RIGHT));
+		JButton applyButton = new JButton("Apply");
+
+		applyButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				config.getInternalPropertyMap().clear();
+				for (String key : values.keySet()) {
+					List<String> properties = values.get(key);
+					config.setPropertyStringList(key, new ArrayList<>(
+							properties));
+				}
+				for (String key : names.keySet())
+					config.setProperty(key, names.get(key));
+				store();
+			}
+		});
+
+		applyPanel.add(applyButton);
+		return applyPanel;
+	}
+
+	private void store() {
+		try {
+			//FIXME
+			//ConfigurationManager.getInstance().store(config);
+		} catch (Exception e1) {
+			logger.error("There was an error storing the configuration:"
+					+ config.getFilePrefix() + " (UUID=" + config.getUUID()
+					+ ")", e1);
+		}
+	}
+
+	private JPanel listButtons() {
+		JPanel panel = new JPanel();
+		panel.setLayout(new BoxLayout(panel, Y_AXIS));
+		JButton addButton = new JButton("+");
+		addButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				String value = input("New property", "Provide new value for: "
+						+ selectedKey);
+				if (value != null) {
+					listModel.addElement(value);
+					values.get(selectedKey).add(value);
+					deleteTypeButton.setEnabled(false);
+				}
+			}
+		});
+
+		JButton deleteButton = new JButton("-");
+		deleteButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				Object value = propertyListItems.getSelectedValue();
+				if (confirm("Confirm removal",
+						"Are you sure you wish to remove " + value + "?")) {
+					listModel.removeElement(value);
+					values.get(selectedKey).remove(value);
+					if (values.get(selectedKey).size() == 0)
+						deleteTypeButton.setEnabled(true);
+				}
+			}
+		});
+
+		panel.add(addButton);
+		panel.add(deleteButton);
+
+		return panel;
+	}
+
+	private boolean confirm(String title, String message) {
+		return showConfirmDialog(this, message, title, YES_NO_OPTION,
+				WARNING_MESSAGE) == YES_OPTION;
+	}
+
+	private String input(String title, String message) {
+		return showInputDialog(this, message, title, INFORMATION_MESSAGE);
+	}
+
+/*	private JButton getAddTypeButton() {
+		JButton result = new JButton("Add");
+		result.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				String val = input("New property value","New property value");
+				if (val!=null) {
+					if (values.get(val) == null) {
+						model.addElement(val);
+						values.put(val, new ArrayList<String>());
+					} else
+						showMessageDialog(ActivityPaletteConfigurationPanel.this, "This property already exists");
+				}
+			}
+		});
+		return result;
+	}
+*/
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java
new file mode 100644
index 0000000..39c4a5a
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+public class ActivityPaletteConfigurationUIFactory implements
+		ConfigurationUIFactory {
+	private ActivityPaletteConfiguration activityPaletteConfiguration;
+
+	@Override
+	public boolean canHandle(String uuid) {
+		return uuid != null && uuid.equals(getConfigurable().getUUID());
+	}
+
+	@Override
+	public Configurable getConfigurable() {
+		return activityPaletteConfiguration;
+	}
+
+	@Override
+	public JPanel getConfigurationPanel() {
+		return new ActivityPaletteConfigurationPanel(
+				activityPaletteConfiguration);
+	}
+
+	public void setActivityPaletteConfiguration(
+			ActivityPaletteConfiguration activityPaletteConfiguration) {
+		this.activityPaletteConfiguration = activityPaletteConfiguration;
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..6c9fed9
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+#net.sf.taverna.t2.workbench.ui.activitypalette.ActivityPaletteConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml
new file mode 100644
index 0000000..34921f5
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="ServiceDescriptionRegistryImpl" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry"/>
+	<service ref="ServiceDescriptionsConfigurationImpl" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration"/>
+
+	<reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+	<reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+	<list id="serviceDescriptionProviders" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider" cardinality="0..N" greedy-proxying="true"/>
+
+</beans:beans>
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml
new file mode 100644
index 0000000..9f7110f
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean name="ServiceDescriptionRegistryImpl" class="net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionRegistryImpl">
+		<constructor-arg name="applicationConfiguration" ref="applicationConfiguration" />
+		<property name="serviceDescriptionProvidersList" ref="serviceDescriptionProviders" />
+		<property name="serviceDescriptionsConfig">
+		 	<ref local="ServiceDescriptionsConfigurationImpl"/>
+		</property>
+	</bean>
+
+	<bean id="ServiceDescriptionsConfigurationImpl" class="net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionsConfigurationImpl">
+		<constructor-arg ref="configurationManager"/>
+	</bean>
+
+	<!-- Don't think ActivityPalette is still used -->
+	<!-- <bean id="ActivityPaletteConfiguration" class="net.sf.taverna.t2.workbench.ui.activitypalette.ActivityPaletteConfiguration">
+		<constructor-arg ref="configurationManager"/>
+	</bean> -->
+
+</beans>
diff --git a/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java b/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java
new file mode 100644
index 0000000..081a9af
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ActivityPaletteConfigurationTest {
+
+	private ActivityPaletteConfiguration conf;
+	private ConfigurationManagerImpl manager;
+
+	@Before
+	public void setup() {
+		File f = new File(System.getProperty("java.io.tmpdir"));
+		final File d = new File(f,UUID.randomUUID().toString());
+		d.mkdir();
+		manager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl() {
+			@Override
+			public File getApplicationHomeDir() {
+				return d;
+			}
+		});
+		conf=new ActivityPaletteConfiguration(manager);
+		conf.restoreDefaults();
+	}
+
+	@Test
+	public void testEmptyList() throws Exception {
+		conf.setPropertyStringList("list", new ArrayList<String>());
+		assertTrue("Result was not a list but was:"+conf.getProperty("list"),conf.getPropertyStringList("list") instanceof List);
+		assertTrue("Result was not a list but was:"+conf.getPropertyStringList("list"),conf.getPropertyStringList("list") instanceof List);
+		List<String> list = conf.getPropertyStringList("list");
+		assertEquals("There should be 0 elements",0,list.size());
+	}
+
+	@Test
+	public void testSingleItem() throws Exception {
+		List<String> list = new ArrayList<>();
+		list.add("fred");
+		conf.setPropertyStringList("single", list);
+
+		assertTrue("should be an ArrayList",conf.getPropertyStringList("single") instanceof List);
+		List<String> l = conf.getPropertyStringList("single");
+		assertEquals("There should be 1 element",1,l.size());
+		assertEquals("Its value should be fred","fred",l.get(0));
+	}
+
+	@Test
+	public void testList() throws Exception {
+		List<String> list = new ArrayList<>();
+		list.add("fred");
+		list.add("bloggs");
+		conf.setPropertyStringList("list", list);
+
+		assertTrue("should be an ArrayList",conf.getPropertyStringList("list") instanceof List);
+		List<String> l = conf.getPropertyStringList("list");
+		assertEquals("There should be 1 element",2,l.size());
+		assertEquals("Its value should be fred","fred",l.get(0));
+		assertEquals("Its value should be bloggs","bloggs",l.get(1));
+	}
+
+	@Test
+	public void testNull() throws Exception {
+		assertNull("Should return null",conf.getProperty("blah blah blah"));
+	}
+}
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI
new file mode 100644
index 0000000..9f3c02d
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.partition.DummyPartitionAlgorithmSet
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI
new file mode 100644
index 0000000..f5e7226
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.partition.DummyExtractor1
+net.sf.taverna.t2.partition.DummyExtractor2
+net.sf.taverna.t2.partition.DummyExtractor3
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory
new file mode 100644
index 0000000..2d26d31a
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.partition.DummyActivityQueryFactory
+net.sf.taverna.t2.partition.DummyQueryFactory
\ No newline at end of file
diff --git a/taverna-workbench-configuration-impl/pom.xml b/taverna-workbench-configuration-impl/pom.xml
new file mode 100644
index 0000000..19356bb
--- /dev/null
+++ b/taverna-workbench-configuration-impl/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>configuration-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Configuration Management Implementations</name>
+	<description>General configuration management</description>
+
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>configuration-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-impl</artifactId>
+			<version>0.1.1-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-impl</artifactId>
+			<version>0.1.1-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+
+</project>
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java
new file mode 100644
index 0000000..0e63a4a
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * An implementation of Configurable for general Workbench configuration
+ * properties
+ * 
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchConfigurationImpl extends AbstractConfigurable implements
+		WorkbenchConfiguration {
+	private static Logger logger = Logger
+			.getLogger(WorkbenchConfiguration.class);
+	private static final int DEFAULT_MAX_MENU_ITEMS = 20;
+	public static final String TAVERNA_DOTLOCATION = "taverna.dotlocation";
+	public static final String MAX_MENU_ITEMS = "taverna.maxmenuitems";
+	public static final String WARN_INTERNAL_ERRORS = "taverna.warninternal";
+	public static final String CAPTURE_CONSOLE = "taverna.captureconsole";
+	private static final String BIN = "bin";
+	private static final String BUNDLE_CONTENTS = "Contents";
+	private static final String BUNDLE_MAC_OS = "MacOS";
+	private static final String DOT_EXE = "dot.exe";
+	private static final String DOT_FALLBACK = "dot";
+	public static String uuid = "c14856f0-5967-11dd-ae16-0800200c9a66";
+	private static final String MAC_OS_X = "Mac OS X";
+	private static final String WIN32I386 = "win32i386";
+	private static final String WINDOWS = "Windows";
+
+	private ApplicationConfiguration applicationConfiguration;
+
+	/**
+	 * Constructs a new <code>WorkbenchConfigurationImpl</code>.
+	 * 
+	 * @param configurationManager
+	 */
+	public WorkbenchConfigurationImpl(ConfigurationManager configurationManager) {
+		super(configurationManager);
+	}
+
+	Map<String, String> defaultWorkbenchProperties = null;
+	Map<String, String> workbenchProperties = new HashMap<String, String>();
+
+	@Override
+	public String getCategory() {
+		return "general";
+	}
+
+	@Override
+	public Map<String, String> getDefaultPropertyMap() {
+		if (defaultWorkbenchProperties == null) {
+			defaultWorkbenchProperties = new HashMap<>();
+			String dotLocation = System.getProperty(TAVERNA_DOTLOCATION) != null ? System
+					.getProperty(TAVERNA_DOTLOCATION) : getDefaultDotLocation();
+			if (dotLocation != null)
+				defaultWorkbenchProperties
+						.put(TAVERNA_DOTLOCATION, dotLocation);
+			defaultWorkbenchProperties.put(MAX_MENU_ITEMS,
+					Integer.toString(DEFAULT_MAX_MENU_ITEMS));
+			defaultWorkbenchProperties.put(WARN_INTERNAL_ERRORS,
+					Boolean.FALSE.toString());
+			defaultWorkbenchProperties.put(CAPTURE_CONSOLE,
+					Boolean.TRUE.toString());
+		}
+		return defaultWorkbenchProperties;
+	}
+
+	@Override
+	public String getDisplayName() {
+		return "Workbench";
+	}
+
+	@Override
+	public String getFilePrefix() {
+		return "Workbench";
+	}
+
+	@Override
+	public String getUUID() {
+		return uuid;
+	}
+
+	@Override
+	public boolean getWarnInternalErrors() {
+		String property = getProperty(WARN_INTERNAL_ERRORS);
+		return Boolean.parseBoolean(property);
+	}
+
+	@Override
+	public boolean getCaptureConsole() {
+		String property = getProperty(CAPTURE_CONSOLE);
+		return Boolean.parseBoolean(property);
+	}
+
+	@Override
+	public void setWarnInternalErrors(boolean warnInternalErrors) {
+		setProperty(WARN_INTERNAL_ERRORS, Boolean.toString(warnInternalErrors));
+	}
+
+	@Override
+	public void setCaptureConsole(boolean captureConsole) {
+		setProperty(CAPTURE_CONSOLE, Boolean.toString(captureConsole));
+	}
+
+	@Override
+	public void setMaxMenuItems(int maxMenuItems) {
+		if (maxMenuItems < 2)
+			throw new IllegalArgumentException(
+					"Maximum menu items must be at least 2");
+		setProperty(MAX_MENU_ITEMS, Integer.toString(maxMenuItems));
+	}
+
+	@Override
+	public int getMaxMenuItems() {
+		String property = getProperty(MAX_MENU_ITEMS);
+		try {
+			int maxMenuItems = Integer.parseInt(property);
+			if (maxMenuItems >= 2)
+				return maxMenuItems;
+			logger.warn(MAX_MENU_ITEMS + " can't be less than 2");
+		} catch (NumberFormatException ex) {
+			logger.warn("Invalid number for " + MAX_MENU_ITEMS + ": "
+					+ property);
+		}
+		// We'll return the default instead
+		return DEFAULT_MAX_MENU_ITEMS;
+	}
+
+	@Override
+	public String getDotLocation() {
+		return getProperty(TAVERNA_DOTLOCATION);
+	}
+
+	@Override
+	public void setDotLocation(String dotLocation) {
+		setProperty(TAVERNA_DOTLOCATION, dotLocation);
+	}
+
+	private String getDefaultDotLocation() {
+		if (applicationConfiguration == null)
+			return null;
+		File startupDir = applicationConfiguration.getStartupDir();
+		if (startupDir == null)
+			return DOT_FALLBACK;
+
+		String os = System.getProperty("os.name");
+		if (os.equals(MAC_OS_X))
+			if (startupDir.getParentFile() != null) {
+				File contentsDir = startupDir.getParentFile().getParentFile();
+				if (contentsDir != null
+						&& contentsDir.getName().equalsIgnoreCase(
+								BUNDLE_CONTENTS)) {
+					File dot = new File(new File(contentsDir, BUNDLE_MAC_OS),
+							DOT_FALLBACK);
+					if (dot.exists())
+						return dot.getAbsolutePath();
+				}
+			} else if (os.startsWith(WINDOWS)) {
+				File binWin386Dir = new File(new File(startupDir, BIN),
+						WIN32I386);
+				File dot = new File(binWin386Dir, DOT_EXE);
+				if (dot.exists())
+					return dot.getAbsolutePath();
+			}
+		return DOT_FALLBACK;
+	}
+
+	/**
+	 * Sets the applicationConfiguration.
+	 * 
+	 * @param applicationConfiguration
+	 *            the new value of applicationConfiguration
+	 */
+	public void setApplicationConfiguration(
+			ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+		defaultWorkbenchProperties = null;
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java
new file mode 100644
index 0000000..ecddc35
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java
@@ -0,0 +1,266 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import static java.awt.GridBagConstraints.*;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openIcon;
+
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkbenchConfigurationPanel extends JPanel {
+	private static final String RESTART_MSG = "For the new configuration to be fully applied, it is advised to restart Taverna.";
+	private static final String DOT_PATH_MSG = "Path to Graphviz executable <code>dot</code>:";
+	private static final String CONTEXT_MENU_SIZE_MSG = "Maximum number of services/ports in right-click menu:";
+	private static Logger logger = Logger
+			.getLogger(WorkbenchConfigurationUIFactory.class);
+
+	private JTextField dotLocation = new JTextField(25);
+	private JTextField menuItems = new JTextField(10);
+	private JCheckBox warnInternal = new JCheckBox("Warn on internal errors");
+	private JCheckBox captureConsole = new JCheckBox(
+			"Capture output on stdout/stderr to log file");
+
+	private final WorkbenchConfiguration workbenchConfiguration;
+
+	public WorkbenchConfigurationPanel(
+			WorkbenchConfiguration workbenchConfiguration) {
+		super();
+		this.workbenchConfiguration = workbenchConfiguration;
+		initComponents();
+	}
+
+	private static JLabel htmlLabel(String html) {
+		return new JLabel("<html><body>" + html + "</body></html>");
+	}
+
+	private void initComponents() {
+		this.setLayout(new GridBagLayout());
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		// Title describing what kind of settings we are configuring here
+		JTextArea descriptionText = new JTextArea(
+				"General Workbench configuration");
+		descriptionText.setLineWrap(true);
+		descriptionText.setWrapStyleWord(true);
+		descriptionText.setEditable(false);
+		descriptionText.setFocusable(false);
+		descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+		gbc.anchor = WEST;
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.gridwidth = 2;
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.fill = HORIZONTAL;
+		this.add(descriptionText, gbc);
+
+		gbc.gridx = 0;
+		gbc.gridy = 1;
+		gbc.gridwidth = 2;
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.insets = new Insets(10, 5, 0, 0);
+		gbc.fill = NONE;
+		this.add(htmlLabel(DOT_PATH_MSG), gbc);
+
+		dotLocation.setText(workbenchConfiguration.getDotLocation());
+		gbc.gridy++;
+		gbc.gridwidth = 1;
+		gbc.weightx = 1.0;
+		gbc.insets = new Insets(0, 0, 0, 0);
+		gbc.fill = HORIZONTAL;
+		this.add(dotLocation, gbc);
+
+		JButton browseButton = new JButton();
+		gbc.gridx = 1;
+		gbc.weightx = 0.0;
+		gbc.fill = NONE;
+		this.add(browseButton, gbc);
+		browseButton.setAction(new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				System.setProperty("com.apple.macos.use-file-dialog-packages",
+						"false");
+				JFileChooser fileChooser = new JFileChooser();
+				fileChooser.putClientProperty(
+						"JFileChooser.appBundleIsTraversable", "always");
+				fileChooser.putClientProperty(
+						"JFileChooser.packageIsTraversable", "always");
+
+				fileChooser.setDialogTitle("Browse for dot");
+
+				fileChooser.resetChoosableFileFilters();
+				fileChooser.setAcceptAllFileFilterUsed(false);
+
+				fileChooser.setMultiSelectionEnabled(false);
+
+				int returnVal = fileChooser
+						.showOpenDialog(WorkbenchConfigurationPanel.this);
+				if (returnVal == APPROVE_OPTION)
+					dotLocation.setText(fileChooser.getSelectedFile()
+							.getAbsolutePath());
+			}
+		});
+		browseButton.setIcon(openIcon);
+
+		gbc.gridx = 0;
+		gbc.gridy++;
+		gbc.gridwidth = 2;
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.insets = new Insets(10, 5, 0, 0);
+		gbc.fill = HORIZONTAL;
+		this.add(htmlLabel(CONTEXT_MENU_SIZE_MSG), gbc);
+
+		menuItems.setText(Integer.toString(workbenchConfiguration
+				.getMaxMenuItems()));
+		gbc.gridy++;
+		gbc.weightx = 1.0;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(0, 0, 0, 0);
+		gbc.fill = HORIZONTAL;
+		this.add(menuItems, gbc);
+
+		gbc.gridx = 0;
+		gbc.gridy++;
+		gbc.gridwidth = 2;
+		gbc.weightx = 1.0;
+		gbc.fill = HORIZONTAL;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		warnInternal
+				.setSelected(workbenchConfiguration.getWarnInternalErrors());
+		this.add(warnInternal, gbc);
+
+		gbc.gridy++;
+		gbc.insets = new Insets(0, 0, 10, 0);
+		captureConsole.setSelected(workbenchConfiguration.getCaptureConsole());
+		this.add(captureConsole, gbc);
+
+		// Add the buttons panel
+		gbc.gridx = 0;
+		gbc.gridy++;
+		gbc.gridwidth = 3;
+		gbc.weightx = 1.0;
+		gbc.weighty = 1.0;
+		gbc.fill = BOTH;
+		gbc.anchor = SOUTH;
+		this.add(getButtonsPanel(), gbc);
+	}
+
+	private Component getButtonsPanel() {
+		final JPanel panel = new JPanel();
+		panel.setLayout(new FlowLayout(FlowLayout.CENTER));
+
+		/**
+		 * The helpButton shows help about the current component
+		 */
+		JButton helpButton = new JButton(new AbstractAction("Help") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				showHelp(panel);
+			}
+		});
+		panel.add(helpButton);
+
+		/**
+		 * The resetButton changes the property values shown to those
+		 * corresponding to the configuration currently applied.
+		 */
+		JButton resetButton = new JButton(new AbstractAction("Reset") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				resetFields();
+			}
+		});
+		panel.add(resetButton);
+
+		JButton applyButton = new JButton(new AbstractAction("Apply") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				String menus = menuItems.getText();
+				try {
+					workbenchConfiguration.setMaxMenuItems(Integer
+							.valueOf(menus));
+				} catch (IllegalArgumentException e) {
+					String message = "Invalid menu items number " + menus
+							+ ":\n" + e.getLocalizedMessage();
+					showMessageDialog(panel, message, "Invalid menu items",
+							WARNING_MESSAGE);
+					return;
+				}
+
+				workbenchConfiguration.setCaptureConsole(captureConsole
+						.isSelected());
+				workbenchConfiguration.setWarnInternalErrors(warnInternal
+						.isSelected());
+				workbenchConfiguration.setDotLocation(dotLocation.getText());
+				try {
+					showMessageDialog(panel, RESTART_MSG, "Restart adviced",
+							INFORMATION_MESSAGE);
+				} catch (Exception e) {
+					logger.error("Error storing updated configuration", e);
+				}
+			}
+		});
+		panel.add(applyButton);
+		return panel;
+	}
+
+	/**
+	 * Resets the shown field values to those currently set (last saved) in the
+	 * configuration.
+	 * 
+	 * @param configurable
+	 */
+	private void resetFields() {
+		menuItems.setText(Integer.toString(workbenchConfiguration
+				.getMaxMenuItems()));
+		dotLocation.setText(workbenchConfiguration.getDotLocation());
+		warnInternal
+				.setSelected(workbenchConfiguration.getWarnInternalErrors());
+		captureConsole.setSelected(workbenchConfiguration.getCaptureConsole());
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java
new file mode 100644
index 0000000..263233f
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+public class WorkbenchConfigurationUIFactory implements ConfigurationUIFactory {
+	private WorkbenchConfiguration workbenchConfiguration;
+
+	@Override
+	public boolean canHandle(String uuid) {
+		return uuid.equals(workbenchConfiguration.getUUID());
+	}
+
+	@Override
+	public JPanel getConfigurationPanel() {
+		return new WorkbenchConfigurationPanel(workbenchConfiguration);
+	}
+
+	@Override
+	public Configurable getConfigurable() {
+		return workbenchConfiguration;
+	}
+
+	public void setWorkbenchConfiguration(
+			WorkbenchConfiguration workbenchConfiguration) {
+		this.workbenchConfiguration = workbenchConfiguration;
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java
new file mode 100644
index 0000000..4c03dbe
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.colour;
+
+import static java.awt.Color.WHITE;
+import static java.awt.Color.decode;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+/**
+ * A factory class that determines the colour that a Colourable UI component
+ * should be displayed as, according to a schema configured by the user.
+ * 
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @see Colourable
+ */
+public class ColourManagerImpl extends AbstractConfigurable implements
+		ColourManager {
+	// Names of things that may be coloured
+	private static final String WORKFLOW_PORT_OBJECT = "uk.org.taverna.scufl2.api.port.WorkflowPort";
+	private static final String PROCESSOR_PORT_OBJECT = "uk.org.taverna.scufl2.api.port.ProcessorPort";
+	private static final String PROCESSOR_OBJECT = "uk.org.taverna.scufl2.api.core.Processor";
+	private static final String MERGE_OBJECT = "net.sf.taverna.t2.workflowmodel.Merge";
+	private static final String NONEXECUTABLE_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/nonExecutable";
+	private static final String XML_SPLITTER_OUT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/xml-splitter/out";
+	private static final String XML_SPLITTER_IN_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/xml-splitter/in";
+	private static final String LOCALWORKER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/localworker";
+	private static final String WSDL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/wsdl";
+	private static final String CONSTANT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/constant";
+	private static final String SOAPLAB_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/soaplab";
+	private static final String RSHELL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/rshell";
+	private static final String NESTED_WORKFLOW = "http://ns.taverna.org.uk/2010/activity/nested-workflow";
+	private static final String MOBY_PARSER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/parser";
+	private static final String MOBY_OBJECT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/object";
+	private static final String MOBY_SERVICE_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/service";
+	private static final String BIOMART_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomart";
+	private static final String BEANSHELL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/beanshell";
+	private static final String APICONSUMER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/apiconsumer";
+
+	// Names of colours used
+	private static final String burlywood2 = "#deb887";
+	private static final String darkgoldenrod1 = "#ffb90f";
+	private static final String darkolivegreen3 = "#a2cd5a";
+	private static final String gold = "#ffd700";
+	private static final String grey = "#777777";
+	private static final String lightcyan2 = "#d1eeee";
+	private static final String lightgoldenrodyellow = "#fafad2";
+	// light purple non standard
+	private static final String lightpurple = "#ab92ea";
+	private static final String lightsteelblue = "#b0c4de";
+	private static final String mediumorchid2 = "#d15fee";
+	private static final String palegreen = "#98fb98";
+	private static final String pink = "#ffc0cb";
+	private static final String purplish = "#8070ff";
+	// ShadedLabel.Orange
+	private static final String shadedorange = "#eece8f";
+	// ShadedLabel.Green
+	private static final String shadedgreen = "#a1c69d";
+	// slightly lighter than the real steelblue4
+	private static final String steelblue4 = "#648faa";
+	private static final String turquoise = "#77aadd";
+	private static final String white = "#ffffff";
+
+	private Map<String, String> defaultPropertyMap;
+	private Map<Object, Color> cachedColours;
+
+	public ColourManagerImpl(ConfigurationManager configurationManager) {
+		super(configurationManager);
+		initialiseDefaults();
+	}
+
+	@Override
+	public String getCategory() {
+		return "colour";
+	}
+
+	@Override
+	public Map<String, String> getDefaultPropertyMap() {
+		if (defaultPropertyMap == null)
+			initialiseDefaults();
+		return defaultPropertyMap;
+	}
+
+	@Override
+	public String getDisplayName() {
+		return "Colour Management";
+	}
+
+	@Override
+	public String getFilePrefix() {
+		return "ColourManagement";
+	}
+
+	/**
+	 * Unique identifier for this ColourManager
+	 */
+	@Override
+	public String getUUID() {
+		return "a2148420-5967-11dd-ae16-0800200c9a66";
+	}
+
+	private void initialiseDefaults() {
+		defaultPropertyMap = new HashMap<>();
+		cachedColours = new HashMap<>();
+
+		defaultPropertyMap.put(APICONSUMER_ACTIVITY, palegreen);
+		defaultPropertyMap.put(BEANSHELL_ACTIVITY, burlywood2);
+		defaultPropertyMap.put(BIOMART_ACTIVITY, lightcyan2);
+		defaultPropertyMap.put(CONSTANT_ACTIVITY, lightsteelblue);
+		defaultPropertyMap.put(LOCALWORKER_ACTIVITY, mediumorchid2);
+		defaultPropertyMap.put(MOBY_SERVICE_ACTIVITY, darkgoldenrod1);
+		defaultPropertyMap.put(MOBY_OBJECT_ACTIVITY, gold);
+		defaultPropertyMap.put(MOBY_PARSER_ACTIVITY, white);
+		defaultPropertyMap.put(NESTED_WORKFLOW, pink);
+		defaultPropertyMap.put(RSHELL_ACTIVITY, steelblue4);
+		defaultPropertyMap.put(SOAPLAB_ACTIVITY, lightgoldenrodyellow);
+		defaultPropertyMap.put(WSDL_ACTIVITY, darkolivegreen3);
+		defaultPropertyMap.put(XML_SPLITTER_IN_ACTIVITY, lightpurple);
+		defaultPropertyMap.put(XML_SPLITTER_OUT_ACTIVITY, lightpurple);
+
+		defaultPropertyMap.put(NONEXECUTABLE_ACTIVITY, grey);
+
+		defaultPropertyMap.put(MERGE_OBJECT, turquoise);
+		defaultPropertyMap.put(PROCESSOR_OBJECT, shadedgreen);
+		defaultPropertyMap.put(PROCESSOR_PORT_OBJECT, purplish);
+		defaultPropertyMap.put(WORKFLOW_PORT_OBJECT, shadedorange);
+	}
+
+	@Override
+	public Color getPreferredColour(String itemKey) {
+		Color colour = cachedColours.get(itemKey);
+		if (colour == null) {
+			String colourString = (String) getProperty(itemKey);
+			colour = colourString == null ? WHITE : decode(colourString);
+			cachedColours.put(itemKey, colour);
+		}
+		return colour;
+	}
+
+	@Override
+	public void setPreferredColour(String itemKey, Color colour) {
+		cachedColours.put(itemKey, colour);
+	}
+
+	@Override
+	public void restoreDefaults() {
+		super.restoreDefaults();
+		if (cachedColours == null)
+			cachedColours = new HashMap<>();
+		else
+			cachedColours.clear();
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java
new file mode 100644
index 0000000..0ff6c65
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.mimetype;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.workbench.configuration.mimetype.MimeTypeManager;
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class MimeTypeManagerImpl extends AbstractConfigurable implements
+		MimeTypeManager {
+	/**
+	 * Constructs a new <code>MimeTypeManagerImpl</code>.
+	 * 
+	 * @param configurationManager
+	 */
+	public MimeTypeManagerImpl(ConfigurationManager configurationManager) {
+		super(configurationManager);
+	}
+
+	@Override
+	public String getCategory() {
+		return "Mime Type";
+	}
+
+	@Override
+	public Map<String, String> getDefaultPropertyMap() {
+		HashMap<String, String> map = new HashMap<>();
+		map.put("text/plain", "Plain Text");
+		map.put("text/xml", "XML Text");
+		map.put("text/html", "HTML Text");
+		map.put("text/rtf", "Rich Text Format");
+		map.put("text/x-graphviz", "Graphviz Dot File");
+		map.put("image/png", "PNG Image");
+		map.put("image/jpeg", "JPEG Image");
+		map.put("image/gif", "GIF Image");
+		map.put("application/octet-stream", "Binary Data");
+		map.put("application/zip", "Zip File");
+		map.put("chemical/x-swissprot", "SWISSPROT Flat File");
+		map.put("chemical/x-embl-dl-nucleotide", "EMBL Flat File");
+		map.put("chemical/x-ppd", "PPD File");
+		map.put("chemical/seq-aa-genpept", "Genpept Protein");
+		map.put("chemical/seq-na-genbank", "Genbank Nucleotide");
+		map.put("chemical/x-pdb", "PDB 3D Structure File");
+		return map;
+	}
+
+	@Override
+	public String getUUID() {
+		return "b9277fa0-5967-11dd-ae16-0800200c9a66";
+	}
+
+	@Override
+	public String getDisplayName() {
+		return "Mime Type Manager";
+	}
+
+	@Override
+	public String getFilePrefix() {
+		return "MimeTypeManagerImpl";
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java
new file mode 100644
index 0000000..4910f78
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.ui;
+
+import static javax.swing.JSplitPane.HORIZONTAL_SPLIT;
+import static net.sf.taverna.t2.workbench.helper.HelpCollator.registerComponent;
+import static net.sf.taverna.t2.workbench.helper.Helper.setKeyCatcher;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.ListModel;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+public class T2ConfigurationFrameImpl implements T2ConfigurationFrame {
+	private static Logger logger = Logger.getLogger(T2ConfigurationFrameImpl.class);
+	private static final int FRAME_WIDTH = 700;
+	private static final int FRAME_HEIGHT = 450;
+
+	private List<ConfigurationUIFactory> configurationUIFactories = new ArrayList<>();
+
+	private JFrame frame;
+	private JSplitPane splitPane;
+	private JList<ConfigurableItem> list;
+
+	public T2ConfigurationFrameImpl() {
+	}
+
+	@Override
+	public void showFrame() {
+		getFrame().setVisible(true);
+	}
+
+	@Override
+	public void showConfiguration(String name) {
+		showFrame();
+		ListModel<ConfigurableItem> lm = list.getModel();
+		for (int i = 0; i < lm.getSize(); i++)
+			if (lm.getElementAt(i).toString().equals(name)) {
+				list.setSelectedIndex(i);
+				break;
+			}
+	}
+
+	private JFrame getFrame() {
+		if (frame != null)
+			return frame;
+
+		frame = new JFrame();
+		setKeyCatcher(frame);
+		registerComponent(frame);
+		frame.setLayout(new BorderLayout());
+
+		/*
+		 * Split pane to hold list of properties (on the left) and their
+		 * configurable options (on the right)
+		 */
+		splitPane = new JSplitPane(HORIZONTAL_SPLIT);
+		splitPane.setBorder(null);
+
+		list = getConfigurationList();
+		JScrollPane jspList = new JScrollPane(list);
+		jspList.setBorder(new EmptyBorder(5, 5, 5, 5));
+		jspList.setMinimumSize(new Dimension(150,
+				jspList.getPreferredSize().height));
+
+		splitPane.setLeftComponent(jspList);
+		splitPane.setRightComponent(new JPanel());
+		splitPane.setDividerSize(1);
+
+		// select first item if one exists
+		if (list.getModel().getSize() > 0)
+			list.setSelectedValue(list.getModel().getElementAt(0), true);
+
+		frame.add(splitPane);
+		frame.setSize(new Dimension(FRAME_WIDTH, FRAME_HEIGHT));
+		return frame;
+	}
+
+	private JList<ConfigurableItem> getConfigurationList() {
+		if (list != null)
+			return list;
+
+		list = new JList<>();
+		list.addListSelectionListener(new ListSelectionListener() {
+			@Override
+			public void valueChanged(ListSelectionEvent e) {
+				if (list.getSelectedValue() instanceof ConfigurableItem) {
+					ConfigurableItem item = (ConfigurableItem) list
+							.getSelectedValue();
+					setMainPanel(item.getPanel());
+				}
+
+				/*
+				 * Keep the split pane's divider at its current position - but
+				 * looks ugly. The problem with divider moving from its current
+				 * position after selecting an item from the list on the left is
+				 * that the right hand side panels are loaded dynamically and it
+				 * seems there is nothing we can do about it - it's just the
+				 * JSplitPane's behaviour
+				 */
+				// splitPane.setDividerLocation(splitPane.getLastDividerLocation());
+			}
+		});
+		list.setListData(getListItems());
+		return list;
+	}
+
+	private void setMainPanel(JPanel panel) {
+		panel.setBorder(new EmptyBorder(15, 15, 15, 15));
+		splitPane.setRightComponent(panel);
+	}
+
+	public void setConfigurationUIFactories(
+			List<ConfigurationUIFactory> configurationUIFactories) {
+		this.configurationUIFactories = configurationUIFactories;
+	}
+
+	private ConfigurableItem[] getListItems() {
+		List<ConfigurableItem> arrayList = new ArrayList<>();
+		for (ConfigurationUIFactory fac : configurationUIFactories) {
+			String name = fac.getConfigurable().getDisplayName();
+			if (name != null) {
+				logger.info("Adding configurable for name: " + name);
+				arrayList.add(new ConfigurableItem(fac));
+			} else {
+				logger.warn("The configurable " + fac.getConfigurable().getClass()
+						+ " has a null name");
+			}
+		}
+		// Sort the list alphabetically
+		ConfigurableItem[] array = arrayList.toArray(new ConfigurableItem[0]);
+		Arrays.sort(array, new Comparator<ConfigurableItem>() {
+			@Override
+			public int compare(ConfigurableItem item1, ConfigurableItem item2) {
+				return item1.toString().compareToIgnoreCase(item2.toString());
+			}
+		});
+		return array;
+	}
+
+	public void update(Object service, Map<?, ?> properties) {
+		getConfigurationList().setListData(getListItems());
+		if (frame != null) {
+			frame.revalidate();
+			frame.repaint();
+			// select first item if one exists
+			if (list.getModel().getSize() > 0)
+				list.setSelectedValue(list.getModel().getElementAt(0), true);
+		}
+	}
+
+	class ConfigurableItem {
+		private final ConfigurationUIFactory factory;
+
+		public ConfigurableItem(ConfigurationUIFactory factory) {
+			this.factory = factory;
+		}
+
+		public JPanel getPanel() {
+			return factory.getConfigurationPanel();
+		}
+
+		@Override
+		public String toString() {
+			return factory.getConfigurable().getDisplayName();
+		}
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java
new file mode 100644
index 0000000..453f0c0
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.ui;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+
+public class WorkbenchConfigurationMenu extends AbstractMenuAction {
+	private static final String MAC_OS_X = "Mac OS X";
+
+	private T2ConfigurationFrame t2ConfigurationFrame;
+
+	public WorkbenchConfigurationMenu() {
+		super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#preferences"),
+				100);
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		return new AbstractAction("Preferences") {
+			@Override
+			public void actionPerformed(ActionEvent event) {
+				t2ConfigurationFrame.showFrame();
+			}
+		};
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return !MAC_OS_X.equalsIgnoreCase(System.getProperty("os.name"));
+	}
+
+	public void setT2ConfigurationFrame(T2ConfigurationFrame t2ConfigurationFrame) {
+		this.t2ConfigurationFrame = t2ConfigurationFrame;
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java
new file mode 100644
index 0000000..d131ac3
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.ui;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class WorkbenchPreferencesSection extends AbstractMenuSection {
+	private static final URI FILE_MENU = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#file");
+	private static final URI PREFERENCES_MENU_ITEM = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#preferences");
+
+	public WorkbenchPreferencesSection() {
+		super(FILE_MENU, 100, PREFERENCES_MENU_ITEM);
+	}
+}
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..3b51dd4
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchPreferencesSection
+net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchConfigurationMenu
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..4af55ec
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml
new file mode 100644
index 0000000..29aea44
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="t2ConfigurationFrame" interface="net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame" />
+
+	<service ref="WorkbenchConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+	<service ref="WorkbenchPreferencesSection" auto-export="interfaces" />
+	<service ref="WorkbenchConfigurationMenu" auto-export="interfaces" />
+
+	<service ref="ColourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+	<service ref="WorkbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+	<service ref="MimeTypeManager" interface="net.sf.taverna.t2.workbench.configuration.mimetype.MimeTypeManager" />
+
+	<reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+	<reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+	<list id="configurationUIFactories" interface="uk.org.taverna.configuration.ConfigurationUIFactory" cardinality="0..N">
+		<listener ref="t2ConfigurationFrame" bind-method="update" unbind-method="update"/>
+	</list>
+
+</beans:beans>
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml
new file mode 100644
index 0000000..40da7fd
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="t2ConfigurationFrame" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.T2ConfigurationFrameImpl">
+		<property name="configurationUIFactories" ref="configurationUIFactories" />
+	</bean>
+
+	<bean id="WorkbenchConfigurationUIFactory" class="net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationUIFactory">
+		<property name="workbenchConfiguration">
+			<ref local="WorkbenchConfiguration"/>
+		</property>
+	</bean>
+
+	<bean id="WorkbenchPreferencesSection" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchPreferencesSection" />
+	<bean id="WorkbenchConfigurationMenu" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchConfigurationMenu">
+		<property name="t2ConfigurationFrame">
+			<ref local="t2ConfigurationFrame"/>
+		</property>
+	</bean>
+
+	<bean id="ColourManager" class="net.sf.taverna.t2.workbench.ui.impl.configuration.colour.ColourManagerImpl">
+		<constructor-arg ref="configurationManager"/>
+	</bean>
+	<bean id="WorkbenchConfiguration" class="net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationImpl">
+		<constructor-arg ref="configurationManager"/>
+		<property name="applicationConfiguration" ref="applicationConfiguration" />
+	</bean>
+	<bean id="MimeTypeManager" class="net.sf.taverna.t2.workbench.ui.impl.configuration.mimetype.MimeTypeManagerImpl">
+		<constructor-arg ref="configurationManager"/>
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java
new file mode 100644
index 0000000..1202c11
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.UUID;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.ui.impl.configuration.colour.ColourManagerImpl;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ConfigurationManagerTest {
+
+	ConfigurationManagerImpl configurationManager;
+
+	@Before
+	public void setup() {
+		configurationManager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl());
+	}
+
+	@Test
+	public void createConfigManager() {
+		assertNotNull("Config Manager should not be null", configurationManager);
+	}
+
+	@Ignore("Hardcoded /Users/Ian") //FIXME: update test to work using File.createTempFile(...)
+	@Test
+	public void populateConfigOfColourmanager() {
+		ColourManager manager= new ColourManagerImpl(null);
+
+		manager.setProperty("colour.first", "25");
+		manager.setProperty("colour.second", "223");
+
+		configurationManager.setBaseConfigLocation(new File("/Users/Ian/scratch"));
+		try {
+			configurationManager.store(manager);
+		} catch (Exception e1) {
+			e1.printStackTrace();
+		}
+
+		ColourManager manager2 = new ColourManagerImpl(configurationManager);
+
+		try {
+			configurationManager.populate(manager2);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+
+		assertEquals("Properties do not match", manager2.getProperty("colour.first"), manager.getProperty("colour.first"));
+		assertEquals("Properties do not match", manager2.getProperty("colour.second"), manager.getProperty("colour.second"));
+
+
+	}
+
+	@Test
+	public void saveColoursForDummyColourable() {
+		String dummy = "";
+		ColourManager manager=new ColourManagerImpl(configurationManager);
+		manager.setProperty(dummy.getClass().getCanonicalName(), "#000000");
+
+		File f = new File(System.getProperty("java.io.tmpdir"));
+		File d = new File(f, UUID.randomUUID().toString());
+		d.mkdir();
+		configurationManager.setBaseConfigLocation(d);
+		try {
+			configurationManager.store(manager);
+		} catch (Exception e1) {
+			e1.printStackTrace();
+		}
+
+		try {
+			configurationManager.populate(manager);
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+
+
+	}
+
+}
diff --git a/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java
new file mode 100644
index 0000000..0239ea8
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.colour;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.UUID;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ColourManagerTest {
+
+	private ConfigurationManagerImpl configurationManager;
+
+	@Before
+	public void setup() {
+		configurationManager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl());
+
+		File f = new File(System.getProperty("java.io.tmpdir"));
+		File d = new File(f, UUID.randomUUID().toString());
+		d.mkdir();
+		configurationManager.setBaseConfigLocation(d);
+	}
+
+	@Test
+	public void testGetPreferredColourEqualsWhite() throws Exception {
+		String dummy = new String();
+
+		Color c = new ColourManagerImpl(configurationManager).getPreferredColour(dummy);
+		assertEquals("The default colour should be WHITE", Color.WHITE, c);
+	}
+
+	@Test
+	public void testConfigurableness() throws Exception {
+		ColourManager manager = new ColourManagerImpl(configurationManager);
+		assertTrue(manager instanceof Configurable);
+
+		assertEquals("wrong category", "colour", manager.getCategory());
+		assertEquals("wrong name", "Colour Management", manager.getDisplayName());
+		assertEquals("wrong UUID", "a2148420-5967-11dd-ae16-0800200c9a66",
+				manager.getUUID());
+		assertNotNull("there is no default property map", manager
+				.getDefaultPropertyMap());
+	}
+
+	@Test
+	public void saveAsWrongArrayType() throws Exception {
+		String dummy = "";
+		ColourManager manager = new ColourManagerImpl(configurationManager);
+		manager.setProperty(dummy.getClass().getCanonicalName(), "#ffffff");
+
+		File baseLoc = File.createTempFile("test", "scratch");
+		baseLoc.delete();
+		assertTrue("Could not make directory " + baseLoc, baseLoc.mkdir());
+		configurationManager.setBaseConfigLocation(baseLoc);
+		configurationManager.store(manager);
+		configurationManager.populate(manager);
+		manager.getPreferredColour(dummy);
+	}
+
+}
diff --git a/taverna-workbench-contextual-views-impl/pom.xml b/taverna-workbench-contextual-views-impl/pom.xml
new file mode 100644
index 0000000..1cafa80
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>contextual-views-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Contextual Views Implementation</name>
+	<description>Contextual views for the activities</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>contextual-views-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>selection-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>javax.help</groupId>
+			<artifactId>javahelp</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java
new file mode 100644
index 0000000..8bac354
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+
+/**
+ * @author Alan R Williams
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+
+/**
+ * An SPI registry for discovering ActivityViewFactories for a given object,
+ * like an {@link net.sf.taverna.t2.workflowmodel.processor.activity.Activity}.
+ * <p>
+ * For {@link ContextualViewFactory factories} to be found, its full qualified
+ * name needs to be defined as a resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewFactory</code>
+ * 
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @author Stian Soiland-Reyes
+ * 
+ * @see ContextualViewFactory
+ */
+public class ContextualViewFactoryRegistryImpl implements
+		ContextualViewFactoryRegistry {
+	private List<ContextualViewFactory<?>> contextualViewFactories;
+
+	/**
+	 * Discover and return the ContextualViewFactory associated to the provided
+	 * object. This is accomplished by returning the discovered
+	 * {@link ContextualViewFactory#canHandle(Object)} that returns true for
+	 * that Object.
+	 * 
+	 * @param object
+	 * @return
+	 * @see ContextualViewFactory#canHandle(Object)
+	 */
+	@Override
+	public List<ContextualViewFactory<?>> getViewFactoriesForObject(
+			Object object) {
+		List<ContextualViewFactory<?>> result = new ArrayList<>();
+		for (ContextualViewFactory<?> factory : contextualViewFactories)
+			if (factory.canHandle(object))
+				result.add(factory);
+		return result;
+	}
+
+	public void setContextualViewFactories(
+			List<ContextualViewFactory<?>> contextualViewFactories) {
+		this.contextualViewFactories = contextualViewFactories;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java
new file mode 100644
index 0000000..018a121
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated;
+
+import static javax.swing.BoxLayout.Y_AXIS;
+
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.regex.Pattern;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * This is a ContextualView that should be able to display and allow editing of
+ * Annotation information for any Annotated. At the moment it is only used for
+ * Dataflow.
+ * 
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+class AnnotatedContextualView extends ContextualView {
+	private static final int WORKFLOW_NAME_LENGTH = 20;
+	public static final String VIEW_TITLE = "Annotations";
+	private final static String MISSING_VALUE = "Type here to give details";
+	private final static int DEFAULT_AREA_WIDTH = 60;
+	private final static int DEFAULT_AREA_ROWS = 8;
+
+	private static Logger logger = Logger
+			.getLogger(AnnotatedContextualView.class);
+	private static PropertyResourceBundle prb = (PropertyResourceBundle) ResourceBundle
+			.getBundle("annotatedcontextualview");
+
+	// TODO convert to scufl2
+	// private static AnnotationTools annotationTools = new AnnotationTools();
+
+	/**
+	 * The object to which the Annotations apply
+	 */
+	private Annotated<?> annotated;
+	private SelectionManager selectionManager;
+	private EditManager editManager;
+	private boolean isStandalone = false;
+	private JPanel panel;
+	@SuppressWarnings("unused")
+	private final List<AnnotationBeanSPI> annotationBeans;
+
+	public AnnotatedContextualView(Annotated<?> annotated,
+			EditManager editManager, SelectionManager selectionManager,
+			List<AnnotationBeanSPI> annotationBeans) {
+		super();
+		this.editManager = editManager;
+		this.selectionManager = selectionManager;
+		this.annotationBeans = annotationBeans;
+		this.annotated = annotated;
+
+		initialise();
+		initView();
+	}
+
+	@Override
+	public void refreshView() {
+		initialise();
+	}
+
+	private void initialise() {
+		if (panel == null) {
+			panel = new JPanel();
+			panel.setLayout(new BoxLayout(panel, Y_AXIS));
+		} else
+			panel.removeAll();
+		populatePanel();
+		revalidate();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		return panel;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return VIEW_TITLE;
+	}
+
+	private Map<String,String> getAnnotations() {
+		// TODO convert to scufl2
+		Map<String, String> result = new HashMap<>();
+		//for (Class<?> c : annotationTools.getAnnotatingClasses(annotated)) {
+		// String name = "";
+		// try {
+		// name = prb.getString(c.getCanonicalName());
+		// } catch (MissingResourceException e) {
+		// name = c.getCanonicalName();
+		// }
+		// String value = annotationTools.getAnnotationString(annotated, c,
+		// MISSING_VALUE);
+		// result.put(name,value);
+		//}
+		return result;
+	}
+	public void populatePanel() {
+		JPanel scrollPanel = new JPanel();
+		scrollPanel.setLayout(new BoxLayout(scrollPanel, Y_AXIS));
+		panel.setBorder(new EmptyBorder(5, 5, 5, 5));
+		Map<String,String>annotations = getAnnotations();
+		for (String name : annotations.keySet()) {
+			JPanel subPanel = new JPanel();
+			subPanel.setBorder(new TitledBorder(name));
+			subPanel.add(createTextArea(String.class, annotations.get(name)));
+			scrollPanel.add(subPanel);
+		}
+		JScrollPane scrollPane = new JScrollPane(scrollPanel);
+		panel.add(scrollPane);
+	}
+
+	private JScrollPane createTextArea(Class<?> c, String value) {
+		DialogTextArea area = new DialogTextArea(value);
+		area.setFocusable(true);
+		area.addFocusListener(new TextAreaFocusListener(area, c));
+		area.setColumns(DEFAULT_AREA_WIDTH);
+		area.setRows(DEFAULT_AREA_ROWS);
+		area.setLineWrap(true);
+		area.setWrapStyleWord(true);
+
+		return new JScrollPane(area);
+	}
+
+	private class TextAreaFocusListener implements FocusListener {
+		String oldValue = null;
+		Class<?> annotationClass;
+		DialogTextArea area = null;
+
+		public TextAreaFocusListener(DialogTextArea area, Class<?> c) {
+			annotationClass = c;
+			oldValue = area.getText();
+			this.area = area;
+		}
+
+		@Override
+		public void focusGained(FocusEvent e) {
+			if (area.getText().equals(MISSING_VALUE))
+				area.setText("");
+		}
+
+		@Override
+		public void focusLost(FocusEvent e) {
+			String currentValue = area.getText();
+			if (currentValue.isEmpty() || currentValue.equals(MISSING_VALUE)) {
+				currentValue = MISSING_VALUE;
+				area.setText(currentValue);
+			}
+			if (!currentValue.equals(oldValue)) {
+				if (currentValue == MISSING_VALUE)
+					currentValue = "";
+				try {
+					WorkflowBundle currentDataflow = selectionManager
+							.getSelectedWorkflowBundle();
+					List<Edit<?>> editList = new ArrayList<>();
+					addWorkflowNameEdits(currentValue, currentDataflow,
+							editList);
+					if (!isStandalone)
+						ContextualViewComponent.selfGenerated = true;
+					editManager.doDataflowEdit(currentDataflow,
+							new CompoundEdit(editList));
+					ContextualViewComponent.selfGenerated = false;
+				} catch (EditException e1) {
+					logger.warn("Can't set annotation", e1);
+				}
+				oldValue = area.getText();
+			}
+		}
+
+		private boolean isTitleAnnotation() {
+			// TODO convert to scufl2
+			return prb.getString(annotationClass.getCanonicalName()).equals(
+					"Title");
+		}
+
+		// TODO convert to scufl2
+		private void addWorkflowNameEdits(String currentValue,
+				WorkflowBundle currentDataflow, List<Edit<?>> editList) {
+			//editList.add(annotationTools.setAnnotationString(annotated,
+			//		annotationClass, currentValue, edits));
+			if (annotated == currentDataflow && isTitleAnnotation()
+					&& !currentValue.isEmpty()) {
+				@SuppressWarnings("unused")
+				String sanitised = sanitiseName(currentValue);
+				//editList.add(edits.getUpdateDataflowNameEdit(currentDataflow,
+				//		sanitised));
+			}
+		}
+	}
+
+	/**
+	 * Checks that the name does not have any characters that are invalid for a
+	 * processor name.
+	 * <p>
+	 * The resulting name must contain only the chars [A-Za-z_0-9].
+	 * 
+	 * @param name
+	 *            the original name
+	 * @return the sanitised name
+	 */
+	private static String sanitiseName(String name) {
+		if (name.length() > WORKFLOW_NAME_LENGTH)
+			name = name.substring(0, WORKFLOW_NAME_LENGTH);
+		if (Pattern.matches("\\w++", name))
+			return name;
+		StringBuilder temp = new StringBuilder();
+		for (char c : name.toCharArray())
+			temp.append(Character.isLetterOrDigit(c) || c == '_' ? c : '_');
+		return temp.toString();
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 500;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java
new file mode 100644
index 0000000..eb18803
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java
@@ -0,0 +1,43 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated;
+
+import static java.util.Collections.singletonList;
+
+import java.util.List;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+
+public class AnnotatedContextualViewFactory implements
+		ContextualViewFactory<Annotated<?>> {
+	private EditManager editManager;
+	private List<AnnotationBeanSPI> annotationBeans;
+	private SelectionManager selectionManager;
+
+	@Override
+	public boolean canHandle(Object selection) {
+		return ((selection instanceof Annotated) && !(selection instanceof Activity));
+	}
+
+	@Override
+	public List<ContextualView> getViews(Annotated<?> selection) {
+		return singletonList((ContextualView) new AnnotatedContextualView(
+				selection, editManager, selectionManager, annotationBeans));
+	}
+
+	public void setEditManager(EditManager editManager) {
+		this.editManager = editManager;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+	}
+
+	public void setAnnotationBeans(List<AnnotationBeanSPI> annotationBeans) {
+		this.annotationBeans = annotationBeans;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java
new file mode 100644
index 0000000..f9308b5
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.condition;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+
+/**
+ * Contextual view for dataflow's control (condition) links.
+ * 
+ * @author David Withers
+ */
+class ConditionContextualView extends ContextualView {
+	private static final long serialVersionUID = -894521200616176439L;
+
+	private final BlockingControlLink condition;
+	private JPanel contitionView;
+
+	public ConditionContextualView(BlockingControlLink condition) {
+		this.condition = condition;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		refreshView();
+		return contitionView;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Control link: " + condition.getBlock().getName()
+				+ " runs after " + condition.getUntilFinished().getName();
+	}
+
+	@Override
+	public void refreshView() {
+		contitionView = new JPanel(new FlowLayout(FlowLayout.LEFT));
+		contitionView.setBorder(new EmptyBorder(5, 5, 5, 5));
+		JLabel label = new JLabel(
+				"<html><body><i>No details available.</i></body><html>");
+		contitionView.add(label);
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java
new file mode 100644
index 0000000..ea69f1a
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.condition;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Condition;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+
+/**
+ * A factory of contextual views for dataflow's condition links.
+ * 
+ * @author David Withers
+ * 
+ */
+public class ConditionContextualViewFactory implements
+		ContextualViewFactory<BlockingControlLink> {
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof Condition;
+	}
+
+	@Override
+	public List<ContextualView> getViews(BlockingControlLink condition) {
+		return asList(new ContextualView[] { new ConditionContextualView(
+				condition) });
+	}
+
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java
new file mode 100644
index 0000000..4a63868
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java
@@ -0,0 +1,108 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow;
+
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.panelForHtml;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+class DataflowContextualView extends ContextualView {
+	private static int MAX_LENGTH = 50;
+	private static final String ELLIPSIS = "...";
+
+	private Workflow dataflow;
+	private JEditorPane editorPane;
+	private final FileManager fileManager;
+	private final ColourManager colourManager;
+
+	public DataflowContextualView(Workflow dataflow, FileManager fileManager,
+			ColourManager colourManager) {
+		this.dataflow = dataflow;
+		this.fileManager = fileManager;
+		this.colourManager = colourManager;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		editorPane = createEditorPane(buildHtml());
+		return panelForHtml(editorPane);
+	}
+
+	private String buildHtml() {
+		StringBuilder html = new StringBuilder(getHtmlHead(getBackgroundColour()));
+		html.append(buildTableOpeningTag());
+
+		html.append("<tr><td colspan=\"2\" align=\"center\"><b>Source</b></td></tr>");
+		String source = "Newly created";
+		if (fileManager.getDataflowSource(dataflow.getParent()) != null)
+			source = fileManager.getDataflowName(dataflow.getParent());
+
+		html.append("<tr><td colspan=\"2\" align=\"center\">").append(source)
+				.append("</td></tr>");
+		if (!dataflow.getInputPorts().isEmpty()) {
+			html.append("<tr><th>Input Port Name</th><th>Depth</th></tr>");
+			for (InputWorkflowPort dip : dataflow.getInputPorts())
+				html.append("<tr><td>")
+						.append(dip.getName())
+						.append("</td><td>")
+						.append(dip.getDepth() < 0 ? "invalid/unpredicted"
+								: dip.getDepth()).append("</td></tr>");
+		}
+		if (!dataflow.getOutputPorts().isEmpty()) {
+			html.append("<tr><th>Output Port Name</th><th>Depth</th></tr>");
+			for (OutputWorkflowPort dop : dataflow.getOutputPorts())
+				html.append("<tr><td>")
+						.append(dop.getName())
+						.append("</td><td>")
+						.append(/*(dop.getDepth() < 0 ?*/ "invalid/unpredicted" /*: dop.getDepth())*/)
+						.append("</td>" + "</tr>");
+		}
+
+		return html.append("</table>").append("</body></html>").toString();
+	}
+
+	public String getBackgroundColour() {
+		return colourManager.getDefaultPropertyMap().get(
+				Dataflow.class.toString());
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+
+	private String limitName(String fullName) {
+		if (fullName.length() <= MAX_LENGTH)
+			return fullName;
+		return fullName.substring(0, MAX_LENGTH - ELLIPSIS.length()) + ELLIPSIS;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Workflow " + limitName(dataflow.getName());
+	}
+
+	@Override
+	public void refreshView() {
+		editorPane.setText(buildHtml());
+		repaint();
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java
new file mode 100644
index 0000000..0d7f3c0
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java
@@ -0,0 +1,41 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowContextualViewFactory implements
+		ContextualViewFactory<Workflow> {
+	private FileManager fileManager;
+	private ColourManager colourManager;
+
+	@Override
+	public boolean canHandle(Object selection) {
+		return selection instanceof Workflow;
+	}
+
+	@Override
+	public List<ContextualView> getViews(Workflow selection) {
+		return Arrays.asList(new ContextualView[] {
+				new DataflowContextualView(selection, fileManager, colourManager)});
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+
+	public void setColourManager(ColourManager colourManager) {
+		this.colourManager = colourManager;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java
new file mode 100644
index 0000000..3f17a65
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's input ports.
+ *
+ * @author Alex Nenadic
+ */
+class DataflowInputPortContextualView extends ContextualView{
+	private static final long serialVersionUID = -8746856072335775933L;
+
+	private InputWorkflowPort dataflowInputPort;
+	private JPanel dataflowInputPortView;
+	@SuppressWarnings("unused")
+	private FileManager fileManager;
+
+	public DataflowInputPortContextualView(InputWorkflowPort inputport,
+			FileManager fileManager) {
+		this.dataflowInputPort = inputport;
+		this.fileManager = fileManager;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		refreshView();
+		return dataflowInputPortView;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Workflow input port: " + dataflowInputPort.getName();
+	}
+
+	@Override
+	public void refreshView() {
+		dataflowInputPortView = new JPanel(new FlowLayout(LEFT));
+		dataflowInputPortView.setBorder(new EmptyBorder(5, 5, 5, 5));
+		JLabel label = new JLabel(getTextFromDepth("port",
+				dataflowInputPort.getDepth()));
+		dataflowInputPortView.add(label);
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	public Action getConfigureAction(Frame owner) {
+		return new AbstractAction("Update prediction") {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				// fileManager.getCurrentDataflow().checkValidity();
+				refreshView();
+			}
+		};
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java
new file mode 100644
index 0000000..5dc5434
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+/**
+ * A factory of contextual views for dataflow's input ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowInputPortContextualViewFactory implements
+		ContextualViewFactory<InputWorkflowPort> {
+	private FileManager fileManager;
+
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof InputWorkflowPort;
+	}
+
+	@Override
+	public List<ContextualView> getViews(InputWorkflowPort inputport) {
+		return Arrays.asList(new ContextualView[] {
+				new DataflowInputPortContextualView(inputport, fileManager)});
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java
new file mode 100644
index 0000000..9ba55fe
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowOutputPortContextualView extends ContextualView {
+	private static final long serialVersionUID = 5496014085110553051L;
+
+	private OutputWorkflowPort dataflowOutputPort;
+	private JPanel dataflowOutputPortView;
+	@SuppressWarnings("unused")
+	private FileManager fileManager;
+
+	public DataflowOutputPortContextualView(OutputWorkflowPort outputport,
+			FileManager fileManager) {
+		this.dataflowOutputPort = outputport;
+		this.fileManager = fileManager;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		refreshView();
+		return dataflowOutputPortView;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Workflow output port: " + dataflowOutputPort.getName();
+	}
+
+	@Override
+	public void refreshView() {
+		dataflowOutputPortView = new JPanel(new FlowLayout(LEFT));
+		dataflowOutputPortView.setBorder(new EmptyBorder(5,5,5,5));
+		JLabel label = new JLabel(getTextForLabel());
+		dataflowOutputPortView.add(label);
+	}
+
+	private String getTextForLabel() {
+		//FIXME
+		//return getTextFromDepth("port", dataflowOutputPort.getDepth());
+		return "Fix depth for OutputWorkflowPort";
+	}
+
+	private void updatePrediction() {
+		//FIXME
+		// fileManager.getCurrentDataflow().checkValidity();
+	}
+
+	@Override
+	@SuppressWarnings("serial")
+	public Action getConfigureAction(Frame owner) {
+		return new AbstractAction("Update prediction") {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				updatePrediction();
+				refreshView();
+			}
+		};
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java
new file mode 100644
index 0000000..20ac960
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * A factory of contextual views for dataflow's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowOutputPortContextualViewFactory implements
+		ContextualViewFactory<OutputWorkflowPort> {
+	private FileManager fileManager;
+
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof DataflowOutputPort;
+	}
+
+	@Override
+	public List<ContextualView> getViews(OutputWorkflowPort outputport) {
+		return Arrays.asList(new ContextualView[] {
+				new DataflowOutputPortContextualView(outputport, fileManager)});
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java
new file mode 100644
index 0000000..daa3414
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's datalinks.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+class DatalinkContextualView extends ContextualView {
+	private static final long serialVersionUID = -5031256519235454876L;
+
+	private DataLink datalink;
+	private JPanel datalinkView;
+	@SuppressWarnings("unused")
+	private final FileManager fileManager;
+
+	public DatalinkContextualView(DataLink datalink, FileManager fileManager) {
+		this.datalink = datalink;
+		this.fileManager = fileManager;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		refreshView();
+		return datalinkView;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Data link: " + datalink.getReceivesFrom().getName() + " -> " + datalink.getSendsTo().getName();
+	}
+
+	@Override
+	public void refreshView() {
+		datalinkView = new JPanel(new FlowLayout(LEFT));
+		datalinkView.setBorder(new EmptyBorder(5,5,5,5));
+		JLabel label = new JLabel (getTextForLabel());
+		datalinkView.add(label);
+	}
+
+	private String getTextForLabel() {
+		//FIXME
+		// return getTextFromDepth("link", datalink.getResolvedDepth());
+		return "Fix DataLink resolved depth";
+	}
+
+	private void updatePrediction() {
+		//FIXME
+		// fileManager.getCurrentDataflow().checkValidity();
+	}
+
+	@Override
+	@SuppressWarnings("serial")
+	public Action getConfigureAction(Frame owner) {
+		return new AbstractAction("Update prediction") {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				updatePrediction();
+				refreshView();
+			}
+		};
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java
new file mode 100644
index 0000000..fa8bf96
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Datalink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * A factory of contextual views for dataflow's datalinks.
+ *
+ * @author Alex Nenadic
+ */
+public class DatalinkContextualViewFactory implements
+		ContextualViewFactory<DataLink> {
+	private FileManager fileManager;
+
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof Datalink;
+	}
+
+	@Override
+	public List<ContextualView> getViews(DataLink datalink) {
+		return Arrays.asList(new ContextualView[] {
+				new DatalinkContextualView(datalink, fileManager)});
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java
new file mode 100644
index 0000000..11306d0
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java
@@ -0,0 +1,389 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.BLUE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.ORANGE;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.minusIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.plusIcon;
+
+import java.awt.Color;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.Utils;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class ContextualViewComponent extends JScrollPane implements UIComponentSPI {
+	/** delay before contextual view is redrawn */
+	private static final int DELAY = 250;
+	private static final Color[] colors = new Color[] { BLUE, GREEN, ORANGE };
+	// HACK ALERT!
+	public static boolean selfGenerated = false;
+
+	private Observer<DataflowSelectionMessage> dataflowSelectionListener = new DataflowSelectionListener();
+	private SelectionManager selectionManager;
+	private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+	GridBagConstraints gbc;
+	protected Map<JPanel, SectionLabel> panelToLabelMap = new HashMap<>();
+	private String lastOpenedSectionName = "";
+	private JPanel mainPanel;
+	private List<JPanel> shownComponents = null;
+	int colorIndex = 0;
+	private Timer updateSelectionTimer = null;
+	private Object lastSelectedObject = null;
+
+	private static final Comparator<ContextualView> viewComparator = new Comparator<ContextualView>() {
+		@Override
+		public int compare(ContextualView o1, ContextualView o2) {
+			return o1.getPreferredPosition() - o2.getPreferredPosition();
+		}
+	};
+
+	public ContextualViewComponent(EditManager editManager,
+			SelectionManager selectionManager,
+			ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+		this.selectionManager = selectionManager;
+		this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+		updateSelectionTimer = new Timer(DELAY, updateSelectionListener);
+		updateSelectionTimer.setRepeats(false);
+
+		initialise();
+
+		editManager.addObserver(new EditManagerObserver());
+		selectionManager.addObserver(new SelectionManagerObserver());
+	}
+
+	@Override
+	public ImageIcon getIcon() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		return "Details";
+	}
+
+	private void initialise() {
+		mainPanel = new JPanel(new GridBagLayout());
+		this.setViewportView(mainPanel);
+	}
+
+	@Override
+	public void onDisplay() {
+	}
+
+	@Override
+	public void onDispose() {
+		updateSelectionTimer.stop();
+	}
+
+	@SuppressWarnings("unchecked")
+	private void updateContextualView(List<ContextualViewFactory<?>> viewFactories,
+			Object selection) {
+		if (selection == lastSelectedObject)
+			return;
+		lastSelectedObject = selection;
+		mainPanel = new JPanel(new GridBagLayout());
+		panelToLabelMap.clear();
+
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.gridx = 0;
+		gbc.weightx = 0.1;
+		gbc.fill = HORIZONTAL;
+
+		gbc.gridy = 0;
+		shownComponents = new ArrayList<>();
+		List<ContextualView> views = new ArrayList<>();
+		for (ContextualViewFactory<?> cvf : viewFactories)
+			views.addAll(((ContextualViewFactory<Object>) cvf)
+					.getViews(selection));
+		Collections.sort(views, viewComparator);
+		colorIndex = 0;
+		if (views.isEmpty())
+			mainPanel.add(new JLabel("No details available"));
+		else
+			populateContextualView(viewFactories, gbc, views);
+		gbc.weighty = 0.1;
+		gbc.fill = BOTH;
+		mainPanel.add(new JPanel(), gbc);
+		// mainPanel.revalidate();
+		// mainPanel.repaint();
+		this.setViewportView(mainPanel);
+		// this.revalidate();
+		// this.repaint();
+	}
+
+	private void populateContextualView(
+			List<ContextualViewFactory<?>> viewFactories,
+			GridBagConstraints gbc, List<ContextualView> views) {
+		JPanel firstPanel = null;
+		JPanel lastOpenedSection = null;
+		for (ContextualView view : views) {
+			SectionLabel label = new SectionLabel(view.getViewTitle(), nextColor());
+			mainPanel.add(label, gbc);
+			gbc.gridy++;
+			JPanel subPanel = new JPanel();
+			if (view.getViewTitle().equals(lastOpenedSectionName))
+				lastOpenedSection = subPanel;
+			subPanel.setLayout(new GridBagLayout());
+
+			GridBagConstraints constraints = new GridBagConstraints();
+			constraints.gridx = 0;
+			constraints.gridy = 0;
+			constraints.weightx = 0.1;
+			constraints.weighty = 0;
+			constraints.anchor = CENTER;
+			constraints.fill = HORIZONTAL;
+
+			subPanel.add(view, constraints);
+			Frame frame = Utils.getParentFrame(this);
+			Action configureAction = view.getConfigureAction(frame);
+			if (configureAction != null) {
+				JButton configButton = new JButton(configureAction);
+				if (configButton.getText() == null
+						|| configButton.getText().isEmpty())
+					configButton.setText("Configure");
+				constraints.gridy++;
+				constraints.fill = NONE;
+				constraints.anchor = LINE_START;
+				subPanel.add(configButton, constraints);
+			}
+			if (firstPanel == null)
+				firstPanel = subPanel;
+			mainPanel.add(subPanel, gbc);
+			shownComponents.add(subPanel);
+			gbc.gridy++;
+			if (viewFactories.size() != 1)
+				makeCloseable(subPanel, label);
+			else {
+				lastOpenedSectionName = label.getText();
+				lastOpenedSection = subPanel;
+				panelToLabelMap.put(subPanel, label);
+				subPanel.setVisible(false);
+			}
+		}
+		if (lastOpenedSection != null)
+			openSection(lastOpenedSection);
+		else if (firstPanel != null)
+			openSection(firstPanel);
+	}
+
+	private void clearContextualView() {
+		lastSelectedObject = null;
+		mainPanel = new JPanel(new GridBagLayout());
+		mainPanel.add(new JLabel("No details available"));
+		this.setViewportView(mainPanel);
+		this.revalidate();
+	}
+
+	public void updateSelection(Object selectedItem) {
+		findContextualView(selectedItem);
+	}
+
+	private Runnable updateSelectionRunnable = new Runnable() {
+		@Override
+		public void run() {
+			Object selection = getSelection();
+			if (selection == null)
+				clearContextualView();
+			else
+				updateSelection(selection);
+		}
+	};
+
+	private ActionListener updateSelectionListener = new ActionListener() {
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			SwingUtilities.invokeLater(updateSelectionRunnable);
+		}
+	};
+
+	public void updateSelection() {
+		updateSelectionTimer.restart();
+	}
+
+	private Object getSelection() {
+		WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+
+		/*
+		 * If there is no currently opened dataflow, clear the contextual view
+		 * panel
+		 */
+		if (workflowBundle == null) {
+			return null;
+		}
+		DataflowSelectionModel selectionModel = selectionManager
+				.getDataflowSelectionModel(workflowBundle);
+		Set<Object> selection = selectionModel.getSelection();
+
+		/*
+		 * If the dataflow is opened but no component of the dataflow is
+		 * selected, clear the contextual view panel
+		 */
+		if (selection.isEmpty())
+			return null;
+		return selection.iterator().next();
+	}
+
+	private void findContextualView(Object selection) {
+		List<ContextualViewFactory<?>> viewFactoriesForBeanType = contextualViewFactoryRegistry
+				.getViewFactoriesForObject(selection);
+		updateContextualView(viewFactoriesForBeanType, selection);
+	}
+
+	private final class SelectionManagerObserver extends SwingAwareObserver<SelectionManagerEvent> {
+		@Override
+		public void notifySwing(Observable<SelectionManagerEvent> sender, SelectionManagerEvent message) {
+			if (message instanceof WorkflowBundleSelectionEvent)
+				bundleSelected((WorkflowBundleSelectionEvent) message);
+		}
+
+		private void bundleSelected(WorkflowBundleSelectionEvent event) {
+			WorkflowBundle oldBundle = event
+					.getPreviouslySelectedWorkflowBundle();
+			WorkflowBundle newBundle = event.getSelectedWorkflowBundle();
+
+			if (oldBundle != null)
+				selectionManager.getDataflowSelectionModel(oldBundle)
+						.removeObserver(dataflowSelectionListener);
+			if (newBundle != null)
+				selectionManager.getDataflowSelectionModel(newBundle)
+						.addObserver(dataflowSelectionListener);
+			lastSelectedObject = null;
+			updateSelection();
+		}
+	}
+
+	private final class DataflowSelectionListener extends SwingAwareObserver<DataflowSelectionMessage> {
+		@Override
+		public void notifySwing(Observable<DataflowSelectionMessage> sender,
+				DataflowSelectionMessage message) {
+			updateSelection();
+		}
+	}
+
+	private final class EditManagerObserver extends SwingAwareObserver<EditManagerEvent> {
+		@Override
+		public void notifySwing(Observable<EditManagerEvent> sender, EditManagerEvent message) {
+			Object selection = getSelection();
+			if ((selection != lastSelectedObject) && !selfGenerated) {
+				lastSelectedObject = null;
+				refreshView();
+			}
+		}
+	}
+
+	public void refreshView() {
+		if (mainPanel != null)
+			updateSelection();
+	}
+
+	private final class SectionLabel extends ShadedLabel {
+		private JLabel expand;
+
+		private SectionLabel(String text, Color colour) {
+			super(text, colour);
+			expand = new JLabel(minusIcon);
+			add(expand, 0);
+			setExpanded(true);
+		}
+
+		public void setExpanded(boolean expanded) {
+			if (expanded)
+				expand.setIcon(minusIcon);
+			else
+				expand.setIcon(plusIcon);
+		}
+	}
+
+	private void makeCloseable(JPanel panel, SectionLabel label) {
+		panel.setVisible(false);
+		if (panelToLabelMap.get(panel) != label) {
+			panelToLabelMap.put(panel, label);
+			// Only add mouse listener once
+			label.addMouseListener(new SectionOpener(panel));
+		}
+	}
+
+	protected class SectionOpener extends MouseAdapter {
+		private final JPanel sectionToOpen;
+
+		public SectionOpener(JPanel sectionToOpen) {
+			this.sectionToOpen = sectionToOpen;
+		}
+
+		@Override
+		public void mouseClicked(MouseEvent e) {
+			openSection(sectionToOpen);
+		}
+	}
+
+	public synchronized void openSection(JPanel sectionToOpen) {
+		lastOpenedSectionName = "";
+		for (Entry<JPanel, SectionLabel> entry : panelToLabelMap.entrySet()) {
+			JPanel section = entry.getKey();
+			SectionLabel sectionLabel = entry.getValue();
+
+			if (section != sectionToOpen)
+				section.setVisible(false);
+			else {
+				section.setVisible(!section.isVisible());
+				if (section.isVisible())
+					lastOpenedSectionName = sectionLabel.getText();
+			}
+			sectionLabel.setExpanded(section.isVisible());
+		}
+		this.revalidate();
+		this.repaint();
+	}
+
+	private Color nextColor() {
+		if (colorIndex >= colors.length)
+			colorIndex = 0;
+		return colors[colorIndex++];
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java
new file mode 100644
index 0000000..db43a0d
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2008 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.impl;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+public class ContextualViewComponentFactory implements UIComponentFactorySPI {
+	private EditManager editManager;
+	private SelectionManager selectionManager;
+	private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+
+	@Override
+	public UIComponentSPI getComponent() {
+		return new ContextualViewComponent(editManager, selectionManager,
+				contextualViewFactoryRegistry);
+	}
+
+	@Override
+	public ImageIcon getIcon() {
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		return "Details";
+	}
+
+	public void setEditManager(EditManager editManager) {
+		this.editManager = editManager;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+	}
+
+	public void setContextualViewFactoryRegistry(
+			ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+		this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java
new file mode 100644
index 0000000..c1b3d06
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow procerssor's input ports.
+ * 
+ * @author Alex Nenadic
+ */
+class InputPortContextualView extends ContextualView {
+	private static final String NO_DETAILS_AVAILABLE_HTML = "<html><body>"
+			+ "<i>No details available.</i>" + "</body><html>";
+	private static final long serialVersionUID = -7743029534480678624L;
+
+	private InputActivityPort inputPort;
+	private JPanel inputPortView;
+
+	public InputPortContextualView(InputActivityPort inputport) {
+		this.inputPort = inputport;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		refreshView();
+		return inputPortView;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Service input port: " + inputPort.getName();
+	}
+
+	@Override
+	public void refreshView() {
+		inputPortView = new JPanel(new FlowLayout(LEFT));
+		inputPortView.setBorder(new EmptyBorder(5, 5, 5, 5));
+		JLabel label = new JLabel(NO_DETAILS_AVAILABLE_HTML);
+		inputPortView.add(label);
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java
new file mode 100644
index 0000000..490e5b7
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+
+/**
+ * A factory of contextual views for dataflow proessor's (i.e. its associated
+ * activity's) input ports.
+ *
+ * @author Alex Nenadic
+ */
+public class InputPortContextualViewFactory implements
+		ContextualViewFactory<InputActivityPort> {
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof InputActivityPort;
+	}
+
+	@Override
+	public List<ContextualView> getViews(InputActivityPort inputport) {
+		return Arrays.asList(new ContextualView[] {
+				new InputPortContextualView(inputport)});
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java
new file mode 100644
index 0000000..567cc4b
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.ReorderMergePositionsEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * Configuration action for a Merge. This action changes the order of
+ * merge's incoming ports.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+class MergeConfigurationAction extends AbstractAction {
+	private static Logger logger = Logger
+			.getLogger(MergeConfigurationAction.class);
+
+	private final List<DataLink> reorderedDataLinksList;
+	private final List<DataLink> datalinks;
+	private final EditManager editManager;
+	private final SelectionManager selectionManager;
+
+	MergeConfigurationAction(List<DataLink> datalinks,
+			List<DataLink> reorderedDataLinksList, EditManager editManager,
+			SelectionManager selectionManager) {
+		this.datalinks = datalinks;
+		this.reorderedDataLinksList = reorderedDataLinksList;
+		this.editManager = editManager;
+		this.selectionManager = selectionManager;
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		ReorderMergePositionsEdit edit = new ReorderMergePositionsEdit(
+				datalinks, reorderedDataLinksList);
+
+		WorkflowBundle bundle = selectionManager.getSelectedWorkflowBundle();
+
+		try {
+			editManager.doDataflowEdit(bundle, edit);
+		} catch (IllegalStateException ex1) {
+			logger.error("Could not configure merge", ex1);
+		} catch (EditException ex2) {
+			logger.error("Could not configure merge", ex2);
+		}
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java
new file mode 100644
index 0000000..66eeb3e
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java
@@ -0,0 +1,233 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.lang.Math.max;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.ListSelectionModel.SINGLE_SELECTION;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
+import static javax.swing.SwingConstants.CENTER;
+import static javax.swing.SwingConstants.LEFT;
+import static javax.swing.SwingConstants.RIGHT;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.downArrowIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.upArrowIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+@SuppressWarnings("serial")
+public class MergeConfigurationView extends HelpEnabledDialog {
+	private static final String TITLE = "<html><body><b>Order of incoming links</b></body></html>";
+
+	private List<DataLink> dataLinks;
+	private List<DataLink> reorderedDataLinks;
+	/** Ordered list of labels for dataLinks to be displayed to the user */
+	private DefaultListModel<String> labelListModel;
+	/** JList that displays the labelListModel */
+	JList<String> list;
+	/** Button to push the dataLink up the list */
+	private JButton upButton;
+	/** Button to push the dataLink down the list */
+	private JButton downButton;
+	private final EditManager editManager;
+	private final SelectionManager selectionManager;
+
+	public MergeConfigurationView(List<DataLink> dataLinks, EditManager editManager,
+			SelectionManager selectionManager) {
+		super((Frame)null, "Merge Configuration", true);
+
+		this.dataLinks = new ArrayList<>(dataLinks);
+		reorderedDataLinks = new ArrayList<>(dataLinks);
+		this.editManager = editManager;
+		this.selectionManager = selectionManager;
+		labelListModel = new DefaultListModel<>();
+		for (DataLink dataLink : dataLinks)
+			labelListModel.addElement(dataLink.toString());
+
+		initComponents();
+	}
+
+	private void initComponents() {
+        getContentPane().setLayout(new BorderLayout());
+
+		JPanel listPanel = new JPanel();
+		listPanel.setLayout(new BorderLayout());
+		listPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10, 10),
+				new EtchedBorder()));
+
+		JLabel title = new JLabel(TITLE);
+		title.setBorder(new EmptyBorder(5, 5, 5, 5));
+		listPanel.add(title, NORTH);
+
+		list = new JList<>(labelListModel);
+		list.setSelectionMode(SINGLE_SELECTION);
+		list.setVisibleRowCount(-1);
+		list.addListSelectionListener(new ListSelectionListener() {
+			/**
+			 * Enable and disable up and down buttons based on which item in the
+			 * list is selected
+			 */
+			@Override
+			public void valueChanged(ListSelectionEvent e) {
+				int index = list.getSelectedIndex();
+				if ((index == -1) || (index == 0 && labelListModel.size() == 0)) {
+					// nothing selected or only one item in the list
+					upButton.setEnabled(false);
+					downButton.setEnabled(false);
+				} else {
+					upButton.setEnabled(index > 0);
+					downButton.setEnabled(index < labelListModel.size() - 1);
+				}
+			}
+		});
+
+		final JScrollPane listScroller = new JScrollPane(list);
+		listScroller.setBorder(new EmptyBorder(5, 5, 5, 5));
+		listScroller.setBackground(listPanel.getBackground());
+		listScroller.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_ALWAYS);
+		listScroller.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
+		// Set the size of scroll pane to make all list items visible
+		FontMetrics fm = listScroller.getFontMetrics(this.getFont());
+		int listScrollerHeight = fm.getHeight() * labelListModel.size() + 75; //+75 just in case
+		listScroller.setPreferredSize(new Dimension(listScroller
+				.getPreferredSize().width, max(listScrollerHeight,
+				listScroller.getPreferredSize().height)));
+		listPanel.add(listScroller, BorderLayout.CENTER);
+
+		JPanel upDownButtonPanel = new JPanel();
+		upDownButtonPanel.setLayout(new BoxLayout(upDownButtonPanel, Y_AXIS));
+		upDownButtonPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+
+		upButton = new JButton(new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				int index = list.getSelectedIndex();
+				if (index != -1) {
+					// Swap the labels
+					String label = (String) labelListModel.elementAt(index);
+					labelListModel.set(index, labelListModel.get(index - 1));
+					labelListModel.set(index - 1, label);
+					// Swap the dataLinks
+					DataLink dataLink = reorderedDataLinks.get(index);
+					reorderedDataLinks.set(index,
+							reorderedDataLinks.get(index - 1));
+					reorderedDataLinks.set(index - 1, dataLink);
+					// Make the pushed item selected
+					list.setSelectedIndex(index - 1);
+					// Refresh the list
+					listScroller.repaint();
+					listScroller.revalidate();
+				}
+			}
+		});
+		upButton.setIcon(upArrowIcon);
+		upButton.setText("Up");
+	    // Place text to the right of icon, vertically centered
+		upButton.setVerticalTextPosition(CENTER);
+		upButton.setHorizontalTextPosition(RIGHT);
+		// Set the horizontal alignment of the icon and text
+		upButton.setHorizontalAlignment(LEFT);
+		upButton.setEnabled(false);
+		upDownButtonPanel.add(upButton);
+
+		downButton = new JButton(new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				int index = list.getSelectedIndex();
+				if (index != -1) {
+					// Swap the labels
+					String label = (String) labelListModel.elementAt(index);
+					labelListModel.set(index, labelListModel.get(index + 1));
+					labelListModel.set(index + 1, label);
+					// Swap the dataLinks
+					DataLink dataLink = reorderedDataLinks.get(index);
+					reorderedDataLinks.set(index,
+							reorderedDataLinks.get(index + 1));
+					reorderedDataLinks.set(index + 1, dataLink);
+					// Make the pushed item selected
+					list.setSelectedIndex(index + 1);
+					// Refresh the list
+					list.repaint();
+					listScroller.revalidate();
+				}
+			}
+		});
+		downButton.setIcon(downArrowIcon);
+		downButton.setText("Down");
+	    // Place text to the right of icon, vertically centered
+		downButton.setVerticalTextPosition(CENTER);
+		downButton.setHorizontalTextPosition(RIGHT);
+		// Set the horizontal alignment of the icon and text
+		downButton.setHorizontalAlignment(LEFT);
+		downButton.setEnabled(false);
+		// set the up button to be of the same size as down button
+		upButton.setPreferredSize(downButton.getPreferredSize());
+		upButton.setMaximumSize(downButton.getPreferredSize());
+		upButton.setMinimumSize(downButton.getPreferredSize());
+		upDownButtonPanel.add(downButton);
+
+		listPanel.add(upDownButtonPanel, EAST);
+
+		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+
+		JButton jbOK = new JButton("OK");
+		jbOK.addActionListener(new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				new MergeConfigurationAction(dataLinks, reorderedDataLinks,
+						editManager, selectionManager).actionPerformed(e);
+				closeDialog();
+			}
+		});
+
+		JButton jbCancel = new JButton("Cancel");
+		jbCancel.addActionListener(new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				closeDialog();
+			}
+		});
+
+        buttonPanel.add(jbOK);
+        buttonPanel.add(jbCancel);
+
+        getContentPane().add(listPanel, BorderLayout.CENTER);
+        getContentPane().add(buttonPanel, SOUTH);
+        pack();
+	}
+
+	/**
+	 * Close the dialog.
+	 */
+	private void closeDialog() {
+		setVisible(false);
+		dispose();
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java
new file mode 100644
index 0000000..deb09fb
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.LEFT;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Merge;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * Contextual view for a {@link Merge}.
+ * 
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+class MergeContextualView extends ContextualView {
+	@SuppressWarnings("unused")
+	private DataLink dataLink;
+	private List<DataLink> datalinks;
+	@SuppressWarnings("unused")
+	private WorkflowBundle workflow;
+	private JEditorPane editorPane;
+	private final EditManager editManager;
+	private final ColourManager colourManager;
+	private final SelectionManager selectionManager;
+
+	// TODO inject from Spring via factory?
+	private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+	public MergeContextualView(DataLink dataLink, EditManager editManager,
+			SelectionManager selectionManager, ColourManager colourManager) {
+		this.dataLink = dataLink;
+		this.selectionManager = selectionManager;
+		datalinks = scufl2Tools.datalinksTo(dataLink.getSendsTo());
+		this.editManager = editManager;
+		this.colourManager = colourManager;
+		workflow = selectionManager.getSelectedWorkflowBundle();
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		editorPane = createEditorPane(buildHtml());
+		return panelForHtml(editorPane);
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Merge Position";
+	}
+
+	/**
+	 * Update the view with the latest information from the configuration bean.
+	 */
+	@Override
+	public void refreshView() {
+		editorPane.setText(buildHtml());
+		repaint();
+	}
+
+	private String buildHtml() {
+		StringBuilder html = new StringBuilder(
+				getHtmlHead(getBackgroundColour()));
+		html.append(buildTableOpeningTag())
+				.append("<tr><td colspan=\"2\"><b>")
+				.append(getViewTitle())
+				.append("</b></td></tr>")
+				.append("<tr><td colspan=\"2\"><b>Ordered incoming links</b></td></tr>");
+
+		int counter = 1;
+		for (DataLink datalink : datalinks)
+			html.append("<tr><td>").append(counter++).append(".</td><td>")
+					.append(datalink).append("</td></tr>");
+
+		return html.append("</table>").append("</body></html>").toString();
+	}
+
+	protected JPanel panelForHtml(JEditorPane editorPane) {
+		final JPanel panel = new JPanel();
+
+		JPanel buttonPanel = new JPanel(new FlowLayout(LEFT));
+
+		JButton configureButton = new JButton(new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				MergeConfigurationView mergeConfigurationView = new MergeConfigurationView(
+						datalinks, editManager, selectionManager);
+				mergeConfigurationView.setLocationRelativeTo(panel);
+				mergeConfigurationView.setVisible(true);
+			}
+		});
+		configureButton.setText("Configure");
+		buttonPanel.add(configureButton);
+
+		panel.setLayout(new BorderLayout());
+		panel.add(editorPane, CENTER);
+		panel.add(buttonPanel, SOUTH);
+		return panel;
+	}
+
+	public String getBackgroundColour() {
+		return colourManager.getDefaultPropertyMap().get(
+				"net.sf.taverna.t2.workflowmodel.Merge");
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java
new file mode 100644
index 0000000..712b183
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * A factory of contextual views for dataflow's merges.
+ *
+ * @author Alex Nenadic
+ */
+public class MergeContextualViewFactory implements ContextualViewFactory<DataLink> {
+	private EditManager editManager;
+	private SelectionManager selectionManager;
+	private ColourManager colourManager;
+
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof DataLink
+				&& ((DataLink) object).getMergePosition() != null;
+	}
+
+	@Override
+	public List<ContextualView> getViews(DataLink merge) {
+		return Arrays.asList(new ContextualView[] {
+				new MergeContextualView(merge, editManager, selectionManager, colourManager)});
+	}
+
+	public void setEditManager(EditManager editManager) {
+		this.editManager = editManager;
+	}
+
+	public void setColourManager(ColourManager colourManager) {
+		this.colourManager = colourManager;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java
new file mode 100644
index 0000000..f2c7861
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+/**
+ * Contextual view for dataflow procerssor's output ports.
+ * 
+ * @author Alex Nenadic
+ */
+public class OutputPortContextualView extends ContextualView {
+	private static final String NO_DETAILS_AVAILABLE_HTML = "<html><body>"
+			+ "<i>No details available.</i>" + "</body><html>";
+	private static final long serialVersionUID = -7743029534480678624L;
+
+	private ActivityOutputPort outputPort;
+	private JPanel outputPortView;
+
+	public OutputPortContextualView(ActivityOutputPort outputport) {
+		this.outputPort = outputport;
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		refreshView();
+		return outputPortView;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Service output port: " + outputPort.getName();
+	}
+
+	@Override
+	public void refreshView() {
+		outputPortView = new JPanel(new FlowLayout(LEFT));
+		outputPortView.setBorder(new EmptyBorder(5,5,5,5));
+		JLabel label = new JLabel(NO_DETAILS_AVAILABLE_HTML);
+		outputPortView.add(label);
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 100;
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java
new file mode 100644
index 0000000..71e2cd4
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+/**
+ * A factory of contextual views for dataflow proessor's (i.e. its associated
+ * activity's) output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class OutputPortContextualViewFactory implements
+		ContextualViewFactory<ActivityOutputPort> {
+	@Override
+	public boolean canHandle(Object object) {
+		return object instanceof ActivityOutputPort;
+	}
+
+	@Override
+	public List<ContextualView> getViews(ActivityOutputPort outputport) {
+		return Arrays.asList(new ContextualView[] {
+				new OutputPortContextualView(outputport)});
+	}
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..7744cb3
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1,9 @@
+net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport.OutputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport.InputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport.DataflowOutputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport.DataflowInputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink.DatalinkContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.condition.ConditionContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow.DataflowContextualViewFactory
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..a564691
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml
new file mode 100644
index 0000000..767943d
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="ContextualViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+	<service ref="OutputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="InputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="DataflowOutputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="DataflowInputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="DatalinkContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="ConditionContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="MergeContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="AnnotatedContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+	<service ref="DataflowContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+	<service ref="ContextualViewFactoryRegistry" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry" />
+
+	<reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+	<reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+	<reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+	<reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+
+	<list id="annotationBeans" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
+	<list id="contextualViewFactories" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml
new file mode 100644
index 0000000..18bbd36
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="ContextualViewComponentFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponentFactory">
+			<property name="editManager" ref="editManager" />
+			<property name="selectionManager" ref="selectionManager" />
+			<property name="contextualViewFactoryRegistry" ref="ContextualViewFactoryRegistry"/>
+	</bean>
+
+	<bean id="OutputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport.OutputPortContextualViewFactory" />
+	<bean id="InputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport.InputPortContextualViewFactory" />
+	<bean id="DataflowOutputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport.DataflowOutputPortContextualViewFactory">
+			<property name="fileManager" ref="fileManager" />
+	</bean>
+	<bean id="DataflowInputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport.DataflowInputPortContextualViewFactory">
+			<property name="fileManager" ref="fileManager" />
+	</bean>
+	<bean id="DatalinkContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink.DatalinkContextualViewFactory">
+			<property name="fileManager" ref="fileManager" />
+	</bean>
+	<bean id="ConditionContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.condition.ConditionContextualViewFactory" />
+	<bean id="MergeContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeContextualViewFactory">
+			<property name="editManager" ref="editManager" />
+			<property name="selectionManager" ref="selectionManager" />
+			<property name="colourManager" ref="colourManager" />
+	</bean>
+	<bean id="AnnotatedContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualViewFactory">
+			<property name="editManager" ref="editManager" />
+			<property name="selectionManager" ref="selectionManager" />
+			<property name="annotationBeans" ref ="annotationBeans"/>
+	</bean>
+	<bean id="DataflowContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow.DataflowContextualViewFactory">
+			<property name="fileManager" ref="fileManager" />
+			<property name="colourManager" ref="colourManager" />
+	</bean>
+
+	<bean id="ContextualViewFactoryRegistry" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl.ContextualViewFactoryRegistryImpl">
+			<property name="contextualViewFactories" ref="contextualViewFactories" />
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties b/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties
new file mode 100644
index 0000000..e3196f6
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties
@@ -0,0 +1,4 @@
+net.sf.taverna.t2.annotation.annotationbeans.FreeTextDescription: Description
+net.sf.taverna.t2.annotation.annotationbeans.Author: Author
+net.sf.taverna.t2.annotation.annotationbeans.DescriptiveTitle: Title
+net.sf.taverna.t2.annotation.annotationbeans.ExampleValue: Example
\ No newline at end of file
diff --git a/taverna-workbench-edits-impl/pom.xml b/taverna-workbench-edits-impl/pom.xml
new file mode 100644
index 0000000..b5b725a
--- /dev/null
+++ b/taverna-workbench-edits-impl/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>edits-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Edits implementation</name>
+	<description>
+		Implementation for doing workflow edits and undo.
+	</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>edits-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>selection-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>ui</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
new file mode 100644
index 0000000..f97d36c
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Implementation of {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class EditManagerImpl implements EditManager {
+	private static Logger logger = Logger.getLogger(EditManagerImpl.class);
+
+	private MultiCaster<EditManagerEvent> multiCaster = new MultiCaster<>(this);
+	private Map<WorkflowBundle, DataflowEdits> editsForDataflow = new HashMap<>();
+
+	@Override
+	public void addObserver(Observer<EditManagerEvent> observer) {
+		multiCaster.addObserver(observer);
+	}
+
+	@Override
+	public boolean canRedoDataflowEdit(WorkflowBundle dataflow) {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		return edits.canRedo();
+	}
+
+	@Override
+	public boolean canUndoDataflowEdit(WorkflowBundle dataflow) {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		return edits.canUndo();
+	}
+
+	@Override
+	public void doDataflowEdit(WorkflowBundle dataflow, Edit<?> edit)
+			throws EditException {
+		// We do the edit before we notify the observers
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		synchronized (edits) {
+			// Make sure the edits are in the order they were performed
+			edit.doEdit();
+			edits.addEdit(edit);
+		}
+		multiCaster.notify(new DataflowEditEvent(dataflow, edit));
+	}
+
+	@Override
+	public List<Observer<EditManagerEvent>> getObservers() {
+		return multiCaster.getObservers();
+	}
+
+	@Override
+	public void redoDataflowEdit(WorkflowBundle dataflow) throws EditException {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		Edit<?> edit;
+		synchronized (edits) {
+			if (!edits.canRedo())
+				return;
+			edit = edits.getLastUndo();
+			edit.doEdit();
+			edits.addRedo(edit);
+		}
+		multiCaster.notify(new DataFlowRedoEvent(dataflow, edit));
+	}
+
+	@Override
+	public void removeObserver(Observer<EditManagerEvent> observer) {
+		multiCaster.removeObserver(observer);
+	}
+
+	@Override
+	public void undoDataflowEdit(WorkflowBundle dataflow) {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		Edit<?> edit;
+		synchronized (edits) {
+			if (!edits.canUndo())
+				return;
+			edit = edits.getLastEdit();
+			edit.undo();
+			edits.addUndo(edit);
+		}
+		logger.info("Undoing an edit");
+		multiCaster.notify(new DataFlowUndoEvent(dataflow, edit));
+	}
+
+	/**
+	 * Get the set of edits for a given dataflow, creating if neccessary.
+	 *
+	 * @param dataflow
+	 *            Dataflow the edits relate to
+	 * @return A {@link DataflowEdits} instance to keep edits for the given
+	 *         dataflow
+	 */
+	protected synchronized DataflowEdits getEditsForDataflow(WorkflowBundle dataflow) {
+		DataflowEdits edits = editsForDataflow.get(dataflow);
+		if (edits == null) {
+			edits = new DataflowEdits();
+			editsForDataflow.put(dataflow, edits);
+		}
+		return edits;
+	}
+
+	/**
+	 * A set of edits and undoes for a {@link Dataflow}
+	 *
+	 * @author Stian Soiland-Reyes
+	 *
+	 */
+	public class DataflowEdits {
+		/**
+		 * List of edits that have been performed and can be undone.
+		 */
+		private List<Edit<?>> edits = new ArrayList<>();
+		/**
+		 * List of edits that have been undone and can be redone
+		 */
+		private List<Edit<?>> undoes = new ArrayList<>();
+
+		/**
+		 * Add an {@link Edit} that has been done by the EditManager.
+		 * <p>
+		 * This can later be retrieved using {@link #getLastEdit()}. After
+		 * calling this {@link #canRedo()} will be false.
+		 *
+		 * @param edit
+		 *            {@link Edit} that has been undone
+		 */
+		public synchronized void addEdit(Edit<?> edit) {
+			addEditOrRedo(edit, false);
+		}
+
+		/**
+		 * Add an {@link Edit} that has been redone by the EditManager.
+		 * <p>
+		 * The {@link Edit} must be the same as the last undo returned through
+		 * {@link #getLastUndo()}.
+		 * <p>
+		 * This method works like {@link #addEdit(Edit)} except that instead of
+		 * removing all possible redoes, only the given {@link Edit} is removed.
+		 *
+		 * @param edit
+		 *            {@link Edit} that has been redone
+		 */
+		public synchronized void addRedo(Edit<?> edit) {
+			addEditOrRedo(edit, true);
+		}
+
+		/**
+		 * Add an {@link Edit} that has been undone by the EditManager.
+		 * <p>
+		 * After calling this method {@link #canRedo()} will be true, and the
+		 * edit can be retrieved using {@link #getLastUndo()}.
+		 * </p>
+		 * <p>
+		 * The {@link Edit} must be the last edit returned from
+		 * {@link #getLastEdit()}, after calling this method
+		 * {@link #getLastEdit()} will return the previous edit or
+		 * {@link #canUndo()} will be false if there are no more edits.
+		 *
+		 * @param edit
+		 *            {@link Edit} that has been undone
+		 */
+		public synchronized void addUndo(Edit<?> edit) {
+			int lastIndex = edits.size() - 1;
+			if (lastIndex < 0 || !edits.get(lastIndex).equals(edit))
+				throw new IllegalArgumentException("Can't undo unknown edit "
+						+ edit);
+			undoes.add(edit);
+			edits.remove(lastIndex);
+		}
+
+		/**
+		 * True if there are undone events that can be redone.
+		 *
+		 * @return <code>true</code> if there are undone events
+		 */
+		public boolean canRedo() {
+			return !undoes.isEmpty();
+		}
+
+		/**
+		 * True if there are edits that can be undone and later added with
+		 * {@link #addUndo(Edit)}.
+		 *
+		 * @return <code>true</code> if there are edits that can be undone
+		 */
+		public boolean canUndo() {
+			return !edits.isEmpty();
+		}
+
+		/**
+		 * Get the last edit that can be undone. This edit was the last one to
+		 * be added with {@link #addEdit(Edit)} or {@link #addRedo(Edit)}.
+		 *
+		 * @return The last added {@link Edit}
+		 * @throws IllegalStateException
+		 *             If there are no more edits (Check with {@link #canUndo()}
+		 *             first)
+		 *
+		 */
+		public synchronized Edit<?> getLastEdit() throws IllegalStateException {
+			if (edits.isEmpty())
+				throw new IllegalStateException("No more edits");
+			int lastEdit = edits.size() - 1;
+			return edits.get(lastEdit);
+		}
+
+		/**
+		 * Get the last edit that can be redone. This edit was the last one to
+		 * be added with {@link #addUndo(Edit)}.
+		 *
+		 * @return The last undone {@link Edit}
+		 * @throws IllegalStateException
+		 *             If there are no more edits (Check with {@link #canRedo()}
+		 *             first)
+		 *
+		 */
+		public synchronized Edit<?> getLastUndo() throws IllegalStateException {
+			if (undoes.isEmpty())
+				throw new IllegalStateException("No more undoes");
+			int lastUndo = undoes.size() - 1;
+			return undoes.get(lastUndo);
+		}
+
+		/**
+		 * Add an edit or redo. Common functionallity called by
+		 * {@link #addEdit(Edit)} and {@link #addRedo(Edit)}.
+		 *
+		 * @see #addEdit(Edit)
+		 * @see #addRedo(Edit)
+		 * @param edit
+		 *            The {@link Edit} to add
+		 * @param isRedo
+		 *            True if this is a redo
+		 */
+		protected void addEditOrRedo(Edit<?> edit, boolean isRedo) {
+			edits.add(edit);
+			if (undoes.isEmpty())
+				return;
+			if (isRedo) {
+				// It's a redo, remove only the last one
+				int lastUndoIndex = undoes.size() - 1;
+				Edit<?> lastUndo = undoes.get(lastUndoIndex);
+				if (!edit.equals(lastUndo))
+					throw new IllegalArgumentException(
+							"Can only redo last undo");
+				undoes.remove(lastUndoIndex);
+			} else
+				// It's a new edit, remove all redos
+				undoes.clear();
+		}
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
new file mode 100644
index 0000000..97d14a6
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2012 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_Y;
+import static java.awt.event.KeyEvent.VK_Z;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.redoIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.undoIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractUndoAction extends AbstractAction {
+	protected EditManager editManager;
+	private SelectionManager selectionManager;
+
+	public AbstractUndoAction(String label, EditManager editManager) {
+		super(label);
+		this.editManager = editManager;
+		if (label.equals("Undo")) {
+			this.putValue(SMALL_ICON, undoIcon);
+			this.putValue(SHORT_DESCRIPTION, "Undo an action");
+			putValue(
+					ACCELERATOR_KEY,
+					getKeyStroke(VK_Z, getDefaultToolkit()
+							.getMenuShortcutKeyMask()));
+		} else if (label.equals("Redo")) {
+			this.putValue(SMALL_ICON, redoIcon);
+			this.putValue(SHORT_DESCRIPTION, "Redo an action");
+			putValue(
+					ACCELERATOR_KEY,
+					getKeyStroke(VK_Y, getDefaultToolkit()
+							.getMenuShortcutKeyMask()));
+		}
+		editManager.addObserver(new EditManagerObserver());
+		updateStatus();
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		WorkflowBundle workflowBundle = getCurrentDataflow();
+		if (workflowBundle != null)
+			performUndoOrRedo(workflowBundle);
+	}
+
+	/**
+	 * Check if action should be enabled or disabled and update its status.
+	 */
+	public void updateStatus() {
+		WorkflowBundle workflowBundle = getCurrentDataflow();
+		if (workflowBundle == null)
+			setEnabled(false);
+		setEnabled(isActive(workflowBundle));
+	}
+
+	/**
+	 * Retrieve the current dataflow from the {@link ModelMap}, or
+	 * <code>null</code> if no workflow is active.
+	 * 
+	 * @return The current {@link Dataflow}
+	 */
+	protected WorkflowBundle getCurrentDataflow() {
+		if (selectionManager == null)
+			return null;
+		return selectionManager.getSelectedWorkflowBundle();
+	}
+
+	/**
+	 * Return <code>true</code> if the action should be enabled when the given
+	 * {@link Dataflow} is the current, ie. if it's undoable or redoable.
+	 * 
+	 * @param dataflow
+	 *            Current {@link Dataflow}
+	 * @return <code>true</code> if the action should be enabled.
+	 */
+	protected abstract boolean isActive(WorkflowBundle workflowBundle);
+
+	/**
+	 * Called by {@link #actionPerformed(ActionEvent)} when the current dataflow
+	 * is not <code>null</code>.
+	 * 
+	 * @param dataflow
+	 *            {@link Dataflow} on which to undo or redo
+	 */
+	protected abstract void performUndoOrRedo(WorkflowBundle workflowBundle);
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+		if (selectionManager != null)
+			selectionManager.addObserver(new SelectionManagerObserver());
+	}
+
+	/**
+	 * Update the status if there's been an edit done on the current workflow.
+	 * 
+	 */
+	protected class EditManagerObserver implements Observer<EditManagerEvent> {
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			if (!(message instanceof AbstractDataflowEditEvent))
+				return;
+			AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+			if (dataflowEdit.getDataFlow().equals(dataflowEdit.getDataFlow()))
+				// It's an edit that could effect our undoability
+				updateStatus();
+		}
+	}
+
+	private final class SelectionManagerObserver extends
+			SwingAwareObserver<SelectionManagerEvent> {
+		private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+		@Override
+		public void notifySwing(Observable<SelectionManagerEvent> sender,
+				SelectionManagerEvent message) {
+			if (message instanceof WorkflowBundleSelectionEvent)
+				updateStatus();
+			else if (message instanceof PerspectiveSelectionEvent) {
+				PerspectiveSelectionEvent perspectiveSelectionEvent = (PerspectiveSelectionEvent) message;
+				if (DESIGN_PERSPECTIVE_ID.equals(perspectiveSelectionEvent
+						.getSelectedPerspective().getID()))
+					updateStatus();
+				else
+					setEnabled(false);
+			}
+		}
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
new file mode 100644
index 0000000..2abc139
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Redo the previous {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class RedoMenuAction extends AbstractMenuAction {
+	private static Logger logger = Logger.getLogger(RedoMenuAction.class);
+	private final EditManager editManager;
+	private SelectionManager selectionManager;
+	private AbstractUndoAction undoAction;
+
+	public RedoMenuAction(EditManager editManager) {
+		super(UNDO_SECTION_URI, 20);
+		this.editManager = editManager;
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		undoAction = new AbstractUndoAction("Redo", editManager) {
+			@Override
+			protected boolean isActive(WorkflowBundle workflowBundle) {
+				return editManager.canRedoDataflowEdit(workflowBundle);
+			}
+
+			@Override
+			protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+				try {
+					editManager.redoDataflowEdit(workflowBundle);
+				} catch (EditException | RuntimeException e) {
+					logger.warn("Could not redo for " + workflowBundle, e);
+					showMessageDialog(null, "Could not redo for workflow "
+							+ workflowBundle + ":\n" + e, "Could not redo",
+							ERROR_MESSAGE);
+				}
+			}
+		};
+		undoAction.setSelectionManager(selectionManager);
+		return undoAction;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+		if (undoAction != null)
+			undoAction.setSelectionManager(selectionManager);
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
new file mode 100644
index 0000000..e1242b3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Undo the last {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ * 
+ * @author Stian Soiland-Reyes
+ * 
+ */
+public class UndoMenuAction extends AbstractMenuAction {
+	private static Logger logger = Logger.getLogger(UndoMenuAction.class);
+	private final EditManager editManager;
+	private SelectionManager selectionManager;
+	private AbstractUndoAction undoAction;
+
+	public UndoMenuAction(EditManager editManager) {
+		super(UNDO_SECTION_URI, 10);
+		this.editManager = editManager;
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		undoAction = new AbstractUndoAction("Undo", editManager) {
+			@Override
+			protected boolean isActive(WorkflowBundle workflowBundle) {
+				return editManager.canUndoDataflowEdit(workflowBundle);
+			}
+
+			@Override
+			protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+				try {
+					editManager.undoDataflowEdit(workflowBundle);
+				} catch (RuntimeException e) {
+					logger.warn("Could not undo for " + workflowBundle, e);
+					showMessageDialog(null, "Could not undo for workflow "
+							+ workflowBundle + ":\n" + e, "Could not undo",
+							ERROR_MESSAGE);
+				}
+			}
+		};
+		undoAction.setSelectionManager(selectionManager);
+		return undoAction;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+		if (undoAction != null)
+			undoAction.setSelectionManager(selectionManager);
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
new file mode 100644
index 0000000..b83a650
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * A section of the Edit menu that contains {@link UndoMenuSection undo} and
+ * {@link RedoMenuAction redo}.
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class UndoMenuSection extends AbstractMenuSection {
+	public static final URI UNDO_SECTION_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/edits#undoSection");
+	public static final URI EDIT_MENU_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#edit");
+
+	public UndoMenuSection() {
+		super(EDIT_MENU_URI, 10, UNDO_SECTION_URI);
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
new file mode 100644
index 0000000..9eea85a
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class EditToolbarSection extends AbstractMenuSection {
+	public static final URI EDIT_TOOLBAR_SECTION = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+
+	public EditToolbarSection() {
+		super(DEFAULT_TOOL_BAR, 60, EDIT_TOOLBAR_SECTION);
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
new file mode 100644
index 0000000..09c0058
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction;
+
+public class RedoToolbarAction extends AbstractMenuAction {
+	private static final URI EDIT_TOOLBAR_REDO_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarRedo");
+	private final RedoMenuAction redoMenuAction;
+
+	public RedoToolbarAction(RedoMenuAction redoMenuAction) {
+		super(EDIT_TOOLBAR_SECTION, 20, EDIT_TOOLBAR_REDO_URI);
+		this.redoMenuAction = redoMenuAction;
+	}
+
+	@Override
+	protected Action createAction() {
+		return redoMenuAction.getAction();
+	}
+}
\ No newline at end of file
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
new file mode 100644
index 0000000..8e31ed3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction;
+
+public class UndoToolbarAction extends AbstractMenuAction {
+	private static final URI EDIT_TOOLBAR_UNDO_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarUndo");
+	private final UndoMenuAction undoMenuAction;
+
+	public UndoToolbarAction(UndoMenuAction undoMenuAction) {
+		super(EDIT_TOOLBAR_SECTION, 10, EDIT_TOOLBAR_UNDO_URI);
+		this.undoMenuAction = undoMenuAction;
+	}
+
+	@Override
+	protected Action createAction() {
+		return undoMenuAction.getAction();
+	}
+}
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6938308
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,6 @@
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection
+net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
new file mode 100644
index 0000000..92ee088
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
new file mode 100644
index 0000000..8eb7041
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="UndoMenuSection" auto-export="interfaces" />
+	<service ref="UndoMenuAction" auto-export="interfaces" />
+	<service ref="RedoMenuAction" auto-export="interfaces" />
+	<service ref="EditToolbarSection" auto-export="interfaces" />
+	<service ref="UndoToolbarAction" auto-export="interfaces" />
+	<service ref="RedoToolbarAction" auto-export="interfaces" />
+
+	<service ref="EditManagerImpl" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+
+	<reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" cardinality="0..1" />
+
+</beans:beans>
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
new file mode 100644
index 0000000..33f0b7b
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="UndoMenuSection" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection" />
+	<bean id="UndoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction">
+		<constructor-arg name="editManager">
+			<ref local="EditManagerImpl" />
+		</constructor-arg>
+		<property name="selectionManager" ref="selectionManager" />
+	</bean>
+	<bean id="RedoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction">
+		<constructor-arg name="editManager">
+			<ref local="EditManagerImpl" />
+		</constructor-arg>
+		<property name="selectionManager" ref="selectionManager" />
+	</bean>
+	<bean id="EditToolbarSection" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection" />
+	<bean id="UndoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction">
+		<constructor-arg>
+			<ref local="UndoMenuAction" />
+		</constructor-arg>
+	</bean>
+	<bean id="RedoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction">
+		<constructor-arg>
+			<ref local="RedoMenuAction" />
+		</constructor-arg>
+	</bean>
+
+	<bean id="EditManagerImpl" class="net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl" />
+
+</beans>
diff --git a/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
new file mode 100644
index 0000000..9123671
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class TestEditManagerImpl {
+
+	private Workflow dataflow;
+
+	private EditManagerObserver editManagerObserver = new EditManagerObserver();
+
+	private Processor processor;
+
+	@Test
+	public void addProcessor() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+		assertFalse("Edit was already applied", edit.isApplied());
+		assertTrue("Did already add processor", dataflow.getProcessors()
+				.isEmpty());
+
+		editManager.doDataflowEdit(dataflow.getParent(), edit);
+		assertTrue("Edit was not applied", edit.isApplied());
+		assertEquals("Did not add processor", processor, dataflow.getProcessors().first());
+
+		// Should have received the edit event
+		assertEquals("Incorrect number of events", 1,
+				editManagerObserver.events.size());
+		EditManagerEvent event = editManagerObserver.events.get(0);
+		assertTrue("Event was not a DataflowEditEvent",
+				event instanceof DataflowEditEvent);
+		DataflowEditEvent dataEditEvent = (DataflowEditEvent) event;
+		assertEquals("Event did not have correct workflow", dataflow,
+				dataEditEvent.getDataFlow().getWorkflows().first());
+		assertEquals("Event did not have correct edit", edit, dataEditEvent
+				.getEdit());
+
+	}
+
+	@Test
+	public void undoAddProcessor() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+		editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+		assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+		editManager.undoDataflowEdit(dataflow.getParent());
+		assertTrue("Did not undo add processor", dataflow.getProcessors()
+				.isEmpty());
+
+		// Should have received the undo event
+		assertEquals("Incorrect number of events", 2,
+				editManagerObserver.events.size());
+		EditManagerEvent event = editManagerObserver.events.get(1);
+		assertTrue("Event was not a DataflowEditEvent",
+				event instanceof DataFlowUndoEvent);
+		DataFlowUndoEvent dataEditEvent = (DataFlowUndoEvent) event;
+		assertEquals("Event did not have correct workflow", dataflow,
+				dataEditEvent.getDataFlow().getWorkflows().first());
+		assertEquals("Event did not have correct edit", edit, dataEditEvent
+				.getEdit());
+		assertFalse("Edit was still applied", edit.isApplied());
+	}
+
+	@Test
+	public void multipleUndoesRedoes() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Workflow dataflowA = createDataflow();
+		Workflow dataflowB = createDataflow();
+		Workflow dataflowC = createDataflow();
+
+		Processor processorA1 = createProcessor();
+		Processor processorA2 = createProcessor();
+		Processor processorA3 = createProcessor();
+		Processor processorB1 = createProcessor();
+		Processor processorC1 = createProcessor();
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflowA, processorA1);
+		editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowB, processorB1);
+		editManager.doDataflowEdit(dataflowB.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowA, processorA2);
+		editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowC, processorC1);
+		editManager.doDataflowEdit(dataflowC.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowA, processorA3);
+		editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+
+
+		assertFalse("Did not add processors", dataflowA.getProcessors().isEmpty());
+		assertEquals(3, dataflowA.getProcessors().size());
+		editManager.undoDataflowEdit(dataflowA.getParent());
+		assertEquals(2, dataflowA.getProcessors().size());
+		editManager.undoDataflowEdit(dataflowA.getParent());
+		assertEquals(1, dataflowA.getProcessors().size());
+		editManager.undoDataflowEdit(dataflowA.getParent());
+		assertEquals(0, dataflowA.getProcessors().size());
+
+		assertEquals(1, dataflowB.getProcessors().size());
+		assertEquals(1, dataflowC.getProcessors().size());
+
+		assertTrue(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+		editManager.undoDataflowEdit(dataflowC.getParent());
+		assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+		editManager.undoDataflowEdit(dataflowC.getParent()); // extra one
+		assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+
+
+		assertEquals(1, dataflowB.getProcessors().size());
+		assertEquals(0, dataflowC.getProcessors().size());
+
+		editManager.undoDataflowEdit(dataflowB.getParent());
+		assertEquals(0, dataflowA.getProcessors().size());
+		assertEquals(0, dataflowB.getProcessors().size());
+		assertEquals(0, dataflowC.getProcessors().size());
+
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(1, dataflowA.getProcessors().size());
+
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(2, dataflowA.getProcessors().size());
+
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(3, dataflowA.getProcessors().size());
+
+		// does not affect it
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(3, dataflowA.getProcessors().size());
+		assertEquals(0, dataflowB.getProcessors().size());
+		assertEquals(0, dataflowC.getProcessors().size());
+	}
+
+	@Test
+	public void emptyUndoDoesNotFail() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+		editManager.undoDataflowEdit(dataflow.getParent());
+	}
+
+	@Test
+	public void extraUndoesDoesNotFail() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+		editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+		assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+		editManager.undoDataflowEdit(dataflow.getParent());
+		assertTrue("Did not undo add processor", dataflow.getProcessors()
+				.isEmpty());
+		editManager.undoDataflowEdit(dataflow.getParent());
+	}
+
+	@Before
+	public void makeDataflow() {
+		dataflow = createDataflow();
+	}
+
+	protected Workflow createDataflow() {
+		WorkflowBundle workflowBundle = new WorkflowBundle();
+		Workflow workflow = new Workflow();
+		workflow.setParent(workflowBundle);
+		return workflow;
+	}
+
+	protected Processor createProcessor() {
+		Processor processor = new Processor();
+		processor.setName("proc-" + UUID.randomUUID());
+		return processor;
+	}
+
+	@Before
+	public void makeProcessor() {
+		processor = createProcessor();
+	}
+
+	private class EditManagerObserver implements Observer<EditManagerEvent> {
+
+		public List<EditManagerEvent> events = new ArrayList<>();
+
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			events.add(message);
+			if (message instanceof DataflowEditEvent) {
+				DataflowEditEvent dataflowEdit = (DataflowEditEvent) message;
+				assertTrue("Edit was not applied on edit event", dataflowEdit
+						.getEdit().isApplied());
+			} else if (message instanceof DataFlowUndoEvent) {
+				DataFlowUndoEvent dataflowUndo = (DataFlowUndoEvent) message;
+				assertFalse("Edit was applied on undo event", dataflowUndo
+						.getEdit().isApplied());
+			} else if (message instanceof DataFlowRedoEvent) {
+				DataFlowRedoEvent dataflowEdit = (DataFlowRedoEvent) message;
+				assertTrue("Edit was not applied on edit event", dataflowEdit
+						.getEdit().isApplied());
+			} else {
+				fail("Unknown event: " + message);
+			}
+		}
+	}
+
+}
diff --git a/taverna-workbench-file-impl/pom.xml b/taverna-workbench-file-impl/pom.xml
new file mode 100644
index 0000000..98dcce7
--- /dev/null
+++ b/taverna-workbench-file-impl/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>file-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>File opening implementation</name>
+	<description>
+		Implementation for doing file (i.e. workflow) open/save in the
+		workbench.
+	</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>file-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>edits-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>workbench-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>observer</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>com.springsource.org.apache.commons.lang</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jdom</groupId>
+			<artifactId>com.springsource.org.jdom</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-impl</groupId>
+			<artifactId>edits-impl</artifactId>
+			<version>${project.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-t2flow</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-rdfxml</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
new file mode 100644
index 0000000..86bc091
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowFromDataflowPersistenceHandler extends
+		AbstractDataflowPersistenceHandler implements
+		DataflowPersistenceHandler {
+	private static final WorkflowBundleFileType WORKFLOW_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+
+	@Override
+	public DataflowInfo openDataflow(FileType fileType, Object source)
+			throws OpenException {
+		if (!getOpenFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+
+		WorkflowBundle workflowBundle = (WorkflowBundle) source;
+		Date lastModified = null;
+		Object canonicalSource = null;
+		return new DataflowInfo(WORKFLOW_BUNDLE_FILE_TYPE, canonicalSource,
+				workflowBundle, lastModified);
+	}
+
+	@Override
+	public List<FileType> getOpenFileTypes() {
+		return Arrays.<FileType> asList(WORKFLOW_BUNDLE_FILE_TYPE);
+	}
+
+	@Override
+	public List<Class<?>> getOpenSourceTypes() {
+		return Arrays.<Class<?>> asList(Workflow.class);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
new file mode 100644
index 0000000..39117e9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static org.apache.commons.collections.map.LazyMap.decorate;
+import static org.apache.commons.lang.ClassUtils.getAllInterfaces;
+import static org.apache.commons.lang.ClassUtils.getAllSuperclasses;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.commons.collections.Factory;
+
+// TODO: Cache lookups / build one massive structure
+public class DataflowPersistenceHandlerRegistry {
+	private static final MapFactory MAP_FACTORY = new MapFactory();
+	private static final SetFactory SET_FACTORY = new SetFactory();
+
+	@SuppressWarnings("unchecked")
+	protected static List<Class<?>> findAllParentClasses(
+			final Class<?> sourceClass) {
+		List<Class<?>> superClasses = new ArrayList<>();
+		superClasses.add(sourceClass);
+		superClasses.addAll(getAllSuperclasses(sourceClass));
+		superClasses.addAll(getAllInterfaces(sourceClass));
+		return superClasses;
+	}
+
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> openClassToHandlers;
+	private Map<Class<?>, Set<FileType>> openClassToTypes;
+	private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> openFileClassToHandler;
+	private Map<FileType, Set<DataflowPersistenceHandler>> openFileToHandler;
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> saveClassToHandlers;
+	private Map<Class<?>, Set<FileType>> saveClassToTypes;
+	private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> saveFileClassToHandler;
+	private Map<FileType, Set<DataflowPersistenceHandler>> saveFileToHandler;
+
+	private List<DataflowPersistenceHandler> dataflowPersistenceHandlers;
+
+	public DataflowPersistenceHandlerRegistry() {
+	}
+
+	public Set<FileType> getOpenFileTypes() {
+		return getOpenFileClassToHandler().keySet();
+	}
+
+	public Set<FileType> getOpenFileTypesFor(Class<?> sourceClass) {
+		Set<FileType> fileTypes = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			fileTypes.addAll(getOpenClassToTypes().get(candidateClass));
+		return fileTypes;
+	}
+
+	public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+			Class<? extends Object> sourceClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			handlers.addAll(getOpenClassToHandlers().get(candidateClass));
+		return handlers;
+	}
+
+	public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+			FileType fileType, Class<? extends Object> sourceClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+					candidateClass));
+		return handlers;
+	}
+
+	public Set<DataflowPersistenceHandler> getOpenHandlersForType(
+			FileType fileType) {
+		return getOpenFileToHandler().get(fileType);
+	}
+
+	public synchronized Set<DataflowPersistenceHandler> getOpenHandlersForType(
+			FileType fileType, Class<?> sourceClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+					candidateClass));
+		return handlers;
+	}
+
+	public Set<FileType> getSaveFileTypes() {
+		return getSaveFileClassToHandler().keySet();
+	}
+
+	public Set<FileType> getSaveFileTypesFor(Class<?> destinationClass) {
+		Set<FileType> fileTypes = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+			fileTypes.addAll(getSaveClassToTypes().get(candidateClass));
+		return fileTypes;
+	}
+
+	public Set<DataflowPersistenceHandler> getSaveHandlersFor(
+			Class<? extends Object> destinationClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+			handlers.addAll(getSaveClassToHandlers().get(candidateClass));
+		return handlers;
+	}
+
+	public Set<DataflowPersistenceHandler> getSaveHandlersForType(
+			FileType fileType, Class<?> destinationClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+			handlers.addAll(getSaveFileClassToHandler().get(fileType).get(
+					candidateClass));
+		return handlers;
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	private synchronized void createCollections() {
+		openFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+		openFileToHandler = decorate(new HashMap(), SET_FACTORY);
+		openClassToTypes = decorate(new HashMap(), SET_FACTORY);
+		openClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+
+		saveFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+		saveFileToHandler = decorate(new HashMap(), SET_FACTORY);
+		saveClassToTypes = decorate(new HashMap(), SET_FACTORY);
+		saveClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+	}
+
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> getOpenClassToHandlers() {
+		return openClassToHandlers;
+	}
+
+	private synchronized Map<Class<?>, Set<FileType>> getOpenClassToTypes() {
+		return openClassToTypes;
+	}
+
+	private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getOpenFileClassToHandler() {
+		return openFileClassToHandler;
+	}
+
+	private Map<FileType, Set<DataflowPersistenceHandler>> getOpenFileToHandler() {
+		return openFileToHandler;
+	}
+
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> getSaveClassToHandlers() {
+		return saveClassToHandlers;
+	}
+
+	private synchronized Map<Class<?>, Set<FileType>> getSaveClassToTypes() {
+		return saveClassToTypes;
+	}
+
+	private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getSaveFileClassToHandler() {
+		return saveFileClassToHandler;
+	}
+
+	/**
+	 * Bind method for SpringDM.
+	 * 
+	 * @param service
+	 * @param properties
+	 */
+	public void update(Object service, Map<?, ?> properties) {
+		if (dataflowPersistenceHandlers != null)
+			updateColletions();
+	}
+
+	public synchronized void updateColletions() {
+		createCollections();
+		for (DataflowPersistenceHandler handler : dataflowPersistenceHandlers) {
+			for (FileType openFileType : handler.getOpenFileTypes()) {
+				Set<DataflowPersistenceHandler> set = openFileToHandler
+						.get(openFileType);
+				set.add(handler);
+				for (Class<?> openClass : handler.getOpenSourceTypes()) {
+					openFileClassToHandler.get(openFileType).get(openClass)
+							.add(handler);
+					openClassToTypes.get(openClass).add(openFileType);
+				}
+			}
+			for (Class<?> openClass : handler.getOpenSourceTypes())
+				openClassToHandlers.get(openClass).add(handler);
+
+			for (FileType saveFileType : handler.getSaveFileTypes()) {
+				saveFileToHandler.get(saveFileType).add(handler);
+				for (Class<?> saveClass : handler.getSaveDestinationTypes()) {
+					saveFileClassToHandler.get(saveFileType).get(saveClass)
+							.add(handler);
+					saveClassToTypes.get(saveClass).add(saveFileType);
+				}
+			}
+			for (Class<?> openClass : handler.getSaveDestinationTypes())
+				saveClassToHandlers.get(openClass).add(handler);
+		}
+	}
+
+	public void setDataflowPersistenceHandlers(
+			List<DataflowPersistenceHandler> dataflowPersistenceHandlers) {
+		this.dataflowPersistenceHandlers = dataflowPersistenceHandlers;
+	}
+
+	private static class MapFactory implements Factory {
+		@Override
+		@SuppressWarnings("rawtypes")
+		public Object create() {
+			return decorate(new HashMap(), SET_FACTORY);
+		}
+	}
+
+	private static class SetFactory implements Factory {
+		@Override
+		public Object create() {
+			return new LinkedHashSet<Object>();
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
new file mode 100644
index 0000000..89ae39c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Information about an open dataflow that was opened from or saved to a
+ * {@link File}.
+ * 
+ * @see DataflowInfo
+ * @see FileManager
+ * @author Stian Soiland-Reyes
+ */
+public class FileDataflowInfo extends DataflowInfo {
+	private static Logger logger = Logger.getLogger(FileDataflowInfo.class);
+
+	public FileDataflowInfo(FileType fileType, File source,
+			WorkflowBundle workflowBundle) {
+		super(fileType, canonicalFile(source), workflowBundle,
+				lastModifiedFile(source));
+	}
+
+	protected static Date lastModifiedFile(File file) {
+		long lastModifiedLong = file.lastModified();
+		if (lastModifiedLong == 0)
+			return null;
+		return new Date(lastModifiedLong);
+	}
+
+	public static File canonicalFile(File file) {
+		try {
+			return file.getCanonicalFile();
+		} catch (IOException e) {
+			logger.warn("Could not find canonical file for " + file);
+			return file;
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
new file mode 100644
index 0000000..aadb3f1
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
@@ -0,0 +1,601 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+import static java.util.Collections.singleton;
+import static javax.swing.SwingUtilities.invokeAndWait;
+import static javax.swing.SwingUtilities.isEventDispatchThread;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.ClosingDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Implementation of {@link FileManager}
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class FileManagerImpl implements FileManager {
+	private static Logger logger = Logger.getLogger(FileManagerImpl.class);
+	private static int nameIndex = 1;
+
+	/**
+	 * The last blank workflowBundle created using #newDataflow() until it has
+	 * been changed - when this variable will be set to null again. Used to
+	 * automatically close unmodified blank workflowBundles on open.
+	 */
+	private WorkflowBundle blankWorkflowBundle = null;
+	@SuppressWarnings("unused")
+	private EditManager editManager;
+	private EditManagerObserver editManagerObserver = new EditManagerObserver();
+	protected MultiCaster<FileManagerEvent> observers = new MultiCaster<>(this);
+	/**
+	 * Ordered list of open WorkflowBundle
+	 */
+	private LinkedHashMap<WorkflowBundle, OpenDataflowInfo> openDataflowInfos = new LinkedHashMap<>();
+	private DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry;
+	private Scufl2Tools scufl2Tools = new Scufl2Tools();
+	private WorkflowBundle currentWorkflowBundle;
+
+	public DataflowPersistenceHandlerRegistry getPersistanceHandlerRegistry() {
+		return dataflowPersistenceHandlerRegistry;
+	}
+
+	public FileManagerImpl(EditManager editManager) {
+		this.editManager = editManager;
+		editManager.addObserver(editManagerObserver);
+	}
+
+	/**
+	 * Add an observer to be notified of {@link FileManagerEvent}s, such as
+	 * {@link OpenedDataflowEvent} and {@link SavedDataflowEvent}.
+	 * 
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void addObserver(Observer<FileManagerEvent> observer) {
+		observers.addObserver(observer);
+	}
+
+	@Override
+	public boolean canSaveWithoutDestination(WorkflowBundle workflowBundle) {
+		OpenDataflowInfo dataflowInfo = getOpenDataflowInfo(workflowBundle);
+		if (dataflowInfo.getSource() == null)
+			return false;
+		Set<?> handlers = getPersistanceHandlerRegistry()
+				.getSaveHandlersForType(
+						dataflowInfo.getFileType(),
+						dataflowInfo.getDataflowInfo().getCanonicalSource()
+								.getClass());
+		return !handlers.isEmpty();
+	}
+
+	@Override
+	public boolean closeDataflow(WorkflowBundle workflowBundle,
+			boolean failOnUnsaved) throws UnsavedException {
+		if (workflowBundle == null)
+			throw new NullPointerException("Dataflow can't be null");
+		ClosingDataflowEvent message = new ClosingDataflowEvent(workflowBundle);
+		observers.notify(message);
+		if (message.isAbortClose())
+			return false;
+		if ((failOnUnsaved && getOpenDataflowInfo(workflowBundle).isChanged()))
+			throw new UnsavedException(workflowBundle);
+		if (workflowBundle.equals(getCurrentDataflow())) {
+			// We'll need to change current workflowBundle
+			// Find best candidate to the left or right
+			List<WorkflowBundle> workflowBundles = getOpenDataflows();
+			int openIndex = workflowBundles.indexOf(workflowBundle);
+			if (openIndex == -1)
+				throw new IllegalArgumentException("Workflow was not opened "
+						+ workflowBundle);
+
+			if (openIndex > 0)
+				setCurrentDataflow(workflowBundles.get(openIndex - 1));
+			else if (openIndex == 0 && workflowBundles.size() > 1)
+				setCurrentDataflow(workflowBundles.get(1));
+			else
+				// If it was the last one, start a new, empty workflowBundle
+				newDataflow();
+		}
+		if (workflowBundle == blankWorkflowBundle)
+			blankWorkflowBundle = null;
+		openDataflowInfos.remove(workflowBundle);
+		observers.notify(new ClosedDataflowEvent(workflowBundle));
+		return true;
+	}
+
+	@Override
+	public WorkflowBundle getCurrentDataflow() {
+		return currentWorkflowBundle;
+	}
+
+	@Override
+	public WorkflowBundle getDataflowBySource(Object source) {
+		for (Entry<WorkflowBundle, OpenDataflowInfo> infoEntry : openDataflowInfos
+				.entrySet()) {
+			OpenDataflowInfo info = infoEntry.getValue();
+			if (source.equals(info.getSource()))
+				return infoEntry.getKey();
+		}
+		// Not found
+		return null;
+	}
+
+	@Override
+	public String getDataflowName(WorkflowBundle workflowBundle) {
+		Object source = null;
+		if (isDataflowOpen(workflowBundle))
+			source = getDataflowSource(workflowBundle);
+		// Fallback
+		String name;
+		Workflow workflow = workflowBundle.getMainWorkflow();
+		if (workflow != null)
+			name = workflow.getName();
+		else
+			name = workflowBundle.getName();
+		if (source == null)
+			return name;
+		if (source instanceof File)
+			return ((File) source).getAbsolutePath();
+		else if (source instanceof URL)
+			return source.toString();
+
+		// Check if it has implemented a toString() method
+		Method toStringMethod = null;
+		Method toStringMethodFromObject = null;
+		try {
+			toStringMethod = source.getClass().getMethod("toString");
+			toStringMethodFromObject = Object.class.getMethod("toString");
+		} catch (Exception e) {
+			throw new IllegalStateException(
+					"Source did not implement Object.toString() " + source);
+		}
+		if (!toStringMethod.equals(toStringMethodFromObject))
+			return source.toString();
+		return name;
+	}
+
+	@Override
+	public String getDefaultWorkflowName() {
+		return "Workflow" + (nameIndex++);
+	}
+
+	@Override
+	public Object getDataflowSource(WorkflowBundle workflowBundle) {
+		return getOpenDataflowInfo(workflowBundle).getSource();
+	}
+
+	@Override
+	public FileType getDataflowType(WorkflowBundle workflowBundle) {
+		return getOpenDataflowInfo(workflowBundle).getFileType();
+	}
+
+	@Override
+	public List<Observer<FileManagerEvent>> getObservers() {
+		return observers.getObservers();
+	}
+
+	/**
+	 * Get the {@link OpenDataflowInfo} for the given WorkflowBundle
+	 * 
+	 * @throws NullPointerException
+	 *             if the WorkflowBundle was <code>null</code>
+	 * @throws IllegalArgumentException
+	 *             if the WorkflowBundle was not open.
+	 * @param workflowBundle
+	 *            WorkflowBundle which information is to be found
+	 * @return The {@link OpenDataflowInfo} describing the WorkflowBundle
+	 */
+	protected synchronized OpenDataflowInfo getOpenDataflowInfo(
+			WorkflowBundle workflowBundle) {
+		if (workflowBundle == null)
+			throw new NullPointerException("Dataflow can't be null");
+		OpenDataflowInfo info = openDataflowInfos.get(workflowBundle);
+		if (info == null)
+			throw new IllegalArgumentException("Workflow was not opened "
+					+ workflowBundle);
+		return info;
+	}
+
+	@Override
+	public List<WorkflowBundle> getOpenDataflows() {
+		return new ArrayList<>(openDataflowInfos.keySet());
+	}
+
+	@Override
+	public List<FileFilter> getOpenFileFilters() {
+		List<FileFilter> fileFilters = new ArrayList<>();
+
+		Set<FileType> fileTypes = getPersistanceHandlerRegistry()
+				.getOpenFileTypes();
+		if (!fileTypes.isEmpty())
+			fileFilters.add(new MultipleFileTypes(fileTypes,
+					"All supported workflows"));
+		for (FileType fileType : fileTypes)
+			fileFilters.add(new FileTypeFileFilter(fileType));
+		return fileFilters;
+	}
+
+	@Override
+	public List<FileFilter> getOpenFileFilters(Class<?> sourceClass) {
+		List<FileFilter> fileFilters = new ArrayList<>();
+		for (FileType fileType : getPersistanceHandlerRegistry()
+				.getOpenFileTypesFor(sourceClass))
+			fileFilters.add(new FileTypeFileFilter(fileType));
+		return fileFilters;
+	}
+
+	@Override
+	public List<FileFilter> getSaveFileFilters() {
+		List<FileFilter> fileFilters = new ArrayList<>();
+		for (FileType fileType : getPersistanceHandlerRegistry()
+				.getSaveFileTypes())
+			fileFilters.add(new FileTypeFileFilter(fileType));
+		return fileFilters;
+	}
+
+	@Override
+	public List<FileFilter> getSaveFileFilters(Class<?> destinationClass) {
+		List<FileFilter> fileFilters = new ArrayList<>();
+		for (FileType fileType : getPersistanceHandlerRegistry()
+				.getSaveFileTypesFor(destinationClass))
+			fileFilters.add(new FileTypeFileFilter(fileType));
+		return fileFilters;
+	}
+
+	@Override
+	public boolean isDataflowChanged(WorkflowBundle workflowBundle) {
+		return getOpenDataflowInfo(workflowBundle).isChanged();
+	}
+
+	@Override
+	public boolean isDataflowOpen(WorkflowBundle workflowBundle) {
+		return openDataflowInfos.containsKey(workflowBundle);
+	}
+
+	@Override
+	public WorkflowBundle newDataflow() {
+		WorkflowBundle workflowBundle = new WorkflowBundle();
+		workflowBundle.setMainWorkflow(new Workflow());
+		workflowBundle.getMainWorkflow().setName(getDefaultWorkflowName());
+		workflowBundle.setMainProfile(new Profile());
+		scufl2Tools.setParents(workflowBundle);
+		blankWorkflowBundle = null;
+		openDataflowInternal(workflowBundle);
+		blankWorkflowBundle = workflowBundle;
+		observers.notify(new OpenedDataflowEvent(workflowBundle));
+		return workflowBundle;
+	}
+
+	@Override
+	public void openDataflow(WorkflowBundle workflowBundle) {
+		openDataflowInternal(workflowBundle);
+		observers.notify(new OpenedDataflowEvent(workflowBundle));
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public WorkflowBundle openDataflow(FileType fileType, Object source)
+			throws OpenException {
+		if (isHeadless())
+			return performOpenDataflow(fileType, source);
+
+		OpenDataflowRunnable r = new OpenDataflowRunnable(this, fileType,
+				source);
+		if (isEventDispatchThread()) {
+			r.run();
+		} else
+			try {
+				invokeAndWait(r);
+			} catch (InterruptedException | InvocationTargetException e) {
+				throw new OpenException("Opening was interrupted", e);
+			}
+		OpenException thrownException = r.getException();
+		if (thrownException != null)
+			throw thrownException;
+		return r.getDataflow();
+	}
+
+	public WorkflowBundle performOpenDataflow(FileType fileType, Object source)
+			throws OpenException {
+		DataflowInfo dataflowInfo;
+		WorkflowBundle workflowBundle;
+		dataflowInfo = openDataflowSilently(fileType, source);
+		workflowBundle = dataflowInfo.getDataflow();
+		openDataflowInternal(workflowBundle);
+		getOpenDataflowInfo(workflowBundle).setOpenedFrom(dataflowInfo);
+		observers.notify(new OpenedDataflowEvent(workflowBundle));
+		return workflowBundle;
+	}
+
+	@Override
+	public DataflowInfo openDataflowSilently(FileType fileType, Object source)
+			throws OpenException {
+		Set<DataflowPersistenceHandler> handlers;
+		Class<? extends Object> sourceClass = source.getClass();
+
+		boolean unknownFileType = (fileType == null);
+		if (unknownFileType)
+			handlers = getPersistanceHandlerRegistry().getOpenHandlersFor(
+					sourceClass);
+		else
+			handlers = getPersistanceHandlerRegistry().getOpenHandlersFor(
+					fileType, sourceClass);
+		if (handlers.isEmpty())
+			throw new OpenException("Unsupported file type or class "
+					+ fileType + " " + sourceClass);
+
+		Throwable lastException = null;
+		for (DataflowPersistenceHandler handler : handlers) {
+			Collection<FileType> fileTypes;
+			if (unknownFileType)
+				fileTypes = handler.getOpenFileTypes();
+			else
+				fileTypes = singleton(fileType);
+			for (FileType candidateFileType : fileTypes) {
+				if (unknownFileType && (source instanceof File))
+					/*
+					 * If source is file but fileType was not explicitly set
+					 * from the open workflow dialog - check the file extension
+					 * and decide which handler to use based on that (so that we
+					 * do not loop though all handlers)
+					 */
+					if (!((File) source).getPath().endsWith(
+							candidateFileType.getExtension()))
+						continue;
+
+				try {
+					DataflowInfo openDataflow = handler.openDataflow(
+							candidateFileType, source);
+					WorkflowBundle workflowBundle = openDataflow.getDataflow();
+					logger.info("Loaded workflow: " + workflowBundle.getName()
+							+ " " + workflowBundle.getGlobalBaseURI()
+							+ " from " + source + " using " + handler);
+					return openDataflow;
+				} catch (OpenException ex) {
+					logger.warn("Could not open workflow " + source + " using "
+							+ handler + " of type " + candidateFileType);
+					lastException = ex;
+				}
+			}
+		}
+		throw new OpenException("Could not open workflow " + source + "\n",
+				lastException);
+	}
+
+	/**
+	 * Mark the WorkflowBundle as opened, and close the blank WorkflowBundle if
+	 * needed.
+	 * 
+	 * @param workflowBundle
+	 *            WorkflowBundle that has been opened
+	 */
+	protected void openDataflowInternal(WorkflowBundle workflowBundle) {
+		if (workflowBundle == null)
+			throw new NullPointerException("Dataflow can't be null");
+		if (isDataflowOpen(workflowBundle))
+			throw new IllegalArgumentException("Workflow is already open: "
+					+ workflowBundle);
+
+		openDataflowInfos.put(workflowBundle, new OpenDataflowInfo());
+		setCurrentDataflow(workflowBundle);
+		if (openDataflowInfos.size() == 2 && blankWorkflowBundle != null)
+			/*
+			 * Behave like a word processor and close the blank WorkflowBundle
+			 * when another workflow has been opened
+			 */
+			try {
+				closeDataflow(blankWorkflowBundle, true);
+			} catch (UnsavedException e) {
+				logger.error("Blank workflow was modified "
+						+ "and could not be closed");
+			}
+	}
+
+	@Override
+	public void removeObserver(Observer<FileManagerEvent> observer) {
+		observers.removeObserver(observer);
+	}
+
+	@Override
+	public void saveDataflow(WorkflowBundle workflowBundle,
+			boolean failOnOverwrite) throws SaveException {
+		if (workflowBundle == null)
+			throw new NullPointerException("Dataflow can't be null");
+		OpenDataflowInfo lastSave = getOpenDataflowInfo(workflowBundle);
+		if (lastSave.getSource() == null)
+			throw new SaveException("Can't save without source "
+					+ workflowBundle);
+		saveDataflow(workflowBundle, lastSave.getFileType(),
+				lastSave.getSource(), failOnOverwrite);
+	}
+
+	@Override
+	public void saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+			Object destination, boolean failOnOverwrite) throws SaveException {
+		DataflowInfo savedDataflow = saveDataflowSilently(workflowBundle,
+				fileType, destination, failOnOverwrite);
+		getOpenDataflowInfo(workflowBundle).setSavedTo(savedDataflow);
+		observers.notify(new SavedDataflowEvent(workflowBundle));
+	}
+
+	@Override
+	public DataflowInfo saveDataflowSilently(WorkflowBundle workflowBundle,
+			FileType fileType, Object destination, boolean failOnOverwrite)
+			throws SaveException, OverwriteException {
+		Set<DataflowPersistenceHandler> handlers;
+
+		Class<? extends Object> destinationClass = destination.getClass();
+		if (fileType != null)
+			handlers = getPersistanceHandlerRegistry().getSaveHandlersForType(
+					fileType, destinationClass);
+		else
+			handlers = getPersistanceHandlerRegistry().getSaveHandlersFor(
+					destinationClass);
+
+		SaveException lastException = null;
+		for (DataflowPersistenceHandler handler : handlers) {
+			if (failOnOverwrite) {
+				OpenDataflowInfo openDataflowInfo = getOpenDataflowInfo(workflowBundle);
+				if (handler.wouldOverwriteDataflow(workflowBundle, fileType,
+						destination, openDataflowInfo.getDataflowInfo()))
+					throw new OverwriteException(destination);
+			}
+			try {
+				DataflowInfo savedDataflow = handler.saveDataflow(
+						workflowBundle, fileType, destination);
+				savedDataflow.getDataflow();
+				logger.info("Saved workflow: " + workflowBundle.getName() + " "
+						+ workflowBundle.getGlobalBaseURI() + " to "
+						+ savedDataflow.getCanonicalSource() + " using "
+						+ handler);
+				return savedDataflow;
+			} catch (SaveException ex) {
+				logger.warn("Could not save to " + destination + " using "
+						+ handler);
+				lastException = ex;
+			}
+		}
+
+		if (lastException == null)
+			throw new SaveException("Unsupported file type or class "
+					+ fileType + " " + destinationClass);
+		throw new SaveException("Could not save to " + destination + ":\n"
+				+ lastException.getLocalizedMessage(), lastException);
+	}
+
+	@Override
+	public void setCurrentDataflow(WorkflowBundle workflowBundle) {
+		setCurrentDataflow(workflowBundle, false);
+	}
+
+	@Override
+	public void setCurrentDataflow(WorkflowBundle workflowBundle,
+			boolean openIfNeeded) {
+		currentWorkflowBundle = workflowBundle;
+		if (!isDataflowOpen(workflowBundle)) {
+			if (!openIfNeeded)
+				throw new IllegalArgumentException("Workflow is not open: "
+						+ workflowBundle);
+			openDataflow(workflowBundle);
+			return;
+		}
+		observers.notify(new SetCurrentDataflowEvent(workflowBundle));
+	}
+
+	@Override
+	public void setDataflowChanged(WorkflowBundle workflowBundle,
+			boolean isChanged) {
+		getOpenDataflowInfo(workflowBundle).setIsChanged(isChanged);
+		if (blankWorkflowBundle == workflowBundle)
+			blankWorkflowBundle = null;
+	}
+
+	@Override
+	public Object getCanonical(Object source) throws IllegalArgumentException,
+			URISyntaxException, IOException {
+		Object canonicalSource = source;
+
+		if (source instanceof URL) {
+			URL url = ((URL) source);
+			if (url.getProtocol().equalsIgnoreCase("file"))
+				canonicalSource = new File(url.toURI());
+		}
+
+		if (canonicalSource instanceof File)
+			canonicalSource = ((File) canonicalSource).getCanonicalFile();
+		return canonicalSource;
+	}
+
+	public void setDataflowPersistenceHandlerRegistry(
+			DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry) {
+		this.dataflowPersistenceHandlerRegistry = dataflowPersistenceHandlerRegistry;
+	}
+
+	/**
+	 * Observe the {@link EditManager} for changes to open workflowBundles. A
+	 * change of an open workflow would set it as changed using
+	 * {@link FileManagerImpl#setDataflowChanged(Dataflow, boolean)}.
+	 * 
+	 * @author Stian Soiland-Reyes
+	 * 
+	 */
+	private final class EditManagerObserver implements
+			Observer<EditManagerEvent> {
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			if (message instanceof AbstractDataflowEditEvent) {
+				AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+				WorkflowBundle workflowBundle = dataflowEdit.getDataFlow();
+				/**
+				 * TODO: on undo/redo - keep last event or similar to determine
+				 * if workflow was saved before. See
+				 * FileManagerTest#isChangedWithUndo().
+				 */
+				setDataflowChanged(workflowBundle, true);
+			}
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
new file mode 100644
index 0000000..6416163
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class FileTypeFileFilter extends FileFilter {
+	private final FileType fileType;
+
+	public FileTypeFileFilter(FileType fileType) {
+		this.fileType = fileType;
+	}
+
+	@Override
+	public String getDescription() {
+		return fileType.getDescription();
+	}
+
+	@Override
+	public boolean accept(File file) {
+		if (file.isDirectory())
+			// Don't grey out directories
+			return true;
+		if (fileType.getExtension() == null)
+			return false;
+		return file.getName().toLowerCase()
+				.endsWith("." + fileType.getExtension());
+	}
+
+	public FileType getFileType() {
+		return fileType;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
new file mode 100644
index 0000000..c398805
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.util.Set;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class MultipleFileTypes extends FileFilter {
+	private String description;
+	private final Set<FileType> fileTypes;
+
+	public MultipleFileTypes(Set<FileType> fileTypes, String description) {
+		this.fileTypes = fileTypes;
+		this.description = description;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public boolean accept(File file) {
+		if (file.isDirectory())
+			return true;
+
+		String lowerFileName = file.getName().toLowerCase();
+		for (FileType fileType : fileTypes) {
+			if (fileType.getExtension() == null)
+				continue;
+			if (lowerFileName.endsWith(fileType.getExtension()))
+				return true;
+		}
+		return false;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
new file mode 100644
index 0000000..dc08cff
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
@@ -0,0 +1,88 @@
+
+/*******************************************************************************
+ * Copyright (C) 2009 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static java.awt.BorderLayout.CENTER;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * Dialog that is popped up while we are opening a workflow.
+ * 
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class OpenDataflowInProgressDialog extends HelpEnabledDialog {
+	private boolean userCancelled = false;
+
+	public OpenDataflowInProgressDialog() {
+		super(getMainWindow(), "Opening workflow", true);
+		setResizable(false);
+		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+		
+		JPanel panel = new JPanel(new BorderLayout());
+		panel.setBorder(new EmptyBorder(10,10,10,10));
+		
+		JPanel textPanel = new JPanel();
+		JLabel text = new JLabel(workingIcon);
+		text.setText("Opening workflow...");
+		text.setBorder(new EmptyBorder(10,0,10,0));
+		textPanel.add(text);
+		panel.add(textPanel, CENTER);
+		
+/*
+ * Cancellation does not work when opening
+ 
+		// Cancel button
+		JButton cancelButton = new JButton("Cancel");
+		cancelButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				userCancelled = true;
+				setVisible(false);
+				dispose();
+			}
+		});
+		JPanel cancelButtonPanel = new JPanel();
+		cancelButtonPanel.add(cancelButton);
+		panel.add(cancelButtonPanel, BorderLayout.SOUTH);
+*/
+		setContentPane(panel);
+		setPreferredSize(new Dimension(300, 100));
+
+		pack();		
+	}
+
+	public boolean hasUserCancelled() {
+		return userCancelled;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
new file mode 100644
index 0000000..4a4a1e3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+/**
+ * Information about an open dataflow.
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class OpenDataflowInfo {
+	private DataflowInfo dataflowInfo;
+	private boolean isChanged;
+	private Date openedAt;
+
+	public OpenDataflowInfo() {
+	}
+
+	public FileType getFileType() {
+		if (dataflowInfo == null)
+			return null;
+		return dataflowInfo.getFileType();
+	}
+
+	public Date getLastModified() {
+		if (dataflowInfo == null)
+			return null;
+		return dataflowInfo.getLastModified();
+	}
+
+	public Date getOpenedAtDate() {
+		return openedAt;
+	}
+
+	public Object getSource() {
+		if (dataflowInfo == null)
+			return null;
+		return dataflowInfo.getCanonicalSource();
+	}
+
+	public boolean isChanged() {
+		return isChanged;
+	}
+
+	public void setIsChanged(boolean isChanged) {
+		this.isChanged = isChanged;
+	}
+
+	public synchronized void setOpenedFrom(DataflowInfo dataflowInfo) {
+		setDataflowInfo(dataflowInfo);
+		setOpenedAt(new Date());
+		setIsChanged(false);
+	}
+
+	public synchronized void setSavedTo(DataflowInfo dataflowInfo) {
+		setDataflowInfo(dataflowInfo);
+		setIsChanged(false);
+	}
+
+	private void setDataflowInfo(DataflowInfo dataflowInfo) {
+		this.dataflowInfo = dataflowInfo;
+	}
+
+	private void setOpenedAt(Date openedAt) {
+		this.openedAt = openedAt;
+	}
+
+	public DataflowInfo getDataflowInfo() {
+		return dataflowInfo;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
new file mode 100644
index 0000000..9d687b8
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static java.lang.Thread.sleep;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ */
+public class OpenDataflowRunnable implements Runnable {
+	private final FileManagerImpl fileManager;
+	private final FileType fileType;
+	private final Object source;
+	private WorkflowBundle dataflow;
+	private OpenException e;
+
+	public OpenDataflowRunnable(FileManagerImpl fileManager, FileType fileType,
+			Object source) {
+		this.fileManager = fileManager;
+		this.fileType = fileType;
+		this.source = source;
+	}
+
+	@Override
+	public void run() {
+		OpenDataflowSwingWorker openDataflowSwingWorker = new OpenDataflowSwingWorker(
+				fileType, source, fileManager);
+		OpenDataflowInProgressDialog dialog = new OpenDataflowInProgressDialog();
+		openDataflowSwingWorker
+				.addPropertyChangeListener(new SwingWorkerCompletionWaiter(
+						dialog));
+		openDataflowSwingWorker.execute();
+
+		/*
+		 * Give a chance to the SwingWorker to finish so we do not have to
+		 * display the dialog
+		 */
+		try {
+			sleep(500);
+		} catch (InterruptedException e) {
+		    this.e = new OpenException("Opening was interrupted");
+		}
+		if (!openDataflowSwingWorker.isDone())
+			dialog.setVisible(true); // this will block the GUI
+		boolean userCancelled = dialog.hasUserCancelled(); // see if user cancelled the dialog
+
+		if (userCancelled) {
+			// Stop the OpenDataflowSwingWorker if it is still working
+			openDataflowSwingWorker.cancel(true);
+			dataflow = null;
+			this.e = new OpenException("Opening was cancelled");
+			// exit
+			return;
+		}
+		dataflow = openDataflowSwingWorker.getDataflow();
+		this.e = openDataflowSwingWorker.getException();
+	}
+
+	public WorkflowBundle getDataflow() {
+		return dataflow;
+	}
+
+	public OpenException getException() {
+		return this.e;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
new file mode 100644
index 0000000..4cbd2f8
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2009 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import javax.swing.SwingWorker;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class OpenDataflowSwingWorker extends
+		SwingWorker<WorkflowBundle, Object> {
+	@SuppressWarnings("unused")
+	private Logger logger = Logger.getLogger(OpenDataflowSwingWorker.class);
+	private FileType fileType;
+	private Object source;
+	private FileManagerImpl fileManagerImpl;
+	private WorkflowBundle workflowBundle;
+	private OpenException e = null;
+
+	public OpenDataflowSwingWorker(FileType fileType, Object source,
+			FileManagerImpl fileManagerImpl) {
+		this.fileType = fileType;
+		this.source = source;
+		this.fileManagerImpl = fileManagerImpl;
+	}
+
+	@Override
+	protected WorkflowBundle doInBackground() throws Exception {
+		try {
+			workflowBundle = fileManagerImpl.performOpenDataflow(fileType,
+					source);
+		} catch (OpenException e) {
+			this.e = e;
+		}
+		return workflowBundle;
+	}
+
+	public WorkflowBundle getDataflow() {
+		return workflowBundle;
+	}
+
+	public OpenException getException() {
+		return e;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
new file mode 100644
index 0000000..bf37faf
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class T2DataflowOpener extends AbstractDataflowPersistenceHandler
+		implements DataflowPersistenceHandler {
+	private static final T2FlowFileType T2_FLOW_FILE_TYPE = new T2FlowFileType();
+	private static Logger logger = Logger.getLogger(T2DataflowOpener.class);
+
+	private WorkflowBundleIO workflowBundleIO;
+
+	@SuppressWarnings("resource")
+	@Override
+	public DataflowInfo openDataflow(FileType fileType, Object source)
+			throws OpenException {
+		if (!getOpenFileTypes().contains(fileType))
+			throw new OpenException("Unsupported file type "
+					+ fileType);
+		InputStream inputStream;
+		Date lastModified = null;
+		Object canonicalSource = source;
+		if (source instanceof InputStream)
+			inputStream = (InputStream) source;
+		else if (source instanceof File)
+			try {
+				inputStream = new FileInputStream((File) source);
+			} catch (FileNotFoundException e) {
+				throw new OpenException("Could not open file " + source + ":\n" + e.getLocalizedMessage(), e);
+			}
+		else if (source instanceof URL) {
+			URL url = ((URL) source);
+			try {
+				URLConnection connection = url.openConnection();
+				connection.setRequestProperty("Accept", "text/xml");
+				inputStream = connection.getInputStream();
+				if (connection.getLastModified() != 0)
+					lastModified = new Date(connection.getLastModified());
+			} catch (IOException e) {
+				throw new OpenException("Could not open connection to URL "
+						+ source+ ":\n" + e.getLocalizedMessage(), e);
+			}
+			try {
+				if (url.getProtocol().equalsIgnoreCase("file"))
+					canonicalSource = new File(url.toURI());
+			} catch (URISyntaxException e) {
+				logger.warn("Invalid file URI created from " + url);
+			}
+		} else {
+			throw new OpenException("Unsupported source type "
+					+ source.getClass());
+		}
+
+		final WorkflowBundle workflowBundle;
+		try {
+			workflowBundle = openDataflowStream(inputStream);
+		} finally {
+			try {
+				if (!(source instanceof InputStream))
+					// We created the stream, we'll close it
+					inputStream.close();
+			} catch (IOException ex) {
+				logger.warn("Could not close inputstream " + inputStream, ex);
+			}
+		}
+		if (canonicalSource instanceof File)
+			return new FileDataflowInfo(T2_FLOW_FILE_TYPE,
+					(File) canonicalSource, workflowBundle);
+		return new DataflowInfo(T2_FLOW_FILE_TYPE, canonicalSource,
+				workflowBundle, lastModified);
+	}
+
+	protected WorkflowBundle openDataflowStream(InputStream workflowXMLstream)
+			throws OpenException {
+		WorkflowBundle workflowBundle;
+		try {
+			workflowBundle = workflowBundleIO.readBundle(workflowXMLstream, null);
+		} catch (ReaderException e) {
+			throw new OpenException("Could not read the workflow", e);
+		} catch (IOException e) {
+			throw new OpenException("Could not open the workflow file for parsing", e);
+		} catch (Exception e) {
+			throw new OpenException("Error while opening workflow", e);
+		}
+
+		return workflowBundle;
+	}
+
+	@Override
+	public List<FileType> getOpenFileTypes() {
+		return Arrays.<FileType> asList(new T2FlowFileType());
+	}
+
+	@Override
+	public List<Class<?>> getOpenSourceTypes() {
+		return Arrays.<Class<?>> asList(InputStream.class, URL.class,
+				File.class);
+	}
+
+	public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+		this.workflowBundleIO = workflowBundleIO;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
new file mode 100644
index 0000000..62b5892
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/**
+ * 
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public class T2FileFilter extends FileFilter {
+	@Override
+	public boolean accept(final File file) {
+		return file.getName().toLowerCase().endsWith(".t2flow");
+	}
+
+	@Override
+	public String getDescription() {
+		return "Taverna 2 workflows";
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
new file mode 100644
index 0000000..a2774e4
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class T2FlowFileType extends FileType {
+	public static final String APPLICATION_VND_TAVERNA_T2FLOW_XML = "application/vnd.taverna.t2flow+xml";
+
+	@Override
+	public String getDescription() {
+		return "Taverna 2 workflow";
+	}
+
+	@Override
+	public String getExtension() {
+		return "t2flow";
+	}
+
+	@Override
+	public String getMimeType() {
+		return APPLICATION_VND_TAVERNA_T2FLOW_XML;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
new file mode 100644
index 0000000..d272b41
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public class WorkflowBundleFileFilter extends FileFilter {
+	@Override
+	public boolean accept(final File file) {
+		return file.getName().toLowerCase().endsWith(".wfbundle");
+	}
+
+	@Override
+	public String getDescription() {
+		return "Taverna 3 workflows";
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
new file mode 100644
index 0000000..09b30b0
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class WorkflowBundleFileType extends FileType {
+	public static final String APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE = "application/vnd.taverna.scufl2.workflow-bundle";
+
+	@Override
+	public String getDescription() {
+		return "Taverna 3 workflow";
+	}
+
+	@Override
+	public String getExtension() {
+		return "wfbundle";
+	}
+
+	@Override
+	public String getMimeType() {
+		return APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
new file mode 100644
index 0000000..7c54f7e
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class WorkflowBundleOpener extends AbstractDataflowPersistenceHandler
+		implements DataflowPersistenceHandler {
+	private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+	private static Logger logger = Logger.getLogger(WorkflowBundleOpener.class);
+	private WorkflowBundleIO workflowBundleIO;
+
+	@SuppressWarnings("resource")
+	@Override
+	public DataflowInfo openDataflow(FileType fileType, Object source)
+			throws OpenException {
+		if (!getOpenFileTypes().contains(fileType))
+			throw new OpenException("Unsupported file type " + fileType);
+		InputStream inputStream;
+		Date lastModified = null;
+		Object canonicalSource = source;
+		if (source instanceof InputStream) {
+			inputStream = (InputStream) source;
+		} else if (source instanceof File) {
+			try {
+				inputStream = new FileInputStream((File) source);
+			} catch (FileNotFoundException e) {
+				throw new OpenException("Could not open file " + source + ":\n"
+						+ e.getLocalizedMessage(), e);
+			}
+		} else if (source instanceof URL) {
+			URL url = ((URL) source);
+			try {
+				URLConnection connection = url.openConnection();
+				connection.setRequestProperty("Accept", "application/zip");
+				inputStream = connection.getInputStream();
+				if (connection.getLastModified() != 0)
+					lastModified = new Date(connection.getLastModified());
+			} catch (IOException e) {
+				throw new OpenException("Could not open connection to URL "
+						+ source + ":\n" + e.getLocalizedMessage(), e);
+			}
+			try {
+				if (url.getProtocol().equalsIgnoreCase("file"))
+					canonicalSource = new File(url.toURI());
+			} catch (URISyntaxException e) {
+				logger.warn("Invalid file URI created from " + url);
+			}
+		} else
+			throw new OpenException("Unsupported source type "
+					+ source.getClass());
+
+		final WorkflowBundle workflowBundle;
+		try {
+			workflowBundle = openDataflowStream(inputStream);
+		} finally {
+			// We created the stream, we'll close it
+			try {
+				if (!(source instanceof InputStream))
+					inputStream.close();
+			} catch (IOException ex) {
+				logger.warn("Could not close inputstream " + inputStream, ex);
+			}
+		}
+		if (canonicalSource instanceof File)
+			return new FileDataflowInfo(WF_BUNDLE_FILE_TYPE,
+					(File) canonicalSource, workflowBundle);
+		return new DataflowInfo(WF_BUNDLE_FILE_TYPE, canonicalSource,
+				workflowBundle, lastModified);
+	}
+
+	protected WorkflowBundle openDataflowStream(InputStream inputStream)
+			throws OpenException {
+		WorkflowBundle workflowBundle;
+		try {
+			workflowBundle = workflowBundleIO.readBundle(inputStream, null);
+		} catch (ReaderException e) {
+			throw new OpenException("Could not read the workflow", e);
+		} catch (IOException e) {
+			throw new OpenException("Could not open the workflow for parsing",
+					e);
+		} catch (Exception e) {
+			throw new OpenException("Error while opening workflow", e);
+		}
+
+		return workflowBundle;
+	}
+
+	@Override
+	public List<FileType> getOpenFileTypes() {
+		return Arrays.<FileType> asList(WF_BUNDLE_FILE_TYPE);
+	}
+
+	@Override
+	public List<Class<?>> getOpenSourceTypes() {
+		return Arrays.<Class<?>> asList(InputStream.class, URL.class,
+				File.class);
+	}
+
+	public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+		this.workflowBundleIO = workflowBundleIO;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
new file mode 100644
index 0000000..f6b7108
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static net.sf.taverna.t2.workbench.file.impl.WorkflowBundleFileType.APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class WorkflowBundleSaver extends AbstractDataflowPersistenceHandler
+		implements DataflowPersistenceHandler {
+	private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+	private static Logger logger = Logger.getLogger(WorkflowBundleSaver.class);
+	private WorkflowBundleIO workflowBundleIO;
+
+	@Override
+	public DataflowInfo saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+			Object destination) throws SaveException {
+		if (!getSaveFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+		OutputStream outStream;
+		if (destination instanceof File)
+			try {
+				outStream = new FileOutputStream((File) destination);
+			} catch (FileNotFoundException e) {
+				throw new SaveException("Can't create workflow file "
+						+ destination + ":\n" + e.getLocalizedMessage(), e);
+			}
+		else if (destination instanceof OutputStream)
+			outStream = (OutputStream) destination;
+		else
+			throw new SaveException("Unsupported destination type "
+					+ destination.getClass());
+
+		try {
+			saveDataflowToStream(workflowBundle, outStream);
+		} finally {
+			try {
+				// Only close if we opened the stream
+				if (!(destination instanceof OutputStream))
+					outStream.close();
+			} catch (IOException e) {
+				logger.warn("Could not close stream", e);
+			}
+		}
+
+		if (destination instanceof File)
+			return new FileDataflowInfo(WF_BUNDLE_FILE_TYPE, (File) destination,
+					workflowBundle);
+		return new DataflowInfo(WF_BUNDLE_FILE_TYPE, destination, workflowBundle);
+	}
+
+	protected void saveDataflowToStream(WorkflowBundle workflowBundle,
+			OutputStream fileOutStream) throws SaveException {
+		try {
+			workflowBundleIO.writeBundle(workflowBundle, fileOutStream,
+					APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE);
+		} catch (Exception e) {
+			throw new SaveException("Can't write workflow:\n"
+					+ e.getLocalizedMessage(), e);
+		}
+	}
+
+	@Override
+	public List<FileType> getSaveFileTypes() {
+		return Arrays.<FileType> asList(WF_BUNDLE_FILE_TYPE);
+	}
+
+	@Override
+	public List<Class<?>> getSaveDestinationTypes() {
+		return Arrays.<Class<?>> asList(File.class, OutputStream.class);
+	}
+
+	@Override
+	public boolean wouldOverwriteDataflow(WorkflowBundle workflowBundle, FileType fileType,
+			Object destination, DataflowInfo lastDataflowInfo) {
+		if (!getSaveFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+		if (!(destination instanceof File))
+			return false;
+
+		File file;
+		try {
+			file = ((File) destination).getCanonicalFile();
+		} catch (IOException e) {
+			return false;
+		}
+		if (!file.exists())
+			return false;
+		if (lastDataflowInfo == null)
+			return true;
+		Object lastDestination = lastDataflowInfo.getCanonicalSource();
+		if (!(lastDestination instanceof File))
+			return true;
+		File lastFile = (File) lastDestination;
+		if (!lastFile.getAbsoluteFile().equals(file))
+			return true;
+
+		Date lastModified = new Date(file.lastModified());
+		if (lastModified.equals(lastDataflowInfo.getLastModified()))
+			return false;
+		return true;
+	}
+
+	public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+		this.workflowBundleIO = workflowBundleIO;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java
new file mode 100644
index 0000000..2309b9c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_L;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeAllIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class CloseAllWorkflowsAction extends AbstractAction {
+	@SuppressWarnings("unused")
+	private static Logger logger = Logger.getLogger(CloseWorkflowAction.class);
+	private static final String CLOSE_ALL_WORKFLOWS = "Close all workflows";
+	private FileManager fileManager;
+	private CloseWorkflowAction closeWorkflowAction;
+
+	public CloseAllWorkflowsAction(EditManager editManager, FileManager fileManager) {
+		super(CLOSE_ALL_WORKFLOWS, closeAllIcon);
+		this.fileManager = fileManager;
+		closeWorkflowAction = new CloseWorkflowAction(editManager, fileManager);
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_W, getDefaultToolkit().getMenuShortcutKeyMask()
+						| SHIFT_DOWN_MASK));
+		putValue(MNEMONIC_KEY, VK_L);
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent event) {
+		Component parentComponent = null;
+		if (event.getSource() instanceof Component)
+			parentComponent = (Component) event.getSource();
+		closeAllWorkflows(parentComponent);
+	}
+
+	public boolean closeAllWorkflows(Component parentComponent) {
+		// Close in reverse so we can save nested workflows first
+		List<WorkflowBundle> workflowBundles = fileManager.getOpenDataflows();
+
+		Collections.reverse(workflowBundles);
+
+		for (WorkflowBundle workflowBundle : workflowBundles) {
+			boolean success = closeWorkflowAction.closeWorkflow(
+					parentComponent, workflowBundle);
+			if (!success)
+				return false;
+		}
+		return true;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java
new file mode 100644
index 0000000..03c90a6
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_C;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class CloseWorkflowAction extends AbstractAction {
+	private static Logger logger = Logger.getLogger(CloseWorkflowAction.class);
+	private static final String CLOSE_WORKFLOW = "Close workflow";
+	private final SaveWorkflowAction saveWorkflowAction;
+	private FileManager fileManager;
+
+	public CloseWorkflowAction(EditManager editManager, FileManager fileManager) {
+		super(CLOSE_WORKFLOW, closeIcon);
+		this.fileManager = fileManager;
+		saveWorkflowAction = new SaveWorkflowAction(editManager, fileManager);
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_W, getDefaultToolkit().getMenuShortcutKeyMask()));
+		putValue(MNEMONIC_KEY, VK_C);
+
+	}
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		Component parentComponent = null;
+		if (e.getSource() instanceof Component)
+			parentComponent = (Component) e.getSource();
+		closeWorkflow(parentComponent, fileManager.getCurrentDataflow());
+	}
+
+	public boolean closeWorkflow(Component parentComponent, WorkflowBundle workflowBundle) {
+		if (workflowBundle == null) {
+			logger.warn("Attempted to close a null workflow");
+			return false;
+		}
+
+		try {
+			return fileManager.closeDataflow(workflowBundle, true);
+		} catch (UnsavedException e1) {
+			fileManager.setCurrentDataflow(workflowBundle);
+			String msg = "Do you want to save changes before closing the workflow "
+					+ fileManager.getDataflowName(workflowBundle) + "?";
+			switch (showConfirmDialog(parentComponent, msg, "Save workflow?",
+					YES_NO_CANCEL_OPTION)) {
+			case NO_OPTION:
+				try {
+					fileManager.closeDataflow(workflowBundle, false);
+					return true;
+				} catch (UnsavedException e2) {
+					logger.error("Unexpected UnsavedException while "
+							+ "closing workflow", e2);
+					return false;
+				}
+			case YES_OPTION:
+				boolean saved = saveWorkflowAction.saveDataflow(
+						parentComponent, workflowBundle);
+				if (!saved)
+					return false;
+				return closeWorkflow(parentComponent, workflowBundle);
+			case CANCEL_OPTION:
+			default:
+				return false;
+			}
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java
new file mode 100644
index 0000000..3b9256a
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_N;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.newIcon;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class NewWorkflowAction extends AbstractAction {
+	@SuppressWarnings("unused")
+	private static Logger logger = Logger.getLogger(NewWorkflowAction.class);
+	private static final String NEW_WORKFLOW = "New workflow";
+	private FileManager fileManager;
+
+	public NewWorkflowAction(FileManager fileManager) {
+		super(NEW_WORKFLOW, newIcon);
+		this.fileManager = fileManager;
+		putValue(SHORT_DESCRIPTION, NEW_WORKFLOW);
+		putValue(MNEMONIC_KEY, KeyEvent.VK_N);
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_N, getDefaultToolkit().getMenuShortcutKeyMask()));
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		fileManager.newDataflow();
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java
new file mode 100644
index 0000000..08030c7
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import java.awt.Component;
+import java.io.File;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a nested workflow from a file.
+ * 
+ * @author Alex Nenadic
+ */
+public class OpenNestedWorkflowAction extends OpenWorkflowAction {
+	private static final long serialVersionUID = -5398423684000142379L;
+	private static Logger logger = Logger
+			.getLogger(OpenNestedWorkflowAction.class);
+
+	public OpenNestedWorkflowAction(FileManager fileManager) {
+		super(fileManager);
+	}
+
+	/**
+	 * Opens a nested workflow from a file (should be one file even though the
+	 * method takes a list of files - this is because it overrides the
+	 * {@link OpenWorkflowAction#openWorkflows(Component, File[], FileType, OpenCallback)
+	 * openWorkflows(...)} method).
+	 */
+	@Override
+	public void openWorkflows(final Component parentComponent, File[] files,
+			FileType fileType, OpenCallback openCallback) {
+		ErrorLoggingOpenCallbackWrapper callback = new ErrorLoggingOpenCallbackWrapper(
+				openCallback);
+		for (File file : files)
+			try {
+				callback.aboutToOpenDataflow(file);
+				WorkflowBundle workflowBundle = fileManager.openDataflow(
+						fileType, file);
+				callback.openedDataflow(file, workflowBundle);
+			} catch (final RuntimeException ex) {
+				logger.warn("Could not open workflow from " + file, ex);
+				if (!callback.couldNotOpenDataflow(file, ex))
+					showErrorMessage(parentComponent, file, ex);
+			} catch (final OpenException ex) {
+				logger.warn("Could not open workflow from " + file, ex);
+				if (!callback.couldNotOpenDataflow(file, ex))
+					showErrorMessage(parentComponent, file, ex);
+				return;
+			}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java
new file mode 100644
index 0000000..e2ecbd7
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java
@@ -0,0 +1,395 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_O;
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a workflow from a file. All file types exposed by the
+ * {@link FileManager} as compatible with the {@link File} type are supported.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class OpenWorkflowAction extends AbstractAction {
+	private static final long serialVersionUID = 103237694130052153L;
+	private static Logger logger = Logger.getLogger(OpenWorkflowAction.class);
+	private static final String OPEN_WORKFLOW = "Open workflow...";
+
+	public final OpenCallback DUMMY_OPEN_CALLBACK = new OpenCallbackAdapter();
+	protected FileManager fileManager;
+
+	public OpenWorkflowAction(FileManager fileManager) {
+		super(OPEN_WORKFLOW, openIcon);
+		this.fileManager = fileManager;
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_O, getDefaultToolkit().getMenuShortcutKeyMask()));
+		putValue(MNEMONIC_KEY, VK_O);
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		final Component parentComponent;
+		if (e.getSource() instanceof Component)
+			parentComponent = (Component) e.getSource();
+		else
+			parentComponent = null;
+		openWorkflows(parentComponent);
+	}
+
+	/**
+	 * Pop up an Open-dialogue to select one or more workflow files to open.
+	 * <p>
+	 * Note that the file opening occurs in a separate thread. If you want to
+	 * check if the file was opened or not, which workflow was opened, etc, use
+	 * {@link #openWorkflows(Component, OpenCallback)} instead.
+	 *
+	 * @see #openWorkflows(Component, OpenCallback)
+	 * @param parentComponent
+	 *            The UI parent component to use for pop up dialogues
+	 *
+	 * @return <code>false</code> if no files were selected or the dialogue was
+	 *         cancelled, or <code>true</code> if the process of opening one or
+	 *         more files has been started.
+	 */
+	public void openWorkflows(Component parentComponent) {
+		openWorkflows(parentComponent, DUMMY_OPEN_CALLBACK);
+	}
+
+	/**
+	 * Open an array of workflow files.
+	 *
+	 * @param parentComponent
+	 *            Parent component for UI dialogues
+	 * @param files
+	 *            Array of files to be opened
+	 * @param fileType
+	 *            {@link FileType} of the files that are to be opened, for
+	 *            instance
+	 *            {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType},
+	 *            or <code>null</code> to guess.
+	 * @param openCallback
+	 *            An {@link OpenCallback} to be invoked during and after opening
+	 *            the file. Use {@link OpenWorkflowAction#DUMMY_OPEN_CALLBACK}
+	 *            if no callback is needed.
+	 */
+	public void openWorkflows(final Component parentComponent, File[] files,
+			FileType fileType, OpenCallback openCallback) {
+		ErrorLoggingOpenCallbackWrapper callback = new ErrorLoggingOpenCallbackWrapper(
+				openCallback);
+		for (File file : files)
+			try {
+				Object canonicalSource = fileManager.getCanonical(file);
+				WorkflowBundle alreadyOpen = fileManager.getDataflowBySource(canonicalSource);
+				if (alreadyOpen != null) {
+					/*
+					 * The workflow from the same source is already opened - ask
+					 * the user if they want to switch to it or open another
+					 * copy...
+					 */
+
+					Object[] options = { "Switch to opened", "Open new copy",
+							"Cancel" };
+					switch (showOptionDialog(
+							null,
+							"The workflow from the same location is already opened.\n"
+									+ "Do you want to switch to it or open a new copy?",
+							"File Manager Alert", YES_NO_CANCEL_OPTION,
+							QUESTION_MESSAGE, null, options, // the titles of buttons
+							options[0])) { // default button title
+					case YES_OPTION:
+						fileManager.setCurrentDataflow(alreadyOpen);
+						return;
+					case CANCEL_OPTION:
+						// do nothing
+						return;
+					}
+					// else open the workflow as usual
+				}
+
+				callback.aboutToOpenDataflow(file);
+				WorkflowBundle workflowBundle = fileManager.openDataflow(fileType, file);
+				callback.openedDataflow(file, workflowBundle);
+			} catch (RuntimeException ex) {
+				logger.warn("Failed to open workflow from " + file, ex);
+				if (!callback.couldNotOpenDataflow(file, ex))
+					showErrorMessage(parentComponent, file, ex);
+			} catch (Exception ex) {
+				logger.warn("Failed to open workflow from " + file, ex);
+				if (!callback.couldNotOpenDataflow(file, ex))
+					showErrorMessage(parentComponent, file, ex);
+				return;
+			}
+	}
+
+	/**
+	 * Pop up an Open-dialogue to select one or more workflow files to open.
+	 *
+	 * @param parentComponent
+	 *            The UI parent component to use for pop up dialogues
+	 * @param openCallback
+	 *            An {@link OpenCallback} to be called during the file opening.
+	 *            The callback will be invoked for each file that has been
+	 *            opened, as file opening happens in a separate thread that
+	 *            might execute after the return of this method.
+	 * @return <code>false</code> if no files were selected or the dialogue was
+	 *         cancelled, or <code>true</code> if the process of opening one or
+	 *         more files has been started.
+	 */
+	public boolean openWorkflows(final Component parentComponent,
+			OpenCallback openCallback) {
+		JFileChooser fileChooser = new JFileChooser();
+		Preferences prefs = userNodeForPackage(getClass());
+		String curDir = prefs
+				.get("currentDir", System.getProperty("user.home"));
+		fileChooser.setDialogTitle(OPEN_WORKFLOW);
+
+		fileChooser.resetChoosableFileFilters();
+		fileChooser.setAcceptAllFileFilterUsed(false);
+		List<FileFilter> fileFilters = fileManager.getOpenFileFilters();
+		if (fileFilters.isEmpty()) {
+			logger.warn("No file types found for opening workflow");
+			showMessageDialog(parentComponent,
+					"No file types found for opening workflow.", "Error",
+					ERROR_MESSAGE);
+			return false;
+		}
+		for (FileFilter fileFilter : fileFilters)
+			fileChooser.addChoosableFileFilter(fileFilter);
+		fileChooser.setFileFilter(fileFilters.get(0));
+		fileChooser.setCurrentDirectory(new File(curDir));
+		fileChooser.setMultiSelectionEnabled(true);
+
+		int returnVal = fileChooser.showOpenDialog(parentComponent);
+		if (returnVal == APPROVE_OPTION) {
+			prefs.put("currentDir", fileChooser.getCurrentDirectory()
+					.toString());
+			final File[] selectedFiles = fileChooser.getSelectedFiles();
+			if (selectedFiles.length == 0) {
+				logger.warn("No files selected");
+				return false;
+			}
+			FileFilter fileFilter = fileChooser.getFileFilter();
+			FileType fileType;
+			if (fileFilter instanceof FileTypeFileFilter)
+				fileType = ((FileTypeFileFilter) fileChooser.getFileFilter())
+						.getFileType();
+			else
+				// Unknown filetype, try all of them
+				fileType = null;
+			new FileOpenerThread(parentComponent, selectedFiles, fileType,
+					openCallback).start();
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Show an error message if a file could not be opened
+	 * 
+	 * @param parentComponent
+	 * @param file
+	 * @param throwable
+	 */
+	protected void showErrorMessage(final Component parentComponent,
+			final File file, final Throwable throwable) {
+		invokeLater(new Runnable() {
+			@Override
+			public void run() {
+				Throwable cause = throwable;
+				while (cause.getCause() != null)
+					cause = cause.getCause();
+				showMessageDialog(
+						parentComponent,
+						"Failed to open workflow from " + file + ": \n"
+								+ cause.getMessage(), "Warning",
+						WARNING_MESSAGE);
+			}
+		});
+
+	}
+
+	/**
+	 * Callback interface for openWorkflows().
+	 * <p>
+	 * The callback will be invoked during the invocation of
+	 * {@link OpenWorkflowAction#openWorkflows(Component, OpenCallback)} and
+	 * {@link OpenWorkflowAction#openWorkflows(Component, File[], FileType, OpenCallback)}
+	 * as file opening happens in a separate thread.
+	 *
+	 * @author Stian Soiland-Reyes
+	 */
+	public interface OpenCallback {
+		/**
+		 * Called before a workflowBundle is to be opened from the given file
+		 *
+		 * @param file
+		 *            File which workflowBundle is to be opened
+		 */
+		void aboutToOpenDataflow(File file);
+
+		/**
+		 * Called if an exception happened while attempting to open the
+		 * workflowBundle.
+		 *
+		 * @param file
+		 *            File which was attempted to be opened
+		 * @param ex
+		 *            An {@link OpenException} or a {@link RuntimeException}.
+		 * @return <code>true</code> if the error has been handled, or
+		 *         <code>false</code>3 if a UI warning dialogue is to be opened.
+		 */
+		boolean couldNotOpenDataflow(File file, Exception ex);
+
+		/**
+		 * Called when a workflowBundle has been successfully opened. The workflowBundle
+		 * will be registered in {@link FileManager#getOpenDataflows()}.
+		 *
+		 * @param file
+		 *            File from which workflowBundle was opened
+		 * @param workflowBundle
+		 *            WorkflowBundle that was opened
+		 */
+		void openedDataflow(File file, WorkflowBundle workflowBundle);
+	}
+
+	/**
+	 * Adapter for {@link OpenCallback}
+	 *
+	 * @author Stian Soiland-Reyes
+	 */
+	public static class OpenCallbackAdapter implements OpenCallback {
+		@Override
+		public void aboutToOpenDataflow(File file) {
+		}
+
+		@Override
+		public boolean couldNotOpenDataflow(File file, Exception ex) {
+			return false;
+		}
+
+		@Override
+		public void openedDataflow(File file, WorkflowBundle workflowBundle) {
+		}
+	}
+
+	private final class FileOpenerThread extends Thread {
+		private final File[] files;
+		private final FileType fileType;
+		private final OpenCallback openCallback;
+		private final Component parentComponent;
+
+		private FileOpenerThread(Component parentComponent,
+				File[] selectedFiles, FileType fileType,
+				OpenCallback openCallback) {
+			super("Opening workflows(s) " + Arrays.asList(selectedFiles));
+			this.parentComponent = parentComponent;
+			this.files = selectedFiles;
+			this.fileType = fileType;
+			this.openCallback = openCallback;
+		}
+
+		@Override
+		public void run() {
+			openWorkflows(parentComponent, files, fileType, openCallback);
+		}
+	}
+
+	/**
+	 * A wrapper for {@link OpenCallback} implementations that logs exceptions
+	 * thrown without disrupting the caller of the callback.
+	 *
+	 * @author Stian Soiland-Reyes
+	 */
+	protected class ErrorLoggingOpenCallbackWrapper implements OpenCallback {
+		private final OpenCallback wrapped;
+
+		public ErrorLoggingOpenCallbackWrapper(OpenCallback wrapped) {
+			this.wrapped = wrapped;
+		}
+
+		@Override
+		public void aboutToOpenDataflow(File file) {
+			try {
+				wrapped.aboutToOpenDataflow(file);
+			} catch (RuntimeException wrapperEx) {
+				logger.warn("Failed OpenCallback " + wrapped
+						+ ".aboutToOpenDataflow(File)", wrapperEx);
+			}
+		}
+
+		@Override
+		public boolean couldNotOpenDataflow(File file, Exception ex) {
+			try {
+				return wrapped.couldNotOpenDataflow(file, ex);
+			} catch (RuntimeException wrapperEx) {
+				logger.warn("Failed OpenCallback " + wrapped
+						+ ".couldNotOpenDataflow(File, Exception)", wrapperEx);
+				return false;
+			}
+		}
+
+		@Override
+		public void openedDataflow(File file, WorkflowBundle workflowBundle) {
+			try {
+				wrapped.openedDataflow(file, workflowBundle);
+			} catch (RuntimeException wrapperEx) {
+				logger.warn("Failed OpenCallback " + wrapped
+						+ ".openedDataflow(File, Dataflow)", wrapperEx);
+			}
+		}
+	}
+
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
new file mode 100644
index 0000000..e98a8f2
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (C) 2008 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_L;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openurlIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URL;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a workflow from a url.
+ * 
+ * @author David Withers
+ */
+public class OpenWorkflowFromURLAction extends AbstractAction {
+	private static final long serialVersionUID = 1474356457949961974L;
+	private static Logger logger = Logger
+			.getLogger(OpenWorkflowFromURLAction.class);
+	private static Preferences prefs = Preferences
+			.userNodeForPackage(OpenWorkflowFromURLAction.class);
+	private static final String PREF_CURRENT_URL = "currentUrl";
+	private static final String ACTION_NAME = "Open workflow location...";
+	private static final String ACTION_DESCRIPTION = "Open a workflow from the web into a new workflow";
+
+	private Component component;
+	private FileManager fileManager;
+
+	public OpenWorkflowFromURLAction(final Component component,
+			FileManager fileManager) {
+		this.component = component;
+		this.fileManager = fileManager;
+		putValue(SMALL_ICON, openurlIcon);
+		putValue(NAME, ACTION_NAME);
+		putValue(SHORT_DESCRIPTION, ACTION_DESCRIPTION);
+		putValue(MNEMONIC_KEY, VK_L);
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_L, getDefaultToolkit().getMenuShortcutKeyMask()));
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		String currentUrl = prefs.get(PREF_CURRENT_URL, "http://");
+
+		final String url = (String) showInputDialog(component,
+				"Enter the URL of a workflow definition to load",
+				"Workflow URL", QUESTION_MESSAGE, null, null, currentUrl);
+		if (url != null)
+			new Thread("OpenWorkflowFromURLAction") {
+				@Override
+				public void run() {
+					openFromURL(url);
+				}
+			}.start();
+	}
+
+	private void openFromURL(String urlString) {
+		try {
+			URL url = new URL(urlString);
+
+			Object canonicalSource = fileManager.getCanonical(url);
+			WorkflowBundle alreadyOpen = fileManager
+					.getDataflowBySource(canonicalSource);
+			if (alreadyOpen != null) {
+				/*
+				 * The workflow from the same source is already opened - ask the
+				 * user if they want to switch to it or open another copy.
+				 */
+
+				Object[] options = { "Switch to opened", "Open new copy",
+						"Cancel" };
+				int iSelected = showOptionDialog(
+						null,
+						"The workflow from the same location is already opened.\n"
+								+ "Do you want to switch to it or open a new copy?",
+						"File Manager Alert", YES_NO_CANCEL_OPTION,
+						QUESTION_MESSAGE, null, options, // the titles of buttons
+						options[0]); // default button title
+
+				if (iSelected == YES_OPTION) {
+					fileManager.setCurrentDataflow(alreadyOpen);
+					return;
+				} else if (iSelected == CANCEL_OPTION) {
+					// do nothing
+					return;
+				}
+				// else open the workflow as usual
+			}
+
+			fileManager.openDataflow(null, url);
+			prefs.put(PREF_CURRENT_URL, urlString);
+		} catch (Exception ex) {
+			logger.warn("Failed to open the workflow from url " + urlString
+					+ " \n", ex);
+			showMessageDialog(component,
+					"Failed to open the workflow from url " + urlString + " \n"
+							+ ex.getMessage(), "Error!", ERROR_MESSAGE);
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java
new file mode 100644
index 0000000..401a232
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.EventQueue.invokeLater;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+
+/**
+ * Simple dialogue to handle username/password input for workflow URL requiring
+ * http authentication.
+ * 
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class PasswordInput extends HelpEnabledDialog {
+	private static Logger logger = Logger.getLogger(PasswordInput.class);
+
+	private String password = null;
+	private String username = null;
+	private URL url = null;
+	private int tryCount = 0;
+	private final static int MAX_TRIES = 3;
+
+	private JButton cancelButton;
+	private JLabel jLabel1;
+	private JLabel jLabel2;
+	private JLabel messageLabel;
+	private JButton okButton;
+	private JPasswordField passwordTextField;
+	private JLabel urlLabel;
+	private JTextField usernameTextField;
+
+	public void setUrl(URL url) {
+		this.url = url;
+		urlLabel.setText(url.toExternalForm());
+	}
+
+	public String getPassword() {
+		return password;
+	}
+
+	public String getUsername() {
+		return username;
+	}
+
+	public PasswordInput(JFrame parent) {
+		super(parent, "Authorization", true, null);
+		initComponents();
+	}
+
+	/** Creates new form PasswordInput */
+	public PasswordInput() {
+		super((JFrame) null, "Authorization", true, null);
+		initComponents();
+	}
+
+	/**
+	 * This method is called from within the constructor to initialize the form.
+	 * WARNING: Do NOT modify this code. The content of this method is always
+	 * regenerated by the Form Editor.
+	 */
+	private void initComponents() {
+		usernameTextField = new javax.swing.JTextField();
+		cancelButton = new javax.swing.JButton();
+		okButton = new javax.swing.JButton();
+		passwordTextField = new javax.swing.JPasswordField();
+		jLabel1 = new javax.swing.JLabel();
+		jLabel2 = new javax.swing.JLabel();
+		messageLabel = new javax.swing.JLabel();
+		urlLabel = new javax.swing.JLabel();
+
+		getContentPane().setLayout(null);
+
+		setModal(true);
+		// setResizable(false);
+		getContentPane().add(usernameTextField);
+		usernameTextField.setBounds(20, 80, 280, 22);
+
+		cancelButton.setText("Cancel");
+		cancelButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				cancelButtonActionPerformed(evt);
+			}
+		});
+
+		getContentPane().add(cancelButton);
+		cancelButton.setBounds(230, 160, 75, 29);
+
+		okButton.setText("OK");
+		okButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				okButtonActionPerformed(evt);
+			}
+		});
+
+		getContentPane().add(okButton);
+		okButton.setBounds(150, 160, 75, 29);
+
+		getContentPane().add(passwordTextField);
+		passwordTextField.setBounds(20, 130, 280, 22);
+
+		jLabel1.setText("Username");
+		getContentPane().add(jLabel1);
+		jLabel1.setBounds(20, 60, 70, 16);
+
+		jLabel2.setText("Password");
+		getContentPane().add(jLabel2);
+		jLabel2.setBounds(20, 110, 70, 16);
+
+		messageLabel.setText("A username and password is required for:");
+		getContentPane().add(messageLabel);
+		messageLabel.setBounds(20, 10, 270, 20);
+
+		urlLabel.setText("service");
+		getContentPane().add(urlLabel);
+		urlLabel.setBounds(20, 30, 270, 16);
+
+		pack();
+	}
+
+	private void okButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+		String password = String.valueOf(passwordTextField.getPassword());
+		String username = usernameTextField.getText();
+		HttpURLConnection connection;
+		try {
+			connection = (HttpURLConnection) url.openConnection();
+			String userPassword = username + ":" + password;
+			/*
+			 * Note: non-latin1 support for basic auth is fragile/unsupported
+			 * and must be MIME-encoded (RFC2047) according to
+			 * https://bugzilla.mozilla.org/show_bug.cgi?id=41489
+			 */
+			byte[] encoded = Base64.encodeBase64(userPassword
+					.getBytes("latin1"));
+			connection.setRequestProperty("Authorization", "Basic "
+					+ new String(encoded, "ascii"));
+			connection.setRequestProperty("Accept", "text/xml");
+			int code = connection.getResponseCode();
+
+			/*
+			 * NB: myExperiment gives a 500 response for an invalid
+			 * username/password
+			 */
+			if (code == 401 || code == 500) {
+				tryCount++;
+				showMessageDialog(this, "The username and password failed",
+						"Invalid username or password", ERROR_MESSAGE);
+				if (tryCount >= MAX_TRIES) { // close after 3 attempts.
+					this.password = null;
+					this.username = null;
+					this.setVisible(false);
+				}
+			} else {
+				this.username = username;
+				this.password = password;
+				this.setVisible(false);
+			}
+		} catch (IOException ex) {
+			logger.error("Could not get password", ex);
+		}
+	}
+
+	private void cancelButtonActionPerformed(ActionEvent evt) {
+		this.password = null;
+		this.username = null;
+		this.setVisible(false);
+	}
+
+	/**
+	 * @param args
+	 *            the command line arguments
+	 */
+	public static void main(String args[]) {
+		invokeLater(new Runnable() {
+			@Override
+			public void run() {
+				new PasswordInput().setVisible(true);
+			}
+		});
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java
new file mode 100644
index 0000000..6b011d3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_A;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAllIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class SaveAllWorkflowsAction extends AbstractAction {
+	private final class FileManagerObserver implements
+			Observer<FileManagerEvent> {
+		@Override
+		public void notify(Observable<FileManagerEvent> sender,
+				FileManagerEvent message) throws Exception {
+			updateEnabled();
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private static Logger logger = Logger
+			.getLogger(SaveAllWorkflowsAction.class);
+	private static final String SAVE_ALL_WORKFLOWS = "Save all workflows";
+
+	private final SaveWorkflowAction saveWorkflowAction;
+	private FileManager fileManager;
+	private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+	public SaveAllWorkflowsAction(EditManager editManager,
+			FileManager fileManager) {
+		super(SAVE_ALL_WORKFLOWS, saveAllIcon);
+		this.fileManager = fileManager;
+		saveWorkflowAction = new SaveWorkflowAction(editManager, fileManager);
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_S, getDefaultToolkit().getMenuShortcutKeyMask()
+						| SHIFT_DOWN_MASK));
+		putValue(MNEMONIC_KEY, VK_A);
+
+		fileManager.addObserver(fileManagerObserver);
+		updateEnabled();
+	}
+
+	public void updateEnabled() {
+		setEnabled(!(fileManager.getOpenDataflows().isEmpty()));
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent ev) {
+		Component parentComponent = null;
+		if (ev.getSource() instanceof Component)
+			parentComponent = (Component) ev.getSource();
+		saveAllDataflows(parentComponent);
+	}
+
+	public void saveAllDataflows(Component parentComponent) {
+		// Save in reverse so we save nested workflows first
+		List<WorkflowBundle> workflowBundles = fileManager.getOpenDataflows();
+		Collections.reverse(workflowBundles);
+
+		for (WorkflowBundle workflowBundle : workflowBundles)
+			if (!saveWorkflowAction.saveDataflow(parentComponent,
+					workflowBundle))
+				break;
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java
new file mode 100644
index 0000000..9776550
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class SaveWorkflowAction extends AbstractAction {
+	private static Logger logger = Logger.getLogger(SaveWorkflowAction.class);
+	private static final String SAVE_WORKFLOW = "Save workflow";
+
+	private final SaveWorkflowAsAction saveWorkflowAsAction;
+	private EditManagerObserver editManagerObserver = new EditManagerObserver();
+	private FileManager fileManager;
+	private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+	public SaveWorkflowAction(EditManager editManager, FileManager fileManager) {
+		super(SAVE_WORKFLOW, saveIcon);
+		this.fileManager = fileManager;
+		saveWorkflowAsAction = new SaveWorkflowAsAction(fileManager);
+		putValue(
+				ACCELERATOR_KEY,
+				getKeyStroke(VK_S, getDefaultToolkit().getMenuShortcutKeyMask()));
+		putValue(MNEMONIC_KEY, VK_S);
+		editManager.addObserver(editManagerObserver);
+		fileManager.addObserver(fileManagerObserver);
+		updateEnabledStatus(fileManager.getCurrentDataflow());
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent ev) {
+		Component parentComponent = null;
+		if (ev.getSource() instanceof Component)
+			parentComponent = (Component) ev.getSource();
+		saveCurrentDataflow(parentComponent);
+	}
+
+	public boolean saveCurrentDataflow(Component parentComponent) {
+		WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+		return saveDataflow(parentComponent, workflowBundle);
+	}
+
+	public boolean saveDataflow(Component parentComponent,
+			WorkflowBundle workflowBundle) {
+		if (!fileManager.canSaveWithoutDestination(workflowBundle))
+			return saveWorkflowAsAction.saveDataflow(parentComponent,
+					workflowBundle);
+
+		try {
+			try {
+				fileManager.saveDataflow(workflowBundle, true);
+				Object workflowBundleSource = fileManager
+						.getDataflowSource(workflowBundle);
+				logger.info("Saved workflow " + workflowBundle + " to "
+						+ workflowBundleSource);
+				return true;
+			} catch (OverwriteException ex) {
+				Object workflowBundleSource = fileManager
+						.getDataflowSource(workflowBundle);
+				logger.info("Workflow was changed on source: "
+						+ workflowBundleSource);
+				fileManager.setCurrentDataflow(workflowBundle);
+				String msg = "Workflow destination " + workflowBundleSource
+						+ " has been changed from elsewhere, "
+						+ "are you sure you want to overwrite?";
+				int ret = showConfirmDialog(parentComponent, msg,
+						"Workflow changed", YES_NO_CANCEL_OPTION);
+				if (ret == YES_OPTION) {
+					fileManager.saveDataflow(workflowBundle, false);
+					logger.info("Saved workflow " + workflowBundle
+							+ " by overwriting " + workflowBundleSource);
+					return true;
+				} else if (ret == NO_OPTION) {
+					// Pop up Save As instead to choose another name
+					return saveWorkflowAsAction.saveDataflow(parentComponent,
+							workflowBundle);
+				} else {
+					logger.info("Aborted overwrite of " + workflowBundleSource);
+					return false;
+				}
+			}
+		} catch (SaveException ex) {
+			logger.warn("Could not save workflow " + workflowBundle, ex);
+			showMessageDialog(parentComponent, "Could not save workflow: \n\n"
+					+ ex.getMessage(), "Warning", WARNING_MESSAGE);
+			return false;
+		} catch (RuntimeException ex) {
+			logger.warn("Could not save workflow " + workflowBundle, ex);
+			showMessageDialog(parentComponent, "Could not save workflow: \n\n"
+					+ ex.getMessage(), "Warning", WARNING_MESSAGE);
+			return false;
+		}
+	}
+
+	protected void updateEnabledStatus(WorkflowBundle workflowBundle) {
+		setEnabled(workflowBundle != null
+				&& fileManager.isDataflowChanged(workflowBundle));
+	}
+
+	private final class EditManagerObserver implements
+			Observer<EditManagerEvent> {
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			if (message instanceof AbstractDataflowEditEvent) {
+				WorkflowBundle workflowBundle = ((AbstractDataflowEditEvent) message)
+						.getDataFlow();
+				if (workflowBundle == fileManager.getCurrentDataflow())
+					updateEnabledStatus(workflowBundle);
+			}
+		}
+	}
+
+	private final class FileManagerObserver implements
+			Observer<FileManagerEvent> {
+		@Override
+		public void notify(Observable<FileManagerEvent> sender,
+				FileManagerEvent message) throws Exception {
+			if (message instanceof SavedDataflowEvent)
+				updateEnabledStatus(((SavedDataflowEvent) message)
+						.getDataflow());
+			else if (message instanceof SetCurrentDataflowEvent)
+				updateEnabledStatus(((SetCurrentDataflowEvent) message)
+						.getDataflow());
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java
new file mode 100644
index 0000000..1872d5d
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.event.KeyEvent.VK_F6;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAsIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+@SuppressWarnings("serial")
+public class SaveWorkflowAsAction extends AbstractAction {
+	private static final String SAVE_WORKFLOW_AS = "Save workflow as...";
+	private static final String PREF_CURRENT_DIR = "currentDir";
+	private static Logger logger = Logger.getLogger(SaveWorkflowAsAction.class);
+	private FileManager fileManager;
+
+	public SaveWorkflowAsAction(FileManager fileManager) {
+		super(SAVE_WORKFLOW_AS, saveAsIcon);
+		this.fileManager = fileManager;
+		fileManager.addObserver(new FileManagerObserver());
+		putValue(ACCELERATOR_KEY, getKeyStroke(VK_F6, 0));
+		putValue(MNEMONIC_KEY, VK_S);
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		Component parentComponent = null;
+		if (e.getSource() instanceof Component)
+			parentComponent = (Component) e.getSource();
+		WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+		if (workflowBundle == null) {
+			showMessageDialog(parentComponent, "No workflow open yet",
+					"No workflow to save", ERROR_MESSAGE);
+			return;
+		}
+		saveCurrentDataflow(parentComponent);
+	}
+
+	public boolean saveCurrentDataflow(Component parentComponent) {
+		WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+		return saveDataflow(parentComponent, workflowBundle);
+	}
+
+	private String determineFileName(final WorkflowBundle workflowBundle) {
+		String result;
+		Object source = fileManager.getDataflowSource(workflowBundle);
+		String fileName = null;
+		if (source instanceof File)
+			fileName = ((File) source).getName();
+		else if (source instanceof URL)
+			fileName = ((URL) source).getPath();
+
+		if (fileName != null) {
+			int lastIndex = fileName.lastIndexOf(".");
+			if (lastIndex > 0)
+				fileName = fileName.substring(0, fileName.lastIndexOf("."));
+			result = fileName;
+		} else {
+			Workflow mainWorkflow = workflowBundle.getMainWorkflow();
+			if (mainWorkflow != null)
+				result = mainWorkflow.getName();
+			else
+				result = workflowBundle.getName();
+		}
+		return result;
+	}
+
+	public boolean saveDataflow(Component parentComponent, WorkflowBundle workflowBundle) {
+		fileManager.setCurrentDataflow(workflowBundle);
+		JFileChooser fileChooser = new JFileChooser();
+		Preferences prefs = Preferences.userNodeForPackage(getClass());
+		String curDir = prefs
+				.get(PREF_CURRENT_DIR, System.getProperty("user.home"));
+		fileChooser.setDialogTitle(SAVE_WORKFLOW_AS);
+
+		fileChooser.resetChoosableFileFilters();
+		fileChooser.setAcceptAllFileFilterUsed(false);
+
+		List<FileFilter> fileFilters = fileManager
+				.getSaveFileFilters(File.class);
+		if (fileFilters.isEmpty()) {
+			logger.warn("No file types found for saving workflow "
+					+ workflowBundle);
+			showMessageDialog(parentComponent,
+					"No file types found for saving workflow.", "Error",
+					ERROR_MESSAGE);
+			return false;
+		}
+		for (FileFilter fileFilter : fileFilters)
+			fileChooser.addChoosableFileFilter(fileFilter);
+		fileChooser.setFileFilter(fileFilters.get(0));
+		fileChooser.setCurrentDirectory(new File(curDir));
+
+		File possibleName = new File(determineFileName(workflowBundle));
+		boolean tryAgain = true;
+		while (tryAgain) {
+			tryAgain = false;
+			fileChooser.setSelectedFile(possibleName);
+			int returnVal = fileChooser.showSaveDialog(parentComponent);
+			if (returnVal == APPROVE_OPTION) {
+				prefs.put(PREF_CURRENT_DIR, fileChooser.getCurrentDirectory()
+						.toString());
+				File file = fileChooser.getSelectedFile();
+				FileTypeFileFilter fileFilter = (FileTypeFileFilter) fileChooser
+						.getFileFilter();
+				FileType fileType = fileFilter.getFileType();
+				String extension = "." + fileType.getExtension();
+				if (!file.getName().toLowerCase().endsWith(extension)) {
+					String newName = file.getName() + extension;
+					file = new File(file.getParentFile(), newName);
+				}
+
+				// TODO: Open in separate thread to avoid hanging UI
+				try {
+					try {
+						fileManager.saveDataflow(workflowBundle, fileType,
+								file, true);
+						logger.info("Saved workflow " + workflowBundle + " to "
+								+ file);
+						return true;
+					} catch (OverwriteException ex) {
+						logger.info("File already exists: " + file);
+						String msg = "Are you sure you want to overwrite existing file "
+								+ file + "?";
+						int ret = showConfirmDialog(parentComponent, msg,
+								"File already exists", YES_NO_CANCEL_OPTION);
+						if (ret == YES_OPTION) {
+							fileManager.saveDataflow(workflowBundle, fileType,
+									file, false);
+							logger.info("Saved workflow " + workflowBundle
+									+ " by overwriting " + file);
+							return true;
+						} else if (ret == NO_OPTION) {
+							tryAgain = true;
+							continue;
+						} else {
+							logger.info("Aborted overwrite of " + file);
+							return false;
+						}
+					}
+				} catch (SaveException ex) {
+					logger.warn("Could not save workflow to " + file, ex);
+					showMessageDialog(parentComponent,
+							"Could not save workflow to " + file + ": \n\n"
+									+ ex.getMessage(), "Warning",
+							WARNING_MESSAGE);
+					return false;
+				}
+			}
+		}
+		return false;
+	}
+
+	protected void updateEnabledStatus(WorkflowBundle workflowBundle) {
+		setEnabled(workflowBundle != null);
+	}
+
+	private final class FileManagerObserver implements Observer<FileManagerEvent> {
+		@Override
+		public void notify(Observable<FileManagerEvent> sender,
+				FileManagerEvent message) throws Exception {
+			if (message instanceof SetCurrentDataflowEvent)
+				updateEnabledStatus(((SetCurrentDataflowEvent) message)
+						.getDataflow());
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
new file mode 100644
index 0000000..6c0be19
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
@@ -0,0 +1,56 @@
+/*******************************************************************************

+ * Copyright (C) 2010 The University of Manchester

+ *

+ *  Modifications to the initial code base are copyright of their

+ *  respective authors, or their employers as appropriate.

+ *

+ *  This program is free software; you can redistribute it and/or

+ *  modify it under the terms of the GNU Lesser General Public License

+ *  as published by the Free Software Foundation; either version 2.1 of

+ *  the License, or (at your option) any later version.

+ *

+ *  This program is distributed in the hope that it will be useful, but

+ *  WITHOUT ANY WARRANTY; without even the implied warranty of

+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

+ *  Lesser General Public License for more details.

+ *

+ *  You should have received a copy of the GNU Lesser General Public

+ *  License along with this program; if not, write to the Free Software

+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307

+ ******************************************************************************/

+package net.sf.taverna.t2.workbench.file.impl.hooks;

+

+import net.sf.taverna.t2.workbench.ShutdownSPI;

+import net.sf.taverna.t2.workbench.edits.EditManager;

+import net.sf.taverna.t2.workbench.file.FileManager;

+import net.sf.taverna.t2.workbench.file.impl.actions.CloseAllWorkflowsAction;

+

+/**

+ * Close open workflows (and ask the user if she wants to save changes) on

+ * shutdown.

+ * 

+ * @author Stian Soiland-Reyes

+ */

+public class CloseWorkflowsOnShutdown implements ShutdownSPI {

+	private CloseAllWorkflowsAction closeAllWorkflowsAction;

+

+	public CloseWorkflowsOnShutdown(EditManager editManager,

+			FileManager fileManager) {

+		closeAllWorkflowsAction = new CloseAllWorkflowsAction(editManager,

+				fileManager);

+	}

+

+	@Override

+	public int positionHint() {

+		/*

+		 * Quite early, we don't want to do various clean-up in case the user

+		 * clicks Cancel

+		 */

+		return 50;

+	}

+

+	@Override

+	public boolean shutdown() {

+		return closeAllWorkflowsAction.closeAllWorkflows(null);

+	}

+}

diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java
new file mode 100644
index 0000000..e8e5252
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseAllWorkflowsAction;
+
+public class FileCloseAllMenuAction extends AbstractMenuAction {
+	private static final URI FILE_CLOSE_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileCloseAll");
+	private final EditManager editManager;
+	private final FileManager fileManager;
+
+	public FileCloseAllMenuAction(EditManager editManager,
+			FileManager fileManager) {
+		super(FILE_URI, 39, FILE_CLOSE_URI);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new CloseAllWorkflowsAction(editManager, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java
new file mode 100644
index 0000000..a97219f
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseWorkflowAction;
+
+public class FileCloseMenuAction extends AbstractMenuAction {
+	private static final URI FILE_CLOSE_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileClose");
+	private final EditManager editManager;
+	private final FileManager fileManager;
+
+	public FileCloseMenuAction(EditManager editManager, FileManager fileManager) {
+		super(FILE_URI, 30, FILE_CLOSE_URI);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new CloseWorkflowAction(editManager, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java
new file mode 100644
index 0000000..3a48e0d
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.NewWorkflowAction;
+
+public class FileNewMenuAction extends AbstractMenuAction {
+	private static final URI FILE_NEW_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileNew");
+	private final FileManager fileManager;
+
+	public FileNewMenuAction(FileManager fileManager) {
+		super(FILE_OPEN_SECTION_URI, 10, FILE_NEW_URI);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new NewWorkflowAction(fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java
new file mode 100644
index 0000000..9af1d6b
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowFromURLAction;
+
+public class FileOpenFromURLMenuAction extends AbstractMenuAction {
+
+	private static final URI FILE_OPEN_FROM_URL_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenURL");
+	private final FileManager fileManager;
+
+	public FileOpenFromURLMenuAction(FileManager fileManager) {
+		super(FILE_OPEN_SECTION_URI, 30, FILE_OPEN_FROM_URL_URI);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new OpenWorkflowFromURLAction(null, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java
new file mode 100644
index 0000000..4ee4e39
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowAction;
+
+public class FileOpenMenuAction extends AbstractMenuAction {
+	private static final URI FILE_OPEN_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileOpen");
+	private final FileManager fileManager;
+
+	public FileOpenMenuAction(FileManager fileManager) {
+		super(FILE_OPEN_SECTION_URI, 20, FILE_OPEN_URI);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new OpenWorkflowAction(fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java
new file mode 100644
index 0000000..46ef476
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileOpenMenuSection extends AbstractMenuSection {
+	public static final URI FILE_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#file");
+	public static final URI FILE_OPEN_SECTION_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenSection");
+
+	public FileOpenMenuSection() {
+		super(FILE_URI, 20, FILE_OPEN_SECTION_URI);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java
new file mode 100644
index 0000000..76ef759
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java
@@ -0,0 +1,418 @@
+package net.sf.taverna.t2.workbench.file.impl.menu;

+

+import static java.awt.event.KeyEvent.VK_0;

+import static java.awt.event.KeyEvent.VK_R;

+import static javax.swing.Action.MNEMONIC_KEY;

+import static javax.swing.Action.NAME;

+import static javax.swing.JOptionPane.ERROR_MESSAGE;

+import static javax.swing.JOptionPane.showMessageDialog;

+import static javax.swing.SwingUtilities.invokeLater;

+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;

+

+import java.awt.Component;

+import java.awt.event.ActionEvent;

+import java.io.BufferedInputStream;

+import java.io.BufferedOutputStream;

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.FileNotFoundException;

+import java.io.FileOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.OutputStream;

+import java.io.Serializable;

+import java.net.URI;

+import java.util.ArrayList;

+import java.util.List;

+

+import javax.swing.AbstractAction;

+import javax.swing.JMenu;

+import javax.swing.JMenuItem;

+

+import net.sf.taverna.t2.lang.observer.Observable;

+import net.sf.taverna.t2.lang.observer.Observer;

+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;

+import net.sf.taverna.t2.workbench.file.FileManager;

+import net.sf.taverna.t2.workbench.file.FileType;

+import net.sf.taverna.t2.workbench.file.events.AbstractDataflowEvent;

+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;

+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;

+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;

+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;

+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;

+

+import org.apache.log4j.Logger;

+import org.jdom.Document;

+import org.jdom.JDOMException;

+import org.jdom.input.SAXBuilder;

+

+import uk.org.taverna.configuration.app.ApplicationConfiguration;

+import uk.org.taverna.scufl2.api.container.WorkflowBundle;

+

+public class FileOpenRecentMenuAction extends AbstractMenuCustom implements

+		Observer<FileManagerEvent> {

+	public static final URI RECENT_URI = URI

+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenRecent");

+	private static final String CONF = "conf";

+	private static Logger logger = Logger

+			.getLogger(FileOpenRecentMenuAction.class);

+	private static final String RECENT_WORKFLOWS_XML = "recentWorkflows.xml";

+	private static final int MAX_ITEMS = 10;

+

+	private FileManager fileManager;

+	private ApplicationConfiguration applicationConfiguration;

+	private JMenu menu;

+	private List<Recent> recents = new ArrayList<>();

+	private Thread loadRecentThread;

+

+	public FileOpenRecentMenuAction(FileManager fileManager) {

+		super(FILE_OPEN_SECTION_URI, 30, RECENT_URI);

+		this.fileManager = fileManager;

+		fileManager.addObserver(this);

+	}

+

+	@Override

+	public void notify(Observable<FileManagerEvent> sender,

+			FileManagerEvent message) throws Exception {

+		FileManager fileManager = (FileManager) sender;

+		if (message instanceof OpenedDataflowEvent

+				|| message instanceof SavedDataflowEvent) {

+			AbstractDataflowEvent dataflowEvent = (AbstractDataflowEvent) message;

+			WorkflowBundle dataflow = dataflowEvent.getDataflow();

+			Object dataflowSource = fileManager.getDataflowSource(dataflow);

+			FileType dataflowType = fileManager.getDataflowType(dataflow);

+			addRecent(dataflowSource, dataflowType);

+		}

+		if (message instanceof ClosedDataflowEvent)

+			// Make sure enabled/disabled status is correct

+			updateRecentMenu();

+	}

+

+	public void updateRecentMenu() {

+		invokeLater(new Runnable() {

+			@Override

+			public void run() {

+				updateRecentMenuGUI();

+			}

+		});

+		saveRecent();

+	}

+

+	protected void addRecent(Object dataflowSource, FileType dataflowType) {

+		if (dataflowSource == null)

+			return;

+		if (!(dataflowSource instanceof Serializable)) {

+			logger.warn("Can't serialize workflow source for 'Recent workflows': "

+					+ dataflowSource);

+			return;

+		}

+		synchronized (recents) {

+			Recent recent = new Recent((Serializable) dataflowSource, dataflowType);

+			if (recents.contains(recent))

+				recents.remove(recent);

+			recents.add(0, recent); // Add to front

+		}

+		updateRecentMenu();

+	}

+

+	@Override

+	protected Component createCustomComponent() {

+		action = new DummyAction("Recent workflows");

+		action.putValue(MNEMONIC_KEY, VK_R);

+		menu = new JMenu(action);

+		// Disabled until we have loaded the recent workflows

+		menu.setEnabled(false);

+		loadRecentThread = new Thread("Loading recent workflow menu") {

+			// Avoid hanging GUI initialization while deserialising

+			@Override

+			public void run() {

+				loadRecent();

+				updateRecentMenu();

+			}

+		};

+		loadRecentThread.start();

+		return menu;

+	}

+

+	protected synchronized void loadRecent() {

+		File confDir = new File(applicationConfiguration.getApplicationHomeDir(), CONF);

+		confDir.mkdir();

+		File recentFile = new File(confDir, RECENT_WORKFLOWS_XML);

+		if (!recentFile.isFile())

+			return;

+		try {

+			loadRecent(recentFile);

+		} catch (JDOMException|IOException e) {

+			logger.warn("Could not read recent workflows from file "

+					+ recentFile, e);

+		}

+	}

+

+	private void loadRecent(File recentFile) throws FileNotFoundException,

+			IOException, JDOMException {

+		SAXBuilder builder = new SAXBuilder();

+		@SuppressWarnings("unused")

+		Document document;

+		try (InputStream fileInputStream = new BufferedInputStream(

+				new FileInputStream(recentFile))) {

+			document = builder.build(fileInputStream);

+		}

+		synchronized (recents) {

+			recents.clear();

+			//RecentDeserializer deserialiser = new RecentDeserializer();

+			try {

+				// recents.addAll(deserialiser.deserializeRecent(document

+				// .getRootElement()));

+			} catch (Exception e) {

+				logger.warn("Could not read recent workflows from file "

+						+ recentFile, e);

+			}

+		}

+	}

+

+	protected synchronized void saveRecent() {

+		File confDir = new File(applicationConfiguration.getApplicationHomeDir(), CONF);

+		confDir.mkdir();

+		File recentFile = new File(confDir, RECENT_WORKFLOWS_XML);

+

+		try {

+			saveRecent(recentFile);

+//		} catch (JDOMException e) {

+//			logger.warn("Could not generate XML for recent workflows to file "

+//					+ recentFile, e);

+		} catch (IOException e) {

+			logger.warn("Could not write recent workflows to file "

+					+ recentFile, e);

+		}

+	}

+

+	private void saveRecent(File recentFile) throws FileNotFoundException,

+			IOException {

+		// RecentSerializer serializer = new RecentSerializer();

+		// XMLOutputter outputter = new XMLOutputter();

+

+		// Element serializedRecent;

+		synchronized (recents) {

+			if (recents.size() > MAX_ITEMS)

+				// Remove excess entries

+				recents.subList(MAX_ITEMS, recents.size()).clear();

+			// serializedRecent = serializer.serializeRecent(recents);

+		}

+		try (OutputStream outputStream = new BufferedOutputStream(

+				new FileOutputStream(recentFile))) {

+			// outputter.output(serializedRecent, outputStream);

+		}

+	}

+

+	protected void updateRecentMenuGUI() {

+		int items = 0;

+		menu.removeAll();

+		synchronized (recents) {

+			for (Recent recent : recents) {

+				if (++items >= MAX_ITEMS)

+					break;

+				OpenRecentAction openRecentAction = new OpenRecentAction(

+						recent, fileManager);

+				if (fileManager.getDataflowBySource(recent.getDataflowSource()) != null)

+					openRecentAction.setEnabled(false);

+				// else setEnabled(true)

+				JMenuItem menuItem = new JMenuItem(openRecentAction);

+				if (items < 10) {

+					openRecentAction.putValue(NAME, items + " "

+							+ openRecentAction.getValue(NAME));

+					menuItem.setMnemonic(VK_0 + items);

+				}

+				menu.add(menuItem);

+			}

+		}

+		menu.setEnabled(items > 0);

+		menu.revalidate();

+	}

+

+	@SuppressWarnings("serial")

+	public static class OpenRecentAction extends AbstractAction implements

+			Runnable {

+		private final Recent recent;

+		private Component component = null;

+		private final FileManager fileManager;

+

+		public OpenRecentAction(Recent recent, FileManager fileManager) {

+			this.recent = recent;

+			this.fileManager = fileManager;

+			Serializable source = recent.getDataflowSource();

+			String name;

+			if (source instanceof File)

+				name = ((File) source).getAbsolutePath();

+			else

+				name = source.toString();

+			this.putValue(NAME, name);

+			this.putValue(SHORT_DESCRIPTION, "Open the workflow " + name);

+		}

+

+		@Override

+		public void actionPerformed(ActionEvent e) {

+			component = null;

+			if (e.getSource() instanceof Component)

+				component = (Component) e.getSource();

+			setEnabled(false);

+			new Thread(this, "Opening workflow from "

+					+ recent.getDataflowSource()).start();

+		}

+

+		/**

+		 * Opening workflow in separate thread

+		 */

+		@Override

+		public void run() {

+			final Serializable source = recent.getDataflowSource();

+			try {

+				fileManager.openDataflow(recent.makefileType(), source);

+			} catch (OpenException ex) {

+				logger.warn("Failed to open the workflow from  " + source

+						+ " \n", ex);

+				showMessageDialog(component,

+						"Failed to open the workflow from url " + source

+								+ " \n" + ex.getMessage(), "Error!",

+						ERROR_MESSAGE);

+			} finally {

+				setEnabled(true);

+			}

+		}

+	}

+

+	@SuppressWarnings("serial")

+	public static class Recent implements Serializable {

+		private final class RecentFileType extends FileType {

+			@Override

+			public String getMimeType() {

+				return mimeType;

+			}

+

+			@Override

+			public String getExtension() {

+				return extension;

+			}

+

+			@Override

+			public String getDescription() {

+				return "File type " + extension + " " + mimeType;

+			}

+		}

+

+		private Serializable dataflowSource;

+		private String mimeType;

+		private String extension;

+

+		public String getMimeType() {

+			return mimeType;

+		}

+

+		public void setMimeType(String mimeType) {

+			this.mimeType = mimeType;

+		}

+

+		public String getExtension() {

+			return extension;

+		}

+

+		public void setExtension(String extension) {

+			this.extension = extension;

+		}

+

+		public Recent() {

+		}

+

+		public FileType makefileType() {

+			if (mimeType == null && extension == null)

+				return null;

+			return new RecentFileType();

+		}

+

+		public Recent(Serializable dataflowSource, FileType dataflowType) {

+			setDataflowSource(dataflowSource);

+			if (dataflowType != null) {

+				setMimeType(dataflowType.getMimeType());

+				setExtension(dataflowType.getExtension());

+			}

+		}

+

+		@Override

+		public int hashCode() {

+			final int prime = 31;

+			int result = 1;

+			result = prime

+					* result

+					+ ((dataflowSource == null) ? 0 : dataflowSource.hashCode());

+			result = prime * result

+					+ ((extension == null) ? 0 : extension.hashCode());

+			result = prime * result

+					+ ((mimeType == null) ? 0 : mimeType.hashCode());

+			return result;

+		}

+

+		@Override

+		public boolean equals(Object obj) {

+			if (this == obj)

+				return true;

+			if (obj == null)

+				return false;

+			if (!(obj instanceof Recent))

+				return false;

+			Recent other = (Recent) obj;

+

+			if (dataflowSource == null) {

+				if (other.dataflowSource != null)

+					return false;

+			} else if (!dataflowSource.equals(other.dataflowSource))

+				return false;

+

+			if (extension == null) {

+				if (other.extension != null)

+					return false;

+			} else if (!extension.equals(other.extension))

+				return false;

+

+			if (mimeType == null) {

+				if (other.mimeType != null)

+					return false;

+			} else if (!mimeType.equals(other.mimeType))

+				return false;

+

+			return true;

+		}

+

+		public Serializable getDataflowSource() {

+			return dataflowSource;

+		}

+

+		public void setDataflowSource(Serializable dataflowSource) {

+			this.dataflowSource = dataflowSource;

+		}

+

+		@Override

+		public String toString() {

+			return getDataflowSource() + "";

+		}

+	}

+

+	// TODO find new serialization

+//	protected static class RecentDeserializer extends AbstractXMLDeserializer {

+//		public Collection<Recent> deserializeRecent(Element el) {

+//			return (Collection<Recent>) super.createBean(el, getClass()

+//					.getClassLoader());

+//		}

+//	}

+//

+//	protected static class RecentSerializer extends AbstractXMLSerializer {

+//		public Element serializeRecent(List<Recent> x) throws JDOMException,

+//				IOException {

+//			Element beanAsElement = super.beanAsElement(x);

+//			return beanAsElement;

+//		}

+//	}

+

+	public void setApplicationConfiguration(

+			ApplicationConfiguration applicationConfiguration) {

+		this.applicationConfiguration = applicationConfiguration;

+	}

+}

diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java
new file mode 100644
index 0000000..86edacb
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveAllWorkflowsAction;
+
+public class FileSaveAllMenuAction extends AbstractMenuAction {
+	private final EditManager editManager;
+	private final FileManager fileManager;
+
+	public FileSaveAllMenuAction(EditManager editManager,
+			FileManager fileManager) {
+		super(FILE_SAVE_SECTION_URI, 30);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new SaveAllWorkflowsAction(editManager, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java
new file mode 100644
index 0000000..77917c9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAsAction;
+
+public class FileSaveAsMenuAction extends AbstractMenuAction {
+	private final FileManager fileManager;
+
+	public FileSaveAsMenuAction(FileManager fileManager) {
+		super(FILE_SAVE_SECTION_URI, 20);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new SaveWorkflowAsAction(fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java
new file mode 100644
index 0000000..eeaecb3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAction;
+
+public class FileSaveMenuAction extends AbstractMenuAction {
+	private final EditManager editManager;
+	private final FileManager fileManager;
+
+	public FileSaveMenuAction(EditManager editManager, FileManager fileManager) {
+		super(FILE_SAVE_SECTION_URI, 10);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new SaveWorkflowAction(editManager, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java
new file mode 100644
index 0000000..a75a855
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileSaveMenuSection extends AbstractMenuSection {
+	public static final URI FILE_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#file");
+	public static final URI FILE_SAVE_SECTION_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileSaveSection");
+
+	public FileSaveMenuSection() {
+		super(FILE_URI, 40, FILE_SAVE_SECTION_URI);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java
new file mode 100644
index 0000000..e056572
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_0;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.AbstractDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class WorkflowsMenu extends AbstractMenuCustom {
+	private EditManagerObserver editManagerObserver = new EditManagerObserver();
+	private FileManager fileManager;
+	private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+	private JMenu workflowsMenu;
+
+	public WorkflowsMenu(EditManager editManager, FileManager fileManager) {
+		super(DEFAULT_MENU_BAR, 900);
+		this.fileManager = fileManager;
+		fileManager.addObserver(fileManagerObserver);
+		editManager.addObserver(editManagerObserver);
+	}
+
+	@Override
+	protected Component createCustomComponent() {
+		DummyAction action = new DummyAction("Workflows");
+		action.putValue(MNEMONIC_KEY, VK_W);
+
+		workflowsMenu = new JMenu(action);
+
+		updateWorkflowsMenu();
+		return workflowsMenu;
+	}
+
+	public void updateWorkflowsMenu() {
+		invokeLater(new Runnable() {
+			@Override
+			public void run() {
+				updateWorkflowsMenuUI();
+			}
+		});
+	}
+
+	protected void updateWorkflowsMenuUI() {
+		workflowsMenu.setEnabled(false);
+		workflowsMenu.removeAll();
+		ButtonGroup workflowsGroup = new ButtonGroup();
+
+		int i = 0;
+		WorkflowBundle currentDataflow = fileManager.getCurrentDataflow();
+		for (WorkflowBundle workflowBundle : fileManager.getOpenDataflows()) {
+			String name = fileManager.getDataflowName(workflowBundle);
+			if (fileManager.isDataflowChanged(workflowBundle))
+				name = "*" + name;
+			// A counter
+			name = ++i + " " + name;
+
+			SwitchWorkflowAction switchWorkflowAction = new SwitchWorkflowAction(
+					name, workflowBundle);
+			if (i < 10)
+				switchWorkflowAction.putValue(MNEMONIC_KEY, new Integer(VK_0
+						+ i));
+
+			JRadioButtonMenuItem switchWorkflowMenuItem = new JRadioButtonMenuItem(
+					switchWorkflowAction);
+			workflowsGroup.add(switchWorkflowMenuItem);
+			if (workflowBundle.equals(currentDataflow))
+				switchWorkflowMenuItem.setSelected(true);
+			workflowsMenu.add(switchWorkflowMenuItem);
+		}
+		if (i == 0)
+			workflowsMenu.add(new NoWorkflowsOpen());
+		workflowsMenu.setEnabled(true);
+		workflowsMenu.revalidate();
+	}
+
+	private final class EditManagerObserver implements
+			Observer<EditManagerEvent> {
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			if (message instanceof AbstractDataflowEditEvent)
+				updateWorkflowsMenu();
+		}
+	}
+
+	private final class FileManagerObserver implements
+			Observer<FileManagerEvent> {
+		@Override
+		public void notify(Observable<FileManagerEvent> sender,
+				FileManagerEvent message) throws Exception {
+			if (message instanceof AbstractDataflowEvent)
+				updateWorkflowsMenu();
+			// TODO: Don't rebuild whole menu
+		}
+	}
+
+	@SuppressWarnings("serial")
+	private final class NoWorkflowsOpen extends AbstractAction {
+		private NoWorkflowsOpen() {
+			super("No workflows open");
+			setEnabled(false);
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			// No-op
+		}
+	}
+
+	@SuppressWarnings("serial")
+	private final class SwitchWorkflowAction extends AbstractAction {
+		private final WorkflowBundle workflowBundle;
+
+		private SwitchWorkflowAction(String name, WorkflowBundle workflowBundle) {
+			super(name);
+			this.workflowBundle = workflowBundle;
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			fileManager.setCurrentDataflow(workflowBundle);
+		}
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java
new file mode 100644
index 0000000..68ef3f9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseWorkflowAction;
+
+/**
+ * Action to close the current workflow.
+ * 
+ * @author Alex Nenadic
+ */
+public class CloseToolbarAction extends AbstractMenuAction {
+	private static final URI FILE_CLOSE_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarClose");
+	private final EditManager editManager;
+	private final FileManager fileManager;
+
+	public CloseToolbarAction(EditManager editManager, FileManager fileManager) {
+		super(FILE_TOOLBAR_SECTION, 30, FILE_CLOSE_URI);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new CloseWorkflowAction(editManager, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java
new file mode 100644
index 0000000..257d590
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileToolbarMenuSection extends AbstractMenuSection {
+	public static final URI FILE_TOOLBAR_SECTION = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarSection");
+
+	public FileToolbarMenuSection() {
+		super(DEFAULT_TOOL_BAR, 10, FILE_TOOLBAR_SECTION);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java
new file mode 100644
index 0000000..2c8e922
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.NewWorkflowAction;
+
+public class NewToolbarAction extends AbstractMenuAction {
+	private static final URI FILE_NEW_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarNew");
+	private final FileManager fileManager;
+
+	public NewToolbarAction(FileManager fileManager) {
+		super(FILE_TOOLBAR_SECTION, 10, FILE_NEW_URI);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new NewWorkflowAction(fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java
new file mode 100644
index 0000000..ae99509
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowAction;
+
+public class OpenToolbarAction extends AbstractMenuAction {
+	private static final URI FILE_OPEN_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarOpen");
+	private final FileManager fileManager;
+
+	public OpenToolbarAction(FileManager fileManager) {
+		super(FILE_TOOLBAR_SECTION, 20, FILE_OPEN_URI);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new OpenWorkflowAction(fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java
new file mode 100644
index 0000000..2554063
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowFromURLAction;
+
+public class OpenWorkflowFromURLToolbarAction extends AbstractMenuAction {
+	private static final URI FILE_OPEN_FROM_URL_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarOpenFromURL");
+	private final FileManager fileManager;
+
+	public OpenWorkflowFromURLToolbarAction(FileManager fileManager) {
+		super(FILE_TOOLBAR_SECTION, 25, FILE_OPEN_FROM_URL_URI);
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new OpenWorkflowFromURLAction(null, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java
new file mode 100644
index 0000000..53ba720
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAction;
+
+public class SaveToolbarAction extends AbstractMenuAction {
+	private static final URI FILE_SAVE_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarSave");
+	private final EditManager editManager;
+	private final FileManager fileManager;
+
+	public SaveToolbarAction(EditManager editManager, FileManager fileManager) {
+		super(FILE_TOOLBAR_SECTION, 40, FILE_SAVE_URI);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new SaveWorkflowAction(editManager, fileManager);
+	}
+}
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..100915c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,20 @@
+net.sf.taverna.t2.workbench.file.impl.menu.FileCloseMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileNewMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenFromURLMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenRecentMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAllMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAsMenuAction
+
+net.sf.taverna.t2.workbench.file.impl.menu.WorkflowsMenu
+net.sf.taverna.t2.workbench.file.impl.menu.FileCloseAllMenuAction
+
+net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection
+net.sf.taverna.t2.workbench.file.impl.toolbar.NewToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.OpenToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.OpenWorkflowFromURLToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.SaveToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.CloseToolbarAction
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..cc53d36
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.file.impl.hooks.CloseWorkflowsOnShutdown

diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
new file mode 100644
index 0000000..cfd1c7a
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener
+net.sf.taverna.t2.workbench.file.impl.DataflowFromDataflowPersistenceHandler
\ No newline at end of file
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager
new file mode 100644
index 0000000..656feeb
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.file.impl.FileManagerImpl
\ No newline at end of file
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml
new file mode 100644
index 0000000..7c6e290
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="FileCloseMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.close" />
+		</service-properties>
+	</service>
+	<service ref="FileNewMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.new" />
+		</service-properties>
+	</service>
+	<service ref="FileOpenMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.open" />
+		</service-properties>
+	</service>
+	<service ref="FileOpenFromURLMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.open.url" />
+		</service-properties>
+	</service>
+	<service ref="FileOpenMenuSection" auto-export="interfaces" />
+	<service ref="FileOpenRecentMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.open.recent" />
+		</service-properties>
+	</service>
+	<service ref="FileSaveMenuSection" auto-export="interfaces" />
+	<service ref="FileSaveMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.save" />
+		</service-properties>
+	</service>
+	<service ref="FileSaveAllMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.save.all" />
+		</service-properties>
+	</service>
+	<service ref="FileSaveAsMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.save.as" />
+		</service-properties>
+	</service>
+	<service ref="WorkflowsMenu" auto-export="interfaces" />
+	<service ref="FileCloseAllMenuAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.close.all" />
+		</service-properties>
+	</service>
+	<service ref="FileToolbarMenuSection" auto-export="interfaces" />
+	<service ref="NewToolbarAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="toolbar.new" />
+		</service-properties>
+	</service>
+	<service ref="OpenToolbarAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="toolbar.open" />
+		</service-properties>
+	</service>
+	<service ref="OpenWorkflowFromURLToolbarAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="toolbar.open.url" />
+		</service-properties>
+	</service>
+	<service ref="SaveToolbarAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="toolbar.save" />
+		</service-properties>
+	</service>
+	<service ref="CloseToolbarAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="toolbar.close" />
+		</service-properties>
+	</service>
+
+	<service ref="T2DataflowOpener" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+
+	<service ref="WorkflowBundleOpener" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+	<service ref="WorkflowBundleSaver" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+
+	<service ref="CloseWorkflowsOnShutdown" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+	<service ref="FileManagerImpl" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+
+	<reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+	<reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+	<reference id="workflowBundleIO" interface="uk.org.taverna.scufl2.api.io.WorkflowBundleIO" />
+
+	<list id="dataflowPersistenceHandlers" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" cardinality="0..N">
+		<listener ref="DataflowPersistenceHandlerRegistry" bind-method="update" unbind-method="update" />
+	</list>
+</beans:beans>
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml
new file mode 100644
index 0000000..493df5f
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="FileCloseMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileCloseMenuAction">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileNewMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileNewMenuAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileOpenMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileOpenFromURLMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenFromURLMenuAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileOpenMenuSection" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection" />
+	<bean id="FileOpenRecentMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenRecentMenuAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+ 		<property name="applicationConfiguration" ref="applicationConfiguration"/>
+    </bean>
+	<bean id="FileSaveMenuSection" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection" />
+	<bean id="FileSaveMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuAction">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileSaveAllMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAllMenuAction">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileSaveAsMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAsMenuAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="WorkflowsMenu" class="net.sf.taverna.t2.workbench.file.impl.menu.WorkflowsMenu">
+	    <constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+	</bean>
+	<bean id="FileCloseAllMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileCloseAllMenuAction">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="FileToolbarMenuSection" class="net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection" />
+	<bean id="NewToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.NewToolbarAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="OpenToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.OpenToolbarAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="OpenWorkflowFromURLToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.OpenWorkflowFromURLToolbarAction">
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="SaveToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.SaveToolbarAction">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+	<bean id="CloseToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.CloseToolbarAction">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+
+	<bean id="T2DataflowOpener" class="net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener">
+			<property name="workflowBundleIO" ref="workflowBundleIO"/>
+	</bean>
+
+	<bean id="WorkflowBundleOpener" class="net.sf.taverna.t2.workbench.file.impl.WorkflowBundleOpener">
+			<property name="workflowBundleIO" ref="workflowBundleIO"/>
+	</bean>
+	<bean id="WorkflowBundleSaver" class="net.sf.taverna.t2.workbench.file.impl.WorkflowBundleSaver">
+			<property name="workflowBundleIO" ref="workflowBundleIO"/>
+	</bean>
+
+	<bean id="CloseWorkflowsOnShutdown" class="net.sf.taverna.t2.workbench.file.impl.hooks.CloseWorkflowsOnShutdown">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg>
+			<ref local="FileManagerImpl" />
+		</constructor-arg>
+    </bean>
+
+	<bean id="FileManagerImpl" class="net.sf.taverna.t2.workbench.file.impl.FileManagerImpl">
+    	<constructor-arg name="editManager" ref="editManager" />
+    	<property name="dataflowPersistenceHandlerRegistry">
+    		<ref local="DataflowPersistenceHandlerRegistry"/>
+    	</property>
+	</bean>
+
+	<bean id="DataflowPersistenceHandlerRegistry" class="net.sf.taverna.t2.workbench.file.impl.DataflowPersistenceHandlerRegistry">
+    	<property name="dataflowPersistenceHandlers" ref="dataflowPersistenceHandlers" />
+	</bean>
+
+
+</beans>
diff --git a/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java b/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java
new file mode 100644
index 0000000..691b278
--- /dev/null
+++ b/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleReader;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleWriter;
+import uk.org.taverna.scufl2.rdfxml.RDFXMLReader;
+import uk.org.taverna.scufl2.rdfxml.RDFXMLWriter;
+import uk.org.taverna.scufl2.translator.t2flow.T2FlowReader;
+
+public class FileManagerTest {
+
+	private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+	private static final T2FlowFileType T2_FLOW_FILE_TYPE = new T2FlowFileType();
+
+	private static final String DUMMY_WORKFLOW_T2FLOW = "dummy-workflow.t2flow";
+
+	private FileManagerImpl fileManager;
+	private EditManager editManager;
+
+	private FileManagerObserver fileManagerObserver= new FileManagerObserver();;
+
+	@Test
+	public void close() throws Exception {
+		assertTrue("Non-empty set of open dataflows", fileManager
+				.getOpenDataflows().isEmpty());
+		WorkflowBundle dataflow = openDataflow();
+		assertEquals("Unexpected list of open dataflows", Arrays
+				.asList(dataflow), fileManager.getOpenDataflows());
+		fileManager.closeDataflow(dataflow, true);
+		assertNotSame(dataflow, fileManager.getOpenDataflows().get(0));
+		assertTrue("Did not insert empty dataflow after close", fileManager
+				.getOpenDataflows().get(0).getMainWorkflow().getProcessors().isEmpty());
+	}
+
+	@Test
+	public void openRemovesEmptyDataflow() throws Exception {
+		WorkflowBundle newDataflow = fileManager.newDataflow();
+		assertEquals("Unexpected list of open dataflows", Arrays
+				.asList(newDataflow), fileManager.getOpenDataflows());
+		WorkflowBundle dataflow = openDataflow();
+		// Should have removed newDataflow
+		assertEquals("Unexpected list of open dataflows", Arrays
+				.asList(dataflow), fileManager.getOpenDataflows());
+	}
+
+	@Test
+	public void isChanged() throws Exception {
+		WorkflowBundle dataflow = openDataflow();
+		assertFalse("Dataflow should not have changed", fileManager
+				.isDataflowChanged(dataflow));
+
+		// Do a change
+		Processor emptyProcessor = new Processor();
+		Edit<Workflow> addProcessorEdit = new AddProcessorEdit(dataflow.getMainWorkflow(),
+				emptyProcessor);
+		editManager.doDataflowEdit(dataflow, addProcessorEdit);
+		assertTrue("Dataflow should have changed", fileManager
+				.isDataflowChanged(dataflow));
+
+		// Save it with the change
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.deleteOnExit();
+		dataflowFile.delete();
+
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+		assertFalse("Dataflow should no longer be marked as changed",
+				fileManager.isDataflowChanged(dataflow));
+	}
+
+	@Ignore("Undo support for ischanged not yet implemented")
+	@Test
+	public void isChangedWithUndo() throws Exception {
+		WorkflowBundle dataflow = openDataflow();
+		// Do a change
+		Processor emptyProcessor = new Processor();
+		Edit<Workflow> addProcessorEdit = new AddProcessorEdit(dataflow.getMainWorkflow(),
+				emptyProcessor);
+		editManager.doDataflowEdit(dataflow, addProcessorEdit);
+		assertTrue("Dataflow should have changed", fileManager
+				.isDataflowChanged(dataflow));
+		editManager.undoDataflowEdit(dataflow);
+		assertFalse(
+				"Dataflow should no longer be marked as changed after undo",
+				fileManager.isDataflowChanged(dataflow));
+		editManager.redoDataflowEdit(dataflow);
+		assertTrue("Dataflow should have changed after redo before save",
+				fileManager.isDataflowChanged(dataflow));
+
+		// Save it with the change
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.deleteOnExit();
+		dataflowFile.delete();
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+		assertFalse("Dataflow should no longer be marked as changed",
+				fileManager.isDataflowChanged(dataflow));
+
+		editManager.undoDataflowEdit(dataflow);
+		assertTrue("Dataflow should have changed after undo", fileManager
+				.isDataflowChanged(dataflow));
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+		editManager.redoDataflowEdit(dataflow);
+		assertTrue("Dataflow should have changed after redo after save",
+				fileManager.isDataflowChanged(dataflow));
+	}
+
+	@Test
+	public void isListed() throws Exception {
+		assertTrue("Non-empty set of open data flows", fileManager
+				.getOpenDataflows().isEmpty());
+		WorkflowBundle dataflow = openDataflow();
+		assertEquals("Unexpected list of open dataflows", Arrays
+				.asList(dataflow), fileManager.getOpenDataflows());
+	}
+
+	/**
+	 * Always uses a <strong>new</strong> file manager instead of the instance
+	 * one from {@link FileManager#getInstance()}.
+	 *
+	 * @see #getFileManagerInstance()
+	 *
+	 */
+	@Before
+	public void makeFileManager() {
+		System.setProperty("java.awt.headless", "true");
+		editManager = new EditManagerImpl();
+		fileManager = new FileManagerImpl(editManager);
+		fileManagerObserver = new FileManagerObserver();
+		fileManager.addObserver(fileManagerObserver);
+		WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
+		workflowBundleIO.setReaders(Arrays.<WorkflowBundleReader>asList(new RDFXMLReader(), new T2FlowReader()));
+		workflowBundleIO.setWriters(Arrays.<WorkflowBundleWriter>asList(new RDFXMLWriter()));
+		T2DataflowOpener t2DataflowOpener = new T2DataflowOpener();
+		t2DataflowOpener.setWorkflowBundleIO(workflowBundleIO);
+		WorkflowBundleOpener workflowBundleOpener = new WorkflowBundleOpener();
+		workflowBundleOpener.setWorkflowBundleIO(workflowBundleIO);
+		WorkflowBundleSaver workflowBundleSaver = new WorkflowBundleSaver();
+		workflowBundleSaver.setWorkflowBundleIO(workflowBundleIO);
+		DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry = new DataflowPersistenceHandlerRegistry();
+		dataflowPersistenceHandlerRegistry.setDataflowPersistenceHandlers(Arrays.asList(
+				new DataflowPersistenceHandler[] {t2DataflowOpener, workflowBundleOpener, workflowBundleSaver}));
+		dataflowPersistenceHandlerRegistry.updateColletions();
+		fileManager.setDataflowPersistenceHandlerRegistry(dataflowPersistenceHandlerRegistry);
+	}
+
+	@Test
+	public void open() throws Exception {
+		assertTrue("ModelMapObserver already contained messages",
+				fileManagerObserver.messages.isEmpty());
+		WorkflowBundle dataflow = openDataflow();
+		assertNotNull("Dataflow was not loaded", dataflow);
+		assertEquals("Loaded dataflow was not set as current dataflow",
+				dataflow, fileManager.getCurrentDataflow());
+		assertFalse("ModelMapObserver did not contain message",
+				fileManagerObserver.messages.isEmpty());
+		assertEquals("ModelMapObserver contained unexpected messages", 2,
+				fileManagerObserver.messages.size());
+		FileManagerEvent event = fileManagerObserver.messages.get(0);
+		assertTrue(event instanceof SetCurrentDataflowEvent);
+		assertEquals(dataflow, ((SetCurrentDataflowEvent) event).getDataflow());
+	}
+
+	@Test
+	public void openSilently() throws Exception {
+		assertTrue("ModelMapObserver already contained messages",
+				fileManagerObserver.messages.isEmpty());
+		URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+		DataflowInfo info = fileManager.openDataflowSilently(T2_FLOW_FILE_TYPE, url);
+
+		WorkflowBundle dataflow = info.getDataflow();
+		assertNotNull("Dataflow was not loaded", dataflow);
+
+		assertNotSame("Loaded dataflow was set as current dataflow",
+				dataflow, fileManager.getCurrentDataflow());
+		assertTrue("ModelMapObserver contained unexpected messages",
+				fileManagerObserver.messages.isEmpty());
+	}
+
+	@Test
+	public void canSaveDataflow() throws Exception {
+		WorkflowBundle savedDataflow = openDataflow();
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.deleteOnExit();
+		dataflowFile.delete();
+		fileManager.saveDataflow(savedDataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+		assertTrue(fileManager.canSaveWithoutDestination(savedDataflow));
+		fileManager.saveDataflow(savedDataflow, true);
+		fileManager.closeDataflow(savedDataflow, true);
+
+		WorkflowBundle otherFlow = fileManager.openDataflow(WF_BUNDLE_FILE_TYPE, dataflowFile.toURI()
+				.toURL());
+		assertTrue(fileManager.canSaveWithoutDestination(otherFlow));
+	}
+
+	@Test
+	public void save() throws Exception {
+		WorkflowBundle savedDataflow = openDataflow();
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.deleteOnExit();
+		dataflowFile.delete();
+		assertFalse("File should not exist", dataflowFile.isFile());
+		fileManager.saveDataflow(savedDataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+		assertTrue("File should exist", dataflowFile.isFile());
+		WorkflowBundle loadedDataflow = fileManager.openDataflow(WF_BUNDLE_FILE_TYPE, dataflowFile.toURI()
+				.toURL());
+		assertNotSame("Dataflow was not reopened", savedDataflow,
+				loadedDataflow);
+		assertEquals("Unexpected number of processors in saved dataflow", 1,
+				savedDataflow.getMainWorkflow().getProcessors().size());
+		assertEquals("Unexpected number of processors in loaded dataflow", 1,
+				loadedDataflow.getMainWorkflow().getProcessors().size());
+
+		Processor savedProcessor = savedDataflow.getMainWorkflow().getProcessors().first();
+		Processor loadedProcessor = loadedDataflow.getMainWorkflow().getProcessors().first();
+		assertEquals("Loaded processor had wrong name", savedProcessor
+				.getName(), loadedProcessor.getName());
+
+		// TODO convert to scufl2
+//		BeanshellActivity savedActivity = (BeanshellActivity) savedProcessor
+//				.getActivityList().get(0);
+//		BeanshellActivity loadedActivity = (BeanshellActivity) loadedProcessor
+//				.getActivityList().get(0);
+//		String savedScript = savedActivity.getConfiguration().getScript();
+//		String loadedScript = loadedActivity.getConfiguration().getScript();
+//		assertEquals("Unexpected saved script",
+//				"String output = input + \"XXX\";", savedScript);
+//		assertEquals("Loaded script did not matched saved script", savedScript,
+//				loadedScript);
+	}
+
+	@Test
+	public void saveSilent() throws Exception {
+		assertTrue("ModelMapObserver contained unexpected messages",
+				fileManagerObserver.messages.isEmpty());
+
+		URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+		DataflowInfo info = fileManager.openDataflowSilently(T2_FLOW_FILE_TYPE, url);
+		WorkflowBundle dataflow = info.getDataflow();
+		assertTrue("ModelMapObserver contained unexpected messages",
+				fileManagerObserver.messages.isEmpty());
+
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.deleteOnExit();
+		dataflowFile.delete();
+		assertFalse("File should not exist", dataflowFile.isFile());
+
+		fileManager.saveDataflowSilently(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+		assertTrue("File should exist", dataflowFile.isFile());
+
+		assertTrue("ModelMapObserver contained unexpected messages",
+				fileManagerObserver.messages.isEmpty());
+
+	}
+
+	@Test
+	public void saveOverwriteAgain() throws Exception {
+		WorkflowBundle dataflow = openDataflow();
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.delete();
+		dataflowFile.deleteOnExit();
+		// File did NOT exist, should not fail
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+
+		Processor processor = dataflow.getMainWorkflow().getProcessors().first();
+		Edit<Processor> renameEdit = new RenameEdit<Processor>(processor,
+				processor.getName() + "-changed");
+		editManager.doDataflowEdit(dataflow, renameEdit);
+
+		// Last save was OURs, so should *not* fail - even if we now use
+		// the specific saveDataflow() method
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+
+		//Thread.sleep(1500);
+		WorkflowBundle otherFlow = openDataflow();
+		// Saving another flow to same file should still fail
+		try {
+			fileManager.saveDataflow(otherFlow,WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+			fail("Should have thrown OverwriteException");
+		} catch (OverwriteException ex) {
+			// Expected
+		}
+	}
+
+	@Test(expected = OverwriteException.class)
+	public void saveOverwriteWarningFails() throws Exception {
+		WorkflowBundle dataflow = openDataflow();
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.deleteOnExit();
+		// Should fail as file already exists
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+	}
+
+	@Test
+	public void saveOverwriteWarningWorks() throws Exception {
+		WorkflowBundle dataflow = openDataflow();
+		File dataflowFile = File.createTempFile("test", ".t2flow");
+		dataflowFile.delete();
+		dataflowFile.deleteOnExit();
+		// File did NOT exist, should not fail
+		fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+	}
+
+	@After
+	public void stopListeningToModelMap() {
+		fileManager.removeObserver(fileManagerObserver);
+	}
+
+	protected WorkflowBundle openDataflow() throws OpenException {
+		URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+		assertNotNull(url);
+		WorkflowBundle dataflow = fileManager.openDataflow(T2_FLOW_FILE_TYPE, url);
+		assertNotNull(dataflow);
+		return dataflow;
+	}
+
+	private final class FileManagerObserver implements Observer<FileManagerEvent> {
+		protected List<FileManagerEvent> messages = new ArrayList<FileManagerEvent>();
+
+		@Override
+		public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message) throws Exception {
+			messages.add(message);
+			if (message instanceof SetCurrentDataflowEvent) {
+				assertTrue("Dataflow was not listed as open when set current",
+						fileManager.getOpenDataflows().contains(
+								((SetCurrentDataflowEvent) message).getDataflow()));
+			}
+		}
+	}
+
+}
diff --git a/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow b/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow
new file mode 100644
index 0000000..b9a1075
--- /dev/null
+++ b/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow
@@ -0,0 +1,157 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow" version="1" producedBy="test">
+	<dataflow id="ec0991ba-275c-49ed-b1d6-38534180fb7c" role="top">
+		<name>simple_workflow_with_input</name>
+		<inputPorts>
+			<port>
+				<name>input</name>
+				<depth>0</depth>
+				<granularDepth>0</granularDepth>
+			</port>
+		</inputPorts>
+		<outputPorts>
+			<port>
+				<name>output</name>
+			</port>
+		</outputPorts>
+		<processors>
+			<processor>
+				<name>Concat_XXX</name>
+				<inputPorts>
+					<port>
+						<name>input</name>
+						<depth>0</depth>
+					</port>
+				</inputPorts>
+				<outputPorts>
+					<port>
+						<name>output</name>
+						<depth>0</depth>
+						<granularDepth>0</granularDepth>
+					</port>
+				</outputPorts>
+				<annotations />
+				<activities>
+					<activity>
+						<class>
+							net.sf.taverna.t2.activities.beanshell.BeanshellActivity
+						</class>
+						<inputMap>
+							<map from="input" to="input" />
+						</inputMap>
+						<outputMap>
+							<map from="output" to="output" />
+						</outputMap>
+						<configBean encoding="xstream">
+							<net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean
+								xmlns="">
+								<script>String output = input + "XXX";</script>
+								<dependencies />
+								<inputs>
+									<net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+										<handledReferenceSchemes />
+										<translatedElementType>java.lang.String</translatedElementType>
+										<allowsLiteralValues>true</allowsLiteralValues>
+										<name>input</name>
+										<depth>0</depth>
+										<mimeTypes>
+											<string>'text/plain'</string>
+										</mimeTypes>
+									</net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+								</inputs>
+								<outputs>
+									<net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+										<granularDepth>0</granularDepth>
+										<name>output</name>
+										<depth>0</depth>
+										<mimeTypes>
+											<string>'text/plain'</string>
+										</mimeTypes>
+									</net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+								</outputs>
+							</net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean>
+						</configBean>
+					</activity>
+				</activities>
+				<dispatchStack>
+					<dispatchLayer>
+						<class>
+							net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize
+						</class>
+						<configBean encoding="xstream">
+							<net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig
+								xmlns="">
+								<maxJobs>1</maxJobs>
+							</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig>
+						</configBean>
+					</dispatchLayer>
+					<dispatchLayer>
+						<class>
+							net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce
+						</class>
+						<configBean encoding="xstream">
+							<null xmlns="" />
+						</configBean>
+					</dispatchLayer>
+					<dispatchLayer>
+						<class>
+							net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover
+						</class>
+						<configBean encoding="xstream">
+							<null xmlns="" />
+						</configBean>
+					</dispatchLayer>
+					<dispatchLayer>
+						<class>
+							net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry
+						</class>
+						<configBean encoding="xstream">
+							<net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig
+								xmlns="">
+								<backoffFactor>1.0</backoffFactor>
+								<initialDelay>0</initialDelay>
+								<maxDelay>0</maxDelay>
+								<maxRetries>0</maxRetries>
+							</net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig>
+						</configBean>
+					</dispatchLayer>
+					<dispatchLayer>
+						<class>
+							net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke
+						</class>
+						<configBean encoding="xstream">
+							<null xmlns="" />
+						</configBean>
+					</dispatchLayer>
+				</dispatchStack>
+				<iterationStrategyStack>
+					<iteration>
+						<strategy>
+							<port name="input" depth="0" />
+						</strategy>
+					</iteration>
+				</iterationStrategyStack>
+			</processor>
+		</processors>
+		<conditions />
+		<datalinks>
+			<datalink>
+				<sink type="processor">
+					<processor>Concat_XXX</processor>
+					<port>input</port>
+				</sink>
+				<source type="dataflow">
+					<port>input</port>
+				</source>
+			</datalink>
+			<datalink>
+				<sink type="dataflow">
+					<port>output</port>
+				</sink>
+				<source type="processor">
+					<processor>Concat_XXX</processor>
+					<port>output</port>
+				</source>
+			</datalink>
+		</datalinks>
+	</dataflow>
+</workflow>
diff --git a/taverna-workbench-helper/pom.xml b/taverna-workbench-helper/pom.xml
new file mode 100644
index 0000000..70c0621
--- /dev/null
+++ b/taverna-workbench-helper/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>helper</artifactId>
+	<name>Help System (legacy dependency)</name>
+	<dependencies>
+		<dependency>
+            <groupId>net.sf.taverna.t2.ui-api</groupId>
+            <artifactId>helper-api</artifactId>
+            <version>${t2.ui.api.version}</version>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-httpproxy-config/pom.xml b/taverna-workbench-httpproxy-config/pom.xml
new file mode 100644
index 0000000..f1a8328
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>httpproxy-config</artifactId>
+	<packaging>bundle</packaging>
+	<name>HTTP Proxy configuration</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>ui</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+ 	</dependencies>
+</project>
diff --git a/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java
new file mode 100644
index 0000000..1229d57
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java
@@ -0,0 +1,582 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.httpproxy.config;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.PROXY_USE_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_NON_PROXY_HOSTS;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_HOST;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_PASSWORD;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_PORT;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_USER;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_NON_PROXY_HOSTS;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_HOST;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_PASSWORD;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_PORT;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_USER;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_NO_PROXY_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_SPECIFIED_VALUES_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_SYSTEM_PROPERTIES_OPTION;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import uk.org.taverna.configuration.proxy.HttpProxyConfiguration;
+
+/**
+ * The HttpProxyConfigurationPanel provides the user interface to a
+ * {@link HttpProxyConfiguration} to determine how HTTP Connections are made by
+ * Taverna.
+ * 
+ * @author alanrw
+ * @author David Withers
+ */
+public class HttpProxyConfigurationPanel extends JPanel {
+	static final long serialVersionUID = 3668473431971125038L;
+	/**
+	 * The size of the field for the JTextFields.
+	 */
+	private static int TEXTFIELD_SIZE = 25;
+
+	private final HttpProxyConfiguration httpProxyConfiguration;
+	/**
+	 * RadioButtons that are in a common ButtonGroup. Selecting one of them
+	 * indicates whether the system http proxy settings, the ad hoc specified
+	 * values or no proxy settings at all should be used.
+	 */
+	private JRadioButton useSystemProperties;
+	private JRadioButton useSpecifiedValues;
+	private JRadioButton useNoProxy;
+	/**
+	 * JTextFields and one DialogTextArea to hold the settings for the HTTP
+	 * proxy properties. The values are only editable if the user picks
+	 * useSpecifiedValues.
+	 */
+	private JTextField proxyHostField;
+	private JTextField proxyPortField;
+	private JTextField proxyUserField;
+	private JTextField proxyPasswordField;
+	private DialogTextArea nonProxyHostsArea;
+	private JScrollPane nonProxyScrollPane;
+	/**
+	 * A string that indicates which HTTP setting option the user has currently
+	 * picked. This does not necesarily match that which has been applied.
+	 */
+	private String shownOption = USE_SYSTEM_PROPERTIES_OPTION;
+
+	/**
+	 * The HttpProxyConfigurationPanel consists of a set of properties where the
+	 * configuration values for HTTP can be specified and a set of buttons where
+	 * the more general apply, help etc. appear.
+	 */
+	public HttpProxyConfigurationPanel(
+			HttpProxyConfiguration httpProxyConfiguration) {
+		this.httpProxyConfiguration = httpProxyConfiguration;
+		initComponents();
+	}
+
+	/**
+	 * Populates the panel with a representation of the current HTTP proxy
+	 * settings for the specified {@link HttpProxyConfiguration} and also the
+	 * capability to alter them.
+	 */
+	private void initComponents() {
+		shownOption = httpProxyConfiguration.getProperty(PROXY_USE_OPTION);
+
+		this.setLayout(new GridBagLayout());
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		// Title describing what kind of settings we are configuring here
+		JTextArea descriptionText = new JTextArea("HTTP proxy configuration");
+		descriptionText.setLineWrap(true);
+		descriptionText.setWrapStyleWord(true);
+		descriptionText.setEditable(false);
+		descriptionText.setFocusable(false);
+		descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+		gbc.anchor = WEST;
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.gridwidth = 2;
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.fill = HORIZONTAL;
+		this.add(descriptionText, gbc);
+
+		/**
+		 * Generate the three radio buttons and put them in a group. Each button
+		 * is bound to an action that alters the shownOption and re-populates
+		 * the shown HTTP property fields.
+		 */
+		useNoProxy = new JRadioButton("Do not use a proxy");
+		useNoProxy.setAlignmentX(LEFT_ALIGNMENT);
+		gbc.gridx = 0;
+		gbc.gridy = 1;
+		gbc.gridwidth = 2;
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.fill = NONE;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		this.add(useNoProxy, gbc);
+		ActionListener useNoProxyListener = new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				shownOption = USE_NO_PROXY_OPTION;
+				populateFields();
+			}
+		};
+		useNoProxy.addActionListener(useNoProxyListener);
+
+		useSystemProperties = new JRadioButton("Use system properties");
+		useSystemProperties.setAlignmentX(LEFT_ALIGNMENT);
+		gbc.gridx = 0;
+		gbc.gridy = 2;
+		gbc.insets = new Insets(0, 0, 0, 0);
+		this.add(useSystemProperties, gbc);
+		ActionListener systemPropertiesListener = new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				shownOption = USE_SYSTEM_PROPERTIES_OPTION;
+				populateFields();
+			}
+		};
+		useSystemProperties.addActionListener(systemPropertiesListener);
+
+		useSpecifiedValues = new JRadioButton("Use specified values");
+		useSpecifiedValues.setAlignmentX(LEFT_ALIGNMENT);
+		gbc.gridx = 0;
+		gbc.gridy = 3;
+		this.add(useSpecifiedValues, gbc);
+		ActionListener specifiedValuesListener = new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				shownOption = USE_SPECIFIED_VALUES_OPTION;
+				populateFields();
+			}
+		};
+		useSpecifiedValues.addActionListener(specifiedValuesListener);
+
+		ButtonGroup bg = new ButtonGroup();
+		bg.add(useSystemProperties);
+		bg.add(useSpecifiedValues);
+		bg.add(useNoProxy);
+
+		/**
+		 * Create the fields to show the HTTP proxy property values. These
+		 * become editable if the shown option is to use specified values.
+		 */
+		proxyHostField = new JTextField(TEXTFIELD_SIZE);
+		gbc.gridx = 0;
+		gbc.gridy = 4;
+		gbc.gridwidth = 1;
+		gbc.fill = NONE;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		this.add(new JLabel("Proxy host"), gbc);
+		gbc.gridx = 1;
+		gbc.gridy = 4;
+		gbc.gridwidth = 1;
+		gbc.fill = HORIZONTAL;
+		this.add(proxyHostField, gbc);
+
+		proxyPortField = new JTextField(TEXTFIELD_SIZE);
+		gbc.gridx = 0;
+		gbc.gridy = 5;
+		gbc.gridwidth = 1;
+		gbc.fill = NONE;
+		gbc.insets = new Insets(0, 0, 0, 0);
+		this.add(new JLabel("Proxy port"), gbc);
+		gbc.gridx = 1;
+		gbc.gridy = 5;
+		gbc.gridwidth = 1;
+		gbc.fill = HORIZONTAL;
+		this.add(proxyPortField, gbc);
+
+		proxyUserField = new JTextField(TEXTFIELD_SIZE);
+		gbc.gridx = 0;
+		gbc.gridy = 6;
+		gbc.gridwidth = 1;
+		gbc.fill = NONE;
+		this.add(new JLabel("Proxy user"), gbc);
+		gbc.gridx = 1;
+		gbc.gridy = 6;
+		gbc.gridwidth = 1;
+		gbc.fill = HORIZONTAL;
+		this.add(proxyUserField, gbc);
+
+		proxyPasswordField = new JTextField(TEXTFIELD_SIZE);
+		gbc.gridx = 0;
+		gbc.gridy = 7;
+		gbc.gridwidth = 1;
+		gbc.fill = NONE;
+		this.add(new JLabel("Proxy password"), gbc);
+		gbc.gridx = 1;
+		gbc.gridy = 7;
+		gbc.gridwidth = 1;
+		gbc.fill = HORIZONTAL;
+		this.add(proxyPasswordField, gbc);
+
+		nonProxyHostsArea = new DialogTextArea(10, 40);
+		nonProxyScrollPane = new JScrollPane(nonProxyHostsArea);
+		nonProxyScrollPane
+				.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+		nonProxyScrollPane
+				.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+		// nonProxyScrollPane.setPreferredSize(new Dimension(300, 500));
+		gbc.gridx = 0;
+		gbc.gridy = 8;
+		gbc.gridwidth = 2;
+		gbc.fill = NONE;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		this.add(new JLabel("Non-proxy hosts"), gbc);
+		gbc.gridx = 0;
+		gbc.gridy = 9;
+		gbc.weightx = 1.0;
+		gbc.weighty = 1.0;
+		gbc.gridwidth = 2;
+		gbc.insets = new Insets(0, 0, 0, 0);
+		gbc.fill = BOTH;
+		this.add(nonProxyScrollPane, gbc);
+
+		// Add buttons panel
+		gbc.gridx = 0;
+		gbc.gridy = 10;
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridwidth = 2;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = CENTER;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		this.add(createButtonPanel(), gbc);
+
+		setFields();
+	}
+
+	/**
+	 * Populate the fields in the property panel according to which option is
+	 * being shown and the stored values within the
+	 * {@link HttpProxyConfiguration}.
+	 */
+	private void populateFields() {
+		/**
+		 * Editing of the property fields is only available when the option is
+		 * to use the specified values.
+		 */
+		boolean editingEnabled = shownOption
+				.equals(USE_SPECIFIED_VALUES_OPTION);
+
+		if (shownOption.equals(USE_SYSTEM_PROPERTIES_OPTION)) {
+			proxyHostField.setText(httpProxyConfiguration
+					.getProperty(SYSTEM_PROXY_HOST));
+			proxyPortField.setText(httpProxyConfiguration
+					.getProperty(SYSTEM_PROXY_PORT));
+			proxyUserField.setText(httpProxyConfiguration
+					.getProperty(SYSTEM_PROXY_USER));
+			proxyPasswordField.setText(httpProxyConfiguration
+					.getProperty(SYSTEM_PROXY_PASSWORD));
+			nonProxyHostsArea.setText(httpProxyConfiguration
+					.getProperty(SYSTEM_NON_PROXY_HOSTS));
+		} else if (shownOption.equals(USE_SPECIFIED_VALUES_OPTION)) {
+			proxyHostField.setText(httpProxyConfiguration
+					.getProperty(TAVERNA_PROXY_HOST));
+			proxyPortField.setText(httpProxyConfiguration
+					.getProperty(TAVERNA_PROXY_PORT));
+			proxyUserField.setText(httpProxyConfiguration
+					.getProperty(TAVERNA_PROXY_USER));
+			proxyPasswordField.setText(httpProxyConfiguration
+					.getProperty(TAVERNA_PROXY_PASSWORD));
+			nonProxyHostsArea.setText(httpProxyConfiguration
+					.getProperty(TAVERNA_NON_PROXY_HOSTS));
+		} else {
+			proxyHostField.setText(null);
+			proxyPortField.setText(null);
+			proxyUserField.setText(null);
+			proxyPasswordField.setText(null);
+			nonProxyHostsArea.setText(null);
+		}
+
+		proxyHostField.setEnabled(editingEnabled);
+		proxyPortField.setEnabled(editingEnabled);
+		proxyUserField.setEnabled(editingEnabled);
+		proxyPasswordField.setEnabled(editingEnabled);
+		nonProxyHostsArea.setEnabled(editingEnabled);
+		nonProxyHostsArea.setEditable(editingEnabled);
+		nonProxyScrollPane.setEnabled(editingEnabled);
+	}
+
+	/**
+	 * Create the panel to contain the buttons
+	 * 
+	 * @return
+	 */
+	@SuppressWarnings("serial")
+	private JPanel createButtonPanel() {
+		final JPanel panel = new JPanel();
+
+		/**
+		 * The helpButton shows help about the current component
+		 */
+		JButton helpButton = new JButton(new AbstractAction("Help") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				showHelp(panel);
+			}
+		});
+		panel.add(helpButton);
+
+		/**
+		 * The resetButton changes the property values shown to those
+		 * corresponding to the configuration currently applied.
+		 */
+		JButton resetButton = new JButton(new AbstractAction("Reset") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				setFields();
+			}
+		});
+		panel.add(resetButton);
+
+		/**
+		 * The applyButton applies the shown field values to the
+		 * {@link HttpProxyConfiguration} and saves them for future.
+		 */
+		JButton applyButton = new JButton(new AbstractAction("Apply") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				applySettings();
+				setFields();
+			}
+		});
+		panel.add(applyButton);
+
+		return panel;
+	}
+
+	/**
+	 * Checks that the specified values for the HTTP properties are a valid
+	 * combination and, if so, saves them for future use. It does not apply them
+	 * to the currently executing Taverna.
+	 */
+	private void saveSettings() {
+		if (useSystemProperties.isSelected()) {
+			httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+					USE_SYSTEM_PROPERTIES_OPTION);
+		} else if (useNoProxy.isSelected()) {
+			httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+					USE_NO_PROXY_OPTION);
+		} else {
+			if (validateFields()) {
+				httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+						USE_SPECIFIED_VALUES_OPTION);
+				httpProxyConfiguration.setProperty(TAVERNA_PROXY_HOST,
+						proxyHostField.getText());
+				httpProxyConfiguration.setProperty(TAVERNA_PROXY_PORT,
+						proxyPortField.getText());
+				httpProxyConfiguration.setProperty(TAVERNA_PROXY_USER,
+						proxyUserField.getText());
+				httpProxyConfiguration.setProperty(TAVERNA_PROXY_PASSWORD,
+						proxyPasswordField.getText());
+				httpProxyConfiguration.setProperty(TAVERNA_NON_PROXY_HOSTS,
+						nonProxyHostsArea.getText());
+			}
+		}
+	}
+
+	/**
+	 * Validates and, where appropriate formats, the properties values specified
+	 * for HTTP Proxy configuration.
+	 * 
+	 * @return
+	 */
+	private boolean validateFields() {
+		boolean result = true;
+		result = result && validateHostField();
+		result = result && validatePortField();
+		result = result && validateUserField();
+		result = result && validatePasswordField();
+		result = result && validateNonProxyHostsArea();
+		return result;
+	}
+
+	/**
+	 * Checks that, if a value is specified for non-proxy hosts then a proxy
+	 * host has also been specified. Formats the non-proxy hosts string so that
+	 * if the user has entered the hosts on separate lines, then the stored
+	 * values are separated by bars.
+	 * 
+	 * @return
+	 */
+	private boolean validateNonProxyHostsArea() {
+		boolean result = true;
+		String value = nonProxyHostsArea.getText();
+		if ((value != null) && (!value.equals(""))) {
+			value = value.replaceAll("\\n", "|");
+			nonProxyHostsArea.setText(value);
+			result = result
+					&& dependsUpon("non-proxy host", "host",
+							proxyHostField.getText());
+		}
+		return result;
+	}
+
+	/**
+	 * Checks that, if a password has been specified, then a user has also been
+	 * specified.
+	 * 
+	 * @return
+	 */
+	private boolean validatePasswordField() {
+		boolean result = true;
+		String value = proxyPasswordField.getText();
+		if ((value != null) && !value.isEmpty())
+			result = result
+					&& dependsUpon("password", "user", proxyHostField.getText());
+		return result;
+	}
+
+	/**
+	 * Checks that if a user has been specified, then a host has also been
+	 * specified.
+	 * 
+	 * @return
+	 */
+	private boolean validateUserField() {
+		boolean result = true;
+		String value = proxyUserField.getText();
+		if ((value != null) && !value.isEmpty())
+			result = result
+					&& dependsUpon("user", "host", proxyHostField.getText());
+		return result;
+	}
+
+	/**
+	 * Checks that if a port has been specified then a host has also been
+	 * specified. Checks that the port number is a non-negative integer. If the
+	 * port has not been specified, then if a host has been specified, the
+	 * default value 80 is used.
+	 * 
+	 * @return
+	 */
+	private boolean validatePortField() {
+		boolean result = true;
+		String value = proxyPortField.getText();
+		if ((value != null) && (!value.equals(""))) {
+			result = result
+					&& dependsUpon("port", "host", proxyHostField.getText());
+			try {
+				int parsedNumber = Integer.parseInt(value);
+				if (parsedNumber <= 0) {
+					showMessageDialog(this, "The port must be non-negative");
+					result = false;
+				}
+			} catch (NumberFormatException e) {
+				showMessageDialog(this, "The port must be an integer");
+				result = false;
+			}
+		} else {
+			String hostField = proxyHostField.getText();
+			if ((hostField != null) && !hostField.isEmpty())
+				proxyPortField.setText("80");
+		}
+		return result;
+	}
+
+	/**
+	 * Checks if the targetValue has been specified. If not then a message is
+	 * displayed indicating that the dependent cannot be specified with the
+	 * target.
+	 * 
+	 * @param dependent
+	 * @param target
+	 * @param targetValue
+	 * @return
+	 */
+	private boolean dependsUpon(String dependent, String target,
+			String targetValue) {
+		boolean result = true;
+		if ((targetValue == null) || target.equals("")) {
+			showMessageDialog(this, "A " + dependent
+					+ " cannot be specified without a " + target);
+			result = false;
+		}
+		return result;
+	}
+
+	/**
+	 * Could validate the host field e.g. by establishing a connection.
+	 * Currently no validation is done.
+	 * 
+	 * @return
+	 */
+	private boolean validateHostField() {
+		boolean result = true;
+		// String value = proxyHostField.getText();
+		return result;
+	}
+
+	/**
+	 * Save the currently set field values (if valid) to the
+	 * {@link HttpProxyConfiguration}. Also applies those values to the
+	 * currently running Taverna.
+	 */
+	private void applySettings() {
+		if (validateFields()) {
+			saveSettings();
+			httpProxyConfiguration.changeProxySettings();
+		}
+	}
+
+	/**
+	 * Set the shown field values to those currently in use (i.e. last saved
+	 * configuration).
+	 */
+	private void setFields() {
+		shownOption = httpProxyConfiguration.getProperty(PROXY_USE_OPTION);
+		useSystemProperties.setSelected(shownOption
+				.equals(USE_SYSTEM_PROPERTIES_OPTION));
+		useSpecifiedValues.setSelected(shownOption
+				.equals(USE_SPECIFIED_VALUES_OPTION));
+		useNoProxy.setSelected(shownOption.equals(USE_NO_PROXY_OPTION));
+		populateFields();
+	}
+}
diff --git a/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java
new file mode 100644
index 0000000..9f6ac8c
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.httpproxy.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+import uk.org.taverna.configuration.proxy.HttpProxyConfiguration;
+
+/**
+ * A Factory to create a HttpProxyConfiguration
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public class HttpProxyConfigurationUIFactory implements ConfigurationUIFactory {
+	private HttpProxyConfiguration httpProxyConfiguration;
+
+	@Override
+	public boolean canHandle(String uuid) {
+		return uuid.equals(getConfigurable().getUUID());
+	}
+
+	@Override
+	public JPanel getConfigurationPanel() {
+		return new HttpProxyConfigurationPanel(httpProxyConfiguration);
+	}
+
+	@Override
+	public Configurable getConfigurable() {
+		return httpProxyConfiguration;
+	}
+
+	public void setHttpProxyConfiguration(HttpProxyConfiguration httpProxyConfiguration) {
+		this.httpProxyConfiguration = httpProxyConfiguration;
+	}
+}
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..d87772b
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.httpproxy.config.HttpProxyConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml
new file mode 100644
index 0000000..631bdb4
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="HttpProxyConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+	<reference id="httpProxyConfiguration" interface="uk.org.taverna.configuration.proxy.HttpProxyConfiguration" />
+
+</beans:beans>
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml
new file mode 100644
index 0000000..6d6060f
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="HttpProxyConfigurationUIFactory" class="net.sf.taverna.t2.workbench.httpproxy.config.HttpProxyConfigurationUIFactory">
+		<property name="httpProxyConfiguration" ref="httpProxyConfiguration" />
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-menu-impl/pom.xml b/taverna-workbench-menu-impl/pom.xml
new file mode 100644
index 0000000..d95bf49
--- /dev/null
+++ b/taverna-workbench-menu-impl/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>menu-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Menu generation implementation</name>
+	<description>The main workbench ui</description>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<configuration>
+					<instructions>
+						<Embed-Dependency>javahelp</Embed-Dependency>
+						<Import-Package>org.jdesktop.jdic.browser;resolution:=optional,*</Import-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>workbench-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>ui</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>javax.help</groupId>
+			<artifactId>javahelp</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java
new file mode 100644
index 0000000..ee1fd3d
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java
@@ -0,0 +1,880 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.ui.menu.impl;
+
+import static java.lang.Math.min;
+import static javax.help.CSH.setHelpIDString;
+import static javax.swing.Action.NAME;
+import static javax.swing.Action.SHORT_DESCRIPTION;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.ui.menu.AbstractMenuSection.SECTION_COLOR;
+import static net.sf.taverna.t2.ui.menu.DefaultContextualMenu.DEFAULT_CONTEXT_MENU;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.WeakHashMap;
+
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.AbstractMenuOptionGroup;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.ui.menu.DesignOrResultsAction;
+import net.sf.taverna.t2.ui.menu.MenuComponent;
+import net.sf.taverna.t2.ui.menu.MenuComponent.MenuType;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementation of {@link MenuManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class MenuManagerImpl implements MenuManager {
+	private static Logger logger = Logger.getLogger(MenuManagerImpl.class);
+
+	private boolean needsUpdate;
+	/**
+	 * Cache used by {@link #getURIByComponent(Component)}
+	 */
+	private WeakHashMap<Component, URI> componentToUri;
+	/**
+	 * {@link MenuElementComparator} used for sorting menu components from the
+	 * SPI registry.
+	 */
+	private MenuElementComparator menuElementComparator = new MenuElementComparator();
+	/**
+	 * Map of {@link URI} to it's discovered children. Populated by
+	 * {@link #findChildren()}.
+	 */
+	private HashMap<URI, List<MenuComponent>> menuElementTree;
+	/**
+	 * Multicaster to distribute messages to {@link Observer}s of this menu
+	 * manager.
+	 */
+	private MultiCaster<MenuManagerEvent> multiCaster;
+	/**
+	 * Lock for {@link #update()}
+	 */
+	private final Object updateLock = new Object();
+	/**
+	 * True if {@link #doUpdate()} is running, subsequents call to
+	 * {@link #update()} will return immediately.
+	 */
+	private boolean updating;
+	/**
+	 * Cache used by {@link #getComponentByURI(URI)}
+	 */
+	private Map<URI, WeakReference<Component>> uriToComponent;
+	/**
+	 * Map from {@link URI} to defining {@link MenuComponent}. Children are in
+	 * {@link #menuElementTree}.
+	 */
+	private Map<URI, MenuComponent> uriToMenuElement;
+	// Note: Not reset by #resetCollections()
+	private Map<URI, List<WeakReference<Component>>> uriToPublishedComponents = new HashMap<>();
+	private List<MenuComponent> menuComponents = new ArrayList<>();
+
+	/**
+	 * Construct the MenuManagerImpl. Observes the SPI registry and does an
+	 * initial {@link #update()}.
+	 */
+	public MenuManagerImpl() {
+		multiCaster = new MultiCaster<>(this);
+		needsUpdate = true;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void addMenuItemsWithExpansion(List<JMenuItem> menuItems,
+			JMenu parentMenu, int maxItemsInMenu,
+			ComponentFactory headerItemFactory) {
+		if (menuItems.size() <= maxItemsInMenu) {
+			// Just add them directly
+			for (JMenuItem menuItem : menuItems)
+				parentMenu.add(menuItem);
+			return;
+		}
+		int index = 0;
+		while (index < menuItems.size()) {
+			int toIndex = min(menuItems.size(), index + maxItemsInMenu);
+			if (toIndex == menuItems.size() - 1)
+				// Don't leave a single item left for the last subMenu
+				toIndex--;
+			List<JMenuItem> subList = menuItems.subList(index, toIndex);
+			JMenuItem firstItem = subList.get(0);
+			JMenuItem lastItem = subList.get(subList.size() - 1);
+			JMenu subMenu = new JMenu(firstItem.getText() + " ... "
+					+ lastItem.getText());
+			if (headerItemFactory != null)
+				subMenu.add(headerItemFactory.makeComponent());
+			for (JMenuItem menuItem : subList)
+				subMenu.add(menuItem);
+			parentMenu.add(subMenu);
+			index = toIndex;
+		}
+	}
+
+	@Override
+	public void addObserver(Observer<MenuManagerEvent> observer) {
+		multiCaster.addObserver(observer);
+	}
+
+	@Override
+	public JPopupMenu createContextMenu(Object parent, Object selection,
+			Component relativeToComponent) {
+		ContextualSelection contextualSelection = new ContextualSelection(
+				parent, selection, relativeToComponent);
+		JPopupMenu popupMenu = new JPopupMenu();
+		populateContextMenu(popupMenu, DEFAULT_CONTEXT_MENU,
+				contextualSelection);
+		registerComponent(DEFAULT_CONTEXT_MENU, popupMenu, true);
+		return popupMenu;
+	}
+
+	@Override
+	public JMenuBar createMenuBar() {
+		return createMenuBar(DEFAULT_MENU_BAR);
+	}
+
+	@Override
+	public JMenuBar createMenuBar(URI id) {
+		JMenuBar menuBar = new JMenuBar();
+		if (needsUpdate)
+			update();
+		populateMenuBar(menuBar, id);
+		registerComponent(id, menuBar, true);
+		return menuBar;
+	}
+
+	@Override
+	public JToolBar createToolBar() {
+		return createToolBar(DEFAULT_TOOL_BAR);
+	}
+
+	@Override
+	public JToolBar createToolBar(URI id) {
+		JToolBar toolbar = new JToolBar();
+		if (needsUpdate)
+			update();
+		populateToolBar(toolbar, id);
+		registerComponent(id, toolbar, true);
+		return toolbar;
+	}
+
+	@Override
+	public synchronized Component getComponentByURI(URI id) {
+		WeakReference<Component> componentRef = uriToComponent.get(id);
+		if (componentRef == null)
+			return null;
+		// Might also be null it reference has gone dead
+		return componentRef.get();
+	}
+
+	@Override
+	public List<Observer<MenuManagerEvent>> getObservers() {
+		return multiCaster.getObservers();
+	}
+
+	@Override
+	public synchronized URI getURIByComponent(Component component) {
+		return componentToUri.get(component);
+	}
+
+	@Override
+	public void removeObserver(Observer<MenuManagerEvent> observer) {
+		multiCaster.removeObserver(observer);
+	}
+
+	@Override
+	public void update() {
+		synchronized (updateLock) {
+			if (updating && !needsUpdate)
+				return;
+			updating = true;
+		}
+		try {
+			doUpdate();
+		} finally {
+			synchronized (updateLock) {
+				updating = false;
+				needsUpdate = false;
+			}
+		}
+	}
+
+	public void update(Object service, Map<?, ?> properties) {
+		needsUpdate = true;
+		update();
+	}
+
+	/**
+	 * Add a {@link JMenu} to the list of components as described by the menu
+	 * component. If there are no children, the menu is not added.
+	 *
+	 * @param components
+	 *            List of components where to add the created {@link JMenu}
+	 * @param menuComponent
+	 *            The {@link MenuComponent} definition for this menu
+	 * @param isToolbar
+	 *            True if the list of components is to be added to a toolbar
+	 */
+	private void addMenu(List<Component> components,
+			MenuComponent menuComponent, MenuOptions menuOptions) {
+		URI menuId = menuComponent.getId();
+		if (menuOptions.isToolbar()) {
+			logger.warn("Can't have menu " + menuComponent
+					+ " within toolBar element");
+			return;
+		}
+		MenuOptions childOptions = new MenuOptions(menuOptions);
+		List<Component> subComponents = makeComponents(menuId, childOptions);
+		if (subComponents.isEmpty()) {
+			logger.warn("No sub components found for menu " + menuId);
+			return;
+		}
+
+		JMenu menu = new JMenu(menuComponent.getAction());
+		for (Component menuItem : subComponents)
+			if (menuItem == null)
+				menu.addSeparator();
+			else
+				menu.add(menuItem);
+		registerComponent(menuId, menu);
+		components.add(menu);
+	}
+
+	/**
+	 * Add <code>null</code> to the list of components, meaning that a separator
+	 * is to be created. Subsequent separators are ignored, and if there are no
+	 * components on the list already no separator will be added.
+	 * 
+	 * @param components
+	 *            List of components
+	 */
+	private void addNullSeparator(List<Component> components) {
+		if (components.isEmpty())
+			// Don't start with a separator
+			return;
+		if (components.get(components.size() - 1) == null)
+			// Already a separator in last position
+			return;
+		components.add(null);
+	}
+
+	/**
+	 * Add an {@link AbstractMenuOptionGroup option group} to the list of
+	 * components
+	 *
+	 * @param components
+	 *            List of components where to add the created {@link JMenu}
+	 * @param optionGroupId
+	 *            The {@link URI} identifying the option group
+	 * @param isToolbar
+	 *            True if the option group is to be added to a toolbar
+	 */
+	private void addOptionGroup(List<Component> components, URI optionGroupId,
+			MenuOptions menuOptions) {
+		MenuOptions childOptions = new MenuOptions(menuOptions);
+		childOptions.setOptionGroup(true);
+
+		List<Component> buttons = makeComponents(optionGroupId, childOptions);
+		addNullSeparator(components);
+		if (buttons.isEmpty()) {
+			logger.warn("No sub components found for option group "
+					+ optionGroupId);
+			return;
+		}
+		ButtonGroup buttonGroup = new ButtonGroup();
+
+		for (Component button : buttons) {
+			if (button instanceof AbstractButton)
+				buttonGroup.add((AbstractButton) button);
+			else
+				logger.warn("Component of button group " + optionGroupId
+						+ " is not an AbstractButton: " + button);
+			if (button == null) {
+				logger.warn("Separator found within button group");
+				addNullSeparator(components);
+			} else
+				components.add(button);
+		}
+		addNullSeparator(components);
+	}
+
+	/**
+	 * Add a section to a list of components.
+	 *
+	 * @param components
+	 *            List of components
+	 * @param sectionId
+	 *            The {@link URI} identifying the section
+	 * @param menuOptions
+	 *            {@link MenuOptions options} for creating the menu
+	 */
+	private void addSection(List<Component> components, URI sectionId,
+			MenuOptions menuOptions) {
+		List<Component> childComponents = makeComponents(sectionId, menuOptions);
+
+		MenuComponent sectionDef = uriToMenuElement.get(sectionId);
+		addNullSeparator(components);
+		if (childComponents.isEmpty()) {
+			logger.warn("No sub components found for section " + sectionId);
+			return;
+		}
+		Action sectionAction = sectionDef.getAction();
+		if (sectionAction != null) {
+			String sectionLabel = (String) sectionAction.getValue(NAME);
+			if (sectionLabel != null) {
+				// No separators before the label
+				stripTrailingNullSeparator(components);
+				Color labelColor = (Color) sectionAction.getValue(SECTION_COLOR);
+				if (labelColor == null)
+					labelColor = GREEN;
+				ShadedLabel label = new ShadedLabel(sectionLabel, labelColor);
+				components.add(label);
+			}
+		}
+		for (Component childComponent : childComponents)
+			if (childComponent == null) {
+				logger.warn("Separator found within section " + sectionId);
+				addNullSeparator(components);
+			} else
+				components.add(childComponent);
+		addNullSeparator(components);
+	}
+
+	/**
+	 * Remove the last <code>null</code> separator from the list of components
+	 * if it's present.
+	 *
+	 * @param components
+	 *            List of components
+	 */
+	private void stripTrailingNullSeparator(List<Component> components) {
+		if (!components.isEmpty()) {
+			int lastIndex = components.size() - 1;
+			if (components.get(lastIndex) == null)
+				components.remove(lastIndex);
+		}
+	}
+
+	/**
+	 * Perform the actual update, called by {@link #update()}. Reset all the
+	 * collections, refresh from SPI, modify any previously published components
+	 * and notify any observers.
+	 */
+	protected synchronized void doUpdate() {
+		resetCollections();
+		findChildren();
+		updatePublishedComponents();
+		multiCaster.notify(new UpdatedMenuManagerEvent());
+	}
+
+	/**
+	 * Find all children for all known menu components. Populates
+	 * {@link #uriToMenuElement}.
+	 *
+	 */
+	protected void findChildren() {
+		for (MenuComponent menuElement : menuComponents) {
+			uriToMenuElement.put(menuElement.getId(), menuElement);
+			logger.debug("Found menu element " + menuElement.getId() + " "
+					+ menuElement);
+			if (menuElement.getParentId() == null)
+				continue;
+			List<MenuComponent> siblings = menuElementTree.get(menuElement
+					.getParentId());
+			if (siblings == null) {
+				siblings = new ArrayList<>();
+				synchronized (menuElementTree) {
+					menuElementTree.put(menuElement.getParentId(), siblings);
+				}
+			}
+			siblings.add(menuElement);
+		}
+//		if (uriToMenuElement.isEmpty()) {
+//			logger.error("No menu elements found, check classpath/Raven/SPI");
+//		}
+	}
+
+	/**
+	 * Get the children which have the given URI specified as their parent, or
+	 * an empty list if no children exist.
+	 *
+	 * @param id
+	 *            The {@link URI} of the parent
+	 * @return The {@link List} of {@link MenuComponent} which have the given
+	 *         parent
+	 */
+	protected List<MenuComponent> getChildren(URI id) {
+		List<MenuComponent> children = null;
+		synchronized (menuElementTree) {
+			children = menuElementTree.get(id);
+			if (children != null)
+				children = new ArrayList<>(children);
+		}
+		if (children == null)
+			children = Collections.<MenuComponent> emptyList();
+		else
+			Collections.sort(children, menuElementComparator);
+		return children;
+	}
+
+	/**
+	 * Make the list of Swing {@link Component}s that are the children of the
+	 * given {@link URI}.
+	 *
+	 * @param id
+	 *            The {@link URI} of the parent which children are to be made
+	 * @param menuOptions
+	 *            Options of the created menu, for instance
+	 *            {@link MenuOptions#isToolbar()}.
+	 * @return A {@link List} of {@link Component}s that can be added to a
+	 *         {@link JMenuBar}, {@link JMenu} or {@link JToolBar}.
+	 */
+	protected List<Component> makeComponents(URI id, MenuOptions menuOptions) {
+		List<Component> components = new ArrayList<>();
+		for (MenuComponent childElement : getChildren(id)) {
+			if (childElement instanceof ContextualMenuComponent)
+				((ContextualMenuComponent) childElement)
+						.setContextualSelection(menuOptions
+								.getContextualSelection());
+			/*
+			 * Important - check this AFTER setContextualSelection so the item
+			 * can change it's enabled-state if needed.
+			 */
+			if (!childElement.isEnabled())
+				continue;
+			MenuType type = childElement.getType();
+			Action action = childElement.getAction();
+			URI childId = childElement.getId();
+			if (type.equals(MenuType.action)) {
+				if (action == null) {
+					logger.warn("Skipping invalid action " + childId + " for "
+							+ id);
+					continue;
+				}
+
+				Component actionComponent;
+				if (menuOptions.isOptionGroup()) {
+					if (menuOptions.isToolbar()) {
+						actionComponent = new JToggleButton(action);
+						toolbarizeButton((AbstractButton) actionComponent);
+					} else
+						actionComponent = new JRadioButtonMenuItem(action);
+				} else {
+					if (menuOptions.isToolbar()) {
+						actionComponent = new JButton(action);
+						toolbarizeButton((AbstractButton) actionComponent);
+					} else
+						actionComponent = new JMenuItem(action);
+				}
+				registerComponent(childId, actionComponent);
+				components.add(actionComponent);
+			} else if (type.equals(MenuType.toggle)) {
+				if (action == null) {
+					logger.warn("Skipping invalid toggle " + childId + " for "
+							+ id);
+					continue;
+				}
+				Component toggleComponent;
+				if (menuOptions.isToolbar())
+					toggleComponent = new JToggleButton(action);
+				else
+					toggleComponent = new JCheckBoxMenuItem(action);
+				registerComponent(childId, toggleComponent);
+				components.add(toggleComponent);
+			} else if (type.equals(MenuType.custom)) {
+				Component customComponent = childElement.getCustomComponent();
+				if (customComponent == null) {
+					logger.warn("Skipping null custom component " + childId
+							+ " for " + id);
+					continue;
+				}
+				registerComponent(childId, customComponent);
+				components.add(customComponent);
+			} else if (type.equals(MenuType.optionGroup))
+				addOptionGroup(components, childId, menuOptions);
+			else if (type.equals(MenuType.section))
+				addSection(components, childId, menuOptions);
+			else if (type.equals(MenuType.menu))
+				addMenu(components, childElement, menuOptions);
+			else {
+				logger.warn("Skipping invalid/unknown type " + type + " for "
+						+ id);
+				continue;
+			}
+		}
+		stripTrailingNullSeparator(components);
+		return components;
+	}
+
+	/**
+	 * Fill the specified menu bar with the menu elements that have the given
+	 * URI as their parent.
+	 * <p>
+	 * Existing elements on the menu bar will be removed.
+	 *
+	 * @param menuBar
+	 *            The {@link JMenuBar} to update
+	 * @param id
+	 *            The {@link URI} of the menu bar
+	 */
+	protected void populateMenuBar(JMenuBar menuBar, URI id) {
+		menuBar.removeAll();
+		MenuComponent menuDef = uriToMenuElement.get(id);
+		if (menuDef == null)
+			throw new IllegalArgumentException("Unknown menuBar " + id);
+		if (!menuDef.getType().equals(MenuType.menu))
+			throw new IllegalArgumentException("Element " + id
+					+ " is not a menu, but a " + menuDef.getType());
+		MenuOptions menuOptions = new MenuOptions();
+		for (Component component : makeComponents(id, menuOptions))
+			if (component == null)
+				logger.warn("Ignoring separator in menu bar " + id);
+			else
+				menuBar.add(component);
+	}
+
+	/**
+	 * Fill the specified menu bar with the menu elements that have the given
+	 * URI as their parent.
+	 * <p>
+	 * Existing elements on the menu bar will be removed.
+	 *
+	 * @param popupMenu
+	 *            The {@link JPopupMenu} to update
+	 * @param id
+	 *            The {@link URI} of the menu bar
+	 * @param contextualSelection
+	 *            The current selection for the context menu
+	 */
+	protected void populateContextMenu(JPopupMenu popupMenu, URI id,
+			ContextualSelection contextualSelection) {
+		popupMenu.removeAll();
+		MenuComponent menuDef = uriToMenuElement.get(id);
+		if (menuDef == null)
+			throw new IllegalArgumentException("Unknown menuBar " + id);
+		if (!menuDef.getType().equals(MenuType.menu))
+			throw new IllegalArgumentException("Element " + id
+					+ " is not a menu, but a " + menuDef.getType());
+		MenuOptions menuOptions = new MenuOptions();
+		menuOptions.setContextualSelection(contextualSelection);
+		for (Component component : makeComponents(id, menuOptions))
+			if (component == null)
+				popupMenu.addSeparator();
+			else
+				popupMenu.add(component);
+	}
+
+	/**
+	 * Fill the specified tool bar with the elements that have the given URI as
+	 * their parent.
+	 * <p>
+	 * Existing elements on the tool bar will be removed.
+	 *
+	 * @param toolbar
+	 *            The {@link JToolBar} to update
+	 * @param id
+	 *            The {@link URI} of the tool bar
+	 */
+	protected void populateToolBar(JToolBar toolbar, URI id) {
+		toolbar.removeAll();
+		MenuComponent toolbarDef = uriToMenuElement.get(id);
+		if (toolbarDef == null)
+			throw new IllegalArgumentException("Unknown toolBar " + id);
+		if (!toolbarDef.getType().equals(MenuType.toolBar))
+			throw new IllegalArgumentException("Element " + id
+					+ " is not a toolBar, but a " + toolbarDef.getType());
+		if (toolbarDef.getAction() != null) {
+			String name = (String) toolbarDef.getAction().getValue(Action.NAME);
+			toolbar.setName(name);
+		} else
+			toolbar.setName("");
+		MenuOptions menuOptions = new MenuOptions();
+		menuOptions.setToolbar(true);
+		for (Component component : makeComponents(id, menuOptions)) {
+			if (component == null) {
+				toolbar.addSeparator();
+				continue;
+			}
+			if (component instanceof JButton) {
+				JButton toolbarButton = (JButton) component;
+				toolbarButton.putClientProperty("hideActionText", true);
+			}
+			toolbar.add(component);
+		}
+	}
+
+	/**
+	 * Register a component that has been created. Such a component can be
+	 * resolved through {@link #getComponentByURI(URI)}.
+	 *
+	 * @param id
+	 *            The {@link URI} that defined the component
+	 * @param component
+	 *            The {@link Component} that was created.
+	 */
+	protected synchronized void registerComponent(URI id, Component component) {
+		registerComponent(id, component, false);
+	}
+
+	/**
+	 * Register a component that has been created. Such a component can be
+	 * resolved through {@link #getComponentByURI(URI)}.
+	 *
+	 * @param id
+	 *            The {@link URI} that defined the component
+	 * @param component
+	 *            The {@link Component} that was created.
+	 * @param published
+	 *            <code>true</code> if the component has been published through
+	 *            {@link #createMenuBar()} or similar, and is to be
+	 *            automatically updated by later calls to {@link #update()}.
+	 */
+	protected synchronized void registerComponent(URI id, Component component,
+			boolean published) {
+		uriToComponent.put(id, new WeakReference<>(component));
+		componentToUri.put(component, id);
+		if (published) {
+			List<WeakReference<Component>> publishedComponents = uriToPublishedComponents
+					.get(id);
+			if (publishedComponents == null) {
+				publishedComponents = new ArrayList<>();
+				uriToPublishedComponents.put(id, publishedComponents);
+			}
+			publishedComponents.add(new WeakReference<>(component));
+		}
+		setHelpStringForComponent(component, id);
+	}
+
+	/**
+	 * Reset all collections
+	 *
+	 */
+	protected synchronized void resetCollections() {
+		menuElementTree = new HashMap<>();
+		componentToUri = new WeakHashMap<>();
+		uriToMenuElement = new HashMap<>();
+		uriToComponent = new HashMap<>();
+	}
+
+	/**
+	 * Set javax.help string to identify the component for later references to
+	 * the help document. Note that the component (ie. the
+	 * {@link AbstractMenuAction} must have an ID for an registration to take
+	 * place.
+	 *
+	 * @param component
+	 *            The {@link Component} to set help string for
+	 * @param componentId
+	 *            The {@link URI} to be used as identifier
+	 */
+	protected void setHelpStringForComponent(Component component,
+			URI componentId) {
+		if (componentId != null) {
+			String helpId = componentId.toASCIIString();
+			setHelpIDString(component, helpId);
+		}
+	}
+
+	/**
+	 * Make an {@link AbstractButton} be configured in a "toolbar-like" way, for
+	 * instance showing only the icon.
+	 *
+	 * @param actionButton
+	 *            Button to toolbarise
+	 */
+	protected void toolbarizeButton(AbstractButton actionButton) {
+		Action action = actionButton.getAction();
+		if (action.getValue(SHORT_DESCRIPTION) == null)
+			action.putValue(SHORT_DESCRIPTION, action.getValue(NAME));
+		actionButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+		// actionButton.setHorizontalTextPosition(JButton.CENTER);
+		// actionButton.setVerticalTextPosition(JButton.BOTTOM);
+		if (action.getValue(Action.SMALL_ICON) != null) {
+			// Don't show the text
+			actionButton.putClientProperty("hideActionText", true);
+			// Since hideActionText seems to be broken in Java 5 and/or OS X
+			actionButton.setText(null);
+		}
+	}
+
+	/**
+	 * Update all components that have been published using
+	 * {@link #createMenuBar()} and similar. Content of such components will be
+	 * removed and replaced by fresh components.
+	 */
+	protected void updatePublishedComponents() {
+		for (Entry<URI, List<WeakReference<Component>>> entry : uriToPublishedComponents
+				.entrySet())
+			for (WeakReference<Component> reference : entry.getValue()) {
+				URI id = entry.getKey();
+				Component component = reference.get();
+				if (component == null)
+					continue;
+				if (component instanceof JToolBar)
+					populateToolBar((JToolBar) component, id);
+				else if (component instanceof JMenuBar)
+					populateMenuBar((JMenuBar) component, id);
+				else
+					logger.warn("Could not update published component " + id
+							+ ": " + component.getClass());
+			}
+	}
+
+	public void setMenuComponents(List<MenuComponent> menuComponents) {
+		this.menuComponents = menuComponents;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		selectionManager.addObserver(new SelectionManagerObserver());
+	}
+
+	/**
+	 * {@link Comparator} that can order {@link MenuComponent}s by their
+	 * {@link MenuComponent#getPositionHint()}.
+	 */
+	protected static class MenuElementComparator implements
+			Comparator<MenuComponent> {
+		@Override
+		public int compare(MenuComponent a, MenuComponent b) {
+			return a.getPositionHint() - b.getPositionHint();
+		}
+	}
+
+	/**
+	 * Various options for
+	 * {@link MenuManagerImpl#makeComponents(URI, MenuOptions)} and friends.
+	 *
+	 * @author Stian Soiland-Reyes
+	 */
+	public static class MenuOptions {
+		private boolean isToolbar = false;
+		private boolean isOptionGroup = false;
+		private ContextualSelection contextualSelection = null;
+
+		public ContextualSelection getContextualSelection() {
+			return contextualSelection;
+		}
+
+		public void setContextualSelection(
+				ContextualSelection contextualSelection) {
+			this.contextualSelection = contextualSelection;
+		}
+
+		public MenuOptions(MenuOptions original) {
+			this.isOptionGroup = original.isOptionGroup();
+			this.isToolbar = original.isToolbar();
+			this.contextualSelection = original.getContextualSelection();
+		}
+
+		public MenuOptions() {
+		}
+
+		@Override
+		protected MenuOptions clone() {
+			return new MenuOptions(this);
+		}
+
+		public boolean isToolbar() {
+			return isToolbar;
+		}
+
+		public void setToolbar(boolean isToolbar) {
+			this.isToolbar = isToolbar;
+		}
+
+		public boolean isOptionGroup() {
+			return isOptionGroup;
+		}
+
+		public void setOptionGroup(boolean isOptionGroup) {
+			this.isOptionGroup = isOptionGroup;
+		}
+	}
+
+	private final class SelectionManagerObserver extends
+			SwingAwareObserver<SelectionManagerEvent> {
+		private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+		private static final String RESULTS_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective";
+
+		@Override
+		public void notifySwing(Observable<SelectionManagerEvent> sender,
+				SelectionManagerEvent message) {
+			if (!(message instanceof PerspectiveSelectionEvent))
+				return;
+			handlePerspectiveSelect((PerspectiveSelectionEvent) message);
+		}
+
+		private void handlePerspectiveSelect(PerspectiveSelectionEvent event) {
+			String perspectiveID = event.getSelectedPerspective().getID();
+			boolean isDesign = DESIGN_PERSPECTIVE_ID.equals(perspectiveID);
+			boolean isResults = RESULTS_PERSPECTIVE_ID.equals(perspectiveID);
+
+			for (MenuComponent menuComponent : menuComponents)
+				if (!(menuComponent instanceof ContextualMenuComponent)) {
+					Action action = menuComponent.getAction();
+					if (action instanceof DesignOnlyAction)
+						action.setEnabled(isDesign);
+					else if (action instanceof DesignOrResultsAction)
+						action.setEnabled(isDesign || isResults);
+				}
+		}
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java
new file mode 100644
index 0000000..9a2f37b
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_A;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class AdvancedMenu extends AbstractMenu {
+	public static final URI ADVANCED_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+	public AdvancedMenu() {
+		super(DEFAULT_MENU_BAR, 1000, ADVANCED_URI, makeAction());
+	}
+
+	public static DummyAction makeAction() {
+		DummyAction action = new DummyAction("Advanced");
+		action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_A));
+		return action;
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java
new file mode 100644
index 0000000..a15237c
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_E;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class EditMenu extends AbstractMenu {
+	public EditMenu() {
+		super(DEFAULT_MENU_BAR, 20, URI
+				.create("http://taverna.sf.net/2008/t2workbench/menu#edit"),
+				makeAction());
+	}
+
+	public static DummyAction makeAction() {
+		DummyAction action = new DummyAction("Edit");
+		action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_E));
+		return action;
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java
new file mode 100644
index 0000000..6b6eb7c
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.Desktop.getDesktop;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu.HELP_URI;
+
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+/**
+ * MenuItem for feedback
+ * 
+ * @author alanrw
+ */
+public class FeedbackMenuAction extends AbstractMenuAction {
+	private static Logger logger = Logger.getLogger(FeedbackMenuAction.class);
+
+	private static String FEEDBACK_URL = "http://www.taverna.org.uk/about/contact-us/feedback/";
+
+	public FeedbackMenuAction() {
+		super(HELP_URI, 20);
+	}
+
+	@Override
+	protected Action createAction() {
+		return new FeedbackAction();
+	}
+
+	@SuppressWarnings("serial")
+	private final class FeedbackAction extends AbstractAction {
+		private FeedbackAction() {
+			super("Contact us");
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			try {
+				getDesktop().browse(new URI(FEEDBACK_URL));
+			} catch (IOException e1) {
+				logger.error("Unable to open URL", e1);
+			} catch (URISyntaxException e1) {
+				logger.error("Invalid URL syntax", e1);
+			}
+		}
+	}
+
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java
new file mode 100644
index 0000000..61f963b
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_F;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+/**
+ * File menu
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class FileMenu extends AbstractMenu {
+	public FileMenu() {
+		super(DEFAULT_MENU_BAR, 10, URI
+				.create("http://taverna.sf.net/2008/t2workbench/menu#file"),
+				makeAction());
+	}
+
+	public static DummyAction makeAction() {
+		DummyAction action = new DummyAction("File");
+		action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_F));
+		return action;
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java
new file mode 100644
index 0000000..c4169cc
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_H;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class HelpMenu extends AbstractMenu {
+	public static final URI HELP_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#help");
+
+	public HelpMenu() {
+		super(DEFAULT_MENU_BAR, 1024, HELP_URI, makeAction());
+	}
+
+	public static DummyAction makeAction() {
+		DummyAction action = new DummyAction("Help");
+		action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_H));
+		return action;
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java
new file mode 100644
index 0000000..d091c8e
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_F1;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.helper.Helper.displayDefaultHelp;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu.HELP_URI;
+
+import java.awt.AWTEvent;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+/**
+ * MenuItem for help
+ * 
+ * @author alanrw
+ */
+public class OnlineHelpMenuAction extends AbstractMenuAction {
+	public OnlineHelpMenuAction() {
+		super(HELP_URI, 10);
+	}
+
+	@Override
+	protected Action createAction() {
+		return new OnlineHelpAction();
+	}
+
+	@SuppressWarnings("serial")
+	private final class OnlineHelpAction extends AbstractAction {
+		private OnlineHelpAction() {
+			super("Online help");
+			putValue(ACCELERATOR_KEY, getKeyStroke(VK_F1, 0));
+
+		}
+
+		/**
+		 * When selected, use the Helper to display the default help.
+		 */
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			displayDefaultHelp((AWTEvent) e);
+			// TODO change helper to bean?
+		}
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java
new file mode 100644
index 0000000..308d51d
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.lang.Runtime.getRuntime;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.showInputDialog;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu.ADVANCED_URI;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class ShowLogsAndDataMenuAction extends AbstractMenuAction {
+	private static final String OPEN = "open";
+	private static final String EXPLORER = "explorer";
+	// TODO Consider using xdg-open instead of gnome-open
+	private static final String GNOME_OPEN = "gnome-open";
+	private static final String WINDOWS = "Windows";
+	private static final String MAC_OS_X = "Mac OS X";
+
+	private ApplicationConfiguration applicationConfiguration;
+
+	public ShowLogsAndDataMenuAction() {
+		super(ADVANCED_URI, 200);
+	}
+
+	private static Logger logger = Logger.getLogger(ShowLogsAndDataMenuAction.class);
+
+	@Override
+	protected Action createAction() {
+		return new AbstractAction("Show logs and data folder") {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				File logsAndDataDir = applicationConfiguration.getApplicationHomeDir();
+				showDirectory(logsAndDataDir, "Taverna logs and data folder");
+			}
+		};
+	}
+
+	public static void showDirectory(File dir, String title) {
+		String path = dir.getAbsolutePath();
+		String os = System.getProperty("os.name");
+		String cmd;
+		boolean isWindows = false;
+		if (os.equals(MAC_OS_X))
+			cmd = OPEN;
+		else if (os.startsWith(WINDOWS)) {
+			cmd = EXPLORER;
+			isWindows = true;
+		} else
+			// Assume Unix - best option is gnome-open
+			cmd = GNOME_OPEN;
+
+		String[] cmdArray = new String[2];
+		cmdArray[0] = cmd;
+		cmdArray[1] = path;
+		try {
+			Process exec = getRuntime().exec(cmdArray);
+			Thread.sleep(300);
+			exec.getErrorStream().close();
+			exec.getInputStream().close();
+			exec.getOutputStream().close();
+			exec.waitFor();
+			if (exec.exitValue() == 0 || isWindows && exec.exitValue() == 1)
+				// explorer.exe thinks 1 means success
+				return;
+			logger.warn("Exit value from " + cmd + " " + path + ": " + exec.exitValue());
+		} catch (Exception ex) {
+			logger.warn("Could not call " + cmd + " " + path, ex);
+		}
+		// Fall-back - just show a dialogue with the path
+		showInputDialog(getMainWindow(), "Copy path from below:", title,
+				INFORMATION_MESSAGE, null, null, path);
+	}
+
+	public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java
new file mode 100644
index 0000000..2df05e5
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class ViewShowMenuSection extends AbstractMenuSection {
+	public static final URI DIAGRAM_MENU = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#diagram");
+	public static final URI VIEW_SHOW_MENU_SECTION = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#viewShowMenuSection");
+
+	public ViewShowMenuSection() {
+		super(DIAGRAM_MENU, 10, VIEW_SHOW_MENU_SECTION);
+	}
+}
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager b/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager
new file mode 100644
index 0000000..3b06fd9
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl
\ No newline at end of file
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml
new file mode 100644
index 0000000..3a1eadf
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="MenuManagerImpl" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+
+	<service ref="FileMenu" auto-export="interfaces" />
+	<service ref="EditMenu" auto-export="interfaces" />
+	<service ref="AdvancedMenu" auto-export="interfaces" />
+	<service ref="HelpMenu" auto-export="interfaces" />
+	<service ref="OnlineHelpMenuAction" auto-export="interfaces" />
+	<service ref="FeedbackMenuAction" auto-export="interfaces" />
+	<service ref="ShowLogsAndDataMenuAction" auto-export="interfaces" />
+
+	<reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+	<reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+	<list id="menuComponents" interface="net.sf.taverna.t2.ui.menu.MenuComponent" cardinality="0..N" greedy-proxying="true">
+		<listener ref="MenuManagerImpl" bind-method="update" unbind-method="update" />
+	</list>
+
+</beans:beans>
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml
new file mode 100644
index 0000000..62fd24e
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="MenuManagerImpl" class="net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl">
+		<property name="menuComponents" ref="menuComponents" />
+		<property name="selectionManager" ref="selectionManager" />
+	</bean>
+
+	<bean id="FileMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.FileMenu" />
+	<bean id="EditMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.EditMenu" />
+	<bean id="AdvancedMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu" />
+	<bean id="HelpMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu" />
+	<bean id="OnlineHelpMenuAction"
+		class="net.sf.taverna.t2.workbench.ui.impl.menu.OnlineHelpMenuAction" />
+	<bean id="FeedbackMenuAction"
+		class="net.sf.taverna.t2.workbench.ui.impl.menu.FeedbackMenuAction" />
+	<bean id="ShowLogsAndDataMenuAction"
+		class="net.sf.taverna.t2.workbench.ui.impl.menu.ShowLogsAndDataMenuAction">
+		<property name="applicationConfiguration" ref="applicationConfiguration" />
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-plugin-manager/pom.xml b/taverna-workbench-plugin-manager/pom.xml
new file mode 100644
index 0000000..4e9f0c1
--- /dev/null
+++ b/taverna-workbench-plugin-manager/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>plugin-manager</artifactId>
+	<packaging>bundle</packaging>
+	<name>Taverna Workbench Plugin Manager</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>workbench-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.commons</groupId>
+			<artifactId>taverna-plugin-api</artifactId>
+			<version> ${taverna.commons.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+
+		<!-- <dependency>
+			<groupId>uk.org.taverna.commons</groupId>
+			<artifactId>taverna-download-impl</artifactId>
+			<version>0.1.0-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.commons</groupId>
+			<artifactId>taverna-plugin-impl</artifactId>
+			<version>0.1.0-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency> -->
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java
new file mode 100644
index 0000000..f189f91
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AvailablePluginPanel extends PluginPanel {
+	private final PluginManager pluginManager;
+	private final PluginVersions pluginVersions;
+
+	public AvailablePluginPanel(PluginVersions pluginVersions,
+			PluginManager pluginManager) {
+		super(pluginVersions.getName(), pluginVersions.getOrganization(),
+				pluginVersions.getLatestVersion().getVersion(), pluginVersions
+						.getDescription());
+		this.pluginVersions = pluginVersions;
+		this.pluginManager = pluginManager;
+	}
+
+	@Override
+	public Action getPluginAction() {
+		return new PluginAction();
+	}
+
+	class PluginAction extends AbstractAction {
+		public PluginAction() {
+			putValue(NAME, "Install");
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			setEnabled(false);
+			putValue(NAME, "Installing");
+			try {
+				pluginManager.installPlugin(pluginVersions.getPluginSiteUrl(),
+						pluginVersions.getLatestVersion().getFile()).start();
+			} catch (PluginException ex) {
+				ex.printStackTrace();
+			}
+			putValue(NAME, "Installed");
+		}
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java
new file mode 100644
index 0000000..a08cf14
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class InstalledPluginPanel extends PluginPanel {
+	private final Plugin plugin;
+
+	public InstalledPluginPanel(Plugin plugin) {
+		super(plugin.getName(), plugin.getOrganization(), plugin.getVersion()
+				.toString(), plugin.getDescription());
+		this.plugin = plugin;
+	}
+
+	@Override
+	public Action getPluginAction() {
+		return new PluginAction();
+	}
+
+	class PluginAction extends AbstractAction {
+		public PluginAction() {
+			putValue(NAME, "Uninstall");
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			setEnabled(false);
+			putValue(NAME, "Uninstalling");
+			try {
+				plugin.uninstall();
+			} catch (PluginException ex) {
+				ex.printStackTrace();
+			}
+			putValue(NAME, "Uninstalled");
+		}
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java
new file mode 100644
index 0000000..dfef2fe
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.EAST;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.lang.Math.max;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
+import static javax.swing.SwingConstants.CENTER;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.LayoutManager;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class PluginManagerPanel extends JPanel {
+	private static final Logger logger = Logger
+			.getLogger(PluginManagerPanel.class);
+
+	private PluginManager pluginManager;
+	private JLabel message = new JLabel("");
+
+	public PluginManagerPanel(PluginManager pluginManager) {
+		this.pluginManager = pluginManager;
+		initialize();
+	}
+
+	public void initialize() {
+		removeAll();
+		setLayout(new GridBagLayout());
+
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.anchor = WEST;
+		gbc.gridy = 0;
+		gbc.insets.left = 5;
+		gbc.insets.right = 5;
+		gbc.insets.top = 5;
+
+		JTabbedPane tabbedPane = new JTabbedPane();
+		tabbedPane.addTab("Available", createAvailablePluginsPanel());
+		tabbedPane.addTab("Installed", createInstalledPluginsPanel());
+		tabbedPane.addTab("Updates", createUpdatePluginsPanel());
+
+		gbc.weightx = 1;
+		gbc.weighty = 1;
+		gbc.gridy = 1;
+		gbc.fill = BOTH;
+		add(tabbedPane, gbc);
+
+		gbc.anchor = EAST;
+		gbc.fill = NONE;
+		gbc.weightx = 0;
+		gbc.weighty = 0;
+		gbc.gridy = 2;
+		gbc.insets.bottom = 5;
+		add(message, gbc);
+	}
+
+	public void checkForUpdates() {
+		message.setText("Checking for updates");
+		try {
+			pluginManager.checkForUpdates();
+		} catch (PluginException e) {
+			logger.info("Error checking for plugin updates", e);
+		} finally {
+			message.setText("");
+		}
+	}
+
+	private static Component scrolled(Component view) {
+		JScrollPane scrollPane = new JScrollPane(view);
+		scrollPane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
+		scrollPane.setBorder(null);
+		return scrollPane;
+	}
+
+	private Component createAvailablePluginsPanel() {
+		try {
+			List<PluginVersions> availablePlugins = pluginManager
+					.getAvailablePlugins();
+			if (availablePlugins.size() == 0)
+				return new JLabel("No new plugins available", CENTER);
+
+			JPanel panel = new JPanel(new ListLayout());
+			for (PluginVersions plugin : availablePlugins)
+				panel.add(new AvailablePluginPanel(plugin, pluginManager));
+			return scrolled(panel);
+		} catch (PluginException e) {
+			logger.info("Error looking for new plugins", e);
+			return new JLabel("No new plugins available", CENTER);
+		}
+	}
+
+	private Component createInstalledPluginsPanel() {
+		try {
+			List<Plugin> installedPlugins = pluginManager.getInstalledPlugins();
+			if (installedPlugins.size() == 0)
+				return new JLabel("No installed plugins", CENTER);
+
+			JPanel panel = new JPanel(new ListLayout());
+			for (Plugin plugin : installedPlugins)
+				panel.add(new InstalledPluginPanel(plugin));
+			return scrolled(panel);
+		} catch (PluginException e) {
+			return new JLabel("No installed plugins", CENTER);
+		}
+	}
+
+	private Component createUpdatePluginsPanel() {
+		try {
+			List<PluginVersions> pluginUpdates = pluginManager
+					.getPluginUpdates();
+			if (pluginUpdates.size() == 0)
+				return new JLabel("All plugins are up to date", CENTER);
+
+			JPanel panel = new JPanel(new ListLayout());
+			for (PluginVersions plugin : pluginUpdates)
+				panel.add(new UpdatePluginPanel(plugin, pluginManager));
+			return scrolled(panel);
+		} catch (PluginException e) {
+			return new JLabel("All plugins are up to date", CENTER);
+		}
+	}
+
+	private final class ListLayout implements LayoutManager {
+		@Override
+		public void addLayoutComponent(String name, Component comp) {
+		}
+
+		@Override
+		public void removeLayoutComponent(Component comp) {
+		}
+
+		@Override
+		public Dimension preferredLayoutSize(Container parent) {
+			Dimension preferredLayoutSize = new Dimension(0, 1);
+			for (Component component : parent.getComponents()) {
+				Dimension preferredSize = component.getPreferredSize();
+				preferredLayoutSize.width = max(preferredSize.width,
+						preferredLayoutSize.width);
+				preferredLayoutSize.height = preferredSize.height
+						+ preferredLayoutSize.height - 1;
+			}
+			return preferredLayoutSize;
+		}
+
+		@Override
+		public Dimension minimumLayoutSize(Container parent) {
+			Dimension minimumLayoutSize = new Dimension(0, 1);
+			for (Component component : parent.getComponents()) {
+				Dimension minimumSize = component.getMinimumSize();
+				minimumLayoutSize.width = max(minimumSize.width,
+						minimumLayoutSize.width);
+				minimumLayoutSize.height = minimumSize.height
+						+ minimumLayoutSize.height - 1;
+			}
+			return minimumLayoutSize;
+		}
+
+		@Override
+		public void layoutContainer(Container parent) {
+			int y = 0;
+			for (Component component : parent.getComponents()) {
+				component.setLocation(0, y);
+				component.setSize(parent.getSize().width,
+						component.getPreferredSize().height);
+				y += component.getHeight() - 1;
+			}
+		}
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java
new file mode 100644
index 0000000..27822a6
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+
+import javax.swing.JDialog;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+/**
+ * @author David Withers
+ */
+public class PluginManagerView implements EventHandler {
+	private JDialog dialog;
+	private PluginManagerPanel pluginManagerPanel;
+	private PluginManager pluginManager;
+
+	public void showDialog() {
+		getDialog().setVisible(true);
+		getPluginManagerPanel().checkForUpdates();
+	}
+
+	private JDialog getDialog() {
+		if (dialog == null) {
+			dialog = new JDialog(getMainWindow(), "Plugin Manager");
+			dialog.add(getPluginManagerPanel());
+			dialog.setSize(700, 500);
+			dialog.setLocationRelativeTo(dialog.getOwner());
+			dialog.setVisible(true);
+		}
+		return dialog;
+	}
+
+	private PluginManagerPanel getPluginManagerPanel() {
+		if (pluginManagerPanel == null)
+			pluginManagerPanel = new PluginManagerPanel(pluginManager);
+		return pluginManagerPanel;
+	}
+
+	@Override
+	public void handleEvent(Event event) {
+		pluginManagerPanel.initialize();
+		pluginManagerPanel.revalidate();
+	}
+
+	public void setPluginManager(PluginManager pluginManager) {
+		this.pluginManager = pluginManager;
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java
new file mode 100644
index 0000000..c156630
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.NORTHWEST;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tavernaCogs64x64Icon;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicButtonUI;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class PluginPanel extends JPanel {
+	@SuppressWarnings("unused")
+	private static final int logoSize = 64;
+
+	private JLabel descriptionLabel;
+	private JLabel descriptionTitle;
+	private JButton actionButton;
+
+	public PluginPanel(String name, String organization, String version,
+			String description) {
+		setLayout(new GridBagLayout());
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.anchor = NORTHWEST;
+		gbc.insets.left = 10;
+		gbc.insets.right = 10;
+		gbc.insets.top = 10;
+		gbc.insets.bottom = 10;
+
+		gbc.gridx = 0;
+		gbc.weightx = 0;
+		gbc.gridheight = 4;
+		JLabel logo = new JLabel(tavernaCogs64x64Icon);
+		add(logo, gbc);
+
+		gbc.gridx = 2;
+		gbc.anchor = CENTER;
+		actionButton = new JButton(getPluginAction());
+		add(actionButton, gbc);
+
+		gbc.gridx = 1;
+		gbc.weightx = 1;
+		gbc.gridheight = 1;
+		gbc.insets.top = 7;
+		gbc.insets.bottom = 0;
+		gbc.anchor = NORTHWEST;
+		JLabel nameLabel = new JLabel(name);
+		nameLabel.setFont(getFont().deriveFont(BOLD));
+		add(nameLabel, gbc);
+
+		gbc.insets.top = 0;
+		add(new JLabel(organization), gbc);
+
+		add(new JLabel("Version " + version), gbc);
+
+		JButton information = new JButton(new InfoAction());
+		information.setFont(information.getFont().deriveFont(BOLD));
+		information.setUI(new BasicButtonUI());
+		information.setBorder(null);
+		add(information, gbc);
+
+		descriptionTitle = new JLabel("Description");
+		descriptionTitle.setFont(getFont().deriveFont(BOLD));
+		descriptionLabel = new JLabel("<html>" + description);
+
+		setBorder(new PluginBorder());
+	}
+
+	private void showInformation() {
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.anchor = NORTHWEST;
+		gbc.insets.left = 10;
+		gbc.insets.right = 10;
+		gbc.insets.bottom = 10;
+		gbc.gridx = 0;
+		gbc.gridwidth = 3;
+
+		add(descriptionTitle, gbc);
+		add(descriptionLabel, gbc);
+		revalidate();
+	}
+
+	private void hideInformation() {
+		remove(descriptionTitle);
+		remove(descriptionLabel);
+		revalidate();
+	}
+
+	public abstract Action getPluginAction();
+
+	class InfoAction extends AbstractAction {
+		private boolean showInformation = true;
+
+		public InfoAction() {
+			putValue(NAME, "Show information");
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			if (showInformation) {
+				showInformation();
+				putValue(NAME, "Hide information");
+				showInformation = false;
+			} else {
+				hideInformation();
+				putValue(NAME, "Show information");
+				showInformation = true;
+			}
+		}
+	}
+
+	class PluginBorder implements Border {
+		@Override
+		public void paintBorder(Component c, Graphics g, int x, int y,
+				int width, int height) {
+			g.setColor(getBackground().darker());
+			g.drawLine(x, y, x + width, y);
+			g.drawLine(x, y + height - 1, x + width, y + height - 1);
+		}
+
+		@Override
+		public boolean isBorderOpaque() {
+			return false;
+		}
+
+		@Override
+		public Insets getBorderInsets(Component c) {
+			return new Insets(0, 0, 0, 0);
+		}
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java
new file mode 100644
index 0000000..bcd427f
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class UpdatePluginPanel extends PluginPanel {
+	private final PluginVersions pluginVersions;
+	private final PluginManager pluginManager;
+
+	public UpdatePluginPanel(PluginVersions pluginVersions,
+			PluginManager pluginManager) {
+		super(pluginVersions.getName(), pluginVersions.getOrganization(),
+				pluginVersions.getLatestVersion().getVersion(), pluginVersions
+						.getDescription());
+		this.pluginVersions = pluginVersions;
+		this.pluginManager = pluginManager;
+	}
+
+	@Override
+	public Action getPluginAction() {
+		return new AbstractAction("Update") {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				setEnabled(false);
+				putValue(NAME, "Updating");
+				boolean succeeded = doUpdate();
+				putValue(NAME, succeeded ? "Updated" : "Failed to update");
+			}
+		};
+	}
+
+	private boolean doUpdate() {
+		try {
+			pluginManager.updatePlugin(pluginVersions);
+			return true;
+		} catch (PluginException e) {
+			// FIXME Log exception properly
+			e.printStackTrace();
+			return false;
+		}
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java
new file mode 100644
index 0000000..6d132de
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.plugin.impl.PluginManagerView;
+
+public class PluginMenuAction extends AbstractMenuAction {
+	private static final URI ADVANCED_MENU_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+	private PluginManagerView pluginManagerView;
+
+	public PluginMenuAction() {
+		super(ADVANCED_MENU_URI, 1100);
+	}
+
+	@Override
+	@SuppressWarnings("serial")
+	protected Action createAction() {
+		return new AbstractAction("Plugin Manager") {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				pluginManagerView.showDialog();
+			}
+		};
+	}
+
+	public void setPluginManagerView(PluginManagerView pluginManagerView) {
+		this.pluginManagerView = pluginManagerView;
+	}
+}
diff --git a/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml
new file mode 100644
index 0000000..5e5b207
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="PluginMenuAction" auto-export="interfaces" />
+
+	<service ref="pluginManagerView" auto-export="interfaces">
+		<service-properties value-type="java.lang.String[]">
+			<beans:entry key="event.topics"
+				value="uk/org/taverna/commons/plugin/PluginManager/*" />
+		</service-properties>
+	</service>
+
+	<reference id="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml
new file mode 100644
index 0000000..2634fb3
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="PluginMenuAction"
+		class="net.sf.taverna.t2.workbench.plugin.impl.menu.PluginMenuAction">
+		<property name="pluginManagerView" ref="pluginManagerView" />
+	</bean>
+
+	<bean id="pluginManagerView" class="net.sf.taverna.t2.workbench.plugin.impl.PluginManagerView">
+		<property name="pluginManager" ref="pluginManager" />
+	</bean>
+</beans>
diff --git a/taverna-workbench-plugins-gui/pom.xml b/taverna-workbench-plugins-gui/pom.xml
new file mode 100644
index 0000000..0468c67
--- /dev/null
+++ b/taverna-workbench-plugins-gui/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>plugins-gui</artifactId>
+	<name>Raven plugin manager GUI</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>workbench-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>0.1.1-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.commons</groupId>
+			<artifactId>taverna-plugin-api</artifactId>
+			<version>0.1.0-SNAPSHOT</version>
+		</dependency>
+
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.4</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java
new file mode 100644
index 0000000..0ada996
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester 
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: AddPluginSiteFrame.java,v $
+ * Revision           $Revision: 1.2 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:51:52 $
+ *               by   $Author: sowen70 $
+ * Created on 8 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+@SuppressWarnings("serial")
+public class AddPluginSiteFrame extends HelpEnabledDialog {
+
+	private JPanel jContentPane = null;
+	private JButton okButton = null;
+	private JButton cancelButton = null;
+	private JTextField urlText = null;
+	private JTextField nameText = null;
+	
+	private String name = null;
+	private String url = null;
+
+	/**
+	 * This method initializes 
+	 * 
+	 */
+	public AddPluginSiteFrame(JDialog parent) {
+		super(parent,"Add plugin site", true);
+		initialize();
+		this.getRootPane().setDefaultButton(okButton);
+	}
+		
+
+	/**
+	 * This method initializes this
+	 * 
+	 */
+	private void initialize() {
+        this.setSize(new Dimension(350, 140));
+        this.setContentPane(getJContentPane());
+			
+	}
+
+	/**
+	 * This method initializes jContentPane	
+	 * 	
+	 * @return javax.swing.JPanel	
+	 */
+	private JPanel getJContentPane() {
+		if (jContentPane == null) {
+			jContentPane = new JPanel();			
+			
+			GridBagConstraints gridBagContraintHeading = new GridBagConstraints();
+			gridBagContraintHeading.ipadx = 10;
+			gridBagContraintHeading.ipady = 5;	
+			gridBagContraintHeading.gridx = 0;
+			gridBagContraintHeading.gridy = 0;	        
+	        gridBagContraintHeading.gridwidth = 2;
+	        gridBagContraintHeading.anchor = GridBagConstraints.CENTER;
+	        gridBagContraintHeading.fill = GridBagConstraints.BOTH;	   
+			
+			GridBagConstraints gridBagContraintNameLabel = new GridBagConstraints();
+
+			gridBagContraintNameLabel.ipadx = 10;
+			gridBagContraintNameLabel.ipady = 5;	       
+			gridBagContraintNameLabel.gridx = 0;
+			gridBagContraintNameLabel.gridy = 1;	        
+	        gridBagContraintNameLabel.gridwidth = 1;
+	        gridBagContraintNameLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+	        gridBagContraintNameLabel.fill = GridBagConstraints.NONE;	   
+	        
+	        GridBagConstraints gridBagContraintURLLabel = new GridBagConstraints();
+
+			gridBagContraintURLLabel.ipadx = 10;
+			gridBagContraintURLLabel.ipady = 5;	       
+			gridBagContraintURLLabel.gridx = 0;
+			gridBagContraintURLLabel.gridy = 2;	        
+	        gridBagContraintURLLabel.gridwidth = 1;
+	        gridBagContraintURLLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+	        gridBagContraintURLLabel.fill = GridBagConstraints.NONE;	   
+	       
+	        
+	        
+
+	        GridBagConstraints gridBagContraintNameText = new GridBagConstraints();
+	        gridBagContraintNameText.ipadx = 10;
+	        gridBagContraintNameText.ipady = 5;
+	        gridBagContraintNameText.anchor = GridBagConstraints.FIRST_LINE_START;
+	        gridBagContraintNameText.fill = GridBagConstraints.HORIZONTAL;
+	        gridBagContraintNameText.gridx = 1;
+	        gridBagContraintNameText.gridy = 1;
+	        gridBagContraintNameText.weightx = 0.1;	        
+	       
+	        GridBagConstraints gridBagContraintURLText = new GridBagConstraints();
+	        gridBagContraintURLText.ipadx = 10;
+	        gridBagContraintURLText.ipady = 5;
+	        gridBagContraintURLText.anchor = GridBagConstraints.FIRST_LINE_START;	        	        
+	        gridBagContraintURLText.fill = GridBagConstraints.HORIZONTAL;
+	        gridBagContraintURLText.gridx = 1;
+	        gridBagContraintURLText.gridy = 2;
+	        gridBagContraintURLText.weightx = 0.1;
+	       	        
+	       
+	        GridBagConstraints gridBagContraintButtons = new GridBagConstraints();
+	        gridBagContraintButtons.gridwidth=2;
+	        gridBagContraintButtons.ipadx = 10;
+	        gridBagContraintButtons.ipady = 5;
+	        gridBagContraintButtons.anchor = GridBagConstraints.SOUTH;
+	        gridBagContraintButtons.fill = GridBagConstraints.BOTH;
+	        gridBagContraintButtons.weightx = 0;
+	        gridBagContraintButtons.weighty = 0.2;
+	        gridBagContraintButtons.gridy = 3;
+	        gridBagContraintButtons.gridx = 0;	        
+	        
+	        JLabel name = new JLabel("Site Name:");
+	        name.setHorizontalAlignment(SwingConstants.RIGHT);	        
+	        JLabel url = new JLabel("Site URL:");
+	        url.setHorizontalAlignment(SwingConstants.RIGHT);
+	        
+	        urlText=new JTextField("http://");
+	        nameText=new JTextField();	        
+	        
+	        
+	        jContentPane.setLayout(new GridBagLayout());	 
+	        jContentPane.add(new JLabel("Enter update site name and url"),gridBagContraintHeading);
+	        jContentPane.add(name, gridBagContraintNameLabel);
+	        jContentPane.add(url, gridBagContraintURLLabel);
+	        jContentPane.add(nameText, gridBagContraintNameText);
+	        jContentPane.add(urlText, gridBagContraintURLText);
+	        jContentPane.add(getButtonPanel(), gridBagContraintButtons);
+	        
+		}
+		return jContentPane;
+	} 
+	
+	public JPanel getButtonPanel() {		
+		return new ButtonPanel(getOKButton(),getCancelButton());
+	}
+	
+	public JButton getOKButton() {
+		if (okButton==null) {
+			okButton=new JButton("OK");
+			final ActionListener okAction = new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					name=nameText.getText();
+					url=urlText.getText();
+					setVisible(false);
+					dispose();
+				}	
+			};	
+			okButton.addActionListener(okAction);
+		    okButton.addKeyListener(new java.awt.event.KeyAdapter() {
+				public void keyPressed(java.awt.event.KeyEvent evt) {
+					if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
+						okAction.actionPerformed(null);
+					}
+				}
+			});
+		}
+		return okButton;
+	}
+	
+	public JButton getCancelButton() {
+		if (cancelButton==null) {
+			cancelButton=new JButton("Cancel");	
+			final ActionListener cancelAction  = new ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					setVisible(false);
+					dispose();
+				}
+			};
+			cancelButton.addActionListener(cancelAction);
+			cancelButton.addKeyListener(new java.awt.event.KeyAdapter() {
+				public void keyPressed(java.awt.event.KeyEvent evt) {
+					if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
+						cancelAction.actionPerformed(null);
+					}
+				}
+			});	
+		}
+		return cancelButton;
+	}
+
+
+	public String getName() {
+		if (name!=null) name=name.trim();
+		return name;
+	}
+
+
+	public String getUrl() {
+		if (url!=null) url=url.trim();
+		if (!url.endsWith("/")) url+="/";
+		return url;
+	}	
+
+}  //  @jve:decl-index=0:visual-constraint="73,21"
+
+
+@SuppressWarnings("serial")
+class ButtonPanel extends JPanel {
+    public ButtonPanel(JButton ok, JButton cancel) {
+        super(new GridBagLayout());
+        GridBagConstraints c = new GridBagConstraints();
+        c.gridx = 0;
+        c.ipadx = 5;
+        c.gridy = GridBagConstraints.RELATIVE;
+        c.fill = GridBagConstraints.BOTH;
+        add(ok);
+        add(cancel);
+    }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java
new file mode 100644
index 0000000..c40cfcf
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java
@@ -0,0 +1,143 @@
+/**
+ * 
+ */
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.GraphicsEnvironment;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JOptionPane;
+
+import org.apache.log4j.Logger;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.io.FileUtils;
+
+import net.sf.taverna.raven.appconfig.ApplicationConfig;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.spi.Profile;
+import net.sf.taverna.raven.spi.ProfileFactory;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ * 
+ * This class looks for a notice on the myGrid website that is later than the
+ * one (if any) in the application directory. It then displays the notice. This
+ * is intended to allow simple messages to be sent to all users.
+ * 
+ * @author alanrw
+ * 
+ */
+public class CheckForNoticeStartupHook implements StartupSPI {
+
+	private static Logger logger = Logger
+			.getLogger(CheckForNoticeStartupHook.class);
+
+	private static final String LAST_NOTICE_CHECK_FILE_NAME = "last_notice";
+
+
+	private static File checkForUpdatesDirectory = CheckForUpdatesStartupHook
+			.getCheckForUpdatesDirectory();
+	private static File lastNoticeCheckFile = new File(checkForUpdatesDirectory,
+			LAST_NOTICE_CHECK_FILE_NAME);
+
+	private static String pattern = "EEE, dd MMM yyyy HH:mm:ss Z";
+	private static SimpleDateFormat format = new SimpleDateFormat(pattern);
+
+	private static Profile profile = ProfileFactory.getInstance().getProfile();
+	private static String version = profile.getVersion();
+
+	private static String BASE_URL = "http://www.mygrid.org.uk/taverna/updates";
+	private static String SUFFIX = "notice";
+	
+	private static int TIMEOUT = 5000;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see net.sf.taverna.t2.workbench.StartupSPI#positionHint()
+	 */
+	public int positionHint() {
+		return 95;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see net.sf.taverna.t2.workbench.StartupSPI#startup()
+	 */
+	public boolean startup() {
+
+		if (GraphicsEnvironment.isHeadless()) {
+			return true; // if we are running headlessly just return
+		}
+
+		long noticeTime = -1;
+		long lastCheckedTime = -1;
+
+		HttpClient client = new HttpClient();
+		client.setConnectionTimeout(TIMEOUT);
+		client.setTimeout(TIMEOUT);
+		PluginManager.setProxy(client);
+		String message = null;
+
+		try {
+			URI noticeURI = new URI(BASE_URL + "/" + version + "/" + SUFFIX);
+			HttpMethod method = new GetMethod(noticeURI.toString());
+			int statusCode = client.executeMethod(method);
+			if (statusCode != HttpStatus.SC_OK) {
+				logger.warn("HTTP status " + statusCode + " while getting "
+						+ noticeURI);
+				return true;
+			}
+			String noticeTimeString = null;
+			Header h = method.getResponseHeader("Last-Modified");
+			message = method.getResponseBodyAsString();
+			if (h != null) {
+				noticeTimeString = h.getValue();
+				noticeTime = format.parse(noticeTimeString).getTime();
+				logger.info("NoticeTime is " + noticeTime);
+			}
+
+		} catch (URISyntaxException e) {
+			logger.error("URI problem", e);
+			return true;
+		} catch (IOException e) {
+			logger.info("Could not read notice", e);
+		} catch (ParseException e) {
+			logger.error("Could not parse last-modified time", e);
+		}
+
+		if (lastNoticeCheckFile.exists()) {
+			lastCheckedTime = lastNoticeCheckFile.lastModified();
+		}
+
+		if ((message != null) && (noticeTime != -1)) {
+			if (noticeTime > lastCheckedTime) {
+				// Show the notice dialog
+				JOptionPane.showMessageDialog(null, message, "Taverna notice",
+						JOptionPane.INFORMATION_MESSAGE,
+						WorkbenchIcons.tavernaCogs64x64Icon);
+				try {
+					FileUtils.touch(lastNoticeCheckFile);
+				} catch (IOException e) {
+					logger.error("Unable to touch file", e);
+				}
+			}
+		}
+		return true;
+	}
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java
new file mode 100644
index 0000000..3849c1b
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * Dialog that lets user know that there are updates available.
+ * 
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+public class CheckForUpdatesDialog extends HelpEnabledDialog {
+	
+	private Logger logger = Logger.getLogger(CheckForUpdatesDialog.class);
+
+	public CheckForUpdatesDialog(){
+		super((Frame)null, "Updates available", true);
+		initComponents();
+	}
+	
+	// For testing
+	public static void main (String[] args){
+		CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+		dialog.setVisible(true);
+	}
+
+	private void initComponents() {
+		// Base font for all components on the form
+		Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+		
+		// Message saying that updates are available
+		JPanel messagePanel = new JPanel(new BorderLayout());
+		messagePanel.setBorder(new CompoundBorder(new EmptyBorder(10,10,10,10), new EtchedBorder(EtchedBorder.LOWERED)));
+		JLabel message = new JLabel(
+				"<html><body>Updates are available for some Taverna components. To review and <br>install them go to 'Updates and plugins' in the 'Advanced' menu.</body><html>");
+		message.setFont(baseFont.deriveFont(12f));
+		message.setBorder(new EmptyBorder(5,5,5,5));
+		message.setIcon(UpdatesAvailableIcon.updateIcon);
+		messagePanel.add(message, BorderLayout.CENTER);
+		
+		// Buttons
+		JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+		JButton okButton = new JButton("OK"); // we'll check for updates again in 2 weeks
+		okButton.setFont(baseFont);
+		okButton.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				okPressed();
+			}
+		});
+		
+		buttonsPanel.add(okButton);
+		
+		getContentPane().setLayout(new BorderLayout());
+		getContentPane().add(messagePanel, BorderLayout.CENTER);
+		getContentPane().add(buttonsPanel, BorderLayout.SOUTH);
+
+		pack();
+		setResizable(false);
+		// Center the dialog on the screen (we do not have the parent)
+		Dimension dimension = getToolkit().getScreenSize();
+		Rectangle abounds = getBounds();
+		setLocation((dimension.width - abounds.width) / 2,
+				(dimension.height - abounds.height) / 2);
+		setSize(getPreferredSize());
+	}
+	
+	protected void okPressed() {
+	       try {
+	            FileUtils.touch(CheckForUpdatesStartupHook.lastUpdateCheckFile);
+	        } catch (IOException ioex) {
+	        	logger.error("Failed to touch the 'Last update check' file for Taverna updates.", ioex);
+	        }
+		closeDialog();		
+	}
+	
+	private void closeDialog() {
+		setVisible(false);
+		dispose();
+	}
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java
new file mode 100644
index 0000000..6e8df5f
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.io.File;
+import java.util.Date;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+
+/**
+ * Startup hook for checking if there are available updates for Taverna plugins.
+ * 
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ * 
+ */
+public class CheckForUpdatesStartupHook implements StartupSPI {
+
+	public static final String CHECK_FOR_UPDATES_DIRECTORY_NAME = "updates";
+	public static final String LAST_UPDATE_CHECK_FILE_NAME = "last_update_check";
+
+	private PluginManager pluginManager;
+	private ApplicationConfiguration applicationConfiguration;
+
+	public static File checkForUpdatesDirectory = getCheckForUpdatesDirectory();
+	public static File lastUpdateCheckFile = new File(checkForUpdatesDirectory,
+			LAST_UPDATE_CHECK_FILE_NAME);
+
+	public int positionHint() {
+		return 90;
+	}
+
+	public boolean startup() {
+
+		// Check if more than 2 weeks passed since we checked for updates.
+		if (lastUpdateCheckFile.exists()) {
+			long lastModified = lastUpdateCheckFile.lastModified();
+			long now = new Date().getTime();
+
+			if (now - lastModified < 14 * 24 * 3600 * 1000) { // 2 weeks have not passed since we
+																// last asked
+				return true;
+			} else { // Check again for updates
+				if (pluginManager.checkForUpdates()) {
+					CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+					dialog.setVisible(true);
+				}
+				return true;
+			}
+		} else {
+			// If we are here - then this is the first time to check for updates
+			if (pluginManager.checkForUpdates()) {
+				CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+				dialog.setVisible(true);
+			}
+			return true;
+		}
+	}
+
+	/**
+	 * Gets the registration directory where info about registration will be saved to.
+	 */
+	public File getCheckForUpdatesDirectory() {
+
+		File home = applicationConfiguration.getApplicationHomeDir();
+
+		File registrationDirectory = new File(home, CHECK_FOR_UPDATES_DIRECTORY_NAME);
+		if (!registrationDirectory.exists()) {
+			registrationDirectory.mkdir();
+		}
+		return registrationDirectory;
+	}
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java
new file mode 100644
index 0000000..094f25e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: PluginListCellRenderer.java,v $
+ * Revision           $Revision: 1.2 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:51:52 $
+ *               by   $Author: sowen70 $
+ * Created on 28 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.AbstractBorder;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+/**
+ *
+ * @author David Withers
+ */
+public class PluginListCellRenderer extends JPanel implements ListCellRenderer {
+
+	private static final long serialVersionUID = 1L;
+
+	private PluginManager pluginManager;
+
+	private JLabel name = null;
+
+	private JLabel description = null;
+
+	private JLabel version = null;
+
+	private JLabel status = null;
+	private JLabel status2 = null;
+
+	/**
+	 * This is the default constructor
+	 */
+	public PluginListCellRenderer(PluginManager pluginManager) {
+		super();
+		this.pluginManager = pluginManager;
+		initialize();
+	}
+
+	/**
+	 * This method initializes this
+	 *
+	 * @return void
+	 */
+	private void initialize() {
+		GridBagConstraints gridBagStatus = new GridBagConstraints();
+		gridBagStatus.gridx = 0;
+		gridBagStatus.gridwidth = 2;
+		gridBagStatus.anchor = GridBagConstraints.NORTHWEST;
+		gridBagStatus.insets = new Insets(3, 3, 3, 3);
+		gridBagStatus.gridy = 2;
+
+		GridBagConstraints gridBagStatus2 = new GridBagConstraints();
+		gridBagStatus2.gridx = 0;
+		gridBagStatus2.gridwidth = 2;
+		gridBagStatus2.anchor = GridBagConstraints.NORTHWEST;
+		gridBagStatus2.insets = new Insets(3, 3, 3, 3);
+		gridBagStatus2.gridy = 3;
+
+		status = new JLabel();
+		status.setFont(getFont().deriveFont(Font.BOLD));
+		status.setForeground(Color.BLUE);
+		status.setText("status");
+		status2 = new JLabel();
+		status2.setFont(getFont().deriveFont(Font.BOLD));
+		status2.setForeground(Color.RED);
+		status2.setText("Status");
+
+
+		GridBagConstraints gridBagVersion = new GridBagConstraints();
+		gridBagVersion.gridx = 1;
+		gridBagVersion.insets = new Insets(3, 8, 3, 3);
+		gridBagVersion.anchor = GridBagConstraints.NORTHWEST;
+		gridBagVersion.fill = GridBagConstraints.NONE;
+		gridBagVersion.gridy = 0;
+
+		version = new JLabel();
+		version.setFont(getFont().deriveFont(Font.PLAIN));
+		version.setText("Version");
+
+		GridBagConstraints gridBagDescription = new GridBagConstraints();
+		gridBagDescription.gridx = 0;
+		gridBagDescription.anchor = GridBagConstraints.NORTHWEST;
+		gridBagDescription.fill = GridBagConstraints.HORIZONTAL;
+		gridBagDescription.weightx = 1.0;
+		gridBagDescription.insets = new Insets(3, 3, 3, 3);
+		gridBagDescription.gridwidth = 2;
+		gridBagDescription.gridy = 1;
+		description = new JLabel();
+		description.setFont(getFont().deriveFont(Font.PLAIN));
+		description.setText("Plugin description");
+
+		GridBagConstraints gridBagName = new GridBagConstraints();
+		gridBagName.gridx = 0;
+		gridBagName.anchor = GridBagConstraints.NORTHWEST;
+		gridBagName.fill = GridBagConstraints.NONE;
+		gridBagName.weightx = 0.0;
+		gridBagName.ipadx = 0;
+		gridBagName.insets = new Insets(3, 3, 3, 3);
+		gridBagName.gridwidth = 1;
+		gridBagName.gridy = 0;
+		name = new JLabel();
+		name.setFont(getFont().deriveFont(Font.BOLD));
+		name.setText("Plugin name");
+
+		this.setSize(297, 97);
+		this.setLayout(new GridBagLayout());
+		this.setBorder(new AbstractBorder() {
+			public void paintBorder(Component c, Graphics g, int x, int y,
+					int width, int height) {
+				Color oldColor = g.getColor();
+				g.setColor(Color.LIGHT_GRAY);
+				g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+				g.setColor(oldColor);
+			}
+		});
+		this.add(name, gridBagName);
+		this.add(description, gridBagDescription);
+		this.add(version, gridBagVersion);
+		this.add(status, gridBagStatus);
+		this.add(status2,gridBagStatus2);
+	}
+
+	public Component getListCellRendererComponent(JList list, Object value,
+			int index, boolean isSelected, boolean cellHasFocus) {
+		if (isSelected) {
+			setBackground(list.getSelectionBackground());
+			setForeground(list.getSelectionForeground());
+		} else {
+			setBackground(list.getBackground());
+			setForeground(list.getForeground());
+		}
+
+		if (value instanceof Plugin) {
+			Plugin plugin = (Plugin) value;
+			name.setText(plugin.getName());
+			version.setText(plugin.getVersion());
+			description.setText("<html>"+plugin.getDescription());
+
+			status2.setText("");
+			if (!plugin.isCompatible()) {
+				status2.setText("This plugin is incompatible.");
+			}
+
+			status.setText("");
+			if (pluginManager.isUpdateAvailable(plugin)) {
+				status.setText("An update is available for this plugin");
+			} else if (!plugin.isEnabled()) {
+				status.setText("This plugin is disabled");
+			}
+		}
+		return this;
+	}
+} // @jve:decl-index=0:visual-constraint="10,10"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java
new file mode 100644
index 0000000..5da76b3
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: PluginListModel.java,v $
+ * Revision           $Revision: 1.2 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:51:52 $
+ *               by   $Author: sowen70 $
+ * Created on 28 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import javax.swing.AbstractListModel;
+
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class PluginListModel extends AbstractListModel implements PluginManagerListener {
+	private PluginManager pluginManager;
+
+	private static Logger logger = Logger.getLogger(PluginListModel.class);
+
+	public PluginListModel(PluginManager pluginManager) {
+		this.pluginManager = pluginManager;
+		PluginManager.addPluginManagerListener(this);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.swing.ListModel#getElementAt(int)
+	 */
+	public Object getElementAt(int index) {
+		return pluginManager.getPlugins().get(index);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.swing.ListModel#getSize()
+	 */
+	public int getSize() {
+		return pluginManager.getPlugins().size();
+	}
+
+	public void pluginAdded(PluginManagerEvent event) {
+		fireIntervalAdded(this, event.getPluginIndex(), event.getPluginIndex());
+	}
+
+	public void pluginRemoved(PluginManagerEvent event) {
+		fireIntervalRemoved(this, event.getPluginIndex(), event.getPluginIndex());
+	}
+
+	public void pluginUpdated(PluginManagerEvent event) {
+		//fireContentsChanged(this, event.getPluginIndex(), event.getPluginIndex());
+	}
+
+	public void pluginStateChanged(PluginManagerEvent event) {
+		fireContentsChanged(this, event.getPluginIndex(), event.getPluginIndex());
+	}
+
+	public void pluginIncompatible(PluginManagerEvent event) {
+
+	}
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java
new file mode 100644
index 0000000..f9f374e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester 
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: PluginManagerFrame.java,v $
+ * Revision           $Revision: 1.3 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:51:52 $
+ *               by   $Author: sowen70 $
+ * Created on 27 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * GUI component for the <code>PluginManager</code>.
+ * 
+ * @author David Withers
+ */
+public class PluginManagerFrame extends HelpEnabledDialog {
+
+	private static final long serialVersionUID = 1L;
+
+	private JPanel jContentPane = null;
+
+	private JButton updateButton = null;
+
+	private JButton findPluginsButton = null;
+
+	private PluginManager pluginManager;
+
+	private JScrollPane jScrollPane = null;
+
+	private JList jList = null;
+
+	private JButton enableButton = null;
+
+	private JButton uninstallButton = null;
+
+	private JButton findUpdatesButton = null;
+	
+	private JButton closeButton = null;
+	
+	private PluginManagerListener managerListener;
+	
+	/**
+	 * This is the default constructor
+	 */
+	public PluginManagerFrame(PluginManager pluginManager) {
+		this((Frame)null, pluginManager);
+	}
+
+	/**
+	 * This is the default constructor
+	 */
+	public PluginManagerFrame(Frame parent,PluginManager pluginManager) {
+		super(parent, "Plugin manager", true);
+		this.pluginManager = pluginManager;
+		initialize();
+	}
+	
+	/**
+	 * This is the default constructor
+	 */
+	public PluginManagerFrame(JDialog parent,PluginManager pluginManager) {
+		super(parent, "Plugin manager", true);
+		this.pluginManager = pluginManager;
+		initialize();
+	}
+
+	/**
+	 * This method initializes this
+	 * 
+	 * @return void
+	 */
+	private void initialize() {
+		this.setSize(613, 444);
+		this.setContentPane(getJContentPane());
+		this.setTitle("Updates and plugins");
+		this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+		this.addWindowListener(new WindowAdapter() {
+
+			@Override
+			public void windowClosed(WindowEvent e) {
+				if (managerListener!=null) PluginManager.removePluginManagerListener(managerListener);
+			}
+			
+		});
+		managerListener = new PluginManagerListener() {
+
+			public void pluginAdded(PluginManagerEvent event) {
+				// For some reason even if a plugin does not declare dependencies to system 
+				// artifacts it is required for Taverna to be restarted to pick the new plugin up
+				// (probably because things like Service Panel and Perspectives where new plugins can
+				// have some effect are not listening to plugin changes). 
+				// So, we have to show the "Restart Taverna" message in any case until this is fixed.
+				//if (event.getPlugin().getProfile().getSystemArtifacts().size()!=0) {
+					JOptionPane.showMessageDialog(PluginManagerFrame.this,"The plugin '"+event.getPlugin().getName()+"' will not be fully functional until Taverna is restarted","Restart Required", JOptionPane.WARNING_MESSAGE);
+				//}
+			}
+
+			public void pluginStateChanged(PluginManagerEvent event)
+			{
+				// As in the pluginAdded() method, it is currently always required 
+				// to restart Taverna for any changes to the plugins to take effect
+				// (probably because things like Service Panel and Perspectives where 
+				// changes to plugin can have some effect are not listening to these changes). 
+				// So, we have to show the "Restart Taverna" message in any case until this is fixed.
+				//if (event.getPlugin().getProfile().getSystemArtifacts().size()!=0) {
+					if (event.getSource() instanceof PluginEvent) {
+						PluginEvent pluginEvent = (PluginEvent)event.getSource();
+						if (pluginEvent.getAction()==PluginEvent.ENABLED) {
+							JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely enabled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+						}
+						else if (pluginEvent.getAction()==PluginEvent.DISABLED) {
+							JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely disabled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+						}
+					}
+				//}
+			}
+
+			public void pluginIncompatible(PluginManagerEvent event) {
+				
+			}
+
+			public void pluginUpdated(PluginManagerEvent event) {
+				JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely updated until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+			}
+			
+			public void pluginRemoved(PluginManagerEvent event) {
+				JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely uninstalled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+			}
+			
+		};
+		PluginManager.addPluginManagerListener(managerListener);
+		
+	}
+
+	/**
+	 * This method initializes jContentPane
+	 * 
+	 * @return javax.swing.JPanel
+	 */
+	private JPanel getJContentPane() {
+		if (jContentPane == null) {
+			GridBagConstraints findUpdatesConstraints = new GridBagConstraints();
+			findUpdatesConstraints.gridx = 0;
+			findUpdatesConstraints.insets = new Insets(5, 5, 5, 5);
+			findUpdatesConstraints.gridy = 3;
+			GridBagConstraints uninstallButtonConstraints = new GridBagConstraints();
+			uninstallButtonConstraints.gridx = 2;
+			uninstallButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+			uninstallButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+			uninstallButtonConstraints.insets = new Insets(5, 0, 0, 5);
+			uninstallButtonConstraints.gridy = 1;
+			GridBagConstraints enableButtonConstraints = new GridBagConstraints();
+			enableButtonConstraints.gridx = 2;
+			enableButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+			enableButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+			enableButtonConstraints.insets = new Insets(5, 0, 0, 5);
+			enableButtonConstraints.gridy = 0;
+			GridBagConstraints scrollPaneConstraints = new GridBagConstraints();
+			scrollPaneConstraints.fill = GridBagConstraints.BOTH;
+			scrollPaneConstraints.gridy = 0;
+			scrollPaneConstraints.weightx = 1.0;
+			scrollPaneConstraints.weighty = 1.0;
+			scrollPaneConstraints.gridwidth = 2;
+			scrollPaneConstraints.insets = new Insets(5, 5, 5, 5);
+			scrollPaneConstraints.gridx = 0;
+			scrollPaneConstraints.gridheight = 3;
+			scrollPaneConstraints.anchor = GridBagConstraints.NORTHWEST;
+			GridBagConstraints findPluginsConstraints = new GridBagConstraints();
+			findPluginsConstraints.gridx = 1;
+			findPluginsConstraints.anchor = GridBagConstraints.WEST;
+			findPluginsConstraints.insets = new Insets(5, 5, 5, 5);
+			findPluginsConstraints.gridy = 3;
+			GridBagConstraints updateButtonConstraints = new GridBagConstraints();
+			updateButtonConstraints.gridx = 2;
+			updateButtonConstraints.gridwidth = 1;
+			updateButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+			updateButtonConstraints.insets = new Insets(5, 0, 0, 5);
+			updateButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+			updateButtonConstraints.gridy = 2;
+			
+			GridBagConstraints closeButtonConstraints = new GridBagConstraints();
+			closeButtonConstraints.gridx = 2;
+			closeButtonConstraints.insets = new Insets(5, 5, 5, 5);
+			closeButtonConstraints.gridy = 3;
+			closeButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+//			closeButtonConstraints.gridx = 2;
+//			closeButtonConstraints.gridwidth = 1;
+//			closeButtonConstraints.anchor = GridBagConstraints.SOUTHEAST;
+//			closeButtonConstraints.insets = new Insets(5, 0, 0, 5);
+//			closeButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+//			closeButtonConstraints.gridy = 3;
+			
+			
+			jContentPane = new JPanel();
+			jContentPane.setLayout(new GridBagLayout());
+			jContentPane.add(getUpdateButton(), updateButtonConstraints);
+			jContentPane.add(getFindPluginsButton(), findPluginsConstraints);
+			jContentPane.add(getJScrollPane(), scrollPaneConstraints);
+			jContentPane.add(getEnableButton(), enableButtonConstraints);
+			jContentPane.add(getUninstallButton(), uninstallButtonConstraints);
+			jContentPane.add(getFindUpdatesButton(), findUpdatesConstraints);
+			jContentPane.add(getCloseButton(),closeButtonConstraints);
+		}
+		return jContentPane;
+	}
+
+	/**
+	 * This method initializes jButton
+	 * 
+	 * @return javax.swing.JButton
+	 */
+	private JButton getUpdateButton() {
+		if (updateButton == null) {
+			updateButton = new JButton();
+			updateButton.setText("Update");
+			updateButton.setEnabled(false);
+			updateButton.addActionListener(new java.awt.event.ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					Object selectedObject = getJList().getSelectedValue();
+					if (selectedObject instanceof Plugin) {
+						pluginManager.updatePlugin((Plugin) selectedObject);
+					}
+					jList.setSelectedValue(selectedObject, true);
+					updateButton.setEnabled(false);
+				}
+			});
+		}
+		return updateButton;
+	}
+
+	/**
+	 * This method initializes jScrollPane
+	 * 
+	 * @return javax.swing.JScrollPane
+	 */
+	private JScrollPane getJScrollPane() {
+		if (jScrollPane == null) {
+			jScrollPane = new JScrollPane();
+			jScrollPane.setViewportView(getJList());
+		}
+		return jScrollPane;
+	}
+
+	/**
+	 * This method initializes jList
+	 * 
+	 * @return javax.swing.JList
+	 */
+	private JList getJList() {
+		if (jList == null) {
+			jList = new JList();
+			jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+			jList.setModel(new PluginListModel(pluginManager));
+			jList.setSelectionBackground(new Color(135,206,250)); //LightSkyBlue 
+			jList.setCellRenderer(new PluginListCellRenderer(pluginManager));
+			jList.addListSelectionListener(new ListSelectionListener() {
+
+				public void valueChanged(ListSelectionEvent e) {
+					if (!e.getValueIsAdjusting()) {
+						respondToSelectedPlugin();
+					}
+				}
+
+			});
+			if (jList.getComponentCount() > 0) {
+				jList.setSelectedIndex(0);
+				respondToSelectedPlugin();
+			}
+		}
+		return jList;
+	}
+
+	/**
+	 * This method initializes jButton2
+	 * 
+	 * @return javax.swing.JButton
+	 */
+	private JButton getEnableButton() {
+		if (enableButton == null) {
+			enableButton = new JButton();
+			enableButton.setText("Enable");
+			enableButton.setEnabled(false);
+			enableButton.setActionCommand("enable");
+			enableButton.addActionListener(new java.awt.event.ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					Object selectedObject = jList.getSelectedValue();
+					if (selectedObject instanceof Plugin) {
+						Plugin plugin = (Plugin) selectedObject;
+						if ("enable".equals(e.getActionCommand())) {
+							plugin.setEnabled(true);
+							enableButton.setText("Disable");
+							enableButton.setActionCommand("disable");
+						} else if ("disable".equals(e.getActionCommand())) {
+							plugin.setEnabled(false);
+							enableButton.setText("Enable");
+							enableButton.setActionCommand("enable");
+						}
+					}
+					jList.setSelectedValue(selectedObject, true);
+				}
+			});
+		}
+		return enableButton;
+	}
+
+	/**
+	 * This method initializes jButton3
+	 * 
+	 * @return javax.swing.JButton
+	 */
+	private JButton getUninstallButton() {
+		if (uninstallButton == null) {
+			uninstallButton = new JButton();
+			uninstallButton.setText("Uninstall");
+			uninstallButton.addActionListener(new java.awt.event.ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					int index = jList.getSelectedIndex();
+					Object selectedObject = jList.getSelectedValue();
+					if (selectedObject instanceof Plugin) {
+						pluginManager.removePlugin((Plugin) selectedObject);
+						pluginManager.savePlugins();
+						}
+					int listSize = jList.getModel().getSize();
+					if (listSize > index) {
+						jList.setSelectedIndex(index);
+					} else {
+						jList.setSelectedIndex(listSize - 1);
+					}
+				}
+			});
+		}
+		return uninstallButton;
+	}
+
+	private JButton getCloseButton() {
+		if (closeButton==null) {
+			closeButton = new JButton("Close");
+			closeButton.setEnabled(true);
+			closeButton.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					setVisible(false);
+					dispose();
+				}				
+			});
+		}
+		return closeButton;
+	} 
+	
+	/**
+	 * This method initializes jButton1
+	 * 
+	 * @return javax.swing.JButton
+	 */
+	private JButton getFindPluginsButton() {
+		if (findPluginsButton == null) {
+			findPluginsButton = new JButton();
+			findPluginsButton.setText("Find New Plugins");
+			findPluginsButton.addActionListener(new java.awt.event.ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					Object selectedObject = getJList().getSelectedValue();
+					PluginSiteFrame pluginSiteFrame = new PluginSiteFrame(PluginManagerFrame.this);
+					pluginSiteFrame.setLocationRelativeTo(PluginManagerFrame.this);
+					pluginSiteFrame.setVisible(true);
+					if (selectedObject != null) {
+						jList.setSelectedValue(selectedObject, true);
+					} else {
+						jList.setSelectedIndex(0);
+					}
+				}
+			});
+		}
+		return findPluginsButton;
+	}
+
+	/**
+	 * This method initializes jButton4	
+	 * 	
+	 * @return javax.swing.JButton	
+	 */
+	private JButton getFindUpdatesButton() {
+		if (findUpdatesButton == null) {
+			findUpdatesButton = new JButton();
+			findUpdatesButton.setText("Find Updates");
+			findUpdatesButton.addActionListener(new java.awt.event.ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					Object selectedObject = getJList().getSelectedValue();
+					if (!pluginManager.checkForUpdates()) {
+						JOptionPane.showMessageDialog(PluginManagerFrame.this, "No updates available");
+					}
+					if (selectedObject != null) {
+						jList.setSelectedValue(selectedObject, true);
+					} else {
+						jList.setSelectedIndex(0);
+					}
+					// Respond to selected plugin - i.e. enable/disable action buttons as appropriate 
+					respondToSelectedPlugin();
+				}
+			});
+		}
+		return findUpdatesButton;
+	}
+
+	private void respondToSelectedPlugin() {
+		Object selectedObject = jList.getSelectedValue();
+		if (selectedObject!=null && selectedObject instanceof Plugin) {
+			Plugin plugin = (Plugin) selectedObject;
+			
+			// If this is a build-in plugin - set the text of the enableButton
+			// to 'Disable' but also disable the button to indicate that
+			// built-in plugins cannot be disabled.
+			// Similarly, uninstallButton should be disabled in this case.
+			if (plugin.isBuiltIn()){
+				getEnableButton().setText("Disable");
+				getEnableButton().setActionCommand("disable");
+				getEnableButton().setEnabled(false);
+			}
+			else{
+				if (plugin.isEnabled()) {
+					getEnableButton().setText("Disable");
+					getEnableButton().setActionCommand("disable");
+				} else {
+					getEnableButton().setText("Enable");
+					getEnableButton().setActionCommand("enable");
+				}
+				
+				//only allow plugin to be enabled if it is compatible.
+				if (plugin.isCompatible()) {								
+					getEnableButton().setEnabled(true);
+				}
+				else {
+					getEnableButton().setEnabled(false);
+				}
+			}
+
+			if (pluginManager.isUpdateAvailable(plugin)) {
+				getUpdateButton().setEnabled(true);
+			} else {
+				getUpdateButton().setEnabled(false);
+			}
+			
+			//disable the uninstall button if this is a built in plugin
+			getUninstallButton().setEnabled(!plugin.isBuiltIn());
+		}
+	}
+
+} // @jve:decl-index=0:visual-constraint="33,9"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java
new file mode 100644
index 0000000..c952e9c
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester 
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: PluginRepositoryListener.java,v $
+ * Revision           $Revision: 1.2 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:51:52 $
+ *               by   $Author: sowen70 $
+ * Created on 7 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import javax.swing.JProgressBar;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.raven.RavenException;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.repository.Artifact;
+import net.sf.taverna.raven.repository.ArtifactStatus;
+import net.sf.taverna.raven.repository.DownloadStatus;
+import net.sf.taverna.raven.repository.RepositoryListener;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class PluginRepositoryListener implements
+		RepositoryListener {
+	
+	private final JProgressBar bar = new JProgressBar();
+
+	private static Logger logger = Logger
+			.getLogger(PluginRepositoryListener.class);
+
+	public PluginRepositoryListener() {
+		bar.setMaximum(100);
+		bar.setMinimum(0);
+		bar.setStringPainted(true);
+	}
+
+	public void statusChanged(final Artifact a, ArtifactStatus oldStatus,
+			ArtifactStatus newStatus) {
+
+		bar.setString(a.getArtifactId() + "-" + a.getVersion() + " : "
+				+ newStatus.toString());
+
+		if (newStatus.equals(ArtifactStatus.JarFetching)) {
+
+			final DownloadStatus dls;
+			try {
+				dls = PluginManager.getInstance().getRepository()
+						.getDownloadStatus(a);
+			} catch (RavenException ex) {
+				logger.warn("Could not get download status for: " + a, ex);
+				return;
+			}
+
+			Thread progressThread = new Thread(new Runnable() {
+				public void run() {
+					while (true) {
+						try {
+							Thread.sleep(100);
+						} catch (InterruptedException e) {
+							logger.warn("Progress thread interrupted", e);
+							return;
+						}
+						int progress = Math.min(100, (dls.getReadBytes() * 100)
+								/ dls.getTotalBytes());						
+						setProgress(progress);
+						if (dls.isFinished()) {
+							return;
+						}
+					}
+				}
+			}, "Update repository progress bar");
+			progressThread.start();
+		}
+	}
+	
+
+	public JProgressBar getProgressBar() {
+		return bar;
+	}
+	
+	public void setVisible(final boolean val) {
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {				
+				bar.setVisible(val);
+			}			
+		});
+		
+	}
+	
+	public void setProgress(final int percentage) {
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {				
+				if (percentage > 0) {
+					bar.setValue(percentage);
+				} else {
+					bar.setValue(0);
+				}	
+			}			
+		});
+					
+	}
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java
new file mode 100644
index 0000000..ecdb06e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java
@@ -0,0 +1,542 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: PluginSiteFrame.java,v $
+ * Revision           $Revision: 1.3 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/10/27 13:39:56 $
+ *               by   $Author: stain $
+ * Created on 29 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.border.EtchedBorder;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+
+/**
+ *
+ * @author David Withers
+ */
+public class PluginSiteFrame extends HelpEnabledDialog {
+
+	private static final long serialVersionUID = 1L;
+
+	private PluginManager pluginManager;
+
+	private List<PluginSite> pluginSites;
+
+	private List<Plugin> installationScheduled = new ArrayList<Plugin>(); // @jve:decl-index=0:
+
+	private JButton installButton = null; // @jve:decl-index=0:visual-constraint="446,247"
+
+	private JButton cancelButton = null;
+
+	private JButton addSiteButton = null;
+
+	private Map<Plugin, PluginRepositoryListener> listeners = new HashMap<Plugin, PluginRepositoryListener>();
+
+	private AddPluginSiteFrame addSiteFrame = null;
+
+	private JScrollPane scrollPane = null;
+
+	/**
+	 * This is the default constructor
+	 */
+	public PluginSiteFrame(Frame owner) {
+		super(owner, "Update sites", true);
+		initialize();
+	}
+
+	/**
+	 * This is the default constructor
+	 */
+	public PluginSiteFrame(JDialog owner) {
+		super(owner, "Update sites", true);
+		initialize();
+	}
+
+	/**
+	 * This method initializes this
+	 *
+	 * @return void
+	 */
+	private void initialize() {
+		this.pluginManager = PluginManager.getInstance();
+		this.pluginSites = this.pluginManager.getPluginSites();
+		this.setSize(600, 450);
+		this.setContentPane(getJContentPane());
+	}
+
+	/**
+	 * This method initializes jContentPane
+	 *
+	 * @return javax.swing.JPanel
+	 */
+	private JPanel getJContentPane() {
+		GridBagConstraints gridBagConstraints14 = new GridBagConstraints();
+		gridBagConstraints14.gridx = 1;
+		gridBagConstraints14.anchor = GridBagConstraints.SOUTHWEST;
+		gridBagConstraints14.gridy = GridBagConstraints.RELATIVE;
+		gridBagConstraints14.insets = new Insets(5, 5, 5, 5);
+		GridBagConstraints gridBagConstraints12 = new GridBagConstraints();
+		gridBagConstraints12.fill = GridBagConstraints.BOTH;
+		gridBagConstraints12.gridx = 0;
+		gridBagConstraints12.gridy = 0;
+		gridBagConstraints12.weightx = 1.0;
+		gridBagConstraints12.weighty = 1.0;
+		gridBagConstraints12.gridwidth = 3;
+		gridBagConstraints12.anchor = GridBagConstraints.NORTHWEST;
+		GridBagConstraints gridBagConstraints = new GridBagConstraints();
+		gridBagConstraints.gridx = 0;
+		gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+		gridBagConstraints.anchor = GridBagConstraints.SOUTHWEST;
+		gridBagConstraints.insets = new Insets(5, 5, 5, 5);
+		GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+		gridBagConstraints1.gridx = 2;
+		gridBagConstraints1.gridy = GridBagConstraints.RELATIVE;
+		gridBagConstraints1.anchor = GridBagConstraints.SOUTHEAST;
+		gridBagConstraints1.insets = new Insets(5, 5, 5, 5);
+		JPanel jContentPane = new JPanel();
+		jContentPane.setLayout(new GridBagLayout());
+		jContentPane.add(getJScrollPane(), gridBagConstraints12);
+		jContentPane.add(getInstallButton(), gridBagConstraints);
+		jContentPane.add(getCloseButton(), gridBagConstraints1);
+		jContentPane.add(getAddPluginSiteButton(), gridBagConstraints14);
+		return jContentPane;
+	}
+
+	/**
+	 * This method initializes jScrollPane
+	 *
+	 * @return javax.swing.JScrollPane
+	 */
+	private JScrollPane getJScrollPane() {
+		scrollPane = new JScrollPane();
+		scrollPane.setViewportView(getJPanel());
+		return scrollPane;
+	}
+
+	/**
+	 * This method initializes jPanel
+	 *
+	 * @return javax.swing.JPanel
+	 */
+	private JPanel getJPanel() {
+		JPanel jPanel = new JPanel();
+		jPanel.setLayout(new GridBagLayout());
+		for (PluginSite pluginSite : pluginSites) {
+			GridBagConstraints gridBagConstraints = new GridBagConstraints();
+			gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+			gridBagConstraints.gridx = 0;
+			gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+			gridBagConstraints.weightx = 1.0;
+			gridBagConstraints.weighty = 0.0;
+			gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
+			gridBagConstraints.insets = new Insets(5, 5, 0, 5);
+			jPanel.add(getJPanel1(pluginSite), gridBagConstraints);
+		}
+		GridBagConstraints gridBagConstraints = new GridBagConstraints();
+		gridBagConstraints.fill = GridBagConstraints.BOTH;
+		gridBagConstraints.gridx = 0;
+		gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+		gridBagConstraints.weighty = 1.0;
+		jPanel.add(new JPanel(), gridBagConstraints);
+		return jPanel;
+	}
+
+	private JPanel getJPanel1(final PluginSite pluginSite) {
+		final JPanel pluginSitePanel = new JPanel();
+		pluginSitePanel.setBackground(Color.WHITE);
+		pluginSitePanel.setLayout(new GridBagLayout());
+		GridBagConstraints gridBagConstraints = new GridBagConstraints();
+		gridBagConstraints.gridx = 0;
+		gridBagConstraints.gridy = 0;
+		gridBagConstraints.anchor = GridBagConstraints.WEST;
+		gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+		gridBagConstraints.gridwidth = 2;
+		gridBagConstraints.insets = new Insets(5, 5, 5, 5);
+		gridBagConstraints.ipadx = 5;
+		gridBagConstraints.ipady = 5;
+		gridBagConstraints.weightx = 1.0;
+		//ShadedLabel siteNameLabel = getSiteLabel(pluginSite);
+		JLabel siteNameLabel = getSiteLabel(pluginSite);
+
+		pluginSitePanel.add(siteNameLabel, gridBagConstraints);
+
+		final GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+		gridBagConstraints1.gridx = 0;
+		gridBagConstraints1.gridy = 1;
+		gridBagConstraints1.anchor = GridBagConstraints.WEST;
+		gridBagConstraints1.fill = GridBagConstraints.HORIZONTAL;
+		gridBagConstraints1.gridwidth = 2;
+		gridBagConstraints1.insets = new Insets(5, 5, 5, 5);
+		gridBagConstraints1.weightx = 1.0;
+		final JProgressBar progressBar = new JProgressBar();
+		progressBar.setIndeterminate(true);
+		progressBar.setStringPainted(true);
+		progressBar.setString("Checking for new plugins");
+		pluginSitePanel.add(progressBar, gridBagConstraints1);
+
+		new Thread("Checking update site " + pluginSite) {
+			public void run() {
+				try {
+					List<Plugin> plugins = pluginManager
+							.getUninstalledPluginsFromSite(pluginSite);
+					if (plugins.size() > 0) {
+						Collections.sort(plugins, new Comparator<Plugin>() {
+
+							public int compare(Plugin o1, Plugin o2) {
+								return o1.getName().compareTo(o2.getName());
+							}
+
+						});
+
+						pluginSitePanel.remove(progressBar);
+						int gridY = 0;
+						for (Plugin plugin : plugins) {
+							gridY++;
+							GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+							gridBagConstraints1.gridx = 0;
+							gridBagConstraints1.gridy = gridY;
+							gridBagConstraints1.anchor = GridBagConstraints.WEST;
+							gridBagConstraints1.insets = new Insets(5, 20, 0, 0);
+							pluginSitePanel.add(getJCheckBox(plugin),
+									gridBagConstraints1);
+
+							gridY++;
+
+							GridBagConstraints gridBagConstraintsDescription = new GridBagConstraints();
+							gridBagConstraintsDescription.gridx = 0;
+							gridBagConstraintsDescription.ipadx = 50;
+							gridBagConstraintsDescription.gridy = gridY;
+							gridBagConstraintsDescription.anchor = GridBagConstraints.WEST;
+							gridBagConstraintsDescription.insets = new Insets(5, 25, 10, 5);
+
+							gridY++;
+
+							GridBagConstraints gridBagConstraintsProgress = new GridBagConstraints();
+							gridBagConstraintsProgress.gridx = 0;
+							gridBagConstraintsProgress.gridy = gridY;
+							gridBagConstraintsProgress.fill = GridBagConstraints.HORIZONTAL;
+							gridBagConstraintsProgress.anchor = GridBagConstraints.WEST;
+							gridBagConstraintsProgress.insets = new Insets(5, 5, 0, 0);
+
+							JLabel description = new JLabel();
+							description.setText("<html>"+plugin.getDescription());
+							description.setFont(getFont().deriveFont(Font.PLAIN));
+							pluginSitePanel.add(description,gridBagConstraintsDescription);
+
+							PluginRepositoryListener progress = new PluginRepositoryListener();
+							listeners.put(plugin, progress);
+							progress.setVisible(false);
+
+							pluginSitePanel.add(progress.getProgressBar(),
+									gridBagConstraintsProgress);
+						}
+					} else {
+						pluginSitePanel.remove(progressBar);
+						pluginSitePanel.add(new JLabel(
+								"This update site contains no new plugins"),
+								gridBagConstraints1);
+					}
+				} catch (Exception e) {
+					pluginSitePanel.remove(progressBar);
+					pluginSitePanel.add(new JLabel(
+							"Unable to contact the update site"),
+							gridBagConstraints1);
+				} finally {
+					pluginSitePanel.revalidate();
+					pluginSitePanel.repaint();
+				}
+			}
+		}.start();
+
+		pluginSitePanel.setBorder(new EtchedBorder());
+		return pluginSitePanel;
+	}
+
+	private JLabel getSiteLabel(final PluginSite pluginSite) {
+		//ShadedLabel result = new ShadedLabel(pluginSite.getName(),Color.LIGHT_GRAY);
+		JLabel result = new JLabel(pluginSite.getName());
+
+
+		//add popup remove option, except on the Taverna main plugin site.
+		if (!(pluginSite instanceof TavernaPluginSite)) {
+			result.addMouseListener(new MouseAdapter() {
+				@Override
+				public void mouseClicked(MouseEvent e) {
+					popup(e);
+				}
+
+				@Override
+				public void mousePressed(MouseEvent e) {
+					popup(e);
+				}
+
+				private void popup(MouseEvent e) {
+					if (e.isPopupTrigger()) {
+						JPopupMenu menu=new JPopupMenu();
+						//JMenuItem item = new JMenuItem("Remove site",TavernaIcons.deleteIcon);
+						JMenuItem item = new JMenuItem("Remove site");
+
+						menu.add(item);
+						menu.show(e.getComponent(), e.getX(), e.getY());
+						item.addActionListener(new ActionListener() {
+
+							public void actionPerformed(ActionEvent e) {
+								int response=JOptionPane.showConfirmDialog(PluginSiteFrame.this, "Are you sure you want to remove the update site:"+pluginSite.getName(),"Remove update site",JOptionPane.YES_NO_OPTION);
+								if (response==JOptionPane.YES_OPTION) {
+									pluginManager.removePluginSite(pluginSite);
+									pluginManager.savePluginSites();
+									scrollPane.setViewportView(getJPanel());
+								}
+							}
+						});
+					}
+				}
+			});
+		}
+		return result;
+	}
+
+	/**
+	 * This method initializes jButton
+	 *
+	 * @return javax.swing.JButton
+	 */
+	private JCheckBox getJCheckBox(Plugin plugin) {
+		PluginCheckBox checkBox = new PluginCheckBox();
+		checkBox.plugin = plugin;
+		checkBox.setText(plugin.getName() + " " + plugin.getVersion());
+		checkBox.setOpaque(false);
+		checkBox.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				if (e.getSource() instanceof PluginCheckBox) {
+					PluginCheckBox checkBox = (PluginCheckBox) e.getSource();
+					if (checkBox.isSelected()) {
+						installationScheduled.add(checkBox.plugin);
+
+
+
+						getInstallButton().setEnabled(true);
+					} else {
+						installationScheduled.remove(checkBox.plugin);
+						if (installationScheduled.size() == 0) {
+							getInstallButton().setEnabled(false);
+						}
+					}
+				}
+			}
+
+		});
+		return checkBox;
+	}
+
+	private final Thread getUpdateRepositoryThread() {
+		return new Thread("Update Repository Progress") {
+
+			public void run() {
+				cancelButton.setEnabled(false);
+				for (int i = 0; i < installationScheduled.size(); i++) {
+					final Plugin plugin = installationScheduled.get(i);
+					PluginRepositoryListener listener = listeners.get(plugin);
+					if (listener != null) {
+						pluginManager.getRepository().addRepositoryListener(
+								listener);
+						listener.getProgressBar().setVisible(true);
+					}
+
+					pluginManager.addPlugin(plugin);
+
+					if (listener != null) {
+						pluginManager.getRepository().removeRepositoryListener(
+								listener);
+						listener.getProgressBar().setVisible(false);
+					}
+					plugin.setEnabled(true);
+
+				}
+				pluginManager.savePlugins();
+				installationScheduled.clear();
+				setVisible(false);
+				dispose();
+			}
+
+		};
+	}
+
+	/**
+	 * This method initializes jButton
+	 *
+	 * @return javax.swing.JButton
+	 */
+	private JButton getInstallButton() {
+		if (installButton == null) {
+			installButton = new JButton();
+			installButton.setText("Install");
+			installButton.setEnabled(false);
+			installButton
+					.addActionListener(new java.awt.event.ActionListener() {
+						public void actionPerformed(java.awt.event.ActionEvent e) {
+							installButton.setEnabled(false);
+							getUpdateRepositoryThread().start();
+						}
+
+					});
+		}
+		return installButton;
+	}
+
+	/**
+	 * This method initializes jButton
+	 *
+	 * @return javax.swing.JButton
+	 */
+	private JButton getCloseButton() {
+		if (cancelButton == null) {
+			cancelButton = new JButton();
+			cancelButton.setText("Close");
+			cancelButton.addActionListener(new ActionListener() {
+				public void actionPerformed(java.awt.event.ActionEvent e) {
+					setVisible(false);
+					dispose();
+				}
+			});
+		}
+		return cancelButton;
+	}
+
+	@SuppressWarnings("serial")
+	class PluginCheckBox extends JCheckBox {
+		public Plugin plugin;
+	}
+
+	private final void addPluginSite() {
+
+		addSiteFrame=new AddPluginSiteFrame(this);
+
+		addSiteFrame.setLocationRelativeTo(this);
+		addSiteFrame.setVisible(true);
+
+		if (addSiteFrame.getName()!=null) {
+			if (addSiteFrame.getName().length()==0) {
+				JOptionPane.showMessageDialog(this, "You must provide a name for your site.","Error adding update site",JOptionPane.ERROR_MESSAGE);
+				addPluginSite();
+			}
+			else {
+				if (addSiteFrame.getUrl()!=null) {
+					try {
+						PluginSite site = new PluginSite(addSiteFrame.getName(), new URL(addSiteFrame.getUrl()));
+						pluginManager.addPluginSite(site);
+						pluginManager.savePluginSites();
+
+						//refresh
+						scrollPane.setViewportView(getJPanel());
+						addSiteFrame=null; //so that the name and url are reset.
+					}
+					catch(Exception e) {
+						JOptionPane.showMessageDialog(this, "There was a problem adding the site you provided: "+e.getMessage(),"Error adding update site",JOptionPane.ERROR_MESSAGE);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * This method initializes jButton
+	 *
+	 * @return javax.swing.JButton
+	 */
+	private JButton getAddPluginSiteButton() {
+		if (addSiteButton == null) {
+			addSiteButton = new JButton();
+			addSiteButton.setText("Add update site");
+			addSiteButton.setEnabled(true);
+			addSiteButton.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					addSiteButton.setEnabled(false);
+					addPluginSite();
+					addSiteButton.setEnabled(true);
+				}
+			});
+
+		}
+		return addSiteButton;
+	}
+
+} // @jve:decl-index=0:visual-constraint="10,10"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java
new file mode 100644
index 0000000..a0b15bf
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: UpdatesAvailableIcon.java,v $
+ * Revision           $Revision: 1.5 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/12/01 12:32:40 $
+ *               by   $Author: alaninmcr $
+ * Created on 12 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Component;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+
+import org.apache.log4j.Logger;
+
+/**
+ * A JLabel that periodically checks for updates, running on a daemon thread. If
+ * updates are available it makes itself visible and responds to click events to
+ * display the appropriate update response.
+ *
+ * Also acts as a pluginmanager listener to refresh itself whenever a new plugin
+ * is added.
+ *
+ * @author Stuart Owen
+ *
+ */
+
+@SuppressWarnings("serial")
+public class UpdatesAvailableIcon extends JLabel implements PluginManagerListener {
+
+	private UpdatePluginsMouseAdaptor updatePluginMouseAdaptor = new UpdatePluginsMouseAdaptor();
+	private static Logger logger = Logger.getLogger(UpdatesAvailableIcon.class);
+
+	private final int CHECK_INTERVAL = 1800000; // every 30 minutes
+
+	public static final Icon updateIcon = new ImageIcon(
+			UpdatesAvailableIcon.class.getResource("update.png"));
+	public static final Icon updateRecommendedIcon = new ImageIcon(
+			UpdatesAvailableIcon.class.getResource("updateRecommended.png"));
+
+	public UpdatesAvailableIcon() {
+		super();
+		setVisible(false);
+
+		startCheckThread();
+		PluginManager.addPluginManagerListener(this);
+	}
+
+	public void pluginAdded(PluginManagerEvent event) {
+		logger.info("Plugin Added");
+		if (!isVisible())
+			checkForUpdates();
+	}
+
+	public void pluginStateChanged(PluginManagerEvent event) {
+
+	}
+
+	public void pluginUpdated(PluginManagerEvent event) {
+		logger.info("Plugin Updated");
+	}
+
+	public void pluginRemoved(PluginManagerEvent event) {
+		logger.info("Plugin Removed");
+		if (isVisible())
+			checkForUpdates();
+	}
+
+	public void pluginIncompatible(PluginManagerEvent event) {
+		logger
+				.warn("Plugin found to be incompatible with the current version of Taverna: "
+						+ event.getPlugin());
+	}
+
+	private void startCheckThread() {
+		Thread checkThread = new Thread("Check for updates thread") {
+
+			@Override
+			public void run() {
+				while (true) {
+					try {
+						checkForUpdates();
+						Thread.sleep(CHECK_INTERVAL);
+					} catch (InterruptedException e) {
+						logger.warn("Interruption exception in checking for updates thread",
+										e);
+					}
+				}
+			}
+		};
+		checkThread.setDaemon(true); // daemon so that taverna will stop the
+										// thread and close on exit.
+		checkThread.start();
+	}
+
+	private Object updateLock = new Object();
+
+	private void checkForUpdates() {
+		synchronized (updateLock) {
+			if (pluginUpdateAvailable()) {
+				logger.info("Plugin update available");
+				try {
+					SwingUtilities.invokeAndWait(new Runnable() {
+						public void run() {
+							// TODO Auto-generated method stub
+							setToolTipText("Plugin updates are available");
+
+							setVisible(true);
+							setIcon(updateIcon);
+							if (!Arrays.asList(getMouseListeners()).contains(
+									updatePluginMouseAdaptor)) {
+								addMouseListener(updatePluginMouseAdaptor);
+							}
+
+						}
+					});
+				} catch (InterruptedException e) {
+					logger.error("Could not check for updates", e);
+				} catch (InvocationTargetException e) {
+					logger.error("Could not check for updates", e);
+				}
+			} else {
+				setToolTipText("");
+				setVisible(false);
+
+			}
+		}
+
+	}
+
+	private boolean pluginUpdateAvailable() {
+		return PluginManager.getInstance().checkForUpdates();
+	}
+
+	private final class UpdatePluginsMouseAdaptor extends MouseAdapter {
+
+		@Override
+		public void mouseClicked(MouseEvent e) {
+			// FIXME: this assumes the button is on the toolbar.
+			Component parent = UpdatesAvailableIcon.this.getParent()
+					.getParent();
+
+			final PluginManagerFrame pluginManagerUI = new PluginManagerFrame(
+					PluginManager.getInstance());
+			pluginManagerUI.setLocationRelativeTo(parent);
+			pluginManagerUI.setVisible(true);
+		}
+
+	}
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java
new file mode 100644
index 0000000..b0c036c
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester 
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: ProfileVersionCellRenderer.java,v $
+ * Revision           $Revision: 1.2 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:52:06 $
+ *               by   $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.AbstractBorder;
+
+import net.sf.taverna.raven.profile.ProfileHandler;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.spi.Profile;
+import net.sf.taverna.raven.spi.ProfileFactory;
+
+public class ProfileVersionCellRenderer extends JPanel implements
+		ListCellRenderer {
+
+	private JLabel name;
+	private JLabel version;
+	private JLabel description;
+	private String currentVersion;
+	
+	public ProfileVersionCellRenderer() {
+		super();
+		initialise();
+	}
+	
+	private void initialise() {
+		Profile currentProfile = ProfileFactory.getInstance().getProfile();
+		if (currentProfile!=null) {
+			currentVersion=currentProfile.getVersion();
+		}
+		else {
+			currentVersion="UNKNOWN";
+		}
+		GridBagConstraints gridBagVersion = new GridBagConstraints();
+		gridBagVersion.gridx = 1;
+		gridBagVersion.insets = new Insets(3, 8, 3, 3);
+		gridBagVersion.anchor = GridBagConstraints.NORTHWEST;
+		gridBagVersion.fill = GridBagConstraints.NONE;
+		gridBagVersion.gridy = 0;		
+		version = new JLabel();
+		version.setFont(getFont().deriveFont(Font.BOLD));
+		version.setText("Version");
+		
+		GridBagConstraints gridBagDescription = new GridBagConstraints();
+		gridBagDescription.gridx = 0;
+		gridBagDescription.anchor = GridBagConstraints.NORTHWEST;
+		gridBagDescription.fill = GridBagConstraints.HORIZONTAL;
+		gridBagDescription.weightx = 1.0;
+		gridBagDescription.insets = new Insets(3, 3, 3, 3);
+		gridBagDescription.gridwidth = 2;
+		gridBagDescription.gridy = 1;
+		description = new JLabel();
+		description.setFont(getFont().deriveFont(Font.PLAIN));
+		description.setText("Plugin description");
+		
+		GridBagConstraints gridBagName = new GridBagConstraints();
+		gridBagName.gridx = 0;
+		gridBagName.anchor = GridBagConstraints.NORTHWEST;
+		gridBagName.fill = GridBagConstraints.NONE;
+		gridBagName.weightx = 0.0;
+		gridBagName.ipadx = 0;
+		gridBagName.insets = new Insets(3, 3, 3, 3);
+		gridBagName.gridwidth = 1;
+		gridBagName.gridy = 0;
+		name = new JLabel();
+		name.setFont(getFont().deriveFont(Font.PLAIN));
+		name.setText("Plugin name");
+		
+		this.setSize(297, 97);
+		this.setLayout(new GridBagLayout());
+		this.setBorder(new AbstractBorder() {
+			public void paintBorder(Component c, Graphics g, int x, int y,
+					int width, int height) {
+				Color oldColor = g.getColor();
+				g.setColor(Color.LIGHT_GRAY);
+				g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+				g.setColor(oldColor);
+			}
+		});
+		this.add(name, gridBagName);
+		this.add(description, gridBagDescription);
+		this.add(version, gridBagVersion);		
+	}
+	
+	public Component getListCellRendererComponent(JList list, Object value,
+			int index, boolean isSelected, boolean cellHasFocus) {
+		if (isSelected) {
+			setBackground(list.getSelectionBackground());
+			setForeground(list.getSelectionForeground());
+		} else {
+			setBackground(list.getBackground());
+			setForeground(list.getForeground());
+		}
+		
+		if (value instanceof ProfileVersion) {
+			ProfileVersion version = (ProfileVersion) value;			
+			this.name.setText(version.getName());
+			if (version.getVersion().equalsIgnoreCase(currentVersion)) {
+				this.name.setText(version.getName()+" (Current)");
+				this.name.setForeground(Color.BLUE);
+			}
+			else {
+				this.name.setText(version.getName());
+				this.name.setForeground(Color.BLACK);
+			}
+			this.version.setText(version.getVersion());
+			this.description.setText("<html>"+version.getDescription());
+		}
+		
+		
+		return this;
+	}
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java
new file mode 100644
index 0000000..861ed9a
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester 
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: ProfileVersionListFrame.java,v $
+ * Revision           $Revision: 1.3 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:52:06 $
+ *               by   $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.raven.appconfig.bootstrap.RavenProperties;
+import net.sf.taverna.raven.plugins.Plugin;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.profile.ProfileHandler;
+import net.sf.taverna.raven.profile.ProfileUpdateHandler;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.spi.ProfileFactory;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ProfileVersionListFrame extends HelpEnabledDialog {
+	
+	private JPanel contentPane = null;
+	private JScrollPane scrollPane = null;
+	private JList list = null;
+	private JButton switchButton = null;
+	private JButton closeButton = null;
+	private String currentVersion = null;
+	
+	private static Logger logger = Logger
+			.getLogger(ProfileVersionListFrame.class);
+
+	public ProfileVersionListFrame() {
+		this(null);
+	}
+	
+	public ProfileVersionListFrame(Frame parent) {
+		super(parent,"Taverna versions", true);
+		initialise();
+	}
+	
+	protected JPanel getJContentPane() {
+		if (contentPane==null) {
+			GridBagConstraints scrollPaneConstraints = new GridBagConstraints();
+			scrollPaneConstraints.fill = GridBagConstraints.BOTH;
+			scrollPaneConstraints.gridy = 0;
+			scrollPaneConstraints.weightx = 1.0;
+			scrollPaneConstraints.weighty = 1.0;
+			scrollPaneConstraints.gridwidth = 2;
+			scrollPaneConstraints.insets = new Insets(5, 5, 5, 5);
+			scrollPaneConstraints.gridx = 0;
+			scrollPaneConstraints.gridheight = 3;
+			
+			GridBagConstraints switchButtonConstraints = new GridBagConstraints();			
+			switchButtonConstraints.gridy=0;			
+			switchButtonConstraints.gridx=2;
+			switchButtonConstraints.insets = new Insets(5,5,5,5);
+			switchButtonConstraints.fill=GridBagConstraints.HORIZONTAL;
+			
+			GridBagConstraints closeButtonConstraints = new GridBagConstraints();
+			closeButtonConstraints.gridy=2;			
+			closeButtonConstraints.gridx=2;
+			closeButtonConstraints.insets = new Insets(5,5,5,5);
+			closeButtonConstraints.anchor=GridBagConstraints.SOUTH;
+			closeButtonConstraints.fill=GridBagConstraints.HORIZONTAL;
+			
+			contentPane = new JPanel();
+			contentPane.setLayout(new GridBagLayout());
+			contentPane.add(getScrollPane(),scrollPaneConstraints);
+			contentPane.add(getSwitchButton(),switchButtonConstraints);
+			contentPane.add(getCloseButton(),closeButtonConstraints);
+			
+		}
+		return contentPane;
+	}
+	
+	protected JScrollPane getScrollPane() {
+		if (scrollPane==null) {
+			scrollPane=new JScrollPane();
+			scrollPane.setViewportView(getJList());
+		}
+		return scrollPane;
+	}
+	
+	protected JList getJList() {
+		if (list==null) {
+			list=new JList();
+			list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+			list.setModel(new ProfileVersionListModel());	
+			list.setCellRenderer(new ProfileVersionCellRenderer());
+			list.addListSelectionListener(new ListSelectionListener() {
+
+				public void valueChanged(ListSelectionEvent e) {
+					if (!e.getValueIsAdjusting()) {
+						respondToSelection();
+					}
+				}
+
+			});
+			if (list.getComponentCount() > 0) {
+				list.setSelectedIndex(0);
+				respondToSelection();
+				
+			}
+		}
+		return list;
+	}
+	
+	protected void respondToSelection() {
+		Object selected=list.getSelectedValue();
+		if (selected!=null && selected instanceof ProfileVersion) {
+			ProfileVersion version = (ProfileVersion)selected;
+			if (currentVersion==null || version.getVersion().equals(currentVersion)) {
+				getSwitchButton().setEnabled(false);
+			}
+			else {
+				getSwitchButton().setEnabled(true);
+			}
+		}
+	}
+	
+	protected JButton getSwitchButton() {
+		if (switchButton==null) {
+			switchButton=new JButton("Switch");
+			switchButton.setEnabled(true);
+			switchButton.addActionListener(new ActionListener() {
+
+				public void actionPerformed(ActionEvent e) {					
+					Object selected = getJList().getSelectedValue();
+					if (selected!=null && selected instanceof ProfileVersion) {
+						performSwitch((ProfileVersion)selected);
+					}
+				}
+				
+			});
+		}
+		return switchButton;
+	}
+	
+	protected JButton getCloseButton() {
+		if (closeButton==null) {
+			closeButton=new JButton("Close");
+			closeButton.setEnabled(true);
+			closeButton.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {					
+					setVisible(false);
+					dispose();
+				}				
+			});			
+		}
+		return closeButton;		
+	}
+	
+	protected void performSwitch(ProfileVersion newVersion) {
+		List<Plugin> incompatiblePlugins = PluginManager.getInstance().getIncompatiblePlugins(newVersion.getVersion(),true);
+		if (incompatiblePlugins.size()>0) {
+			int response=JOptionPane.showConfirmDialog(this, "Some plugins will be incompatible with the new version and will be disabled. Do you wish to continue?","Confirm version switch",JOptionPane.YES_OPTION);
+			if (response!=JOptionPane.YES_OPTION) {
+				return;
+			}			
+		}
+		try {		
+			URL localProfile = new URL(RavenProperties.getInstance().getRavenProfileLocation());
+			URL profileList = new URL(RavenProperties.getInstance().getRavenProfileListLocation());
+			
+			ProfileUpdateHandler handler=new ProfileUpdateHandler(profileList,localProfile);
+			handler.updateLocalProfile(newVersion,new File(localProfile.toURI()));			
+			
+			//disable plugins after everything else has been acheived
+			for (Plugin plugin : incompatiblePlugins) {
+				plugin.setEnabled(false);
+			}			
+			JOptionPane.showMessageDialog(this, "You must restart taverna for the version switch to be activated");
+		}
+		catch(Exception e) {
+			logger.error("Error occurred switching to a new profile",e);
+			JOptionPane.showMessageDialog(this, "An error occurred switching to your new profile, try again later.");
+		}				
+	}
+	
+	
+	protected void initialise() {
+		try {
+			currentVersion = ProfileFactory.getInstance().getProfile().getVersion();
+		} catch (Exception e) {
+			logger.error("Unable to determine current taverna version",e);
+			currentVersion=null;
+		}
+		setSize(600,400);		
+		setContentPane(getJContentPane());
+	}	
+	
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java
new file mode 100644
index 0000000..c952413
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester 
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.  Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename           $RCSfile: ProfileVersionListModel.java,v $
+ * Revision           $Revision: 1.3 $
+ * Release status     $State: Exp $
+ * Last modified on   $Date: 2008/09/04 14:52:06 $
+ *               by   $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+
+import net.sf.taverna.raven.appconfig.bootstrap.RavenProperties;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.profile.ProfileVersions;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ProfileVersionListModel extends AbstractListModel {
+	
+	private static Logger logger = Logger
+			.getLogger(ProfileVersionListModel.class);
+	
+	private List<ProfileVersion> versions;
+	
+	
+	public ProfileVersionListModel() {		
+		try {
+			URL url = getProfileListLocation();
+			versions = ProfileVersions.getProfileVersions(url);			
+		}
+		catch(MalformedURLException e) {
+			logger.error("Error with profile list URL",e);
+			versions = new ArrayList<ProfileVersion>();
+		}
+	}
+
+	public Object getElementAt(int index) {
+		return versions.get(index);
+	}
+
+	public int getSize() {
+		return versions.size();
+	}
+	
+	private URL getProfileListLocation() throws MalformedURLException{		
+		return new URL(RavenProperties.getInstance().getRavenProfileListLocation());
+	}
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..a58f87a
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.raven.plugins.ui.CheckForUpdatesStartupHook
+net.sf.taverna.raven.plugins.ui.CheckForNoticeStartupHook
\ No newline at end of file
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml
new file mode 100644
index 0000000..38c21e5
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans 
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi 
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="CheckForUpdatesStartupHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+
+</beans:beans>
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml
new file mode 100644
index 0000000..33e2207
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans 
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="CheckForUpdatesStartupHook" class="net.sf.taverna.raven.plugins.ui.CheckForUpdatesStartupHook" />
+
+</beans>
diff --git a/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png
new file mode 100644
index 0000000..e3695cb
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png
Binary files differ
diff --git a/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png
new file mode 100644
index 0000000..5adc4b1
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png
Binary files differ
diff --git a/taverna-workbench-renderers-impl/pom.xml b/taverna-workbench-renderers-impl/pom.xml
new file mode 100644
index 0000000..24e06c0
--- /dev/null
+++ b/taverna-workbench-renderers-impl/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>renderers-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Renderers Implementation</name>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Private-Package>org.fife.ui.hex.*,net.sf.taverna.t2.renderers.impl</Private-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>renderers-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>ui</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+    		<groupId>uk.org.taverna.databundle</groupId>
+    		<artifactId>databundle</artifactId>
+    		<version>${taverna.databundle.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jdom</groupId>
+			<artifactId>com.springsource.org.jdom</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.fife.ui.hex</groupId>
+			<artifactId>hexeditor</artifactId>
+			<version>1.1-SNAPSHOT</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java
new file mode 100644
index 0000000..14ef62f
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java
@@ -0,0 +1,124 @@
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static net.sf.taverna.t2.renderers.RendererUtils.getSizeInBytes;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.BIG_DATA_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.CANCELLED_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.NO_DATA_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.NO_SIZE_MSG;
+import static uk.org.taverna.databundle.DataBundles.isValue;
+import static uk.org.taverna.databundle.DataBundles.isReference;
+
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements some of the common code across the renderers.
+ * 
+ * @author Donal Fellows
+ */
+abstract class AbstractRenderer implements Renderer {
+	protected Logger logger = Logger.getLogger(AbstractRenderer.class);
+	/** Size of a <s>mibibyte</s> megabyte. */
+	private int MEGABYTE = 1024 * 1024;
+
+	/**
+	 * Work out size of file in megabytes
+	 * 
+	 * @param bytes
+	 *            Number of bytes
+	 * @return Number of megabytes
+	 */
+	private int bytesToMeg(long bytes) {
+		float f = bytes / MEGABYTE;
+		return Math.round(f);
+	}
+
+	/**
+	 * Implements basic checks on the entity to render before delegating to
+	 * subclasses to actually do the rendering.
+	 * 
+	 * @see #getRendererComponent(Path)
+	 */
+	@Override
+	public JComponent getComponent(Path path) throws RendererException {
+		if (!isValue(path) && !isReference(path)) {
+			logger.error("unrenderable: data is not a value or reference");
+			return new JTextArea(NO_DATA_MSG);
+		}
+
+		// Get the size
+		long sizeInBytes;
+		try {
+			sizeInBytes = getSizeInBytes(path);
+		} catch (Exception ex) {
+			logger.error("unrenderable: failed to get data size", ex);
+			return new JTextArea(NO_SIZE_MSG + ex.getMessage());
+		}
+
+		// over the limit for this renderer?
+		if (sizeInBytes > MEGABYTE * getSizeLimit()) {
+			JComponent alternative = sizeCheck(path, bytesToMeg(sizeInBytes));
+			if (alternative != null)
+				return alternative;
+		}
+
+		return getRendererComponent(path);
+	}
+
+	/**
+	 * Implements the user-visible part of the size check. Default version just
+	 * does a simple yes/no proceed.
+	 * 
+	 * @return <tt>null</tt> if the normal flow is to continue, or a component
+	 *         to show to the user if it is to be used instead (e.g., to show a
+	 *         message that the entity was too large).
+	 */
+	protected JComponent sizeCheck(Path path, int approximateSizeInMB) {
+		int response = showConfirmDialog(null,
+				format(BIG_DATA_MSG, getResultType(), approximateSizeInMB),
+				getSizeQueryTitle(), YES_NO_OPTION);
+		if (response != YES_OPTION) // NO_OPTION or ESCAPE key
+			return new JTextArea(CANCELLED_MSG);
+		return null;
+	}
+
+	/**
+	 * How should we describe the data to the user? Should be capitalised.
+	 */
+	protected String getResultType() {
+		return "Result";
+	}
+
+	/**
+	 * At what size (in megabytes) do we query the user?
+	 * 
+	 * @see #sizeCheck(Path,log)
+	 */
+	protected int getSizeLimit() {
+		return 1;
+	}
+
+	/**
+	 * What title do we use on the dialog used to query the user?
+	 * 
+	 * @see #sizeCheck(Path,log)
+	 */
+	protected String getSizeQueryTitle() {
+		return "Render this image?";
+	}
+
+	/** Actually get the renderer; the basic checks have passed. */
+	protected abstract JComponent getRendererComponent(Path path)
+			throws RendererException;
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java
new file mode 100644
index 0000000..6c15834
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static javax.imageio.ImageIO.getReaderMIMETypes;
+import static javax.imageio.ImageIO.getWriterFormatNames;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.awt.Image;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextArea;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Advanced renderer for mime type image/* that can render tiff files. Uses Java
+ * Advanced Imaging (JAI) ImageIO from https://jai-imageio.dev.java.net/.
+ * 
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class AdvancedImageRenderer extends AbstractRenderer {
+	private static final String BAD_FORMAT_MSG = "Data does not seem to contain an image in any of the recognised formats:\n";
+	private static final String UNRENDERABLE_MSG = "Failed to create image renderer " + SEE_LOG_MSG;
+
+	private Logger logger = Logger.getLogger(AdvancedImageRenderer.class);
+	private List<String> mimeTypesList;
+
+	public AdvancedImageRenderer() {
+		mimeTypesList = Arrays.asList(getReaderMIMETypes());
+	}
+
+	@Override
+	public boolean canHandle(String mimeType) {
+		return mimeTypesList.contains(mimeType);
+	}
+
+	@Override
+	public String getType() {
+		return "Image";
+	}
+
+	@Override
+	public JComponent getRendererComponent(Path path) {
+		// Render into a label
+		try (InputStream inputStream = getInputStream(path)) {
+			Image image = ImageIO.read(inputStream);
+			if (image == null)
+				return new JTextArea(BAD_FORMAT_MSG
+						+ Arrays.asList(getWriterFormatNames()));
+			return new JLabel(new ImageIcon(image));
+		} catch (Exception e) {
+			logger.error("unrenderable: failed to create image renderer", e);
+			return new JTextArea(UNRENDERABLE_MSG + e.getMessage());
+		}
+	}
+
+	@Override
+	protected int getSizeLimit() {
+		return 4;
+	}
+
+	@Override
+	protected String getResultType() {
+		return "Image";
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java
new file mode 100644
index 0000000..2d90764
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/**
+ * This file is a component of the Taverna project,
+ * and is licensed under the GNU LGPL.
+ * Copyright Tom Oinn, EMBL-EBI
+ */
+package net.sf.taverna.t2.renderers.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A FileFilter implementation that can be configured to show only specific file
+ * suffixes.
+ * 
+ * @author Tom Oinn
+ */
+public class ExtensionFileFilter extends FileFilter {
+	String[] allowedExtensions;
+
+	public ExtensionFileFilter(String[] allowedExtensions) {
+		this.allowedExtensions = allowedExtensions;
+	}
+
+	@Override
+	public boolean accept(File f) {
+		if (f.isDirectory())
+			return true;
+		String extension = getExtension(f);
+		if (extension != null)
+			for (int i = 0; i < allowedExtensions.length; i++)
+				if (extension.equalsIgnoreCase(allowedExtensions[i]))
+					return true;
+		return false;
+	}
+
+	String getExtension(File f) {
+		String ext = null;
+		String s = f.getName();
+		int i = s.lastIndexOf('.');
+		if (i > 0 && i < s.length() - 1)
+			ext = s.substring(i + 1).toLowerCase();
+		return ext;
+	}
+
+	@Override
+	public String getDescription() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("Filter for extensions : ");
+		String sep = "";
+		for (String ext : allowedExtensions) {
+			sb.append(sep).append(ext);
+			sep = ", ";
+		}
+		return sb.toString();
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java
new file mode 100644
index 0000000..f1d8f29
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.io.InputStream;
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererException;
+
+import org.fife.ui.hex.swing.HexEditor;
+
+/**
+ * Renderer for binary data. Uses HexEditor from
+ * http://www.fifesoft.com/hexeditor/.
+ * 
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class HexBinaryRenderer extends AbstractRenderer {
+	private static final String RENDER_FAILED_MSG = "Failed to create binary hexadecimal renderer "
+			+ SEE_LOG_MSG;
+
+	@Override
+	public boolean canHandle(String mimeType) {
+		return false;
+		/*
+		 * can handle any data but return false here as we do not want this to
+		 * be default renderer
+		 */
+	}
+
+	@Override
+	protected String getSizeQueryTitle() {
+		return "Render binary data (in hexadecimal viewer)?";
+	}
+
+	@Override
+	public JComponent getRendererComponent(Path path) throws RendererException {
+		try (InputStream inputStream = getInputStream(path)) {
+			HexEditor editor = new HexEditor();
+			editor.open(inputStream);
+			return editor;
+		} catch (Exception e) {
+			logger.error("unrenderable: failed to create binhex renderer", e);
+			return new JTextArea(RENDER_FAILED_MSG + e.getMessage());
+		}
+	}
+
+	@Override
+	public String getType() {
+		return "Binary (HexDec)";
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java
new file mode 100644
index 0000000..d51c94c
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java
@@ -0,0 +1,13 @@
+package net.sf.taverna.t2.renderers.impl;
+
+interface RendererConstants {
+	String SEE_LOG_MSG = "(see error log for more details):\n";
+	String NO_DATA_MSG = "Failed to obtain the data to render: "
+			+ "data is not a value or reference";
+	String NO_SIZE_MSG = "Failed to get the size of the data " + SEE_LOG_MSG;
+	String BIG_DATA_MSG = "%s is approximately %d MB in size, "
+			+ "there could be issues with rendering this inside Taverna\n"
+			+ "Do you want to continue?";
+	String CANCELLED_MSG = "Rendering cancelled due to size of image. "
+			+ "Try saving and viewing in an external application.";
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java
new file mode 100644
index 0000000..eb41292
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+
+/**
+ * Implementation of a RendererRegistry.
+ *
+ * @author David Withers
+ */
+public class RendererRegistryImpl implements RendererRegistry {
+	private List<Renderer> renderers;
+
+	@Override
+	public List<Renderer> getRenderersForMimeType(String mimeType) {
+		ArrayList<Renderer> list = new ArrayList<>();
+		for (Renderer renderer : renderers)
+			if (renderer.canHandle(mimeType))
+				list.add(renderer);
+		return list;
+	}
+
+	@Override
+	public List<Renderer> getRenderers() {
+		return renderers;
+	}
+
+	public void setRenderers(List<Renderer> renderers) {
+		this.renderers = renderers;
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java
new file mode 100644
index 0000000..87fa364
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.awt.Font.PLAIN;
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.RendererUtils.getString;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.CANCELLED_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.awt.Font;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.RendererException;
+
+/**
+ * Renderer for mime type text/*
+ * 
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class TextRenderer extends AbstractRenderer {
+	private static final String UNREADABLE_MSG = "Reference Service failed to render data "
+			+ SEE_LOG_MSG;
+	private static final String RENDERER_FAILED_MSG = "Failed to create text renderer "
+			+ SEE_LOG_MSG;
+	private static final String QUERY_MSG = "Result is approximately %d MB "
+			+ "in size, there could be issues with rendering this inside "
+			+ "Taverna\nDo you want to cancel, render all of the result, "
+			+ "or only the first part?";
+	private static final Pattern pattern = Pattern.compile(".*text/.*");
+
+	@Override
+	public boolean canHandle(String mimeType) {
+		return pattern.matcher(mimeType).matches();
+	}
+
+	@Override
+	public String getType() {
+		return "Text";
+	}
+
+	private JComponent textRender(String text) {
+		DialogTextArea theTextArea = new DialogTextArea();
+		theTextArea.setWrapStyleWord(true);
+		theTextArea.setEditable(false);
+		theTextArea.setText(text);
+		theTextArea.setFont(new Font("Monospaced", PLAIN, 12));
+		theTextArea.setCaretPosition(0);
+		return theTextArea;
+	}
+
+	private static final Object[] SIZE_OPTIONS = { "Continue rendering",
+			"Render partial", "Cancel" };
+
+	@Override
+	protected JComponent sizeCheck(Path path, int approximateSizeInMB) {
+		// allow partial rendering of text files
+		int response = showOptionDialog(null,
+				format(QUERY_MSG, approximateSizeInMB),
+				"Rendering large result", YES_NO_CANCEL_OPTION,
+				QUESTION_MESSAGE, null, SIZE_OPTIONS, SIZE_OPTIONS[2]);
+		if (response == YES_OPTION)
+			return null;
+		else if (response != NO_OPTION)
+			// if (response == CANCEL_OPTION) or ESCAPE key pressed
+			return new JTextArea(CANCELLED_MSG);
+
+		// Construct a partial result.
+		byte[] smallStringBytes = new byte[1048576];
+		try (InputStream inputStream = getInputStream(path)) {
+			// just copy the first MEGABYTE
+			inputStream.read(smallStringBytes);
+		} catch (Exception ex) {
+			logger.error("unrenderable: Reference Service failed "
+					+ "to render data as byte array", ex);
+			return new JTextArea(UNREADABLE_MSG + ex.getMessage());
+		}
+		try {
+			// TODO beware of encoding problems!
+			return textRender(new String(smallStringBytes));
+		} catch (Exception e1) {
+			logger.error("Failed to create text renderer", e1);
+			return new JTextArea(RENDERER_FAILED_MSG + e1.getMessage());
+		}
+	}
+
+	@Override
+	public JComponent getRendererComponent(Path path) throws RendererException {
+		String resolve;
+		try {
+			// Resolve it as a string
+			resolve = getString(path);
+		} catch (Exception e) {
+			logger.error("unrenderable: Reference Service failed "
+					+ "to render data as string", e);
+			return new JTextArea(UNREADABLE_MSG + e.getMessage());
+		}
+		try {
+			return textRender(resolve);
+		} catch (Exception e1) {
+			logger.error("Failed to create text renderer", e1);
+			return new JTextArea(RENDERER_FAILED_MSG + e1.getMessage());
+		}
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java
new file mode 100644
index 0000000..6c3bf7f
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererException;
+import net.sf.taverna.t2.renderers.RendererUtils;
+
+/**
+ * Renderer for mime type text/rtf
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class TextRtfRenderer extends AbstractRenderer {
+	private static final String UNREADABLE_MSG = "Reference Service failed to render data "
+			+ SEE_LOG_MSG;
+	private static final String RENDERER_FAILED_MSG = "Failed to create RTF renderer "
+			+ SEE_LOG_MSG;
+	private static final Pattern pattern = Pattern.compile(".*text/rtf.*");
+
+	public boolean isTerminal() {
+		return true;
+	}
+
+	@Override
+	public boolean canHandle(String mimeType) {
+		return pattern.matcher(mimeType).matches();
+	}
+
+	@Override
+	public String getType() {
+		return "Text/RTF";
+	}
+
+	@Override
+	protected String getSizeQueryTitle() {
+		return "Render as RTF?";
+	}
+
+	@Override
+	public JComponent getRendererComponent(Path path) throws RendererException {
+		String resolve;
+		try {
+			// Resolve it as a string
+			resolve = RendererUtils.getString(path);
+		} catch (Exception e) {
+			logger.error("Reference Service failed to render data as string", e);
+			return new JTextArea(UNREADABLE_MSG + e.getMessage());
+		}
+		try {
+			return new JEditorPane("text/rtf", resolve);
+		} catch (Exception e) {
+			logger.error("Failed to create RTF renderer", e);
+			return new JTextArea(RENDERER_FAILED_MSG + e.getMessage());
+		}
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java
new file mode 100644
index 0000000..eaad9c6
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererUtils;
+
+/**
+ * Viewer to display XML as a tree.
+ * 
+ * @author Matthew Pocock
+ * @auhor Ian Dunlop
+ * @author David Withers
+ */
+public class TextXMLRenderer extends AbstractRenderer {
+	private static final String UNREADABLE_MSG = "Reference Service failed to render data as string " + SEE_LOG_MSG;
+	private static final String RENDERER_FAILED_MSG = "Failed to create XML renderer " + SEE_LOG_MSG;
+	private Pattern pattern;
+
+	public TextXMLRenderer() {
+		pattern = Pattern.compile(".*text/xml.*");
+	}
+
+	public boolean isTerminal() {
+		return true;
+	}
+
+	@Override
+	public boolean canHandle(String mimeType) {
+		return pattern.matcher(mimeType).matches();
+	}
+
+	@Override
+	public String getType() {
+		return "XML tree";
+	}
+
+	@Override
+	protected String getSizeQueryTitle() {
+		return "Render this as XML?";
+	}
+
+	@Override
+	public JComponent getRendererComponent(Path path) {
+		String resolve = null;
+		try {
+			// Resolve it as a string
+			resolve = RendererUtils.getString(path);
+		} catch (Exception ex) {
+			logger.error("unrenderable: Reference Service failed to render data as string",
+					ex);
+			return new JTextArea(UNREADABLE_MSG + ex.getMessage());
+		}
+		try {
+			return new XMLTree(resolve);
+		} catch (Exception e) {
+			logger.error("unrenderable: failed to create XML renderer", e);
+			return new JTextArea(RENDERER_FAILED_MSG + e.getMessage());
+		}
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java
new file mode 100644
index 0000000..2d53218
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java
@@ -0,0 +1,329 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
+import static org.jdom.output.Format.getPrettyFormat;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.prefs.Preferences;
+
+import javax.swing.JFileChooser;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import org.jdom.Attribute;
+import org.jdom.Content;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.Parent;
+import org.jdom.Text;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.XMLOutputter;
+
+/**
+ * An extension of the {@link JTree} class, constructed with a String of XML and
+ * used to display the XML structure as an interactive tree. Derived from <a
+ * href="http://www.devx.com/gethelpon/10MinuteSolution/16694/0/page/1">original
+ * code by Kyle Gabhart</a> and then subsequently heavily rewritten to move to
+ * JDOM, and moved lots of the setup code to the renderer to cut down
+ * initialisation time. Added text node size limit as well. Displaying large
+ * gene sequences as base64 encoded text in a single node really, <i>really</i>
+ * hurts performance.
+ * 
+ * @author Kyle Gabhart
+ * @author Tom Oinn
+ * @author Kevin Glover
+ * @author Ian Dunlop
+ */
+@SuppressWarnings("serial")
+class XMLTree extends JTree {
+	private class XMLNode extends DefaultMutableTreeNode {
+		public XMLNode(Content userObject) {
+			super(userObject);
+		}
+	}
+
+	int textSizeLimit = 1000;
+	final JFileChooser fc = new JFileChooser();
+	Element rootElement = null;
+
+	/**
+	 * Build a new XMLTree from the supplied String containing XML.
+	 * 
+	 * @param text
+	 * @throws IOException
+	 * @throws JDOMException
+	 */
+	public XMLTree(String text) throws IOException, JDOMException {
+		super();
+		Document document = new SAXBuilder(false).build(new StringReader(text));
+		init(document.getRootElement());
+		revalidate();
+	}
+
+	public String getText() {
+		if (rootElement == null)
+			return "";
+		XMLOutputter xo = new XMLOutputter(getPrettyFormat());
+		return xo.outputString(rootElement);
+	}
+
+	public XMLTree(String text, boolean limit) throws IOException,
+			JDOMException {
+		if (!limit)
+			textSizeLimit = -1;
+		Document document = new SAXBuilder(false).build(new StringReader(text));
+		init(document.getRootElement());
+		revalidate();
+	}
+
+	public XMLTree(Document document) {
+		this(document.getRootElement());
+	}
+	
+	public XMLTree(Element element) {
+		super();
+		init(element);
+		revalidate();
+	}
+
+	private void init(Content content) {
+		rootElement = (Element) content;
+		/*
+		 * Fix for platforms other than metal which can't otherwise cope with
+		 * arbitrary size rows
+		 */
+		setRowHeight(0);
+		getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
+		setShowsRootHandles(true);
+		setEditable(false);
+		setModel(new DefaultTreeModel(createTreeNode(content)));
+		setCellRenderer(new DefaultTreeCellRenderer() {
+			@Override
+			public Color getBackgroundNonSelectionColor() {
+				return null;
+			}
+
+			@Override
+			public Color getBackground() {
+				return null;
+			}
+
+			@Override
+			public Component getTreeCellRendererComponent(JTree tree,
+					Object value, boolean sel, boolean expanded, boolean leaf,
+					int row, boolean hasFocus) {
+				super.getTreeCellRendererComponent(tree, value, sel, expanded,
+						leaf, row, hasFocus);
+				setOpaque(false);
+				if (value instanceof XMLNode) {
+					XMLNode node = (XMLNode) value;
+					if (node.getUserObject() instanceof Element)
+						renderElementNode((Element) node.getUserObject());
+					else if (node.getUserObject() instanceof Text)
+						renderTextNode((Text) node.getUserObject());
+					// TODO what about other node types?
+				}
+				setBackground(new Color(0, 0, 0, 0));
+				return this;
+			}
+
+			private void renderElementNode(Element element) {
+				// setIcon(TavernaIcons.xmlNodeIcon);
+				StringBuilder nameBuffer = new StringBuilder("<html>")
+						.append(element.getQualifiedName());
+				/*
+				 * Bit of a quick and dirty hack here to try to ensure that the
+				 * element namespace is shown. There appears no way to get the
+				 * actual xmlns declarations that are part of an element through
+				 * jdom. Also, please note, there's no namespace handling at all
+				 * for attributes...
+				 */
+				if (element.getParent() instanceof Element) {
+					Element parent = (Element) element.getParent();
+					if (parent.getNamespace(element.getNamespacePrefix()) == null)
+						nameBuffer
+								.append(" <font color=\"purple\">xmlns:")
+								.append(element.getNamespacePrefix())
+								.append("</font>=\"<font color=\"green\">")
+								.append(element.getNamespaceURI() + "</font>\"");
+				} else
+					nameBuffer.append(" <font color=\"purple\">xmlns:")
+							.append(element.getNamespacePrefix())
+							.append("</font>=\"<font color=\"green\">")
+							.append(element.getNamespaceURI() + "</font>\"");
+
+				String sep = "";
+				for (Object a : element.getAttributes()) {
+					Attribute attribute = (Attribute) a;
+					String name = attribute.getName().trim();
+					String value = attribute.getValue().trim();
+					if (value != null && value.length() > 0) {
+						// TODO xml-quote name and value
+						nameBuffer.append(sep)
+								.append(" <font color=\"purple\">")
+								.append(name)
+								.append("</font>=\"<font color=\"green\">")
+								.append(value).append("</font>\"");
+						sep = ",";
+					}
+				}
+
+				nameBuffer.append("</html>");
+				setText(nameBuffer.toString());
+			}
+
+			private void renderTextNode(Text text) {
+				// setIcon(TavernaIcons.leafIcon);
+				String name = text.getText();
+				if (textSizeLimit > -1 && name.length() > textSizeLimit)
+					name = name.substring(0, textSizeLimit) + "...";
+				setText("<html><pre><font color=\"blue\">"
+						+ name.replaceAll("<br>", "\n").replaceAll("<", "&lt;")
+						+ "</font></pre></html>");
+			}
+		});
+		setAllNodesExpanded();
+
+		// Add a listener to present the 'save as text' option
+		addMouseListener(new MouseAdapter() {
+			@Override
+			public void mousePressed(MouseEvent e) {
+				if (e.isPopupTrigger())
+					doEvent(e);
+			}
+
+			@Override
+			public void mouseReleased(MouseEvent e) {
+				if (e.isPopupTrigger())
+					doEvent(e);
+			}
+
+			public void doEvent(MouseEvent e) {
+				JPopupMenu menu = new JPopupMenu();
+				JMenuItem item = new JMenuItem("Save as XML text");
+				menu.add(item);
+				item.addActionListener(new ActionListener() {
+					@Override
+					public void actionPerformed(ActionEvent ae) {
+						saveTreeXML();
+					}
+				});
+				menu.show(XMLTree.this, e.getX(), e.getY());
+			}
+		});
+	}
+
+	private void saveTreeXML() {
+		try {
+			Preferences prefs = userNodeForPackage(XMLTree.class);
+			String curDir = prefs.get("currentDir",
+					System.getProperty("user.home"));
+			fc.resetChoosableFileFilters();
+			fc.setFileFilter(new ExtensionFileFilter(new String[] { "xml" }));
+			fc.setCurrentDirectory(new File(curDir));
+			if (fc.showSaveDialog(this) == APPROVE_OPTION) {
+				prefs.put("currentDir", fc.getCurrentDirectory().toString());
+				saveTreeXML(fc.getSelectedFile());
+			}
+		} catch (Exception ex) {
+			showMessageDialog(this, "Problem saving XML:\n" + ex.getMessage(),
+					"Error!", ERROR_MESSAGE);
+		}
+	}
+
+	private void saveTreeXML(File file) throws IOException {
+		try (PrintWriter out = new PrintWriter(new FileWriter(file))) {
+			out.print(this.getText());
+		}
+	}
+
+	public void setAllNodesExpanded() {
+		synchronized (this.getModel()) {
+			expandAll(this, new TreePath(this.getModel().getRoot()), true);
+		}
+	}
+
+	private void expandAll(JTree tree, TreePath parent, boolean expand) {
+		synchronized (this.getModel()) {
+			/*
+			 * Traverse children
+			 * 
+			 * Ignores nodes who's userObject is a Processor type to avoid
+			 * overloading the UI with nodes at startup.
+			 */
+			TreeNode node = (TreeNode) parent.getLastPathComponent();
+			for (Enumeration<?> e = node.children(); e.hasMoreElements(); ) {
+				TreeNode n = (TreeNode) e.nextElement();
+				expandAll(tree, parent.pathByAddingChild(n), expand);
+			}
+			// Expansion or collapse must be done bottom-up
+			if (expand)
+				tree.expandPath(parent);
+			else
+				tree.collapsePath(parent);
+		}
+	}
+
+	public void setTextNodeSizeLimit(int sizeLimit) {
+		textSizeLimit = sizeLimit;
+	}
+
+	private XMLNode createTreeNode(Content content) {
+		XMLNode node = new XMLNode(content);
+		if (content instanceof Parent) {
+			Parent parent = (Parent) content;
+			for (Object child : parent.getContent()) {
+				if (child instanceof Element)
+					node.add(createTreeNode((Content) child));
+				else if (textSizeLimit != 0 && child instanceof Text) {
+					Text text = (Text) child;
+					if (!text.getTextNormalize().isEmpty())
+						node.add(createTreeNode(text));
+				}
+			}
+		}
+		return node;
+	}
+}
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer b/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
new file mode 100644
index 0000000..044a396
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
@@ -0,0 +1,7 @@
+net.sf.taverna.t2.renderers.impl.AdvancedImageRenderer
+net.sf.taverna.t2.renderers.impl.HexBinaryRenderer
+net.sf.taverna.t2.renderers.impl.TextRenderer
+net.sf.taverna.t2.renderers.impl.TextRtfRenderer
+net.sf.taverna.t2.renderers.impl.TextXMLRenderer
+
+
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml
new file mode 100644
index 0000000..436426a
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="AdvancedImageRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+	<service ref="HexBinaryRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+	<service ref="TextRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+	<service ref="TextRtfRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+	<service ref="TextXMLRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+
+	<service ref="RendererRegistry" interface="net.sf.taverna.t2.renderers.RendererRegistry" />
+
+	<list id="renderers" interface="net.sf.taverna.t2.renderers.Renderer" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml
new file mode 100644
index 0000000..a041c98
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="AdvancedImageRenderer" class="net.sf.taverna.t2.renderers.impl.AdvancedImageRenderer" />
+	<bean id="HexBinaryRenderer" class="net.sf.taverna.t2.renderers.impl.HexBinaryRenderer" />
+	<bean id="TextRenderer" class="net.sf.taverna.t2.renderers.impl.TextRenderer" />
+	<bean id="TextRtfRenderer" class="net.sf.taverna.t2.renderers.impl.TextRtfRenderer" />
+	<bean id="TextXMLRenderer" class="net.sf.taverna.t2.renderers.impl.TextXMLRenderer" />
+
+	<bean id="RendererRegistry" class="net.sf.taverna.t2.renderers.impl.RendererRegistryImpl">
+			<property name="renderers" ref="renderers" />
+	</bean>
+
+
+</beans>
diff --git a/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java b/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
new file mode 100644
index 0000000..ece7cf5
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+@SuppressWarnings("unused")
+public class TestRendererSPI {
+	
+	private static final String TEST_NS = "testNS";
+	
+	@Test
+	public void doNothing() {
+		//do nothing for the moment
+	}
+	
+//	@Test
+//	public void getAllRenderers() {
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		assertEquals(rendererRegistry.getInstances().size(), 10);
+//	}
+//	
+//	@Test
+//	public void checkTextHtmlMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType = "text/html";
+//		String html = "<HTML><HEAD></HEAD><BODY>hello</BODY></HTML>";
+//		EntityIdentifier entityIdentifier = facade.register(html, "utf-8");
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(),2);
+//		assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+//		assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextHtmlRenderer");
+//		assertTrue(renderersForMimeType.get(0).canHandle("text/html"));
+//	}
+//	
+//	@Test
+//	public void checkURLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType = "text/x-taverna-web-url.text";
+//		String url = "http://google.com";
+//		EntityIdentifier entityIdentifier = facade.register(url);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(),2);
+//		assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+//		assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextTavernaWebUrlRenderer");
+//		assertTrue(renderersForMimeType.get(1).canHandle("text/x-taverna-web-url.text"));
+//	}
+//	
+//	@Test
+//	public void checkJMolMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType ="chemical/x-pdb";
+//		String jmol = "jmol";
+//		EntityIdentifier entityIdentifier = facade.register(jmol);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(), 1);
+//		assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "JMolRenderer");
+//		assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-mdl-molfile"));
+//		assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-cml"));
+//	}
+//	
+//	@Test
+//	public void checkSeqVistaMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType ="chemical/x-swissprot";
+//		String type = "seqvista";
+//		EntityIdentifier entityIdentifier = facade.register(type);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(), 1);
+//		assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "SeqVistaRenderer");
+//		assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-embl-dl-nucleotide"));
+//		assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-fasta"));
+//	}
+//	
+//	@Test
+//	public void checkSVGMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType ="image/svg+xml";
+//		String type = "SVG";
+//		EntityIdentifier entityIdentifier = facade.register(type);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(), 2);
+//		assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "SVGRenderer");
+//	}
+//	
+//	@Test
+//	public void checkTextMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType ="text/text";
+//		String type = "text";
+//		EntityIdentifier entityIdentifier = facade.register(type);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(), 1);
+//		assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+//	}
+//	
+//	@Test
+//	public void checkTextRtfMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType ="text/rtf";
+//		String type = "textRTF";
+//		EntityIdentifier entityIdentifier = facade.register(type);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(), 2);
+//		assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextRtfRenderer");
+//	}
+//	
+//	@Test
+//	public void checkTextXMLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+//		String mimeType ="text/xml";
+//		String type = "textXML";
+//		EntityIdentifier entityIdentifier = facade.register(type);
+//		RendererRegistry rendererRegistry = new RendererRegistry();
+//		List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+//		assertEquals(renderersForMimeType.size(), 2);
+//		assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextXMLRenderer");
+//	}
+//	
+//	@Before
+//	public void setDataManager() {
+//		// dManager = new FileDataManager("testNS",
+//		// new HashSet<LocationalContext>(), new File("/tmp/fish"));
+//		dManager = new InMemoryDataManager(TEST_NS,
+//				new HashSet<LocationalContext>());
+//		facade = new DataFacade(dManager);
+//	}
+
+}
diff --git a/taverna-workbench-report-impl/pom.xml b/taverna-workbench-report-impl/pom.xml
new file mode 100644
index 0000000..c0e72bb
--- /dev/null
+++ b/taverna-workbench-report-impl/pom.xml
@@ -0,0 +1,40 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>ui-impl</artifactId>
+		<groupId>net.sf.taverna.t2</groupId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>report-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Reporting Implementation</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.core</groupId>
+			<artifactId>workflowmodel-api</artifactId>
+			<version>${t2.core.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>report-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>file-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>edits-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java
new file mode 100644
index 0000000..b5986b5
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.config.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workflowmodel.health.RemoteHealthChecker;
+
+/**
+ * @author alanrw
+ *
+ */
+public final class ReportManagerConfigurationImpl extends AbstractConfigurable implements ReportManagerConfiguration {
+
+	private static final int DEFAULT_TIMEOUT = 10;
+
+	private Map<String, String> defaultPropertyMap;
+
+	public ReportManagerConfigurationImpl(ConfigurationManager configurationManager) {
+		super(configurationManager);
+	}
+
+    public String getCategory() {
+        return "general";
+    }
+
+    public Map<String, String> getDefaultPropertyMap() {
+
+        if (defaultPropertyMap == null) {
+            defaultPropertyMap = new HashMap<String, String>();
+            defaultPropertyMap.put(TIMEOUT, Integer.toString(DEFAULT_TIMEOUT));
+            defaultPropertyMap.put(ON_EDIT, QUICK_CHECK);
+            defaultPropertyMap.put(ON_OPEN, QUICK_CHECK);
+            defaultPropertyMap.put(BEFORE_RUN, FULL_CHECK);
+            defaultPropertyMap.put(QUERY_BEFORE_RUN, ERRORS_OR_WARNINGS);
+            defaultPropertyMap.put(REPORT_EXPIRATION, Integer.toString(DEFAULT_REPORT_EXPIRATION));
+        }
+        return defaultPropertyMap;
+    }
+
+    public String getDisplayName() {
+        return "Validation report";
+    }
+
+    public String getFilePrefix() {
+        return "ReportManager";
+    }
+
+	public String getUUID() {
+		return "F86378E5-0EC4-4DE9-8A55-6098595413DC";
+	}
+
+	@Override
+	public void applySettings() {
+		RemoteHealthChecker.setTimeoutInSeconds(Integer.parseInt(this.getProperty(TIMEOUT)));
+	}
+
+	public void setProperty(String key, String value) {
+		super.setProperty(key, value);
+		if (key.equals(TIMEOUT)) {
+			applySettings();
+		}
+	}
+
+}
diff --git a/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java
new file mode 100644
index 0000000..825efee
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java
@@ -0,0 +1,564 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.visit.HierarchyTraverser;
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.visit.Visitor;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.report.DataflowReportEvent;
+import net.sf.taverna.t2.workbench.report.FailedEntityKind;
+import net.sf.taverna.t2.workbench.report.IncompleteDataflowKind;
+import net.sf.taverna.t2.workbench.report.InvalidDataflowKind;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.ReportManagerEvent;
+import net.sf.taverna.t2.workbench.report.UnresolvedOutputKind;
+import net.sf.taverna.t2.workbench.report.UnsatisfiedEntityKind;
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.DataflowValidationReport;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Alan R Williams
+ *
+ */
+public class ReportManagerImpl implements Observable<ReportManagerEvent>, ReportManager {
+
+	private static final long MAX_AGE_OUTDATED_MILLIS = 1 * 60 * 60 * 1000; // 1 hour
+	private static Logger logger = Logger.getLogger(ReportManagerImpl.class);
+
+	private ReportManagerConfiguration reportManagerConfiguration;
+	private HierarchyTraverser traverser = null;
+	private Map<Dataflow, Map<Object, Set<VisitReport>>> reportMap = Collections
+			.synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Set<VisitReport>>>());;
+	private Map<Dataflow, Map<Object, Status>> statusMap = Collections
+			.synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Status>>());
+	private Map<Dataflow, Map<Object, String>> summaryMap = Collections
+			.synchronizedMap(new WeakHashMap<Dataflow, Map<Object, String>>());
+	private Map<Dataflow, Long> lastCheckedMap = Collections
+			.synchronizedMap(new WeakHashMap<Dataflow, Long>());
+	private Map<Dataflow, Long> lastFullCheckedMap = Collections
+			.synchronizedMap(new WeakHashMap<Dataflow, Long>());
+	private Map<Dataflow, String> lastFullCheckedDataflowIdMap = Collections
+			.synchronizedMap(new WeakHashMap<Dataflow, String>());
+
+	private EditManager editManager;
+	private FileManager fileManager;
+
+	// private Set<Visitor<?>> visitors;
+
+	protected ReportManagerImpl(EditManager editManager, FileManager fileManager,
+			Set<Visitor<?>> visitors, ReportManagerConfiguration reportManagerConfiguration)
+			throws IllegalStateException {
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+		this.reportManagerConfiguration = reportManagerConfiguration;
+		// this.visitors = visitors;
+		traverser = new HierarchyTraverser(visitors);
+		ReportManagerFileObserver fileObserver = new ReportManagerFileObserver();
+		fileManager.addObserver(fileObserver);
+		addEditObserver();
+		reportManagerConfiguration.applySettings();
+	}
+
+	private void addEditObserver() {
+		synchronized (editManager) {
+			List<Observer<EditManagerEvent>> currentObservers = editManager.getObservers();
+			for (Observer<EditManagerEvent> o : currentObservers) {
+				editManager.removeObserver(o);
+			}
+			editManager.addObserver(new ReportManagerEditObserver());
+			for (Observer<EditManagerEvent> o : currentObservers) {
+				editManager.addObserver(o);
+			}
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#updateReport(net.sf.taverna.t2.workflowmodel
+	 * .Dataflow, boolean, boolean)
+	 */
+	@Override
+	public void updateReport(Dataflow d, boolean includeTimeConsuming, boolean remember) {
+		Set<VisitReport> oldTimeConsumingReports = null;
+		long time = System.currentTimeMillis();
+		long expiration = Integer.parseInt(reportManagerConfiguration
+				.getProperty(ReportManagerConfiguration.REPORT_EXPIRATION)) * 60 * 1000;
+		if (remember && !includeTimeConsuming
+				&& ((expiration == 0) || ((time - getLastFullCheckedTime(d)) < expiration))) {
+			oldTimeConsumingReports = getTimeConsumingReports(d);
+		}
+		Map<Object, Set<VisitReport>> reportsEntry = new HashMap<Object, Set<VisitReport>>();
+		Map<Object, Status> statusEntry = new HashMap<Object, Status>();
+		Map<Object, String> summaryEntry = new HashMap<Object, String>();
+		reportMap.put(d, reportsEntry);
+		statusMap.put(d, statusEntry);
+		summaryMap.put(d, summaryEntry);
+		validateDataflow(d, reportsEntry, statusEntry, summaryEntry);
+
+		Set<VisitReport> newReports = new HashSet<VisitReport>();
+		traverser.traverse(d, new ArrayList<Object>(), newReports, includeTimeConsuming);
+		for (VisitReport vr : newReports) {
+			addReport(reportsEntry, statusEntry, summaryEntry, vr);
+		}
+		if (oldTimeConsumingReports != null) {
+			for (VisitReport vr : oldTimeConsumingReports) {
+				addReport(reportsEntry, statusEntry, summaryEntry, vr);
+			}
+		}
+		time = System.currentTimeMillis();
+		lastCheckedMap.put(d, time);
+		if (includeTimeConsuming) {
+			lastFullCheckedMap.put(d, time);
+			lastFullCheckedDataflowIdMap.put(d, d.getIdentifier());
+		}
+		multiCaster.notify(new DataflowReportEvent(d));
+	}
+
+	private void updateObjectReportInternal(Dataflow d, Object o) {
+		Map<Object, Set<VisitReport>> reportsEntry = reportMap.get(d);
+		Map<Object, Status> statusEntry = statusMap.get(d);
+		Map<Object, String> summaryEntry = summaryMap.get(d);
+		if (reportsEntry == null) {
+			logger.error("Attempt to update reports on an object in a dataflow that has not been checked");
+			reportsEntry = new HashMap<Object, Set<VisitReport>>();
+			statusEntry = new HashMap<Object, Status>();
+			summaryEntry = new HashMap<Object, String>();
+			reportMap.put(d, reportsEntry);
+			statusMap.put(d, statusEntry);
+			summaryMap.put(d, summaryEntry);
+		} else {
+			reportsEntry.remove(o);
+			statusEntry.remove(o);
+			summaryEntry.remove(o);
+		}
+		// Assume o is directly inside d
+		List<Object> ancestry = new ArrayList<Object>();
+		ancestry.add(d);
+		Set<VisitReport> newReports = new HashSet<VisitReport>();
+		traverser.traverse(o, ancestry, newReports, true);
+		for (VisitReport vr : newReports) {
+			addReport(reportsEntry, statusEntry, summaryEntry, vr);
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectSetReport(net.sf.taverna.t2
+	 * .workflowmodel.Dataflow, java.util.Set)
+	 */
+	@Override
+	public void updateObjectSetReport(Dataflow d, Set<Object> objects) {
+		for (Object o : objects) {
+			updateObjectReportInternal(d, o);
+		}
+		multiCaster.notify(new DataflowReportEvent(d));
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectReport(net.sf.taverna.t2.
+	 * workflowmodel.Dataflow, java.lang.Object)
+	 */
+	@Override
+	public void updateObjectReport(Dataflow d, Object o) {
+		updateObjectReportInternal(d, o);
+		multiCaster.notify(new DataflowReportEvent(d));
+	}
+
+	private Set<VisitReport> getTimeConsumingReports(Dataflow d) {
+		Set<VisitReport> result = new HashSet<VisitReport>();
+		Map<Object, Set<VisitReport>> currentReports = getReports(d);
+		if (currentReports != null) {
+			for (Object o : currentReports.keySet()) {
+				for (VisitReport vr : currentReports.get(o)) {
+					if (vr.wasTimeConsuming()) {
+						result.add(vr);
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	private void removeReport(Dataflow d) {
+		reportMap.remove(d);
+		statusMap.remove(d);
+		summaryMap.remove(d);
+	}
+
+	private void addReport(Map<Object, Set<VisitReport>> reports, Map<Object, Status> statusEntry,
+			Map<Object, String> summaryEntry, VisitReport newReport) {
+		if (newReport.getCheckTime() == 0) {
+			newReport.setCheckTime(System.currentTimeMillis());
+		}
+		Object subject = newReport.getSubject();
+		Set<VisitReport> currentReports = reports.get(subject);
+		Status newReportStatus = newReport.getStatus();
+		if (currentReports == null) {
+			currentReports = new HashSet<VisitReport>();
+			reports.put(subject, currentReports);
+			statusEntry.put(subject, newReportStatus);
+			summaryEntry.put(subject, newReport.getMessage());
+		} else {
+			Status currentStatus = statusEntry.get(subject);
+			if (currentStatus.compareTo(newReportStatus) < 0) {
+				statusEntry.put(subject, newReportStatus);
+				summaryEntry.put(subject, newReport.getMessage());
+			} else if (currentStatus.compareTo(newReportStatus) == 0) {
+				if (currentStatus.equals(Status.WARNING)) {
+					summaryEntry.put(subject, "Multiple warnings");
+				} else if (currentStatus.equals(Status.SEVERE)) {
+					summaryEntry.put(subject, "Multiple errors");
+				}
+			}
+		}
+		currentReports.add(newReport);
+	}
+
+	private void validateDataflow(Dataflow d, Map<Object, Set<VisitReport>> reportsEntry,
+			Map<Object, Status> statusEntry, Map<Object, String> summaryEntry) {
+		DataflowValidationReport validationReport = d.checkValidity();
+		if (validationReport.isWorkflowIncomplete()) {
+			addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
+					IncompleteDataflowKind.getInstance(), d, "Incomplete workflow",
+					IncompleteDataflowKind.INCOMPLETE_DATAFLOW, VisitReport.Status.SEVERE));
+		} else if (!validationReport.isValid()) {
+			addReport(reportsEntry, statusEntry, summaryEntry,
+					new VisitReport(InvalidDataflowKind.getInstance(), d, "Invalid workflow",
+							InvalidDataflowKind.INVALID_DATAFLOW, VisitReport.Status.SEVERE));
+		}
+		fillInReport(reportsEntry, statusEntry, summaryEntry, validationReport);
+	}
+
+	private void fillInReport(Map<Object, Set<VisitReport>> reportsEntry,
+			Map<Object, Status> statusEntry, Map<Object, String> summaryEntry,
+			DataflowValidationReport report) {
+		for (Object o : report.getUnresolvedOutputs()) {
+			addReport(reportsEntry, statusEntry, summaryEntry,
+					new VisitReport(UnresolvedOutputKind.getInstance(), o,
+							"Invalid workflow output", UnresolvedOutputKind.OUTPUT,
+							VisitReport.Status.SEVERE));
+		}
+		for (Object o : report.getFailedEntities()) {
+			addReport(reportsEntry, statusEntry, summaryEntry,
+					new VisitReport(FailedEntityKind.getInstance(), o,
+							"Mismatch of input list depths", FailedEntityKind.FAILED_ENTITY,
+							VisitReport.Status.SEVERE));
+		}
+		for (Object o : report.getUnsatisfiedEntities()) {
+			addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
+					UnsatisfiedEntityKind.getInstance(), o, "Unknown prior list depth",
+					UnsatisfiedEntityKind.UNSATISFIED_ENTITY, VisitReport.Status.SEVERE));
+		}
+		// for (DataflowValidationReport subReport : report.getInvalidDataflows().values()) {
+		// fillInReport(descriptionMap, subReport);
+		// }
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
+	 * .Dataflow, java.lang.Object)
+	 */
+	@Override
+	public Set<VisitReport> getReports(Dataflow d, Object object) {
+		Set<VisitReport> result = new HashSet<VisitReport>();
+		Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+		if (objectReports != null) {
+			Set<Object> objects = new HashSet<Object>();
+			objects.add(object);
+			if (object instanceof Processor) {
+				objects.addAll(((Processor) object).getActivityList());
+			}
+			for (Object o : objects) {
+				if (objectReports.containsKey(o)) {
+					result.addAll(objectReports.get(o));
+				}
+			}
+		}
+		return result;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
+	 * .Dataflow)
+	 */
+	@Override
+	public Map<Object, Set<VisitReport>> getReports(Dataflow d) {
+		return reportMap.get(d);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see net.sf.taverna.t2.workbench.report.ReportManagerI#isStructurallySound(net.sf.taverna.t2.
+	 * workflowmodel.Dataflow)
+	 */
+	@Override
+	public boolean isStructurallySound(Dataflow d) {
+		Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+		if (objectReports == null) {
+			return false;
+		}
+		for (Set<VisitReport> visitReportSet : objectReports.values()) {
+			for (VisitReport vr : visitReportSet) {
+				if (vr.getStatus().equals(Status.SEVERE)) {
+					VisitKind vk = vr.getKind();
+					if ((vk instanceof IncompleteDataflowKind)
+							|| (vk instanceof InvalidDataflowKind)
+							|| (vk instanceof UnresolvedOutputKind)
+							|| (vk instanceof FailedEntityKind)
+							|| (vk instanceof UnsatisfiedEntityKind)) {
+						return false;
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
+	 * .Dataflow)
+	 */
+	@Override
+	public Status getStatus(Dataflow d) {
+		Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+		if (objectReports == null) {
+			return Status.OK;
+		}
+		Status currentStatus = Status.OK;
+		for (Set<VisitReport> visitReportSet : objectReports.values()) {
+			for (VisitReport vr : visitReportSet) {
+				Status status = vr.getStatus();
+				if (status.compareTo(currentStatus) > 0) {
+					currentStatus = status;
+				}
+				if (currentStatus.equals(Status.SEVERE)) {
+					return currentStatus;
+				}
+			}
+		}
+		return currentStatus;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
+	 * .Dataflow, java.lang.Object)
+	 */
+	@Override
+	public Status getStatus(Dataflow d, Object object) {
+		Status result = Status.OK;
+		Map<Object, Status> statusEntry = statusMap.get(d);
+		if (statusEntry != null) {
+			Status value = statusEntry.get(object);
+			if (value != null) {
+				result = value;
+			}
+		}
+		return result;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getSummaryMessage(net.sf.taverna.t2.
+	 * workflowmodel.Dataflow, java.lang.Object)
+	 */
+	@Override
+	public String getSummaryMessage(Dataflow d, Object object) {
+		String result = null;
+		if (!getStatus(d, object).equals(Status.OK)) {
+			Map<Object, String> summaryEntry = summaryMap.get(d);
+			if (summaryEntry != null) {
+				result = summaryEntry.get(object);
+			}
+		}
+		return result;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getLastCheckedTime(net.sf.taverna.t2.
+	 * workflowmodel.Dataflow)
+	 */
+	@Override
+	public long getLastCheckedTime(Dataflow d) {
+		Long l = lastCheckedMap.get(d);
+		if (l == null) {
+			return 0;
+		} else {
+			return l.longValue();
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#getLastFullCheckedTime(net.sf.taverna.t2
+	 * .workflowmodel.Dataflow)
+	 */
+	@Override
+	public long getLastFullCheckedTime(Dataflow d) {
+		Long l = lastFullCheckedMap.get(d);
+		if (l == null) {
+			return 0;
+		} else {
+			return l.longValue();
+		}
+	}
+
+	/**
+	 * @author alanrw
+	 *
+	 */
+	public class ReportManagerFileObserver implements Observer<FileManagerEvent> {
+
+		public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
+				throws Exception {
+			String onOpen = reportManagerConfiguration.getProperty(
+					ReportManagerConfiguration.ON_OPEN);
+			if (message instanceof ClosedDataflowEvent) {
+				ReportManagerImpl.this.removeReport(((ClosedDataflowEvent) message).getDataflow());
+			} else if (message instanceof SetCurrentDataflowEvent) {
+				Dataflow dataflow = ((SetCurrentDataflowEvent) message).getDataflow();
+				if (!reportMap.containsKey(dataflow)) {
+					if (!onOpen.equals(ReportManagerConfiguration.NO_CHECK)) {
+						updateReport(dataflow,
+								onOpen.equals(ReportManagerConfiguration.FULL_CHECK), true);
+					} else {
+						ReportManagerImpl.this.multiCaster
+								.notify(new DataflowReportEvent(dataflow));
+					}
+				} else {
+					ReportManagerImpl.this.multiCaster.notify(new DataflowReportEvent(dataflow));
+				}
+			}
+		}
+
+	}
+
+	private MultiCaster<ReportManagerEvent> multiCaster = new MultiCaster<ReportManagerEvent>(this);
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#addObserver(net.sf.taverna.t2.lang.observer
+	 * .Observer)
+	 */
+	@Override
+	public void addObserver(Observer<ReportManagerEvent> observer) {
+		multiCaster.addObserver(observer);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getObservers()
+	 */
+	@Override
+	public List<Observer<ReportManagerEvent>> getObservers() {
+		return multiCaster.getObservers();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see
+	 * net.sf.taverna.t2.workbench.report.ReportManagerI#removeObserver(net.sf.taverna.t2.lang.observer
+	 * .Observer)
+	 */
+	@Override
+	public void removeObserver(Observer<ReportManagerEvent> observer) {
+		multiCaster.removeObserver(observer);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see net.sf.taverna.t2.workbench.report.ReportManagerI#isReportOutdated(net.sf.taverna.t2.
+	 * workflowmodel.Dataflow)
+	 */
+	@Override
+	public boolean isReportOutdated(Dataflow dataflow) {
+		String lastCheckedId = lastFullCheckedDataflowIdMap.get(dataflow);
+		Long lastCheck = lastFullCheckedMap.get(dataflow);
+		if (lastCheckedId == null || lastCheck == null) {
+			// Unknown, so outdated
+			return true;
+		}
+		if (!lastCheckedId.equals(dataflow.getIdentifier())) {
+			// Workflow changed, so outdaeted
+			return true;
+		}
+		long now = System.currentTimeMillis();
+		long age = now - lastCheck;
+		// Outdated if it is older than the maximum
+		return age > MAX_AGE_OUTDATED_MILLIS;
+	}
+
+	public class ReportManagerEditObserver implements Observer<EditManagerEvent> {
+		public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+				throws Exception {
+			String onEdit = reportManagerConfiguration
+					.getProperty(ReportManagerConfiguration.ON_EDIT);
+			Dataflow dataflow = fileManager.getCurrentDataflow();
+			if (message instanceof AbstractDataflowEditEvent) {
+				AbstractDataflowEditEvent adee = (AbstractDataflowEditEvent) message;
+				if (adee.getDataFlow().equals(dataflow)) {
+					if (onEdit.equals(ReportManagerConfiguration.QUICK_CHECK)) {
+						updateReport(dataflow, false, true);
+					} else if (onEdit.equals(ReportManagerConfiguration.FULL_CHECK)) {
+						updateReport(dataflow, true, true);
+					}
+				}
+			}
+		}
+	}
+
+}
diff --git a/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml
new file mode 100644
index 0000000..31c7432
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<!-- <service ref="ProcessorFragilityChecker" interface="net.sf.taverna.t2.visit.fragility.FragilityChecker" /> -->
+
+	<!-- <service ref="FragilityCheck" interface="net.sf.taverna.t2.visit.VisitKind" /> -->
+
+	<service ref="ReportManagerImpl" interface="net.sf.taverna.t2.workbench.report.ReportManager" />
+
+	<service ref="ReportManagerConfiguration" interface="net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration" />
+
+	<reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+	<reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+	<reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+
+	<set id="visitors" interface="net.sf.taverna.t2.visit.Visitor" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml
new file mode 100644
index 0000000..966d50d
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<!-- <bean id="ProcessorFragilityChecker" class="net.sf.taverna.t2.visit.fragility.impl.ProcessorFragilityChecker" /> -->
+
+	<!-- <bean id="FragilityCheck" class="net.sf.taverna.t2.visit.fragility.impl.FragilityCheck" /> -->
+
+	<bean id="ReportManagerImpl" class="net.sf.taverna.t2.workbench.report.impl.ReportManagerImpl">
+    	<constructor-arg ref="editManager" />
+    	<constructor-arg ref="fileManager" />
+    	<constructor-arg ref="visitors" />
+		<constructor-arg ref="ReportManagerConfiguration"/>
+	</bean>
+
+	<bean id="ReportManagerConfiguration" class="net.sf.taverna.t2.workbench.report.config.impl.ReportManagerConfigurationImpl">
+		<constructor-arg ref="configurationManager"/>
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-selection-impl/pom.xml b/taverna-workbench-selection-impl/pom.xml
new file mode 100644
index 0000000..cb6cbcb
--- /dev/null
+++ b/taverna-workbench-selection-impl/pom.xml
@@ -0,0 +1,35 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>selection-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Selection Implementation</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>edits-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>file-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>selection-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java
new file mode 100644
index 0000000..09d6f71
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.selection.impl;
+
+import static net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage.Type.ADDED;
+import static net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage.Type.REMOVED;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+
+/**
+ * Default implementation of a <code>DataflowSelectionModel</code>.
+ *
+ * @author David Withers
+ */
+public class DataflowSelectionModelImpl implements DataflowSelectionModel {
+	private MultiCaster<DataflowSelectionMessage> multiCaster;
+	private Set<Object> selection = new TreeSet<>(new Comparator<Object>() {
+		@Override
+		public int compare(Object o1, Object o2) {
+			if (o1 == o2)
+				return 0;
+			return o1.hashCode() - o2.hashCode();
+		}
+	});
+
+	/**
+	 * Constructs a new instance of DataflowSelectionModelImpl.
+	 */
+	public DataflowSelectionModelImpl() {
+		multiCaster = new MultiCaster<>(this);
+	}
+
+	@Override
+	public void addSelection(Object element) {
+		if (element != null) {
+			if (!selection.contains(element)) {
+				clearSelection();
+				selection.add(element);
+				multiCaster.notify(new DataflowSelectionMessage(ADDED, element));
+			}
+		}
+	}
+
+	@Override
+	public void clearSelection() {
+		for (Object element : new HashSet<>(selection))
+			removeSelection(element);
+	}
+
+	@Override
+	public Set<Object> getSelection() {
+		return new HashSet<>(selection);
+	}
+
+	@Override
+	public void removeSelection(Object element) {
+		if (element != null && selection.remove(element))
+			multiCaster.notify(new DataflowSelectionMessage(REMOVED, element));
+	}
+
+	@Override
+	public void setSelection(Set<Object> elements) {
+		if (elements == null) {
+			clearSelection();
+			return;
+		}
+		Set<Object> newSelection = new HashSet<>(elements);
+		for (Object element : new HashSet<>(selection))
+			if (!newSelection.remove(element))
+				removeSelection(element);
+		for (Object element : newSelection)
+			addSelection(element);
+	}
+
+	@Override
+	public void addObserver(Observer<DataflowSelectionMessage> observer) {
+		multiCaster.addObserver(observer);
+	}
+
+	@Override
+	public List<Observer<DataflowSelectionMessage>> getObservers() {
+		return multiCaster.getObservers();
+	}
+
+	@Override
+	public void removeObserver(Observer<DataflowSelectionMessage> observer) {
+		multiCaster.removeObserver(observer);
+	}
+}
diff --git a/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java
new file mode 100644
index 0000000..2e86f1a
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.selection.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.ProfileSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Implementation of the {@link SelectionManager}.
+ *
+ * @author David Withers
+ */
+public class SelectionManagerImpl implements SelectionManager {
+	private static final String RESULTS_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective";
+
+	private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+	private static final Logger logger = Logger.getLogger(SelectionManagerImpl.class);
+
+	private WorkflowBundle selectedWorkflowBundle;
+	private Map<WorkflowBundle, DataflowSelectionModel> workflowSelectionModels = new IdentityHashMap<>();
+	private Map<WorkflowBundle, Workflow> selectedWorkflows = new IdentityHashMap<>();
+	private Map<WorkflowBundle, Profile> selectedProfiles = new IdentityHashMap<>();
+	private String selectedWorkflowRun;
+	private Map<String, DataflowSelectionModel> workflowRunSelectionModels = new HashMap<>();
+	private PerspectiveSPI selectedPerspective;
+	private MultiCaster<SelectionManagerEvent> observers = new MultiCaster<>(this);
+	private FileManager fileManager;
+	private List<PerspectiveSPI> perspectives;
+
+	@Override
+	public DataflowSelectionModel getDataflowSelectionModel(WorkflowBundle dataflow) {
+		DataflowSelectionModel selectionModel;
+		synchronized (workflowSelectionModels) {
+			selectionModel = workflowSelectionModels.get(dataflow);
+			if (selectionModel == null) {
+				selectionModel = new DataflowSelectionModelImpl();
+				workflowSelectionModels.put(dataflow, selectionModel);
+			}
+		}
+		return selectionModel;
+	}
+
+	@Override
+	public WorkflowBundle getSelectedWorkflowBundle() {
+		return selectedWorkflowBundle;
+	}
+
+	@Override
+	public void setSelectedWorkflowBundle(WorkflowBundle workflowBundle) {
+		setSelectedWorkflowBundle(workflowBundle, true);
+	}
+
+	private void setSelectedWorkflowBundle(WorkflowBundle workflowBundle, boolean notifyFileManager) {
+		if (workflowBundle == null || workflowBundle == selectedWorkflowBundle)
+			return;
+		if (notifyFileManager) {
+			fileManager.setCurrentDataflow(workflowBundle);
+			return;
+		}
+		if (selectedWorkflows.get(workflowBundle) == null)
+			selectedWorkflows.put(workflowBundle,
+					workflowBundle.getMainWorkflow());
+		if (selectedProfiles.get(workflowBundle) == null)
+			selectedProfiles.put(workflowBundle,
+					workflowBundle.getMainProfile());
+		SelectionManagerEvent selectionManagerEvent = new WorkflowBundleSelectionEvent(
+				selectedWorkflowBundle, workflowBundle);
+		selectedWorkflowBundle = workflowBundle;
+		notify(selectionManagerEvent);
+		selectDesignPerspective();
+	}
+
+	private void removeWorkflowBundle(WorkflowBundle dataflow) {
+		synchronized (workflowSelectionModels) {
+			DataflowSelectionModel selectionModel = workflowSelectionModels.remove(dataflow);
+			if (selectionModel != null)
+				for (Observer<DataflowSelectionMessage> observer : selectionModel.getObservers())
+					selectionModel.removeObserver(observer);
+		}
+		synchronized (selectedWorkflows) {
+			selectedWorkflows.remove(dataflow);
+		}
+		synchronized (selectedProfiles) {
+			selectedProfiles.remove(dataflow);
+		}
+	}
+
+	@Override
+	public Workflow getSelectedWorkflow() {
+		return selectedWorkflows.get(selectedWorkflowBundle);
+	}
+
+	@Override
+	public void setSelectedWorkflow(Workflow workflow) {
+		if (workflow != null) {
+			Workflow selectedWorkflow = selectedWorkflows.get(workflow
+					.getParent());
+			if (selectedWorkflow != workflow) {
+				SelectionManagerEvent selectionManagerEvent = new WorkflowSelectionEvent(
+						selectedWorkflow, workflow);
+				selectedWorkflows.put(workflow.getParent(), workflow);
+				notify(selectionManagerEvent);
+			}
+		}
+	}
+
+	@Override
+	public Profile getSelectedProfile() {
+		return selectedProfiles.get(selectedWorkflowBundle);
+	}
+
+	@Override
+	public void setSelectedProfile(Profile profile) {
+		if (profile != null) {
+			Profile selectedProfile = selectedProfiles.get(profile.getParent());
+			if (selectedProfile != profile) {
+				SelectionManagerEvent selectionManagerEvent = new ProfileSelectionEvent(
+						selectedProfile, profile);
+				selectedProfiles.put(profile.getParent(), profile);
+				notify(selectionManagerEvent);
+			}
+		}
+	}
+
+	@Override
+	public String getSelectedWorkflowRun() {
+		return selectedWorkflowRun;
+	}
+
+	@Override
+	public void setSelectedWorkflowRun(String workflowRun) {
+		if ((selectedWorkflowRun == null && workflowRun != null)
+				|| !selectedWorkflowRun.equals(workflowRun)) {
+			SelectionManagerEvent selectionManagerEvent = new WorkflowRunSelectionEvent(
+					selectedWorkflowRun, workflowRun);
+			selectedWorkflowRun = workflowRun;
+			notify(selectionManagerEvent);
+			selectResultsPerspective();
+		}
+	}
+
+	@Override
+	public DataflowSelectionModel getWorkflowRunSelectionModel(String workflowRun) {
+		DataflowSelectionModel selectionModel;
+		synchronized (workflowRunSelectionModels) {
+			selectionModel = workflowRunSelectionModels.get(workflowRun);
+			if (selectionModel == null) {
+				selectionModel = new DataflowSelectionModelImpl();
+				workflowRunSelectionModels.put(workflowRun, selectionModel);
+			}
+		}
+		return selectionModel;
+	}
+
+	@SuppressWarnings("unused")
+	private void removeWorkflowRun(String workflowRun) {
+		synchronized (workflowRunSelectionModels) {
+			DataflowSelectionModel selectionModel = workflowRunSelectionModels
+					.remove(workflowRun);
+			if (selectionModel != null)
+				for (Observer<DataflowSelectionMessage> observer : selectionModel
+						.getObservers())
+					selectionModel.removeObserver(observer);
+		}
+	}
+
+	@Override
+	public PerspectiveSPI getSelectedPerspective() {
+		return selectedPerspective;
+	}
+
+	@Override
+	public void setSelectedPerspective(PerspectiveSPI perspective) {
+		if (selectedPerspective != perspective) {
+			SelectionManagerEvent selectionManagerEvent = new PerspectiveSelectionEvent(
+					selectedPerspective, perspective);
+			selectedPerspective = perspective;
+			notify(selectionManagerEvent);
+		}
+	}
+
+	private void selectDesignPerspective() {
+		for (PerspectiveSPI perspective : perspectives)
+			if (DESIGN_PERSPECTIVE_ID.equals(perspective.getID())) {
+				setSelectedPerspective(perspective);
+				break;
+			}
+	}
+
+	private void selectResultsPerspective() {
+		for (PerspectiveSPI perspective : perspectives)
+			if (RESULTS_PERSPECTIVE_ID.equals(perspective.getID())) {
+				setSelectedPerspective(perspective);
+				break;
+			}
+	}
+
+	@Override
+	public void addObserver(Observer<SelectionManagerEvent> observer) {
+		synchronized (observers) {
+			WorkflowBundle selectedWorkflowBundle = getSelectedWorkflowBundle();
+			Workflow selectedWorkflow = getSelectedWorkflow();
+			Profile selectedProfile = getSelectedProfile();
+			String selectedWorkflowRun = getSelectedWorkflowRun();
+			PerspectiveSPI selectedPerspective = getSelectedPerspective();
+			try {
+				if (selectedWorkflowBundle != null)
+					observer.notify(this, new WorkflowBundleSelectionEvent(
+							null, selectedWorkflowBundle));
+				if (selectedWorkflow != null)
+					observer.notify(this, new WorkflowSelectionEvent(null,
+							selectedWorkflow));
+				if (selectedProfile != null)
+					observer.notify(this, new ProfileSelectionEvent(null,
+							selectedProfile));
+				if (selectedWorkflowRun != null)
+					observer.notify(this, new WorkflowRunSelectionEvent(null,
+							selectedWorkflowRun));
+				if (selectedPerspective != null)
+					observer.notify(this, new PerspectiveSelectionEvent(null,
+							selectedPerspective));
+			} catch (Exception e) {
+				logger.warn("Could not notify " + observer, e);
+			}
+			observers.addObserver(observer);
+		}
+	}
+
+	@Override
+	public void removeObserver(Observer<SelectionManagerEvent> observer) {
+		observers.removeObserver(observer);
+	}
+
+	@Override
+	public List<Observer<SelectionManagerEvent>> getObservers() {
+		return observers.getObservers();
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+		setSelectedWorkflowBundle(fileManager.getCurrentDataflow());
+		fileManager.addObserver(new FileManagerObserver());
+	}
+
+	public void setEditManager(EditManager editManager) {
+		editManager.addObserver(new EditManagerObserver());
+	}
+
+	public void setPerspectives(List<PerspectiveSPI> perspectives) {
+		this.perspectives = perspectives;
+	}
+
+	public class FileManagerObserver implements Observer<FileManagerEvent> {
+		@Override
+		public void notify(Observable<FileManagerEvent> sender,
+				FileManagerEvent message) throws Exception {
+			if (message instanceof ClosedDataflowEvent) {
+				WorkflowBundle workflowBundle = ((ClosedDataflowEvent) message).getDataflow();
+				removeWorkflowBundle(workflowBundle);
+			} else if (message instanceof OpenedDataflowEvent) {
+				WorkflowBundle workflowBundle = ((OpenedDataflowEvent) message).getDataflow();
+				setSelectedWorkflowBundle(workflowBundle, false);
+			} else if (message instanceof SetCurrentDataflowEvent) {
+				WorkflowBundle workflowBundle = ((SetCurrentDataflowEvent) message).getDataflow();
+				setSelectedWorkflowBundle(workflowBundle, false);
+			}
+		}
+	}
+
+	private class EditManagerObserver implements Observer<EditManagerEvent> {
+		@Override
+		public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+				throws Exception {
+			Edit<?> edit = message.getEdit();
+			considerEdit(edit, message instanceof DataFlowUndoEvent);
+		}
+
+		private void considerEdit(Edit<?> edit, boolean undoing) {
+			if (edit instanceof CompoundEdit) {
+				CompoundEdit compound = (CompoundEdit) edit;
+				for (Edit<?> e : compound.getChildEdits())
+					considerEdit(e, undoing);
+			} else if (edit instanceof AddChildEdit
+					&& edit.getSubject() instanceof Workflow) {
+				Workflow subject = (Workflow) edit.getSubject();
+				DataflowSelectionModel selectionModel = getDataflowSelectionModel(subject
+						.getParent());
+				Object child = ((AddChildEdit<?>) edit).getChild();
+				if (child instanceof Processor
+						|| child instanceof InputWorkflowPort
+						|| child instanceof OutputWorkflowPort) {
+					if (undoing
+							&& selectionModel.getSelection().contains(child))
+						selectionModel.clearSelection();
+					else {
+						Set<Object> selection = new HashSet<>();
+						selection.add(child);
+						selectionModel.setSelection(selection);
+					}
+				}
+			}
+		}
+	}
+
+	private void notify(SelectionManagerEvent event) {
+		synchronized (observers) {
+			observers.notify(event);
+		}
+	}
+}
diff --git a/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml
new file mode 100644
index 0000000..19faa00
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+	<reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+	<reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+	<!-- <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" /> -->
+
+	<list id="perspectives" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml
new file mode 100644
index 0000000..84bbff1
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="selectionManager" class="net.sf.taverna.t2.workbench.selection.impl.SelectionManagerImpl" >
+		<property name="fileManager" ref="fileManager"/>
+		<property name="editManager" ref="editManager"/>
+		<property name="perspectives" ref="perspectives"/>
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-update-manager/pom.xml b/taverna-workbench-update-manager/pom.xml
new file mode 100644
index 0000000..c2f2003
--- /dev/null
+++ b/taverna-workbench-update-manager/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>update-manager</artifactId>
+	<version>2.0.1-SNAPSHOT</version>
+	<packaging>bundle</packaging>
+	<name>Taverna Workbench Update Manager</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.commons</groupId>
+			<artifactId>taverna-update-api</artifactId>
+			<version>${taverna.commons.version}</version>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java
new file mode 100644
index 0000000..6b70101
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.update.impl;
+
+import java.awt.GridBagLayout;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.commons.update.UpdateManager;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings({ "serial", "unused" })
+public class UpdateManagerView extends JPanel {
+
+	private UpdateManager updateManager;
+
+	public UpdateManagerView(UpdateManager updateManager) {
+		this.updateManager = updateManager;
+		initialize();
+	}
+
+	private void initialize() {
+		setLayout(new GridBagLayout());
+	}
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java
new file mode 100644
index 0000000..2c1459b
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.update.impl.menu;
+
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.update.UpdateException;
+import uk.org.taverna.commons.update.UpdateManager;
+
+public class UpdateMenuAction extends AbstractMenuAction {
+	private static final Logger logger = Logger.getLogger(UpdateMenuAction.class);
+	private static final URI ADVANCED_MENU_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+	private UpdateManager updateManager;
+
+	public UpdateMenuAction() {
+		super(ADVANCED_MENU_URI, 1000);
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		return new AbstractAction("Check for updates") {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				findUpdates();
+			}
+		};
+	}
+
+	public void setUpdateManager(UpdateManager updateManager) {
+		this.updateManager = updateManager;
+	}
+
+	private void findUpdates() {
+		Component parent = null;
+		try {
+			if (!areUpdatesAvailable()) {
+				showMessageDialog(null, "No update available");
+				return;
+			}
+			if (showConfirmDialog(parent, "Update available. Update Now?") != YES_OPTION)
+				return;
+			applyUpdates();
+			showMessageDialog(parent,
+					"Update complete. Restart Taverna to apply update.");
+		} catch (UpdateException ex) {
+			showMessageDialog(parent, "Update failed: " + ex.getMessage());
+			logger.warn("Update failed", ex);
+		}
+	}
+
+	protected boolean areUpdatesAvailable() throws UpdateException {
+		return updateManager.checkForUpdates();
+	}
+
+	protected void applyUpdates() throws UpdateException {
+		updateManager.update();
+	}
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java
new file mode 100644
index 0000000..e6b4695
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static java.awt.event.KeyEvent.VK_F12;
+import static javax.swing.KeyStroke.getKeyStroke;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+public class PluginMenuAction extends AbstractMenuAction {
+	private static final String UPDATES_AND_PLUGINS = "Updates and plugins";
+
+	@SuppressWarnings("serial")
+	protected class SoftwareUpdates extends AbstractAction {
+		public SoftwareUpdates() {
+			super(UPDATES_AND_PLUGINS, null/*UpdatesAvailableIcon.updateRecommendedIcon*/);
+			putValue(ACCELERATOR_KEY, getKeyStroke(VK_F12, 0));
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			@SuppressWarnings("unused")
+			Component parent = null;
+			if (e.getSource() instanceof Component) {
+				parent = (Component) e.getSource();
+			}
+			//FIXME this does nothing!
+			//final PluginManagerFrame pluginManagerUI = new PluginManagerFrame(
+			//		PluginManager.getInstance());
+			//if (parent != null) {
+			//	pluginManagerUI.setLocationRelativeTo(parent);
+			//}
+			//pluginManagerUI.setVisible(true);
+		}
+	}
+
+	public PluginMenuAction() {
+		super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#advanced"),
+				100);
+	}
+
+	@Override
+	protected Action createAction() {
+		//return new SoftwareUpdates();
+		return null;
+	}
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java
new file mode 100644
index 0000000..e2611be
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static net.sf.taverna.t2.workbench.updatemanager.UpdatesToolbarSection.UPDATES_SECTION;
+
+import java.awt.Component;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+
+public class UpdatesAvailableMenuAction extends AbstractMenuCustom {
+	public UpdatesAvailableMenuAction() {
+		super(UPDATES_SECTION, 10);
+	}
+
+	@Override
+	protected Component createCustomComponent() {
+		//return new UpdatesAvailableIcon();
+		return null;
+	}
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java
new file mode 100644
index 0000000..b16d614
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java
@@ -0,0 +1,16 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class UpdatesToolbarSection extends AbstractMenuSection {
+	public static final URI UPDATES_SECTION = URI
+			.create("http://taverna.sf.net/2008/t2workbench/toolbar#updatesSection");
+
+	public UpdatesToolbarSection() {
+		super(DEFAULT_TOOL_BAR, 10000, UPDATES_SECTION);
+	}
+}
diff --git a/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml
new file mode 100644
index 0000000..ea637dd
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="UpdateMenuAction" auto-export="interfaces" />
+
+	<reference id="updateManager" interface="uk.org.taverna.commons.update.UpdateManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml
new file mode 100644
index 0000000..c3adf1f
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="UpdateMenuAction"
+		class="net.sf.taverna.t2.workbench.update.impl.menu.UpdateMenuAction">
+		<property name="updateManager" ref="updateManager" />
+	</bean>
+
+</beans>
diff --git a/taverna-workbench-workbench-impl/pom.xml b/taverna-workbench-workbench-impl/pom.xml
new file mode 100644
index 0000000..5b52d3a
--- /dev/null
+++ b/taverna-workbench-workbench-impl/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>workbench-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Workbench UI implementation</name>
+	<description>The main workbench ui</description>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<configuration>
+					<instructions>
+						<Embed-Dependency>osxapplication</Embed-Dependency>
+						<Import-Package>com.apple.eawt;resolution:=optional,*</Import-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>workbench-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>edits-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>configuration-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>file-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>selection-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>observer</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>ui</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.commons</groupId>
+			<artifactId>taverna-plugin-api</artifactId>
+			<version>${taverna.commons.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.simplericity.macify</groupId>
+			<artifactId>macify</artifactId>
+			<version>1.6</version>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java
new file mode 100644
index 0000000..4c493d1
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java
@@ -0,0 +1,93 @@
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static uk.org.taverna.scufl2.api.container.WorkflowBundle.generateIdentifier;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.UpdateDataflowInternalIdentifierEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Listens out for any edits on a dataflow and changes its internal id (or back
+ * to the old one in the case of redo/undo). Is first created when the workbench
+ * is initialised.
+ * 
+ * @author Ian Dunlop
+ */
+public class DataflowEditsListener implements Observer<EditManagerEvent> {
+	private static Logger logger = Logger
+			.getLogger(DataflowEditsListener.class);
+
+	private Map<Edit<?>, URI> dataflowEditMap;
+
+	public DataflowEditsListener() {
+		super();
+		dataflowEditMap = new HashMap<>();
+	}
+
+	/**
+	 * Receives {@link EditManagerEvent}s from the {@link EditManager} and
+	 * changes the id of the {@link Dataflow} to a new one or back to its old
+	 * one depending on whether it is a do/undo/redo event. Stores the actual
+	 * edit and the pre-edit dataflow id in a Map and changes the id when it
+	 * gets further actions against this same edit
+	 */
+	@Override
+	public void notify(Observable<EditManagerEvent> observable,
+			EditManagerEvent event) throws Exception {
+		Edit<?> edit = event.getEdit();
+		WorkflowBundle dataFlow = ((AbstractDataflowEditEvent) event)
+				.getDataFlow();
+
+		if (event instanceof DataflowEditEvent) {
+			/*
+			 * the dataflow has been edited in some way so change its internal
+			 * id and store the old one against the edit that is changing
+			 * 'something'
+			 */
+			URI internalIdentifier = dataFlow.getGlobalBaseURI();
+			dataflowEditMap.put(edit, internalIdentifier);
+			URI newIdentifier = generateIdentifier();
+			new UpdateDataflowInternalIdentifierEdit(dataFlow, newIdentifier)
+					.doEdit();
+			logger.debug("Workflow edit, id changed from: "
+					+ internalIdentifier + " to " + newIdentifier);
+		} else if (event instanceof DataFlowRedoEvent) {
+			/*
+			 * change the id back to the old one and store the new one in case
+			 * we want to change it back
+			 */
+			URI newId = dataFlow.getGlobalBaseURI();
+			URI oldId = dataflowEditMap.get(edit);
+			dataflowEditMap.put(edit, newId);
+			new UpdateDataflowInternalIdentifierEdit(dataFlow, oldId).doEdit();
+			logger.debug("Workflow edit, id changed from: " + newId + " to "
+					+ oldId);
+		} else if (event instanceof DataFlowUndoEvent) {
+			/*
+			 * change the id back to the old one and store the new one in case
+			 * we want to change it back
+			 */
+			URI newId = dataFlow.getGlobalBaseURI();
+			URI oldId = dataflowEditMap.get(edit);
+			dataflowEditMap.put(edit, newId);
+			new UpdateDataflowInternalIdentifierEdit(dataFlow, oldId).doEdit();
+			logger.debug("Workflow edit, id changed from: " + newId + " to "
+					+ oldId);
+		}
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java
new file mode 100644
index 0000000..fe13d4d
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java
@@ -0,0 +1,136 @@
+/***************************************
+ *                                     *
+ *  JBoss: The OpenSource J2EE WebOS   *
+ *                                     *
+ *  Distributable under LGPL license.  *
+ *  See terms of license at gnu.org.   *
+ *                                     *
+ *  Modified by Stian Soiland-Reyes    *
+ *                                     *
+ ***************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.Level;
+
+/**
+ * A subclass of PrintStream that redirects its output to a log4j Logger.
+ *
+ * <p>This class is used to map PrintStream/PrintWriter oriented logging onto
+ *    the log4j Categories. Examples include capturing System.out/System.err
+ *
+ * @version <tt>$Revision: 1.1.1.1 $</tt>
+ * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
+ * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
+ */
+//FIXME Replace this class entirely
+class LoggerStream extends PrintStream {
+	/**
+	 * Default flag to enable/disable tracing println calls. from the system
+	 * property <tt>net.sf.taverna.t2.workbench.ui.impl.LoggerStream.trace</tt>
+	 * or if not set defaults to <tt>false</tt>.
+	 */
+   public static final boolean TRACE =
+		   getBoolean(LoggerStream.class.getName() + ".trace", false);
+
+	/**
+	 * Helper to get boolean value from system property or use default if not
+	 * set.
+	 */
+	private static boolean getBoolean(String name, boolean defaultValue) {
+		String value = System.getProperty(name, null);
+		if (value == null)
+			return defaultValue;
+		return new Boolean(value).booleanValue();
+	}
+
+	private Logger logger;
+	private Level level;
+	private boolean issuedWarning;
+
+	/**
+	 * Redirect logging to the indicated logger using Level.INFO
+	 */
+	public LoggerStream(Logger logger) {
+		this(logger, Level.INFO, System.out);
+	}
+
+	/**
+	 * Redirect logging to the indicated logger using the given level. The ps is
+	 * simply passed to super but is not used.
+	 */
+	public LoggerStream(Logger logger, Level level, PrintStream ps) {
+		super(ps);
+		this.logger = logger;
+		this.level = level;
+	}
+
+	@Override
+	public void println(String msg) {
+		if (msg == null)
+			msg = "null";
+		byte[] bytes = msg.getBytes();
+		write(bytes, 0, bytes.length);
+	}
+
+	@Override
+	public void println(Object msg) {
+		if (msg == null)
+			msg = "null";
+		byte[] bytes = msg.toString().getBytes();
+		write(bytes, 0, bytes.length);
+	}
+
+	public void write(byte b) {
+		byte[] bytes = { b };
+		write(bytes, 0, 1);
+	}
+
+	private ThreadLocal<Boolean> recursiveCheck = new ThreadLocal<>();
+
+	@Override
+	public void write(byte[] b, int off, int len) {
+		Boolean recursed = recursiveCheck.get();
+		if (recursed != null && recursed) {
+			/*
+			 * There is a configuration error that is causing looping. Most
+			 * likely there are two console appenders so just return to prevent
+			 * spinning.
+			 */
+			if (issuedWarning == false) {
+				String msg = "ERROR: invalid log settings detected, console capturing is looping";
+				// out.write(msg.getBytes());
+				new Exception(msg).printStackTrace((PrintStream) out);
+				issuedWarning = true;
+			}
+			try {
+				out.write(b, off, len);
+			} catch (IOException e) {
+			}
+			return;
+		}
+
+		// Remove the end of line chars
+		while (len > 0 && (b[len - 1] == '\n' || b[len - 1] == '\r')
+				&& len > off)
+			len--;
+
+		/*
+		 * HACK, something is logging exceptions line by line (including
+		 * blanks), but I can't seem to find it, so for now just ignore empty
+		 * lines... they aren't very useful.
+		 */
+		if (len != 0) {
+			String msg = new String(b, off, len);
+			recursiveCheck.set(true);
+			if (TRACE)
+				logger.log(level, msg, new Throwable());
+			else
+				logger.log(level, msg);
+			recursiveCheck.set(false);
+		}
+	}
+}
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java
new file mode 100644
index 0000000..a8bdfdd
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static org.apache.log4j.Level.ERROR;
+import static org.apache.log4j.Level.WARN;
+
+import java.io.PrintStream;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+
+public class SetConsoleLoggerStartup implements StartupSPI {
+	private static final PrintStream originalErr = System.err;
+	private static final PrintStream originalOut = System.out;
+
+	private final WorkbenchConfiguration workbenchConfiguration;
+
+	public SetConsoleLoggerStartup(WorkbenchConfiguration workbenchConfiguration) {
+		this.workbenchConfiguration = workbenchConfiguration;
+	}
+
+	@Override
+	public int positionHint() {
+		/*
+		 * Must be <b>after</b> PrepareLoggerStarup in file-translator --
+		 * otherwise Taverna 1 libraries will cause double logging
+		 */
+		return 10;
+	}
+
+	@Override
+	public boolean startup() {
+		setSystemOutCapture();
+		return true;
+	}
+
+	public void setSystemOutCapture() {
+		if (!workbenchConfiguration.getCaptureConsole()) {
+			System.setOut(originalOut);
+			System.setErr(originalErr);
+			return;
+		}
+		Logger systemOutLogger = Logger.getLogger("System.out");
+		Logger systemErrLogger = Logger.getLogger("System.err");
+
+		try {
+			/*
+			 * This logger stream not loop with log4j > 1.2.13, which has
+			 * getFollow method
+			 */
+			ConsoleAppender.class.getMethod("getFollow");
+			System.setOut(new LoggerStream(systemOutLogger, WARN, originalOut));
+		} catch (SecurityException e) {
+		} catch (NoSuchMethodException e) {
+			System.err.println("Not capturing System.out, use log4j >= 1.2.13");
+		}
+
+		System.setErr(new LoggerStream(systemErrLogger, ERROR, originalErr));
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java
new file mode 100644
index 0000000..2537f0b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java
@@ -0,0 +1,58 @@
+/*******************************************************************************

+ * Copyright (C) 2008-2010 The University of Manchester

+ *

+ *  Modifications to the initial code base are copyright of their

+ *  respective authors, or their employers as appropriate.

+ *

+ *  This program is free software; you can redistribute it and/or

+ *  modify it under the terms of the GNU Lesser General Public License

+ *  as published by the Free Software Foundation; either version 2.1 of

+ *  the License, or (at your option) any later version.

+ *

+ *  This program is distributed in the hope that it will be useful, but

+ *  WITHOUT ANY WARRANTY; without even the implied warranty of

+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

+ *  Lesser General Public License for more details.

+ *

+ *  You should have received a copy of the GNU Lesser General Public

+ *  License along with this program; if not, write to the Free Software

+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307

+ ******************************************************************************/

+package net.sf.taverna.t2.workbench.ui.impl;

+

+import org.apache.log4j.Logger;

+

+import net.sf.taverna.t2.workbench.ShutdownSPI;

+import net.sf.taverna.t2.workbench.ui.Workbench;

+

+/**

+ * Store Workbench window size and perspectives, so that settings can be used on

+ * next startup.

+ * 

+ * @author Stian Soiland-Reyes

+ */

+public class StoreWindowStateOnShutdown implements ShutdownSPI {

+	private static Logger logger = Logger

+			.getLogger(StoreWindowStateOnShutdown.class);

+

+	private Workbench workbench;

+

+	@Override

+	public int positionHint() {

+		return 1000;

+	}

+

+	@Override

+	public boolean shutdown() {

+		try {

+			workbench.storeSizeAndLocationPrefs();

+		} catch (Exception ex) {

+			logger.error("Error saving the Workbench size and position", ex);

+		}

+		return true;

+	}

+

+	public void setWorkbench(Workbench workbench) {

+		this.workbench = workbench;

+	}

+}

diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java
new file mode 100644
index 0000000..3e32f7b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+public class UserRegistrationData {
+	private String tavernaVersion = "";
+	private String firstName = "";
+	private String lastName = "";
+	private String emailAddress = "";
+	private String institutionOrCompanyName = "";
+	private String industry = "";
+	private String field = "";
+	private String purposeOfUsingTaverna = "";
+	private boolean keepMeInformed = false;
+
+	public void setTavernaVersion(String tavernaVersion) {
+		this.tavernaVersion = tavernaVersion;
+	}
+
+	public String getTavernaVersion() {
+		return tavernaVersion;
+	}
+
+	public void setFirstName(String firstName) {
+		this.firstName = firstName;
+	}
+
+	public String getFirstName() {
+		return firstName;
+	}
+
+	public void setLastName(String lastName) {
+		this.lastName = lastName;
+	}
+
+	public String getLastName() {
+		return lastName;
+	}
+
+	public void setEmailAddress(String emailAddress) {
+		this.emailAddress = emailAddress;
+	}
+
+	public String getEmailAddress() {
+		return emailAddress;
+	}
+
+	public void setInstitutionOrCompanyName(String institutionOrCompanyName) {
+		this.institutionOrCompanyName = institutionOrCompanyName;
+	}
+
+	public String getInstitutionOrCompanyName() {
+		return institutionOrCompanyName;
+	}
+
+	public void setIndustry(String industry) {
+		this.industry = industry;
+	}
+
+	public String getIndustry() {
+		return industry;
+	}
+
+	public void setField(String field) {
+		this.field = field;
+	}
+
+	public String getField() {
+		return field;
+	}
+
+	public void setPurposeOfUsingTaverna(String purposeOfUsingTaverna) {
+		this.purposeOfUsingTaverna = purposeOfUsingTaverna;
+	}
+
+	public String getPurposeOfUsingTaverna() {
+		return purposeOfUsingTaverna;
+	}
+
+	public void setKeepMeInformed(boolean keepMeInformed) {
+		this.keepMeInformed = keepMeInformed;
+	}
+
+	public boolean getKeepMeInformed() {
+		return keepMeInformed;
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java
new file mode 100644
index 0000000..97f831f
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java
@@ -0,0 +1,995 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Color.LIGHT_GRAY;
+import static java.awt.Color.WHITE;
+import static java.awt.FlowLayout.LEFT;
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.FIRST_LINE_START;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.awt.event.KeyEvent.VK_ENTER;
+import static java.awt.event.KeyEvent.VK_TAB;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingConstants.BOTTOM;
+import static javax.swing.SwingConstants.TOP;
+import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tavernaCogs32x32Icon;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Properties;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.Border;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+/**
+ * User registration form.
+ * 
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class UserRegistrationForm extends HelpEnabledDialog {
+	private static final String FAILED = "User registration failed: ";
+
+	private static final String REGISTRATION_FAILED_MSG = "User registration failed. Please try again later.";
+
+	private static final String REGISTRATION_URL = "http://www.mygrid.org.uk/taverna/registration/";
+
+	public static final String TAVERNA_VERSION_PROPERTY_NAME = "Taverna version";
+	public static final String FIRST_NAME_PROPERTY_NAME = "First name";
+	public static final String LAST_NAME_PROPERTY_NAME = "Last name";
+	public static final String EMAIL_ADDRESS_PROPERTY_NAME = "Email address";
+	public static final String INSTITUTION_OR_COMPANY_PROPERTY_NAME = "Institution or company name";
+	public static final String INDUSTRY_PROPERTY_NAME = "Industry";
+	public static final String FIELD_PROPERTY_NAME = "Field of investigation";
+	public static final String PURPOSE_PROPERTY_NAME = "Purpose of using Taverna";
+	public static final String KEEP_ME_INFORMED_PROPERTY_NAME = "Keep me informed by email";
+
+	public static final String TAVERNA_REGISTRATION_POST_PARAMETER_NAME = "taverna_registration";
+	public static final String TAVERNA_VERSION_POST_PARAMETER_NAME = "taverna_version";
+	public static final String FIRST_NAME_POST_PARAMETER_NAME = "first_name";
+	public static final String LAST_NAME_POST_PARAMETER_NAME = "last_name";
+	public static final String EMAIL_ADDRESS_POST_PARAMETER_NAME = "email";
+	public static final String INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME = "institution_or_company";
+	public static final String INDUSTRY_TYPE_POST_PARAMETER_NAME = "industry_type";
+	public static final String FIELD_POST_PARAMETER_NAME = "field";
+	public static final String PURPOSE_POST_PARAMETER_NAME = "purpose";
+	public static final String KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME = "keep_me_informed";
+
+	private static String TRUE = Boolean.TRUE.toString();
+	private static String FALSE = Boolean.FALSE.toString();
+
+	private static final String WELCOME = "Welcome to the Taverna User Registration Form";
+	private static final String PLEASE_FILL_IN_THIS_REGISTRATION_FORM = "Please fill in this registration form to let us know that you are using Taverna";
+
+	private static final String WE_DO = "Note that by registering:\n"
+			+ "   \u25CF We do not have access to your data\n"
+			+ "   \u25CF We do not have access to your service usage\n"
+			+ "   \u25CF You will not be monitored\n"
+			+ "   \u25CF We do record the information you provide\n"
+			+ "     at registration time";
+
+	private static final String WHY_REGISTER = "By registering you will:\n"
+			+ "   \u25CF Allow us to support you better; future plans will be\n"
+			+ "     directed towards solutions Taverna users require\n"
+			+ "   \u25CF Help sustain Taverna development; our continued\n"
+			+ "     funding relies on us showing usage\n"
+			+ "   \u25CF (Optionally) Hear about news and product updates";
+
+	private static final String FIRST_NAME = "*First name:";
+	private static final String LAST_NAME = "*Last name:";
+	private static final String EMAIL_ADDRESS = "*Email address:";
+	private static final String KEEP_ME_INFORMED = "Keep me informed of news and product updates via email";
+	private static final String INSTITUTION_COMPANY_NAME = "*Institution/Company name:";
+	private static final String FIELD_OF_INVESTIGATION = " Field of investigation:\n"
+			+ " (e.g. bioinformatics)";
+	private static final String WHY_YOU_INTEND_TO_USE_TAVERNA = " A brief description of how you intend\n"
+			+ " to use Taverna: (e.g. genome analysis\n"
+			+ " for bacterial strain identification)";
+
+	private static String[] industryTypes = { "", "Academia - Life Sciences",
+			"Academia - Social Sciences", "Academia - Physical Sciences",
+			"Academia - Environmental Sciences", "Academia - Other",
+			"Industry - Biotechnology", "Industry - Pharmaceutical",
+			"Industry - Engineering", "Industry - Other",
+			"Healthcare Services", "Goverment and Public Sector", "Other" };
+
+	private static final String I_AGREE_TO_THE_TERMS_AND_CONDITIONS = "I agree to the terms and conditions of registration at";
+	private static final String TERMS_AND_CONDITIONS_URL = "http://www.taverna.org.uk/legal/terms";
+
+	private Logger logger = Logger.getLogger(UserRegistrationForm.class);
+	private UserRegistrationData previousRegistrationData;
+	private JTextField firstNameTextField;
+	private JTextField lastNameTextField;
+	private JTextField emailTextField;
+	private JCheckBox keepMeInformedCheckBox;
+	private JTextField institutionOrCompanyTextField;
+	private JComboBox<String> industryTypeTextField;
+	private JTextField fieldTextField;
+	private JTextArea purposeTextArea;
+	private JCheckBox termsAndConditionsCheckBox;
+
+	private final File registrationDataFile;
+	private final File remindMeLaterFile;
+	private final File doNotRegisterMeFile;
+	private final String appName;
+
+	public UserRegistrationForm(String appName, File registrationDataFile,
+			File doNotRegisterMeFile, File remindMeLaterFile) {
+		super((Frame) null, "Taverna User Registration", true);
+		this.appName = appName;
+		this.registrationDataFile = registrationDataFile;
+		this.doNotRegisterMeFile = doNotRegisterMeFile;
+		this.remindMeLaterFile = remindMeLaterFile;
+		initComponents();
+	}
+
+	public UserRegistrationForm(String appName,
+			File previousRegistrationDataFile, File registrationDataFile,
+			File doNotRegisterMeFile, File remindMeLaterFile) {
+		super((Frame) null, "Taverna User Registration", true);
+		this.appName = appName;
+		this.registrationDataFile = registrationDataFile;
+		this.doNotRegisterMeFile = doNotRegisterMeFile;
+		this.remindMeLaterFile = remindMeLaterFile;
+		previousRegistrationData = loadUserRegistrationData(previousRegistrationDataFile);
+		initComponents();
+	}
+
+	// For testing only
+	// public static void main(String[] args) throws ClassNotFoundException,
+	// InstantiationException, IllegalAccessException,
+	// UnsupportedLookAndFeelException{
+	// WorkbenchImpl.setLookAndFeel();
+	// UserRegistrationForm form = new UserRegistrationForm();
+	// form.setVisible(true);
+	// }
+
+	private void initComponents() {
+		JPanel mainPanel = new JPanel(new GridBagLayout());
+
+		// Base font for all components on the form
+		Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+
+		// Title panel
+		JPanel titlePanel = new JPanel(new FlowLayout(LEFT));
+		titlePanel.setBackground(WHITE);
+		// titlePanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+		JLabel titleLabel = new JLabel(WELCOME);
+		titleLabel.setFont(baseFont.deriveFont(BOLD, 13.5f));
+		// titleLabel.setBorder(new EmptyBorder(10, 10, 0, 10));
+		JLabel titleIcon = new JLabel(tavernaCogs32x32Icon);
+		// titleIcon.setBorder(new EmptyBorder(10, 10, 10, 10));
+		DialogTextArea titleMessage = new DialogTextArea(
+				PLEASE_FILL_IN_THIS_REGISTRATION_FORM);
+		titleMessage.setMargin(new Insets(0, 20, 0, 10));
+		titleMessage.setFont(baseFont);
+		titleMessage.setEditable(false);
+		titleMessage.setFocusable(false);
+		// titlePanel.setBorder( new EmptyBorder(10, 10, 0, 10));
+		JPanel messagePanel = new JPanel(new BorderLayout());
+		messagePanel.add(titleLabel, NORTH);
+		messagePanel.add(titleMessage, CENTER);
+		messagePanel.setBackground(WHITE);
+		titlePanel.add(titleIcon);
+		titlePanel.add(messagePanel);
+		addDivider(titlePanel, BOTTOM, true);
+
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 2;
+		// gbc.insets = new Insets(5, 10, 0, 0);
+		mainPanel.add(titlePanel, gbc);
+
+		// Registration messages
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 1;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 2;
+		// gbc.insets = new Insets(5, 0, 0, 10);
+		DialogTextArea registrationMessage1 = new DialogTextArea(WHY_REGISTER);
+		registrationMessage1.setMargin(new Insets(0, 10, 0, 0));
+		registrationMessage1.setFont(baseFont);
+		registrationMessage1.setEditable(false);
+		registrationMessage1.setFocusable(false);
+		registrationMessage1.setBackground(getBackground());
+
+		DialogTextArea registrationMessage2 = new DialogTextArea(WE_DO);
+		registrationMessage2.setMargin(new Insets(0, 10, 0, 10));
+		registrationMessage2.setFont(baseFont);
+		registrationMessage2.setEditable(false);
+		registrationMessage2.setFocusable(false);
+		registrationMessage2.setBackground(getBackground());
+		JPanel registrationMessagePanel = new JPanel(new FlowLayout(
+				FlowLayout.CENTER));
+		registrationMessagePanel.add(registrationMessage1);
+		registrationMessagePanel.add(registrationMessage2);
+		addDivider(registrationMessagePanel, BOTTOM, true);
+		mainPanel.add(registrationMessagePanel, gbc);
+
+		// Mandatory label
+		// JLabel mandatoryLabel = new JLabel("* Mandatory fields");
+		// mandatoryLabel.setFont(baseFont);
+		// gbc.weightx = 0.0;
+		// gbc.weighty = 0.0;
+		// gbc.gridx = 0;
+		// gbc.gridy = 3;
+		// gbc.fill = NONE;
+		// gbc.anchor = GridBagConstraints.EAST;
+		// gbc.gridwidth = 2;
+		// gbc.insets = new Insets(0, 10, 0, 20);
+		// mainPanel.add(mandatoryLabel, gbc);
+
+		// First name
+		JLabel firstNameLabel = new JLabel(FIRST_NAME);
+		firstNameLabel.setFont(baseFont);
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 4;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(0, 10, 0, 10);
+		mainPanel.add(firstNameLabel, gbc);
+
+		firstNameTextField = new JTextField();
+		firstNameTextField.setFont(baseFont);
+		if (previousRegistrationData != null)
+			firstNameTextField.setText(previousRegistrationData.getFirstName());
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 4;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(firstNameTextField, gbc);
+
+		// Last name
+		JLabel lastNameLabel = new JLabel(LAST_NAME);
+		lastNameLabel.setFont(baseFont);
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 5;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(0, 10, 0, 10);
+		mainPanel.add(lastNameLabel, gbc);
+
+		lastNameTextField = new JTextField();
+		lastNameTextField.setFont(baseFont);
+		if (previousRegistrationData != null)
+			lastNameTextField.setText(previousRegistrationData.getLastName());
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 5;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(lastNameTextField, gbc);
+
+		// Email address
+		JLabel emailLabel = new JLabel(EMAIL_ADDRESS);
+		emailLabel.setFont(baseFont);
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 6;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(emailLabel, gbc);
+
+		emailTextField = new JTextField();
+		emailTextField.setFont(baseFont);
+		if (previousRegistrationData != null)
+			emailTextField.setText(previousRegistrationData.getEmailAddress());
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 6;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(emailTextField, gbc);
+
+		// Keep me informed
+		keepMeInformedCheckBox = new JCheckBox(KEEP_ME_INFORMED);
+		keepMeInformedCheckBox.setFont(baseFont);
+		if (previousRegistrationData != null)
+			keepMeInformedCheckBox.setSelected(previousRegistrationData
+					.getKeepMeInformed());
+		keepMeInformedCheckBox.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent evt) {
+				if (evt.getKeyCode() == VK_ENTER) {
+					evt.consume();
+					keepMeInformedCheckBox.setSelected(!keepMeInformedCheckBox
+							.isSelected());
+				}
+			}
+		});
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 7;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 2;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(keepMeInformedCheckBox, gbc);
+
+		// Institution name
+		JLabel institutionLabel = new JLabel(INSTITUTION_COMPANY_NAME);
+		institutionLabel.setFont(baseFont);
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 8;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(institutionLabel, gbc);
+
+		institutionOrCompanyTextField = new JTextField();
+		institutionOrCompanyTextField.setFont(baseFont);
+		if (previousRegistrationData != null)
+			institutionOrCompanyTextField.setText(previousRegistrationData
+					.getInstitutionOrCompanyName());
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 8;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(institutionOrCompanyTextField, gbc);
+
+		// Industry type
+		JLabel industryLabel = new JLabel(" Industry type:");
+		industryLabel.setFont(baseFont);
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 9;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(industryLabel, gbc);
+
+		industryTypeTextField = new JComboBox<>(industryTypes);
+		industryTypeTextField.setFont(baseFont);
+		if (previousRegistrationData != null)
+			industryTypeTextField.setSelectedItem(previousRegistrationData
+					.getIndustry());
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 9;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(industryTypeTextField, gbc);
+
+		// Field of investigation
+		JTextArea fieldLabel = new JTextArea(FIELD_OF_INVESTIGATION);
+		fieldLabel.setFont(baseFont);
+		fieldLabel.setEditable(false);
+		fieldLabel.setFocusable(false);
+		fieldLabel.setBackground(getBackground());
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 10;
+		gbc.fill = NONE;
+		gbc.anchor = LINE_START;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(fieldLabel, gbc);
+
+		fieldTextField = new JTextField();
+		fieldTextField.setFont(baseFont);
+		if (previousRegistrationData != null)
+			fieldTextField.setText(previousRegistrationData.getField());
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 10;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = FIRST_LINE_START;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(fieldTextField, gbc);
+
+		// Purpose of using Taverna
+		JTextArea purposeLabel = new JTextArea(WHY_YOU_INTEND_TO_USE_TAVERNA);
+		purposeLabel.setFont(baseFont);
+		purposeLabel.setEditable(false);
+		purposeLabel.setFocusable(false);
+		purposeLabel.setBackground(getBackground());
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 11;
+		gbc.fill = NONE;
+		gbc.anchor = LINE_START;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(purposeLabel, gbc);
+
+		purposeTextArea = new JTextArea(4, 30);
+		purposeTextArea.setFont(baseFont);
+		purposeTextArea.setLineWrap(true);
+		purposeTextArea.setAutoscrolls(true);
+		if (previousRegistrationData != null)
+			purposeTextArea.setText(previousRegistrationData
+					.getPurposeOfUsingTaverna());
+		purposeTextArea.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent evt) {
+				if (evt.getKeyCode() == VK_TAB) {
+					if (evt.getModifiers() > 0)
+						purposeTextArea.transferFocusBackward();
+					else
+						purposeTextArea.transferFocus();
+					evt.consume();
+				}
+			}
+		});
+		JScrollPane purposeScrollPane = new JScrollPane(purposeTextArea);
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 1;
+		gbc.gridy = 11;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = FIRST_LINE_START;
+		gbc.gridwidth = 1;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		mainPanel.add(purposeScrollPane, gbc);
+
+		// Terms and conditions
+		termsAndConditionsCheckBox = new JCheckBox(
+				I_AGREE_TO_THE_TERMS_AND_CONDITIONS);
+		termsAndConditionsCheckBox.setFont(baseFont);
+		termsAndConditionsCheckBox.setBorder(null);
+		termsAndConditionsCheckBox.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent evt) {
+				if (evt.getKeyCode() == VK_ENTER) {
+					evt.consume();
+					termsAndConditionsCheckBox
+							.setSelected(!termsAndConditionsCheckBox
+									.isSelected());
+				}
+			}
+		});
+		// gbc.weightx = 0.0;
+		// gbc.weighty = 0.0;
+		// gbc.gridx = 0;
+		// gbc.gridy = 12;
+		// gbc.fill = NONE;
+		// gbc.anchor = WEST;
+		// gbc.gridwidth = 2;
+		// gbc.insets = new Insets(10, 10, 0, 0);
+		// mainPanel.add(termsAndConditionsCheckBox, gbc);
+
+		// Terms and conditions link
+		JEditorPane termsAndConditionsURL = new JEditorPane();
+		termsAndConditionsURL.setEditable(false);
+		termsAndConditionsURL.setBackground(getBackground());
+		termsAndConditionsURL.setFocusable(false);
+		HTMLEditorKit kit = new HTMLEditorKit();
+		termsAndConditionsURL.setEditorKit(kit);
+		StyleSheet styleSheet = kit.getStyleSheet();
+		// styleSheet.addRule("body {font-family:"+baseFont.getFamily()+"; font-size:"+baseFont.getSize()+";}");
+		// // base font looks bigger when rendered as HTML
+		styleSheet.addRule("body {font-family:" + baseFont.getFamily()
+				+ "; font-size:9px;}");
+		Document doc = kit.createDefaultDocument();
+		termsAndConditionsURL.setDocument(doc);
+		termsAndConditionsURL.setText("<html><body><a href=\""
+				+ TERMS_AND_CONDITIONS_URL + "\">" + TERMS_AND_CONDITIONS_URL
+				+ "</a></body></html>");
+		termsAndConditionsURL.addHyperlinkListener(new HyperlinkListener() {
+			@Override
+			public void hyperlinkUpdate(HyperlinkEvent he) {
+				if (he.getEventType() == ACTIVATED)
+					followHyperlinkToTandCs();
+			}
+		});
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridx = 0;
+		gbc.gridy = 13;
+		gbc.fill = NONE;
+		gbc.anchor = WEST;
+		gbc.gridwidth = 2;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		JPanel termsAndConditionsPanel = new JPanel(new FlowLayout(LEFT));
+		termsAndConditionsPanel.add(termsAndConditionsCheckBox);
+		termsAndConditionsPanel.add(termsAndConditionsURL);
+		mainPanel.add(termsAndConditionsPanel, gbc);
+
+		// Button panel
+		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+		JButton registerButton = new JButton("Register");
+		registerButton.setFont(baseFont);
+		registerButton.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent evt) {
+				if (evt.getKeyCode() == VK_ENTER) {
+					evt.consume();
+					register();
+				}
+			}
+		});
+		registerButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				register();
+			}
+		});
+		JButton doNotRegisterButton = new JButton("Do not ask me again");
+		doNotRegisterButton.setFont(baseFont);
+		doNotRegisterButton.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent evt) {
+				if (evt.getKeyCode() == VK_ENTER) {
+					evt.consume();
+					doNotRegister();
+				}
+			}
+		});
+		doNotRegisterButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				doNotRegister();
+			}
+		});
+		JButton remindMeLaterButton = new JButton("Remind me later"); // in 2 weeks
+		remindMeLaterButton.setFont(baseFont);
+		remindMeLaterButton.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent evt) {
+				if (evt.getKeyCode() == VK_ENTER) {
+					evt.consume();
+					remindMeLater();
+				}
+			}
+		});
+		remindMeLaterButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				remindMeLater();
+			}
+		});
+		buttonPanel.add(registerButton);
+		buttonPanel.add(remindMeLaterButton);
+		buttonPanel.add(doNotRegisterButton);
+		addDivider(buttonPanel, TOP, true);
+		gbc.gridx = 0;
+		gbc.gridy = 14;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = GridBagConstraints.CENTER;
+		gbc.insets = new Insets(5, 10, 0, 10);
+		gbc.gridwidth = 2;
+		mainPanel.add(buttonPanel, gbc);
+
+		getContentPane().setLayout(new BorderLayout());
+		getContentPane().add(mainPanel, CENTER);
+
+		pack();
+		setResizable(false);
+		// Center the dialog on the screen (we do not have the parent)
+		Dimension dimension = getToolkit().getScreenSize();
+		Rectangle abounds = getBounds();
+		setLocation((dimension.width - abounds.width) / 2,
+				(dimension.height - abounds.height) / 2);
+		setSize(getPreferredSize());
+	}
+
+	protected void remindMeLater() {
+		try {
+			FileUtils.touch(remindMeLaterFile);
+		} catch (IOException ioex) {
+			logger.error(
+					"Failed to touch the 'Remind me later' file at user registration.",
+					ioex);
+		}
+		closeDialog();
+	}
+
+	protected void doNotRegister() {
+		try {
+			FileUtils.touch(doNotRegisterMeFile);
+			if (remindMeLaterFile.exists())
+				remindMeLaterFile.delete();
+		} catch (IOException ioex) {
+			logger.error(
+					"Failed to touch the 'Do not register me' file at user registration.",
+					ioex);
+		}
+		closeDialog();
+	}
+
+	private void register() {
+		if (!validateForm())
+			return;
+		UserRegistrationData regData = new UserRegistrationData();
+		regData.setTavernaVersion(appName);
+		regData.setFirstName(firstNameTextField.getText());
+		regData.setLastName(lastNameTextField.getText());
+		regData.setEmailAddress(emailTextField.getText());
+		regData.setKeepMeInformed(keepMeInformedCheckBox.isSelected());
+		regData.setInstitutionOrCompanyName(institutionOrCompanyTextField
+				.getText());
+		regData.setIndustry(industryTypeTextField.getSelectedItem().toString());
+		regData.setField(fieldTextField.getText());
+		regData.setPurposeOfUsingTaverna(purposeTextArea.getText());
+
+		if (postUserRegistrationDataToServer(regData)) {
+			saveUserRegistrationData(regData, registrationDataFile);
+			if (remindMeLaterFile.exists())
+				remindMeLaterFile.delete();
+			closeDialog();
+		}
+	}
+
+	private boolean validateForm() {
+		String errorMessage = "";
+		if (firstNameTextField.getText().isEmpty())
+			errorMessage += "Please provide your first name.<br>";
+		if (lastNameTextField.getText().isEmpty())
+			errorMessage += "Please provide your last name.<br>";
+		if (emailTextField.getText().isEmpty())
+			errorMessage += "Please provide your email address.<br>";
+		if (institutionOrCompanyTextField.getText().isEmpty())
+			errorMessage += "Please provide your institution or company name.";
+		if (!errorMessage.isEmpty()) {
+			showMessageDialog(this, new JLabel("<html><body>"
+					+ errorMessage + "</body></html>"), "Error in form",
+					ERROR_MESSAGE);
+			return false;
+		}
+		if (!termsAndConditionsCheckBox.isSelected()) {
+			showMessageDialog(this, new JLabel(
+					"You must agree to the terms and conditions."),
+					"Error in form", ERROR_MESSAGE);
+			return false;
+		}
+		return true;
+	}
+
+	public UserRegistrationData loadUserRegistrationData(File propertiesFile) {
+		UserRegistrationData regData = new UserRegistrationData();
+		Properties props = new Properties();
+
+		// Try to retrieve data from file
+		try {
+			props.load(new FileInputStream(propertiesFile));
+		} catch (IOException e) {
+			logger.error("Failed to load old user registration data from "
+					+ propertiesFile.getAbsolutePath(), e);
+			return null;
+		}
+		regData.setTavernaVersion(props
+				.getProperty(TAVERNA_VERSION_PROPERTY_NAME));
+		regData.setFirstName(props.getProperty(FIRST_NAME_PROPERTY_NAME));
+		regData.setLastName(props.getProperty(LAST_NAME_PROPERTY_NAME));
+		regData.setEmailAddress(props.getProperty(EMAIL_ADDRESS_PROPERTY_NAME));
+		regData.setKeepMeInformed(props.getProperty(
+				KEEP_ME_INFORMED_PROPERTY_NAME).equals(TRUE));
+		regData.setInstitutionOrCompanyName(props
+				.getProperty(INSTITUTION_OR_COMPANY_PROPERTY_NAME));
+		regData.setIndustry(props.getProperty(INDUSTRY_PROPERTY_NAME));
+		regData.setField(props.getProperty(FIELD_PROPERTY_NAME));
+		regData.setPurposeOfUsingTaverna(props
+				.getProperty(PURPOSE_PROPERTY_NAME));
+		return regData;
+	}
+
+	private void enc(StringBuilder buffer, String name, Object value)
+			throws UnsupportedEncodingException {
+		if (buffer.length() != 0)
+			buffer.append('&');
+		buffer.append(URLEncoder.encode(name, "UTF-8"));
+		buffer.append('=');
+		buffer.append(URLEncoder.encode(value.toString(), "UTF-8"));
+	}
+
+	/**
+	 * Post registration data to our server.
+	 */
+	private boolean postUserRegistrationDataToServer(
+			UserRegistrationData regData) {
+		StringBuilder parameters = new StringBuilder();
+
+		/*
+		 * The 'submit' parameter - to let the server-side script know we are
+		 * submitting the user's registration form - all other requests will be
+		 * silently ignored
+		 */
+		try {
+			// value does not matter
+			enc(parameters, TAVERNA_REGISTRATION_POST_PARAMETER_NAME, "submit");
+
+			enc(parameters, TAVERNA_VERSION_POST_PARAMETER_NAME,
+					regData.getTavernaVersion());
+			enc(parameters, FIRST_NAME_POST_PARAMETER_NAME,
+					regData.getFirstName());
+			enc(parameters, LAST_NAME_POST_PARAMETER_NAME,
+					regData.getLastName());
+			enc(parameters, EMAIL_ADDRESS_POST_PARAMETER_NAME,
+					regData.getEmailAddress());
+			enc(parameters, KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME,
+					regData.getKeepMeInformed());
+			enc(parameters, INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME,
+					regData.getInstitutionOrCompanyName());
+			enc(parameters, INDUSTRY_TYPE_POST_PARAMETER_NAME,
+					regData.getIndustry());
+			enc(parameters, FIELD_POST_PARAMETER_NAME, regData.getField());
+			enc(parameters, PURPOSE_POST_PARAMETER_NAME,
+					regData.getPurposeOfUsingTaverna());
+		} catch (UnsupportedEncodingException ueex) {
+			logger.error(FAILED + "Could not url encode post parameters", ueex);
+			showMessageDialog(null, REGISTRATION_FAILED_MSG,
+					"Error encoding registration data", ERROR_MESSAGE);
+			return false;
+		}
+		String server = REGISTRATION_URL;
+		logger.info("Posting user registartion to " + server
+				+ " with parameters: " + parameters);
+		String response = "";
+		String failure;
+		try {
+			URL url = new URL(server);
+			URLConnection conn = url.openConnection();
+			/*
+			 * Set timeout to e.g. 7 seconds, otherwise we might hang too long
+			 * if server is not responding and it will block Taverna
+			 */
+			conn.setConnectTimeout(7000);
+			// Set connection parameters
+			conn.setDoInput(true);
+			conn.setDoOutput(true);
+			conn.setUseCaches(false);
+			// Make server believe we are HTML form data...
+			conn.setRequestProperty("Content-Type",
+					"application/x-www-form-urlencoded");
+			// Write out the bytes of the content string to the stream.
+			try (DataOutputStream out = new DataOutputStream(
+					conn.getOutputStream())) {
+				out.writeBytes(parameters.toString());
+				out.flush();
+			}
+			// Read response from the input stream.
+			try (BufferedReader in = new BufferedReader(new InputStreamReader(
+					conn.getInputStream()))) {
+				String temp;
+				while ((temp = in.readLine()) != null)
+					response += temp + "\n";
+				// Remove the last \n character
+				if (!response.isEmpty())
+					response = response.substring(0, response.length() - 1);
+			}
+			if (response.equals("Registration successful!"))
+				return true;
+			logger.error(FAILED + "Response form server was: " + response);
+			failure = "Error saving registration data on the server";
+		} catch (ConnectException ceex) {
+			/*
+			 * the connection was refused remotely (e.g. no process is listening
+			 * on the remote address/port).
+			 */
+			logger.error(
+					FAILED
+							+ "Registration server is not listening of the specified url.",
+					ceex);
+			failure = "Registration server is not listening at the specified url";
+		} catch (SocketTimeoutException stex) {
+			// timeout has occurred on a socket read or accept.
+			logger.error(FAILED + "Socket timeout occurred.", stex);
+			failure = "Registration server timeout";
+		} catch (MalformedURLException muex) {
+			logger.error(FAILED + "Registartion server's url is malformed.",
+					muex);
+			failure = "Error with registration server's url";
+		} catch (IOException ioex) {
+			logger.error(
+					FAILED
+							+ "Failed to open url connection to registration server or writing/reading to/from it.",
+					ioex);
+			failure = "Error opening connection to the registration server";
+		}
+		showMessageDialog(null, REGISTRATION_FAILED_MSG, failure, ERROR_MESSAGE);
+		return false;
+	}
+
+	private void saveUserRegistrationData(UserRegistrationData regData,
+			File propertiesFile) {
+		Properties props = new Properties();
+		props.setProperty(TAVERNA_VERSION_PROPERTY_NAME,
+				regData.getTavernaVersion());
+		props.setProperty(FIRST_NAME_PROPERTY_NAME, regData.getFirstName());
+		props.setProperty(LAST_NAME_PROPERTY_NAME, regData.getLastName());
+		props.setProperty(EMAIL_ADDRESS_PROPERTY_NAME,
+				regData.getEmailAddress());
+		props.setProperty(KEEP_ME_INFORMED_PROPERTY_NAME,
+				regData.getKeepMeInformed() ? TRUE : FALSE);
+		props.setProperty(INSTITUTION_OR_COMPANY_PROPERTY_NAME,
+				regData.getInstitutionOrCompanyName());
+		props.setProperty(INDUSTRY_PROPERTY_NAME, regData.getIndustry());
+		props.setProperty(FIELD_PROPERTY_NAME,
+				regData.getPurposeOfUsingTaverna());
+		props.setProperty(PURPOSE_PROPERTY_NAME,
+				regData.getPurposeOfUsingTaverna());
+
+		// Write the properties file.
+		try {
+			props.store(new FileOutputStream(propertiesFile), null);
+		} catch (Exception e) {
+			logger.error("Failed to save user registration data locally on disk.");
+		}
+	}
+
+	private void closeDialog() {
+		setVisible(false);
+		dispose();
+	}
+
+	/**
+	 * Adds a light gray or etched border to the top or bottom of a JComponent.
+	 * 
+	 * @author David Withers
+	 * @param component
+	 */
+	protected void addDivider(JComponent component, final int position,
+			final boolean etched) {
+		component.setBorder(new Border() {
+			private final Color borderColor = new Color(.6f, .6f, .6f);
+
+			@Override
+			public Insets getBorderInsets(Component c) {
+				if (position == TOP)
+					return new Insets(5, 0, 0, 0);
+				else
+					return new Insets(0, 0, 5, 0);
+			}
+
+			@Override
+			public boolean isBorderOpaque() {
+				return false;
+			}
+
+			@Override
+			public void paintBorder(Component c, Graphics g, int x, int y,
+					int width, int height) {
+				if (position == TOP) {
+					if (etched) {
+						g.setColor(borderColor);
+						g.drawLine(x, y, x + width, y);
+						g.setColor(WHITE);
+						g.drawLine(x, y + 1, x + width, y + 1);
+					} else {
+						g.setColor(LIGHT_GRAY);
+						g.drawLine(x, y, x + width, y);
+					}
+				} else {
+					if (etched) {
+						g.setColor(borderColor);
+						g.drawLine(x, y + height - 2, x + width, y + height - 2);
+						g.setColor(WHITE);
+						g.drawLine(x, y + height - 1, x + width, y + height - 1);
+					} else {
+						g.setColor(LIGHT_GRAY);
+						g.drawLine(x, y + height - 1, x + width, y + height - 1);
+					}
+				}
+			}
+		});
+	}
+
+	private void followHyperlinkToTandCs() {
+		// Open a Web browser
+		try {
+			Desktop.getDesktop().browse(new URI(TERMS_AND_CONDITIONS_URL));
+		} catch (Exception ex) {
+			logger.error("User registration: Failed to launch browser to show terms and conditions at "
+					+ TERMS_AND_CONDITIONS_URL);
+		}
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java
new file mode 100644
index 0000000..257c3f3
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class UserRegistrationHook implements StartupSPI {
+	/** Delay between when we ask the user about registration, in milliseconds */
+	private static final int TWO_WEEKS = 14 * 24 * 3600 * 1000;
+	public static final String REGISTRATION_DIRECTORY_NAME = "registration";
+	public static final String REGISTRATION_DATA_FILE_NAME = "registration_data.properties";
+	public static final String REMIND_ME_LATER_FILE_NAME = "remind_me_later";
+	public static final String DO_NOT_REGISTER_ME_FILE_NAME = "do_not_register_me";
+
+	private ApplicationConfiguration applicationConfiguration;
+
+	@Override
+	public int positionHint() {
+		return 50;
+	}
+
+	@Override
+	public boolean startup() {
+		File registrationDirectory = getRegistrationDirectory();
+		File registrationDataFile = new File(registrationDirectory,
+				REGISTRATION_DATA_FILE_NAME);
+		File doNotRegisterMeFile = new File(registrationDirectory,
+				DO_NOT_REGISTER_ME_FILE_NAME);
+		File remindMeLaterFile = new File(registrationDirectory,
+				REMIND_ME_LATER_FILE_NAME);
+
+		// if we are running headlessly just return
+		if (isHeadless())
+			return true;
+		// For Taverna snapshots - do not ask user to register
+		if (applicationConfiguration.getName().toLowerCase().contains("snapshot"))
+			return true;
+
+		// If there is already user's registration data present - exit.
+		if (registrationDataFile.exists())
+			return true;
+
+		// If user did not want to register - exit.
+		if (doNotRegisterMeFile.exists())
+			return true;
+
+		/*
+		 * If user said to remind them - check if more than 2 weeks passed since
+		 * we asked previously.
+		 */
+		if (remindMeLaterFile.exists()) {
+			long lastModified = remindMeLaterFile.lastModified();
+			long now = new Date().getTime();
+			if (now - lastModified < TWO_WEEKS)
+				// 2 weeks have not passed since we last asked
+				return true;
+
+			// Ask user again if they want to register
+			UserRegistrationForm form = new UserRegistrationForm(
+					applicationConfiguration.getName(), registrationDataFile,
+					doNotRegisterMeFile, remindMeLaterFile);
+			form.setVisible(true);
+			return true;
+		}
+
+		/*
+		 * Check if there are previous Taverna versions installed and find the
+		 * latest one that contains user registration data, if any. Ask user if
+		 * they want to upload that previous data.
+		 */
+		final File appHomeDirectory = applicationConfiguration.getApplicationHomeDir();
+		File parentDirectory = appHomeDirectory.getParentFile();
+		FileFilter fileFilter = new FileFilter() {
+			@Override
+			public boolean accept(File file) {
+				return !(file.getName().equals(appHomeDirectory.getName())
+						// Exclude Taverna home directory for this app
+						&& file.isDirectory()
+						&& file.getName().toLowerCase().startsWith("taverna-")
+						// exclude snapshots
+						&& !file.getName().toLowerCase().contains("snapshot")
+						// exclude command line tool
+						&& !file.getName().toLowerCase().contains("cmd")
+						// exclude dataviewer
+						&& !file.getName().toLowerCase().contains("dataviewer"));
+			}
+		};
+		File[] tavernaDirectories = parentDirectory.listFiles(fileFilter);
+		// Find the latest previous registration data file, if any
+		File previousRegistrationDataFile = null;
+		for (File tavernaDirectory : tavernaDirectories) {
+			File regFile = new File(tavernaDirectory, REGISTRATION_DIRECTORY_NAME
+					+ System.getProperty("file.separator") + REGISTRATION_DATA_FILE_NAME);
+			if (!regFile.exists())
+				continue;
+			if (previousRegistrationDataFile == null)
+				previousRegistrationDataFile = regFile;
+			else if (previousRegistrationDataFile.lastModified() < regFile
+					.lastModified())
+				previousRegistrationDataFile = regFile;
+		}
+
+		UserRegistrationForm form;
+		if (previousRegistrationDataFile == null)
+			// No previous registration file - ask user to register
+			form = new UserRegistrationForm(applicationConfiguration.getName(),
+					registrationDataFile, doNotRegisterMeFile,
+					remindMeLaterFile);
+		else
+			/*
+			 * Fill in user's old registration data in the form and ask them to
+			 * register
+			 */
+			form = new UserRegistrationForm(applicationConfiguration.getName(),
+					previousRegistrationDataFile, registrationDataFile,
+					doNotRegisterMeFile, remindMeLaterFile);
+		form.setVisible(true);
+		return true;
+	}
+
+	/**
+	 * Gets the registration directory where info about registration will be
+	 * saved to.
+	 */
+	public File getRegistrationDirectory() {
+		File home = applicationConfiguration.getApplicationHomeDir();
+
+		File registrationDirectory = new File(home, REGISTRATION_DIRECTORY_NAME);
+		if (!registrationDirectory.exists())
+			registrationDirectory.mkdir();
+		return registrationDirectory;
+	}
+
+	public void setApplicationConfiguration(
+			ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java
new file mode 100644
index 0000000..16f189e
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java
@@ -0,0 +1,538 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.lang.Thread.setDefaultUncaughtExceptionHandler;
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+import static javax.swing.UIManager.getCrossPlatformLookAndFeelClassName;
+import static javax.swing.UIManager.getLookAndFeel;
+import static javax.swing.UIManager.getLookAndFeelDefaults;
+import static javax.swing.UIManager.getSystemLookAndFeelClassName;
+import static net.sf.taverna.t2.workbench.MainWindow.setMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.errorMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.infoMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.questionMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.warningMessageIcon;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.CardLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+import javax.swing.UIManager;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.ui.menu.MenuManager.MenuManagerEvent;
+import net.sf.taverna.t2.ui.menu.MenuManager.UpdatedMenuManagerEvent;
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.helper.Helper;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+import org.simplericity.macify.eawt.Application;
+import org.simplericity.macify.eawt.ApplicationAdapter;
+import org.simplericity.macify.eawt.ApplicationEvent;
+import org.simplericity.macify.eawt.ApplicationListener;
+import org.simplericity.macify.eawt.DefaultApplication;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * The main workbench frame.
+ *
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchImpl extends JFrame implements Workbench {
+	private static final String NIMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+	private static final String LAUNCHER_LOGO_PNG = "/launcher_logo.png";
+	private static final long serialVersionUID = 1L;
+	private static Logger logger = getLogger(WorkbenchImpl.class);
+	private static Preferences userPreferences = userNodeForPackage(WorkbenchImpl.class);
+	
+	private Application osxApp = new DefaultApplication();
+	private ApplicationListener osxAppListener = new OSXAppListener();
+	private MenuManager menuManager;
+	private FileManager fileManager;
+	@SuppressWarnings("unused")
+	private EditManager editManager;
+	private PluginManager pluginManager;
+	private SelectionManager selectionManager;
+	private WorkbenchConfiguration workbenchConfiguration;
+	private ApplicationConfiguration applicationConfiguration;
+	private WorkbenchPerspectives workbenchPerspectives;
+	private T2ConfigurationFrame t2ConfigurationFrame;
+	private JToolBar perspectiveToolBar;
+	private List<StartupSPI> startupHooks;
+	private List<ShutdownSPI> shutdownHooks;
+	private final List<PerspectiveSPI> perspectives;
+	private JMenuBar menuBar;
+	private JToolBar toolBar;
+	private MenuManagerObserver menuManagerObserver;
+
+	public WorkbenchImpl(List<StartupSPI> startupHooks,
+			List<ShutdownSPI> shutdownHooks, List<PerspectiveSPI> perspectives) {
+		this.perspectives = perspectives;
+		this.startupHooks = startupHooks;
+		this.shutdownHooks = shutdownHooks;
+		setMainWindow(this);
+	}
+
+	protected void initialize() {
+		setExceptionHandler();
+		setLookAndFeel();
+
+		// Set icons for Error, Information, Question and Warning messages
+		UIManager.put("OptionPane.errorIcon", errorMessageIcon);
+		UIManager.put("OptionPane.informationIcon", infoMessageIcon);
+		UIManager.put("OptionPane.questionIcon", questionMessageIcon);
+		UIManager.put("OptionPane.warningIcon", warningMessageIcon);
+
+		// Call the startup hooks
+		if (!callStartupHooks()) {
+			System.exit(0);
+		}
+
+		makeGUI();
+		fileManager.newDataflow();
+		try {
+			pluginManager.loadPlugins();
+		} catch (PluginException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+
+		/*
+		 * the DataflowEditsListener changes the WorkflowBundle ID for every
+		 * workflow edit and changes the URI so port definitions can't find the
+		 * port they refer to
+		 */
+		// TODO check if it's OK to not update the WorkflowBundle ID
+		//editManager.addObserver(new DataflowEditsListener());
+
+		closeTheSplashScreen();
+		setVisible(true);
+	}
+
+	private void closeTheSplashScreen() {
+//		SplashScreen splash = SplashScreen.getSplashScreen();
+//		if (splash != null) {
+//			splash.setClosable();
+//			splash.requestClose();
+//		}
+	}
+
+	private void showAboutDialog() {
+		// TODO implement this!
+	}
+
+	private void makeGUI() {
+		setLayout(new GridBagLayout());
+
+		addWindowListener(new WindowClosingListener());
+		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+		Helper.setKeyCatcher(this);
+
+		URL launcherLogo = getClass().getResource(LAUNCHER_LOGO_PNG);
+		if (launcherLogo != null) {
+			ImageIcon imageIcon = new ImageIcon(launcherLogo);
+			setIconImage(imageIcon.getImage());
+		}
+		setTitle(applicationConfiguration.getTitle());
+
+		osxApp.setEnabledPreferencesMenu(true);
+		osxApp.setEnabledAboutMenu(true);
+		osxApp.addApplicationListener(osxAppListener);
+
+		/*
+		 * Set the size and position of the Workbench to the last saved values
+		 * or use the default ones the first time it is launched
+		 */
+		loadSizeAndLocationPrefs();
+
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.weightx = 0.1;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = LINE_START;
+
+		add(makeToolbarPanel(), gbc);
+
+		gbc.anchor = CENTER;
+		gbc.fill = BOTH;
+		gbc.gridy = 1;
+		gbc.weightx = 0.1;
+		gbc.weighty = 0.1;
+
+		add(makePerspectivePanel(), gbc);
+
+		menuBar = menuManager.createMenuBar();
+		setJMenuBar(menuBar);
+	}
+
+	protected JPanel makeToolbarPanel() {
+		JPanel toolbarPanel = new JPanel(new GridBagLayout());
+
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.gridwidth = 2;
+		gbc.anchor = LINE_START;
+
+		toolBar = menuManager.createToolBar();
+		toolBar.setFloatable(false);
+		toolbarPanel.add(toolBar, gbc);
+
+		perspectiveToolBar = new JToolBar("Perspectives");
+		perspectiveToolBar.setFloatable(false);
+		gbc.gridy = 1;
+		gbc.weightx = 0.1;
+		gbc.fill = HORIZONTAL;
+		toolbarPanel.add(perspectiveToolBar, gbc);
+
+		return toolbarPanel;
+	}
+
+	private JPanel makePerspectivePanel() {
+		CardLayout perspectiveLayout = new CardLayout();
+		JPanel perspectivePanel = new JPanel(perspectiveLayout);
+		workbenchPerspectives = new WorkbenchPerspectives(perspectiveToolBar,
+				perspectivePanel, perspectiveLayout, selectionManager);
+		workbenchPerspectives.setPerspectives(perspectives);
+		return perspectivePanel;
+	}
+
+	@Override
+	public void makeNamedComponentVisible(String componentName) {
+		// basePane.makeNamedComponentVisible(componentName);
+	}
+
+	protected void setExceptionHandler() {
+		setDefaultUncaughtExceptionHandler(new ExceptionHandler());
+	}
+
+	public void setStartupHooks(List<StartupSPI> startupHooks) {
+		this.startupHooks = startupHooks;
+	}
+
+	/**
+	 * Calls the startup methods on all the {@link StartupSPI}s. If any startup
+	 * method returns <code>false</code> (meaning that the Workbench will not
+	 * function at all) then this method returns <code>false</code>.
+	 */
+	private boolean callStartupHooks() {
+		for (StartupSPI startupSPI : startupHooks)
+			if (!startupSPI.startup())
+				return false;
+		return true;
+	}
+
+	@Override
+	public void exit() {
+		if (callShutdownHooks())
+			System.exit(0);
+	}
+
+	public void setShutdownHooks(List<ShutdownSPI> shutdownHooks) {
+		this.shutdownHooks = shutdownHooks;
+	}
+
+	/**
+	 * Calls all the shutdown on all the {@link ShutdownSPI}s. If a shutdown
+	 * returns <code>false</code> (meaning that the shutdown process should be
+	 * aborted) then this method returns with a value of <code>false</code>
+	 * immediately.
+	 *
+	 * @return <code>true</code> if all the <code>ShutdownSPIs</code> return
+	 *         <code>true</code> and the workbench shutdown should proceed
+	 */
+	private boolean callShutdownHooks() {
+		for (ShutdownSPI shutdownSPI : shutdownHooks)
+			if (!shutdownSPI.shutdown())
+				return false;
+		return true;
+	}
+
+	/**
+	 * Store current Workbench position and size.
+	 */
+	@Override
+	public void storeSizeAndLocationPrefs() throws IOException {
+		userPreferences.putInt("width", getWidth());
+		userPreferences.putInt("height", getHeight());
+		userPreferences.putInt("x", getX());
+		userPreferences.putInt("y", getY());
+	}
+
+	/**
+	 * Loads last saved Workbench position and size.
+	 */
+	private void loadSizeAndLocationPrefs() {
+		Dimension screen = getToolkit().getScreenSize();
+
+		int width = userPreferences.getInt("width", (int) (screen.getWidth() * 0.75));
+		int height = userPreferences.getInt("height", (int) (screen.getHeight() * 0.75));
+		int x = userPreferences.getInt("x", 0);
+		int y = userPreferences.getInt("y", 0);
+
+		// Make sure our window is not too big
+		width = Math.min((int) screen.getWidth(), width);
+		height = Math.min((int) screen.getHeight(), height);
+
+		// Move to upper left corner if we are too far off
+		if (x > (screen.getWidth() - 50) || x < 0)
+			x = 0;
+		if (y > (screen.getHeight() - 50) || y < 0)
+			y = 0;
+
+		this.setSize(width, height);
+		this.setLocation(x, y);
+	}
+
+	public static void setLookAndFeel() {
+		String defaultLaf = System.getProperty("swing.defaultlaf");
+		try {
+			if (defaultLaf != null) {
+				UIManager.setLookAndFeel(defaultLaf);
+				return;
+			}
+		} catch (Exception e) {
+			logger.info("Can't set requested look and feel -Dswing.defaultlaf="
+					+ defaultLaf, e);
+		}
+		String os = System.getProperty("os.name");
+		if (os.contains("Mac") || os.contains("Windows")) {
+			// For OSX and Windows use the system look and feel
+			String systemLF = getSystemLookAndFeelClassName();
+			try {
+				UIManager.setLookAndFeel(systemLF);
+				getLookAndFeelDefaults().put("ClassLoader",
+						WorkbenchImpl.class.getClassLoader());
+				logger.info("Using system L&F " + systemLF);
+				return;
+			} catch (Exception ex2) {
+				logger.error("Unable to load system look and feel " + systemLF,
+						ex2);
+			}
+		}
+		/*
+		 * The system look and feel on *NIX
+		 * (com.sun.java.swing.plaf.gtk.GTKLookAndFeel) looks like Windows 3.1..
+		 * try to use Nimbus (Java 6e10 and later)
+		 */
+		try {
+			UIManager.setLookAndFeel(NIMBUS);
+			logger.info("Using Nimbus look and feel");
+			return;
+		} catch (Exception e) {
+		}
+
+		// Metal should be better than GTK still
+		try {
+			String crossPlatform = getCrossPlatformLookAndFeelClassName();
+			UIManager.setLookAndFeel(crossPlatform);
+			logger.info("Using cross platform Look and Feel " + crossPlatform);
+			return;
+		} catch (Exception e){
+		}
+
+		// Final fallback
+		try {
+			String systemLF = getSystemLookAndFeelClassName();
+			UIManager.setLookAndFeel(systemLF);
+			logger.info("Using system platform Look and Feel " + systemLF);
+		} catch (Exception e){
+			logger.info("Using default Look and Feel " + getLookAndFeel());
+		}
+	}
+
+	public void setMenuManager(MenuManager menuManager) {
+		if (this.menuManager != null && menuManagerObserver != null)
+			this.menuManager.removeObserver(menuManagerObserver);
+		this.menuManager = menuManager;
+		menuManagerObserver = new MenuManagerObserver();
+		menuManager.addObserver(menuManagerObserver);
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+
+	public void setEditManager(EditManager editManager) {
+		this.editManager = editManager;
+	}
+
+	public void refreshPerspectives(Object service, Map<?,?> properties) {
+		workbenchPerspectives.refreshPerspectives();
+	}
+
+	public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+		this.workbenchConfiguration = workbenchConfiguration;
+	}
+
+	public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+	}
+
+	public void setT2ConfigurationFrame(T2ConfigurationFrame t2ConfigurationFrame) {
+		this.t2ConfigurationFrame = t2ConfigurationFrame;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+	}
+
+	public void setPluginManager(PluginManager pluginManager) {
+		this.pluginManager = pluginManager;
+	}
+
+	private final class MenuManagerObserver extends
+			SwingAwareObserver<MenuManagerEvent> {
+		@Override
+		public void notifySwing(Observable<MenuManagerEvent> sender,
+				MenuManagerEvent message) {
+			if (message instanceof UpdatedMenuManagerEvent
+					&& WorkbenchImpl.this.isVisible())
+				refreshMenus();
+		}
+	}
+	
+	private void refreshMenus() {
+		if (menuBar != null) {
+			menuBar.revalidate();
+			menuBar.repaint();
+		}
+		if (toolBar != null) {
+			toolBar.revalidate();
+			toolBar.repaint();
+		}
+	}
+
+	private final class ExceptionHandler implements UncaughtExceptionHandler {
+		@Override
+		public void uncaughtException(Thread t, Throwable e) {
+			logger.error("Uncaught exception in " + t, e);
+			if (e instanceof Exception
+					&& !workbenchConfiguration.getWarnInternalErrors()) {
+				/*
+				 * User preference disables warnings - but we'll show it anyway
+				 * if it is an Error (which is more serious)
+				 */
+				return;
+			}
+			final String message;
+			final String title;
+			final int style;
+			if (t.getClass().getName().equals("java.awt.EventDispatchThread")) {
+				message = "The user action could not be completed due to an unexpected error:\n"
+						+ e;
+				title = "Could not complete user action";
+				style = ERROR_MESSAGE;
+			} else {
+				message = "An unexpected internal error occured in \n" + t + ":\n" + e;
+				title = "Unexpected internal error";
+				style = WARNING_MESSAGE;
+			}
+			invokeLater(new Runnable() {
+				@Override
+				public void run() {
+					showMessageDialog(WorkbenchImpl.this, message, title, style);
+				}
+			});
+		}
+	}
+
+	private class WindowClosingListener extends WindowAdapter {
+		@Override
+		public void windowClosing(WindowEvent e) {
+			exit();
+		}
+	}
+
+	private class OSXAppListener extends ApplicationAdapter {
+		@Override
+		public void handleAbout(ApplicationEvent e) {
+			showAboutDialog();
+			e.setHandled(true);
+		}
+
+		@Override
+		public void handleQuit(ApplicationEvent e) {
+			e.setHandled(true);
+			exit();
+		}
+
+		@Override
+		public void handlePreferences(ApplicationEvent e) {
+			e.setHandled(true);
+			t2ConfigurationFrame.showFrame();
+		}
+
+		@Override
+		public void handleOpenFile(ApplicationEvent e) {
+			try {
+				if (e.getFilename() != null) {
+					fileManager.openDataflow(null, new File(e.getFilename()));
+					e.setHandled(true);
+				}
+			} catch (OpenException | IllegalStateException ex) {
+				logger.warn("Could not open file " + e.getFilename(), ex);
+			}
+		}
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java
new file mode 100644
index 0000000..921260a
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static java.awt.Image.SCALE_SMOOTH;
+import static javax.swing.Action.NAME;
+import static javax.swing.Action.SMALL_ICON;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkbenchPerspectives {
+	private static Logger logger = Logger
+			.getLogger(WorkbenchPerspectives.class);
+
+	private PerspectiveSPI currentPerspective;
+	private ButtonGroup perspectiveButtonGroup = new ButtonGroup();
+	private Map<String, JToggleButton> perspectiveButtonMap = new HashMap<>();
+	private JToolBar toolBar;
+	private JPanel panel;
+	private CardLayout cardLayout;
+	private List<PerspectiveSPI> perspectives = new ArrayList<>();
+	private boolean refreshing;
+	private final SelectionManager selectionManager;
+
+	public WorkbenchPerspectives(JToolBar toolBar, JPanel panel,
+			CardLayout cardLayout, SelectionManager selectionManager) {
+		this.panel = panel;
+		this.toolBar = toolBar;
+		this.cardLayout = cardLayout;
+		this.selectionManager = selectionManager;
+		refreshing = true;
+		selectionManager.addObserver(new SelectionManagerObserver());
+		refreshing = false;
+	}
+
+	public List<PerspectiveSPI> getPerspectives() {
+		return this.perspectives;
+	}
+
+	public void setPerspectives(List<PerspectiveSPI> perspectives) {
+		this.perspectives = perspectives;
+		initialisePerspectives();
+	}
+
+	private void initialisePerspectives() {
+		for (final PerspectiveSPI perspective : perspectives)
+			addPerspective(perspective, false);
+		selectFirstPerspective();
+	}
+
+	private void setPerspective(PerspectiveSPI perspective) {
+		if (perspective != currentPerspective) {
+			if (!perspectiveButtonMap.containsKey(perspective.getID()))
+				addPerspective(perspective, true);
+			if (!(perspective instanceof BlankPerspective))
+				perspectiveButtonMap.get(perspective.getID()).setSelected(true);
+			cardLayout.show(panel, perspective.getID());
+			currentPerspective = perspective;
+		}
+	}
+
+	private void addPerspective(final PerspectiveSPI perspective,
+			boolean makeActive) {
+		// ensure icon image is always 16x16
+		ImageIcon buttonIcon = null;
+		if (perspective.getButtonIcon() != null) {
+			Image buttonImage = perspective.getButtonIcon().getImage();
+			buttonIcon = new ImageIcon(buttonImage.getScaledInstance(16, 16,
+					SCALE_SMOOTH));
+		}
+
+		final JToggleButton toolbarButton = new JToggleButton(
+				perspective.getText(), buttonIcon);
+		toolbarButton.setToolTipText(perspective.getText() + " perspective");
+		Action action = new AbstractAction() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				selectionManager.setSelectedPerspective(perspective);
+			}
+		};
+		action.putValue(NAME, perspective.getText());
+		action.putValue(SMALL_ICON, buttonIcon);
+
+		toolbarButton.setAction(action);
+		toolBar.add(toolbarButton);
+		perspectiveButtonGroup.add(toolbarButton);
+		perspectiveButtonMap.put(perspective.getID(), toolbarButton);
+
+		panel.add(perspective.getPanel(), perspective.getID());
+		if (makeActive)
+			toolbarButton.doClick();
+	}
+
+	/**
+	 * Recreates the toolbar buttons. Useful if a perspective has been removed.
+	 */
+	public void refreshPerspectives() {
+		invokeLater(new RefreshRunner());
+	}
+
+	/** selects the first visible perspective by clicking on the toolbar button */
+	private void selectFirstPerspective() {
+		boolean set = false;
+		for (Component c : toolBar.getComponents())
+			if (c instanceof AbstractButton && c.isVisible()) {
+				((AbstractButton) c).doClick();
+				set = true;
+				break;
+			}
+
+		if (!set) {
+			// no visible perspectives were found
+			logger.info("No visible perspectives.");
+			selectionManager.setSelectedPerspective(new BlankPerspective());
+		}
+	}
+
+	private final class RefreshRunner implements Runnable {
+		@Override
+		public void run() {
+			synchronized (WorkbenchPerspectives.this) {
+				if (refreshing)
+					// We only need one run
+					return;
+				refreshing = true;
+			}
+			try {
+				toolBar.removeAll();
+				toolBar.repaint();
+				initialisePerspectives();
+			} finally {
+				synchronized (WorkbenchPerspectives.this) {
+					refreshing = false;
+				}
+			}
+		}
+	}
+
+	private final class SelectionManagerObserver extends
+			SwingAwareObserver<SelectionManagerEvent> {
+		@Override
+		public void notifySwing(Observable<SelectionManagerEvent> sender,
+				SelectionManagerEvent message) {
+			if (message instanceof PerspectiveSelectionEvent) {
+				PerspectiveSPI selectedPerspective = ((PerspectiveSelectionEvent) message)
+						.getSelectedPerspective();
+				setPerspective(selectedPerspective);
+			}
+		}
+	}
+
+	/**
+	 * A dummy blank perspective for when there are no visible perspectives
+	 * available
+	 *
+	 * @author Stuart Owen
+	 */
+	private class BlankPerspective implements PerspectiveSPI {
+		@Override
+		public String getID() {
+			return BlankPerspective.class.getName();
+		}
+
+		@Override
+		public JPanel getPanel() {
+			return new JPanel();
+		}
+
+		@Override
+		public ImageIcon getButtonIcon() {
+			return null;
+		}
+
+		@Override
+		public String getText() {
+			return null;
+		}
+
+		@Override
+		public int positionHint() {
+			return 0;
+		}
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java
new file mode 100644
index 0000000..d5573be
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+/**
+ * Exit the workbench
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class ExitAction extends AbstractMenuAction {
+	private static final String EXIT_LABEL = "Exit";
+	private static final String MAC_OS_X = "Mac OS X";
+	private Workbench workbench;
+
+	public ExitAction() {
+		super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#file"),
+				10000);
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		return new AbstractAction(EXIT_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				workbench.exit();
+			}
+		};
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return !MAC_OS_X.equalsIgnoreCase(System.getProperty("os.name"));
+	}
+
+	public void setWorkbench(Workbench workbench) {
+		this.workbench = workbench;
+	}
+}
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
new file mode 100644
index 0000000..a9fa855
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.WorkbenchLauncher
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..0c43dec
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,18 @@
+
+net.sf.taverna.t2.workbench.ui.impl.menu.FileMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.ExitAction
+
+net.sf.taverna.t2.workbench.ui.impl.menu.EditMenu
+
+net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.DisplayPerspectivesMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.EditPerspectivesMenu
+
+net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.OnlineHelpMenuAction
+net.sf.taverna.t2.workbench.ui.impl.menu.FeedbackMenuAction
+
+#net.sf.taverna.t2.workbench.ui.impl.menu.ViewShowMenuSection
+#net.sf.taverna.t2.workbench.ui.impl.menu.ChangePerspectiveMenuAction
+
+net.sf.taverna.t2.workbench.ui.impl.menu.ShowLogsAndDataMenuAction
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..c022389
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.StoreWindowStateOnShutdown
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..86349aa
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.impl.UserRegistrationHook
+net.sf.taverna.t2.workbench.ui.impl.SetConsoleLoggerStartup
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml
new file mode 100644
index 0000000..0dc7efc
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="UserRegistrationHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+	<!-- <service ref="SetConsoleLoggerStartup" interface="net.sf.taverna.t2.workbench.StartupSPI" /> -->
+
+	<service ref="StoreWindowStateOnShutdown" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+	<service ref="ExitAction" auto-export="interfaces">
+		<service-properties>
+			<beans:entry key="menu.action" value="file.exit" />
+		</service-properties>
+	</service>
+
+	<service ref="Workbench" interface="net.sf.taverna.t2.workbench.ui.Workbench" />
+
+	<reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+	<reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+	<reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+	<reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+	<reference id="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+	<reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+	<reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+	<reference id="t2ConfigurationFrame" interface="net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame" />
+
+	<list id="perspectives" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" cardinality="0..N" comparator-ref="PerspectiveComparator" greedy-proxying="true">
+		<listener ref="Workbench" bind-method="refreshPerspectives" unbind-method="refreshPerspectives" />
+	</list>
+
+	<list id="startupHooks" interface="net.sf.taverna.t2.workbench.StartupSPI" cardinality="0..N" comparator-ref="StartupComparator"/>
+	<list id="shutdownHooks" interface="net.sf.taverna.t2.workbench.ShutdownSPI" cardinality="0..N" comparator-ref="ShutdownComparator"/>
+
+</beans:beans>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml
new file mode 100644
index 0000000..78c4eb2
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="UserRegistrationHook" class="net.sf.taverna.t2.workbench.ui.impl.UserRegistrationHook">
+		<property name="applicationConfiguration" ref="applicationConfiguration"/>
+	</bean>
+	<bean id="SetConsoleLoggerStartup" class="net.sf.taverna.t2.workbench.ui.impl.SetConsoleLoggerStartup">
+		<constructor-arg ref="workbenchConfiguration" />
+	</bean>
+
+	<bean id="StoreWindowStateOnShutdown" class="net.sf.taverna.t2.workbench.ui.impl.StoreWindowStateOnShutdown">
+		<property name="workbench">
+			<ref local="Workbench"/>
+		</property>
+	</bean>
+
+	<bean id="ExitAction" class="net.sf.taverna.t2.workbench.ui.impl.menu.ExitAction">
+		<property name="workbench">
+			<ref local ="Workbench"/>
+		</property>
+	</bean>
+
+	<bean id="Workbench" class="net.sf.taverna.t2.workbench.ui.impl.WorkbenchImpl" init-method="initialize">
+		<constructor-arg ref="startupHooks"/>
+		<constructor-arg ref="shutdownHooks"/>
+		<constructor-arg ref="perspectives"/>
+		<property name="editManager" ref="editManager"/>
+		<property name="fileManager" ref="fileManager"/>
+		<property name="menuManager" ref="menuManager"/>
+		<property name="t2ConfigurationFrame" ref="t2ConfigurationFrame"/>
+		<property name="workbenchConfiguration" ref="workbenchConfiguration"/>
+		<property name="applicationConfiguration" ref="applicationConfiguration"/>
+		<property name="selectionManager" ref="selectionManager"/>
+		<property name="pluginManager" ref="pluginManager"/>
+	</bean>
+
+	<bean id="PerspectiveComparator" class="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI$PerspectiveComparator" />
+	<bean id="StartupComparator" class="net.sf.taverna.t2.workbench.StartupSPI$StartupComparator" />
+	<bean id="ShutdownComparator" class="net.sf.taverna.t2.workbench.ShutdownSPI$ShutdownComparator" />
+
+</beans>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/Map.jhm b/taverna-workbench-workbench-impl/src/main/resources/Map.jhm
new file mode 100644
index 0000000..ab5f560
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/Map.jhm
@@ -0,0 +1,28 @@
+<?xml version='1.0' encoding='ISO-8859-1' ?>
+<!DOCTYPE map
+  PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN"
+         "http://java.sun.com/javase/technologies/desktop/javahelp/map_1_0.dtd">
+
+<map version="1.0">
+	<mapID target="toplevelfolder" url="images/toplevel.gif" />
+	<mapID target="top" url="help/welcome.html" />
+
+	<mapID target="intro" url="help/welcome.html" />
+	<mapID target="start" url="help/start.html" />
+	<mapID target="overview" url="help/welcome.html" />
+	<mapID target="one" url="help/start.html" />
+	<mapID target="two" url="help/start.html" />
+
+	<mapID target="bean.story" url="help/welcome.html" />
+	<mapID target="bean.story" url="help/start.html" />
+	<mapID target="bean.story" url="help/welcome.html" />
+	
+	<mapID target="http://taverna.sf.net/2008/t2workbench/menu#help"
+		url="http://www.google.com" />
+		
+	<mapID target="http://taverna.sf.net/2008/t2workbench/menu#fileOpen"
+		url="http://www.google.com" />
+
+		
+
+</map>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml b/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml
new file mode 100644
index 0000000..5d84369
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<registration_data>
+	<taverna_version>taverna-2.2.0</taverna_version>
+	<first_name>John</first_name>
+	<last_name>Doe</last_name>
+	<email_address>john.doe@jd-consulting.com</email_address>
+	<keep_me_informed>false</keep_me_informed>
+	<institution_or_company_name>JD Consulting</institution_or_company_name>
+	<industry_type>Industry - Pharmaceutical</industry_type>
+	<field_of_interest>bioinformatics</field_of_interest>
+	<purpose_of_using_taverna>pharmacogenomics</purpose_of_using_taverna>
+</registration_data>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd b/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd
new file mode 100644
index 0000000..776f8e5
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+  <xs:element name="registration_data">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element ref="taverna_version"/>
+        <xs:element ref="first_name"/>
+        <xs:element ref="last_name"/>
+        <xs:element ref="email_address"/>
+        <xs:element ref="keep_me_informed"/>
+        <xs:element ref="institution_or_company_name"/>
+        <xs:element ref="industry_type"/>
+        <xs:element ref="field_of_interest"/>
+        <xs:element ref="purpose_of_using_taverna"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="taverna_version" type="xs:string"/>
+  <xs:element name="first_name" type="xs:string"/>
+  <xs:element name="last_name" type="xs:string"/>
+  <xs:element name="email_address" type="xs:string"/>
+  <xs:element name="keep_me_informed" type="xs:boolean"/>
+  <xs:element name="institution_or_company_name" type="xs:string"/>
+  <xs:element name="industry_type" type="xs:string"/>
+  <xs:element name="field_of_interest" type="xs:string"/>
+  <xs:element name="purpose_of_using_taverna" type="xs:string"/>
+</xs:schema>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/registration.php b/taverna-workbench-workbench-impl/src/main/resources/registration.php
new file mode 100644
index 0000000..902200b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/registration.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * A folder where to write the registrations
+ */
+
+$registrations_folder = "/var/taverna-registration";
+
+
+
+function xmlescape($string){
+          $res = str_replace("&", "&amp;",$string);
+          $res = str_replace("<", "&lt;",$res);
+          $res = str_replace(">", "&gt;",$res);
+          return $res;
+}
+
+
+
+/* From http://php.net/manual/en/function.uiqid.php 
+ * Requires yum install uuid-php
+   and in .htaccess / php.ini:
+     php_value allow_call_time_pass_reference true
+ */
+
+class uuid { 
+    /** 
+     * This class enables you to get real uuids using the OSSP library. 
+     * Note you need php-uuid installed. 
+     * On my system 1000 UUIDs are created in 0.0064 seconds. 
+     * 
+     * @author Marius Karthaus 
+     * 
+     */ 
+    
+    protected $uuidobject; 
+    
+    /** 
+     * On long running deamons i've seen a lost resource. This checks the resource and creates it if needed. 
+     * 
+     */ 
+    protected function create() { 
+        if (! is_resource ( $this->uuidobject )) { 
+            uuid_create ( &$this->uuidobject ); 
+        } 
+    } 
+    
+    /** 
+     * Return a type 1 (MAC address and time based) uuid 
+     * 
+     * @return String 
+     */ 
+    public function v1() { 
+        $this->create (); 
+        uuid_make ( $this->uuidobject, UUID_MAKE_V1 ); 
+        uuid_export ( $this->uuidobject, UUID_FMT_STR, &$uuidstring ); 
+        return trim ( $uuidstring ); 
+    } 
+    
+    /** 
+     * Return a type 4 (random) uuid 
+     * 
+     * @return String 
+     */ 
+    public function v4() { 
+        $this->create (); 
+        uuid_make ( $this->uuidobject, UUID_MAKE_V4 ); 
+        uuid_export ( $this->uuidobject, UUID_FMT_STR, &$uuidstring ); 
+        return trim ( $uuidstring ); 
+    } 
+    
+    /** 
+     * Return a type 5 (SHA-1 hash) uuid 
+     * 
+     * @return String 
+     */ 
+    public function v5() { 
+        $this->create (); 
+        uuid_make ( $this->uuidobject, UUID_MAKE_V5 ); 
+        uuid_export ( $this->uuidobject, UUID_FMT_STR, $uuidstring ); 
+        return trim ( $uuidstring ); 
+    } 
+} 
+
+	if(isset($_POST['taverna_registration'])){
+		
+		$taverna_version   = $_POST['taverna_version'];
+		$first_name   = $_POST['first_name'];
+		$last_name   = $_POST['last_name'];
+		$email   = $_POST['email'];
+		$keep_me_informed   = $_POST['keep_me_informed'];
+		$institution_or_company   = $_POST['institution_or_company'];
+		$industry   = $_POST['industry_type'];
+		$field   = $_POST['field'];
+		$purpose   = $_POST['purpose'];
+		
+		$uuid=new uuid(); 
+         
+		
+		// Generate user registration data file name with a random identifier
+		$random_id = $uuid->v4();
+		$user_registration_file_name = $registrations_folder . "/user_registration_" . $random_id . ".xml";
+		$user_registration_file = fopen($user_registration_file_name,'w') or die ("Could not open file ". $user_registration_file_name . " for writing." );
+		
+		// Save this to a file
+		/*
+		 $registration_data = "";
+		$registration_data .= "Taverna version=" . $taverna_version . "\n";
+		$registration_data .= "First name=" . $first_name . "\n";
+		$registration_data .= "Last name=" . $last_name . "\n";
+		$registration_data .= "Email address=" . $email . "\n";
+		$registration_data .= "Keep me informed by email=" . $keep_me_informed . "\n";
+		$registration_data .= "Institution or company=" . $institution_or_company . "\n";
+		$registration_data .= "Industry=" . $industry_type . "\n";
+		$registration_data .= "Field of interest=" . $field . "\n";
+		$registration_data .= "Purpose of using Taverna=" . $purpose;
+		 */
+		
+		$registration_data = "";
+		$registration_data .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+		$registration_data .= "<registration_data>\n";
+		$registration_data .= "\t<taverna_version>".xmlescape($taverna_version)."</taverna_version>\n";
+		$registration_data .= "\t<first_name>".xmlescape($first_name)."</first_name>\n";
+		$registration_data .= "\t<last_name>".xmlescape($last_name)."</last_name>\n";
+		$registration_data .= "\t<email_address>".xmlescape($email)."</email_address>\n";
+		$registration_data .= "\t<keep_me_informed>".xmlescape($keep_me_informed)."</keep_me_informed>\n";
+		$registration_data .= "\t<institution_or_company_name>".xmlescape($institution_or_company)."</institution_or_company_name>\n";
+		$registration_data .= "\t<industry_type>".xmlescape($industry)."</industry_type>\n";
+		$registration_data .= "\t<field_of_interest>".xmlescape($field)."</field_of_interest>\n";
+		$registration_data .= "\t<purpose_of_using_taverna>".xmlescape($purpose)."</purpose_of_using_taverna>\n";
+		$registration_data .= "</registration_data>\n";
+		
+		fwrite($user_registration_file, $registration_data) or die ("Could not write to file ". $user_registration_file_name );
+		fclose($user_registration_file);
+		echo "Registration successful!";
+	}
+?>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/sample.hs b/taverna-workbench-workbench-impl/src/main/resources/sample.hs
new file mode 100644
index 0000000..9b43dca
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/sample.hs
@@ -0,0 +1,64 @@
+<?xml version='1.0' encoding='ISO-8859-1' ?>
+<!DOCTYPE helpset
+  PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN"
+         "../dtd/helpset_2_0.dtd">
+
+<helpset version="1.0">
+
+  <!-- title -->
+  <title>My Sample Help - Online</title>
+
+  <!-- maps -->
+  <maps>
+     <homeID>top</homeID>
+     <mapref location="Map.jhm"/>
+  </maps>
+
+  <!-- views -->
+  <view>
+    <name>TOC</name>
+    <label>Table Of Contents</label>
+    <type>javax.help.TOCView</type>
+    <data>SampleTOC.xml</data>
+  </view>
+
+  <view>
+    <name>Index</name>
+    <label>Index</label>
+    <type>javax.help.IndexView</type>
+    <data>SampleIndex.xml</data>
+  </view>
+
+  <view>
+    <name>Search</name>
+    <label>Search</label>
+    <type>javax.help.SearchView</type>
+    <data engine="com.sun.java.help.search.DefaultSearchEngine">
+      JavaHelpSearch
+    </data>
+  </view>
+
+  <presentation default="true" displayviewimages="false">
+     <name>main window</name>
+     <size width="700" height="400" />
+     <location x="200" y="200" />
+     <title>My Sample Help - Online</title>
+     <image>toplevelfolder</image>
+     <toolbar>
+	<helpaction>javax.help.BackAction</helpaction>
+	<helpaction>javax.help.ForwardAction</helpaction>
+	<helpaction>javax.help.SeparatorAction</helpaction>
+	<helpaction>javax.help.HomeAction</helpaction>
+	<helpaction>javax.help.ReloadAction</helpaction>
+	<helpaction>javax.help.SeparatorAction</helpaction>
+	<helpaction>javax.help.PrintAction</helpaction>
+	<helpaction>javax.help.PrintSetupAction</helpaction>
+     </toolbar>
+  </presentation>
+  <presentation>
+     <name>main</name>
+     <size width="400" height="400" />
+     <location x="200" y="200" />
+     <title>My Sample Help - Online</title>
+  </presentation>
+</helpset>
diff --git a/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java b/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java
new file mode 100644
index 0000000..4c64aa3
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import static org.junit.Assert.*;
+
+import net.sf.taverna.t2.workbench.ui.impl.UserRegistrationForm;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class UserRegistrationTest {
+
+	@Ignore
+	@Test
+	public void postUserRegistrationDataToServer() {
+
+		String parameters = "";
+
+		// The 'submit' parameter - to let the server-side script know we are
+		// submitting
+		// the user's registration form - all other requests will be silently
+		// ignored
+		try {
+			parameters = URLEncoder
+					.encode(
+							UserRegistrationForm.TAVERNA_REGISTRATION_POST_PARAMETER_NAME,
+							"UTF-8")
+					+ "=" + URLEncoder.encode("submit", "UTF-8"); // value does
+																	// not
+																	// matter
+
+			parameters += "&"
+					+ URLEncoder
+							.encode(
+									UserRegistrationForm.TAVERNA_VERSION_POST_PARAMETER_NAME,
+									"UTF-8") + "="
+					+ URLEncoder.encode("snapshot", "UTF-8");
+			parameters += "&"
+					+ URLEncoder
+							.encode(
+									UserRegistrationForm.FIRST_NAME_POST_PARAMETER_NAME,
+									"UTF-8") + "="
+					+ URLEncoder.encode("Alex", "UTF-8");
+			parameters += "&"
+					+ URLEncoder.encode(
+							UserRegistrationForm.LAST_NAME_POST_PARAMETER_NAME,
+							"UTF-8") + "="
+					+ URLEncoder.encode("Nenadic", "UTF-8");
+			parameters += "&"
+					+ URLEncoder
+							.encode(
+									UserRegistrationForm.EMAIL_ADDRESS_POST_PARAMETER_NAME,
+									"UTF-8") + "="
+					+ URLEncoder.encode("alex@alex.com", "UTF-8");
+			parameters += "&"
+					+ URLEncoder
+							.encode(
+									UserRegistrationForm.KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME,
+									"UTF-8") + "="
+					+ URLEncoder.encode("true", "UTF-8");
+			parameters += "&"
+					+ URLEncoder
+							.encode(
+									UserRegistrationForm.INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME,
+									"UTF-8") + "="
+					+ URLEncoder.encode("Uni of Manchester", "UTF-8");
+			parameters += "&"
+					+ URLEncoder
+							.encode(
+									UserRegistrationForm.INDUSTRY_TYPE_POST_PARAMETER_NAME,
+									"UTF-8") + "="
+					+ URLEncoder.encode("Academia", "UTF-8");
+			parameters += "&"
+					+ URLEncoder.encode(
+							UserRegistrationForm.FIELD_POST_PARAMETER_NAME,
+							"UTF-8") + "="
+					+ URLEncoder.encode("Research", "UTF-8");
+			parameters += "&"
+					+ URLEncoder.encode(
+							UserRegistrationForm.PURPOSE_POST_PARAMETER_NAME,
+							"UTF-8") + "=" + URLEncoder.encode("None", "UTF-8");
+		} catch (UnsupportedEncodingException ueex) {
+			System.out
+					.println("Failed to url encode post parameters when sending user registration data.");
+		}
+		String server = "http://cactus.cs.man.ac.uk/~alex/taverna_registration/registration.php";
+		server = "http://localhost/~alex/taverna_registration/registration.php";
+		// server = "https://somehost.co.uk";
+
+		System.out.println("Posting user registartion to " + server
+				+ " with parameters: " + parameters);
+		String response = "";
+		try {
+			URL url = new URL(server);
+			URLConnection conn = url.openConnection();
+			System.out.println("Opened a connection");
+			// Set timeout for connection, otherwise we might hang too long
+			// if server is not responding and it will block Taverna
+			conn.setConnectTimeout(7000);
+			// Set connection parameters
+			conn.setDoInput(true);
+			conn.setDoOutput(true);
+			conn.setUseCaches(false);
+			// Make server believe we are HTML form data...
+			conn.setRequestProperty("Content-Type",
+					"application/x-www-form-urlencoded");
+			System.out
+					.println("Trying to get an output stream from the connection");
+			DataOutputStream out = new DataOutputStream(conn.getOutputStream());
+			// Write out the bytes of the content string to the stream.
+			out.writeBytes(parameters);
+			out.flush();
+			out.close();
+			// Read response from the input stream.
+			BufferedReader in = new BufferedReader(new InputStreamReader(conn
+					.getInputStream()));
+			String temp;
+			while ((temp = in.readLine()) != null) {
+				response += temp + "\n";
+			}
+			// Remove the last \n character
+			if (!response.equals("")) {
+				response = response.substring(0, response.length() - 1);
+			}
+			in.close();
+			System.out.println(response);
+			if (!response.equals("Registration successful!")) {
+				System.out
+						.println("Registration failed. Response form server was: "
+								+ response);
+			}
+			assertTrue(response.equals("Registration successful!"));
+		}
+		// Catch some runtime exceptions
+		catch (ConnectException ceex) { // the connection was refused remotely
+										// (e.g. no process is listening on the
+										// remote address/port).
+			System.out
+					.println("User registration failed: Registration server is not listening of the specified url.");
+			ceex.printStackTrace();
+		}
+		// Catch some runtime exceptions
+		catch (SocketTimeoutException stex) { // timeout has occurred on a
+												// socket read or accept.
+			System.out
+					.println("User registration failed: Socket timeout occurred.");
+			stex.printStackTrace();
+		} catch (MalformedURLException muex) {
+			System.out
+					.println("User registration failed: Registartion server's url is malformed.");
+			muex.printStackTrace();
+		} catch (IOException ioex) {
+			System.out
+					.println("User registration failed: Failed to open url connection to registration server or writing to it or reading from it.");
+			ioex.printStackTrace();
+		}
+	}
+
+}
diff --git a/taverna-workbench-workbench-impl/src/test/resources/log4j.properties b/taverna-workbench-workbench-impl/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fa27070
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/test/resources/log4j.properties
@@ -0,0 +1,10 @@
+log4j.rootLogger=WARN, CONSOLE
+log4j.logger.net.sf.taverna.t2.workbench=INFO
+log4j.logger.net.sf.taverna.t2.ui=DEBUG
+
+#log4j.logger.org.apache.commons.httpclient=ERROR
+
+# Default output to console
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%-5p %d{ISO8601} (%c:%L) - %m%n
\ No newline at end of file