You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by re...@apache.org on 2015/03/27 16:33:35 UTC

[07/11] incubator-taverna-workbench git commit:

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/T2FlowFileType.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/T2FlowFileType.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/T2FlowFileType.java
new file mode 100644
index 0000000..bfd170b
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/T2FlowFileType.java
@@ -0,0 +1,41 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl;
+
+import org.apache.taverna.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/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileFilter.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileFilter.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileFilter.java
new file mode 100644
index 0000000..0442589
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileFilter.java
@@ -0,0 +1,36 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileType.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileType.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileType.java
new file mode 100644
index 0000000..a64fb0d
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleFileType.java
@@ -0,0 +1,41 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl;
+
+import org.apache.taverna.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/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleOpener.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleOpener.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleOpener.java
new file mode 100644
index 0000000..d85e4ad
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleOpener.java
@@ -0,0 +1,142 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.workbench.file.AbstractDataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.DataflowInfo;
+import org.apache.taverna.workbench.file.DataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.io.ReaderException;
+import org.apache.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/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleSaver.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleSaver.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleSaver.java
new file mode 100644
index 0000000..89043be
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/WorkflowBundleSaver.java
@@ -0,0 +1,144 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl;
+
+import static org.apache.taverna.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 org.apache.taverna.workbench.file.AbstractDataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.DataflowInfo;
+import org.apache.taverna.workbench.file.DataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseAllWorkflowsAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseAllWorkflowsAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseAllWorkflowsAction.java
new file mode 100644
index 0000000..eb88068
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseAllWorkflowsAction.java
@@ -0,0 +1,84 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.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 org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseWorkflowAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseWorkflowAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseWorkflowAction.java
new file mode 100644
index 0000000..091a652
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/CloseWorkflowAction.java
@@ -0,0 +1,106 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.workbench.icons.WorkbenchIcons.closeIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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;
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/NewWorkflowAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/NewWorkflowAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/NewWorkflowAction.java
new file mode 100644
index 0000000..deb0926
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/NewWorkflowAction.java
@@ -0,0 +1,57 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.workbench.icons.WorkbenchIcons.newIcon;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+
+import org.apache.taverna.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();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenNestedWorkflowAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenNestedWorkflowAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenNestedWorkflowAction.java
new file mode 100644
index 0000000..2e2124d
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenNestedWorkflowAction.java
@@ -0,0 +1,75 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl.actions;
+
+import java.awt.Component;
+import java.io.File;
+
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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;
+			}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowAction.java
new file mode 100644
index 0000000..3a7560a
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowAction.java
@@ -0,0 +1,394 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.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 org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.OpenException;
+import org.apache.taverna.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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 org.apache.taverna.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);
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowFromURLAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
new file mode 100644
index 0000000..3c1a4cc
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
@@ -0,0 +1,138 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.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 org.apache.taverna.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/PasswordInput.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/PasswordInput.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/PasswordInput.java
new file mode 100644
index 0000000..61e80e6
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/PasswordInput.java
@@ -0,0 +1,220 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.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);
+			}
+		});
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveAllWorkflowsAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveAllWorkflowsAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveAllWorkflowsAction.java
new file mode 100644
index 0000000..b58b99f
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveAllWorkflowsAction.java
@@ -0,0 +1,103 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.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 org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.events.FileManagerEvent;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAction.java
new file mode 100644
index 0000000..2cf2775
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAction.java
@@ -0,0 +1,174 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.workbench.icons.WorkbenchIcons.saveIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import org.apache.taverna.workbench.edits.EditManager.EditManagerEvent;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.events.FileManagerEvent;
+import org.apache.taverna.workbench.file.events.SavedDataflowEvent;
+import org.apache.taverna.workbench.file.events.SetCurrentDataflowEvent;
+import org.apache.taverna.workbench.file.exceptions.OverwriteException;
+import org.apache.taverna.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.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());
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAsAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAsAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAsAction.java
new file mode 100644
index 0000000..b42fe1c
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/actions/SaveWorkflowAsAction.java
@@ -0,0 +1,218 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.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 org.apache.taverna.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 org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.events.FileManagerEvent;
+import org.apache.taverna.workbench.file.events.SetCurrentDataflowEvent;
+import org.apache.taverna.workbench.file.exceptions.OverwriteException;
+import org.apache.taverna.workbench.file.exceptions.SaveException;
+import org.apache.taverna.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.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());
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
new file mode 100644
index 0000000..4e495a2
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
@@ -0,0 +1,55 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl.hooks;
+
+import org.apache.taverna.workbench.ShutdownSPI;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.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);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseAllMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseAllMenuAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseAllMenuAction.java
new file mode 100644
index 0000000..6449ba7
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseAllMenuAction.java
@@ -0,0 +1,50 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl.menu;
+
+import static org.apache.taverna.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import org.apache.taverna.ui.menu.AbstractMenuAction;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.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);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseMenuAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseMenuAction.java
new file mode 100644
index 0000000..8579c2e
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileCloseMenuAction.java
@@ -0,0 +1,49 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl.menu;
+
+import static org.apache.taverna.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import org.apache.taverna.ui.menu.AbstractMenuAction;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.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);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/bf8a7ea2/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileNewMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileNewMenuAction.java b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileNewMenuAction.java
new file mode 100644
index 0000000..691729b
--- /dev/null
+++ b/taverna-file-impl/src/main/java/org/apache/taverna/workbench/file/impl/menu/FileNewMenuAction.java
@@ -0,0 +1,46 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.workbench.file.impl.menu;
+
+import static org.apache.taverna.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import org.apache.taverna.ui.menu.AbstractMenuAction;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.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);
+	}
+}