You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/03/20 15:22:49 UTC

[35/51] [abbrv] [partial] incubator-taverna-workbench git commit: taverna-workbench-* -> taverna-*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-file-impl/pom.xml b/taverna-file-impl/pom.xml
new file mode 100644
index 0000000..bf0e019
--- /dev/null
+++ b/taverna-file-impl/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<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>org.apache.taverna.workbench</groupId>
+		<artifactId>taverna-workbench</artifactId>
+		<version>3.1.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-file-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Apache Taverna File opening implementation</name>
+	<description>
+		Implementation for doing file (i.e. workflow) open/save in the
+		workbench.
+	</description>
+	<dependencies>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-file-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-edits-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-helper-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-menu-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-workbench-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.engine</groupId>
+			<artifactId>taverna-observer</artifactId>
+			<version>${taverna.engine.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.osgi</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.osgi.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.language</groupId>
+			<artifactId>taverna-scufl2-api</artifactId>
+			<version>${taverna.language.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<version>${log4j.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+			<version>${commons.collections.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>com.springsource.org.apache.commons.lang</artifactId>
+			<version>${commons.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+			<version>${commons.codec.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.jdom</groupId>
+			<artifactId>com.springsource.org.jdom</artifactId>
+			<version>${jdom.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-edits-impl</artifactId>
+			<version>${project.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.language</groupId>
+			<artifactId>taverna-scufl2-wfbundle</artifactId>
+			<version>${taverna.language.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.language</groupId>
+			<artifactId>taverna-scufl2-t2flow</artifactId>
+			<version>${taverna.language.version}</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java b/taverna-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-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);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java b/taverna-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-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>();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java b/taverna-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-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;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java b/taverna-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-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);
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java b/taverna-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-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";
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java b/taverna-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-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";
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java b/taverna-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-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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java b/taverna-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-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;
+	}
+}