Merge branch '2.5' into taverna-3
diff --git a/pom.xml b/pom.xml
index 10df511..fc7b0c9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
 	<groupId>uk.org.taverna.server</groupId>
 	<artifactId>server</artifactId>
 	<packaging>pom</packaging>
-	<version>2.5.5-SNAPSHOT</version>
+	<version>3.0-SNAPSHOT</version>
 	<name>Taverna Server</name>
 	<description>Taverna Server is a service that provides execution of Taverna Workflows, provided they do not access the user interface while executing.</description>
 	<url>http://www.taverna.org.uk/</url>
@@ -21,8 +21,8 @@
 	<!-- Having to edit anything below here is probably indicative of a bug. -->
 	<parent>
 		<groupId>net.sf.taverna</groupId>
-		<artifactId>parent</artifactId>
-		<version>0.2.2014-03-19</version>
+		<artifactId>taverna-parent</artifactId>
+		<version>3.0.1-SNAPSHOT</version>
 	</parent>
 	<prerequisites>
 		<maven>2.2</maven>
@@ -141,13 +141,22 @@
 		<repository>
 			<id>mygrid-repository</id>
 			<name>myGrid Respository</name>
-			<url>http://www.mygrid.org.uk/maven/repository</url>
+			<url>http://build.mygrid.org.uk/maven/repository</url>
 			<snapshots>
 				<enabled>false</enabled>
 			</snapshots>
 			<releases />
 		</repository>
 		<repository>
+			<id>mygrid-snapshot-repository</id>
+			<name>myGrid Snapshot Repository</name>
+			<url>http://build.mygrid.org.uk/maven/snapshot-repository</url>
+			<snapshots>
+				<enabled>true</enabled>
+			</snapshots>
+			<releases />
+		</repository>
+		<repository>
 			<id>mygrid-snapshots</id>
 			<name>myGrid Snapshot Respository</name>
 			<url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
@@ -543,6 +552,7 @@
 		<module>server-unix-forker</module>
 		<module>server-usagerecord</module>
 		<module>server-port-description</module>
+		<module>server-execution-delegate</module>
 		<module>server-rmidaemon</module>
 		<module>server-client</module>
 		<module>server-distribution</module>
diff --git a/server-client/pom.xml b/server-client/pom.xml
index 4cc0492..e62978a 100644
--- a/server-client/pom.xml
+++ b/server-client/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>uk.org.taverna.server</groupId>
 		<artifactId>server</artifactId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 	</parent>
 	<artifactId>server-client</artifactId>
 	<packaging>bundle</packaging>
diff --git a/server-distribution/pom.xml b/server-distribution/pom.xml
index 6f5a146..9eab5d3 100644
--- a/server-distribution/pom.xml
+++ b/server-distribution/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>server</artifactId>
 		<groupId>uk.org.taverna.server</groupId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 		<relativePath>..</relativePath>
 	</parent>
 	<artifactId>server-distribution</artifactId>
diff --git a/server-execution-delegate/pom.xml b/server-execution-delegate/pom.xml
new file mode 100644
index 0000000..6acd601
--- /dev/null
+++ b/server-execution-delegate/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>uk.org.taverna</groupId>
+		<artifactId>platform</artifactId>
+		<version>0.1.3-SNAPSHOT</version>
+	</parent>
+	<groupId>uk.org.taverna.server</groupId>
+	<artifactId>server-execution-delegate</artifactId>
+	<version>3.0-SNAPSHOT</version>
+	<packaging>bundle</packaging>
+	<repositories>
+		<repository>
+			<releases />
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+			<id>mygrid-repository</id>
+			<name>myGrid Repository</name>
+			<url>http://www.mygrid.org.uk/maven/repository
+			</url>
+		</repository>
+		<repository>
+			<releases>
+				<enabled>false</enabled>
+			</releases>
+			<snapshots />
+			<id>mygrid-snapshot-repository</id>
+			<name>myGrid Snapshot Repository</name>
+			<url>http://www.mygrid.org.uk/maven/snapshot-repository</url>
+		</repository>
+	</repositories>
+	<dependencies>
+		<dependency>
+			<groupId>uk.org.taverna.platform</groupId>
+			<artifactId>taverna-execution-api</artifactId>
+			<version>0.1.3-SNAPSHOT</version>
+			<type>bundle</type>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	<name>Reporting Probe</name>
+	<inceptionYear>2012</inceptionYear>
+</project>
\ No newline at end of file
diff --git a/server-execution-delegate/src/main/java/org/taverna/server/execution_delegate/ExecutionDelegate.java b/server-execution-delegate/src/main/java/org/taverna/server/execution_delegate/ExecutionDelegate.java
new file mode 100644
index 0000000..7ea9998
--- /dev/null
+++ b/server-execution-delegate/src/main/java/org/taverna/server/execution_delegate/ExecutionDelegate.java
@@ -0,0 +1,172 @@
+package org.taverna.server.execution_delegate;
+
+import java.net.URI;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Map;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+import org.taverna.server.execution_delegate.RemoteExecution.ProcessorReportDocument.Property;
+
+import uk.org.taverna.platform.data.api.Data;
+import uk.org.taverna.platform.data.api.DataLocation;
+import uk.org.taverna.platform.execution.api.Execution;
+import uk.org.taverna.platform.report.ActivityReport;
+import uk.org.taverna.platform.report.ProcessorReport;
+import uk.org.taverna.platform.report.StatusReport;
+import uk.org.taverna.platform.report.WorkflowReport;
+import uk.org.taverna.scufl2.api.common.AbstractNamed;
+
+public class ExecutionDelegate extends UnicastRemoteObject implements
+		RemoteExecution {
+	private Execution delegated;
+	private DatatypeFactory dtf;
+
+	public ExecutionDelegate(Execution execution) throws RemoteException,
+			DatatypeConfigurationException {
+		super();
+		delegated = execution;
+		dtf = DatatypeFactory.newInstance();
+	}
+
+	@Override
+	public String getID() {
+		return delegated.getID();
+	}
+
+	@Override
+	public void delete() {
+		delegated.delete();
+	}
+
+	@Override
+	public void start() {
+		delegated.start();
+	}
+
+	@Override
+	public void pause() {
+		delegated.pause();
+	}
+
+	@Override
+	public void resume() {
+		delegated.resume();
+	}
+
+	@Override
+	public void cancel() {
+		delegated.cancel();
+	}
+
+	private XMLGregorianCalendar date(Date d) {
+		if (d == null)
+			return null;
+		GregorianCalendar c = new GregorianCalendar();
+		c.setTime(d);
+		return dtf.newXMLGregorianCalendar(c);
+	}
+
+	private <R extends ReportDocument, T extends AbstractNamed> R init(
+			R snapshot, StatusReport<T, ?, ?> report) {
+		snapshot.created = date(report.getCreatedDate());
+		snapshot.completed = date(report.getCompletedDate());
+		snapshot.cancelled = date(report.getCancelledDate());
+		snapshot.failed = date(report.getFailedDate());
+		snapshot.started = date(report.getStartedDate());
+		for (Date d : report.getPausedDates())
+			snapshot.paused.add(date(d));
+		for (Date d : report.getResumedDates())
+			snapshot.resumed.add(date(d));
+		snapshot.subject = report.getSubject().getName();
+		snapshot.state = report.getState().toString();
+		return snapshot;
+	}
+
+	private WorkflowReportDocument getReport(WorkflowReport report) {
+		WorkflowReportDocument snapshot = init(new WorkflowReportDocument(),
+				report);
+		for (ProcessorReport pr : report.getChildReports())
+			snapshot.processor.add(getReport(pr));
+		initMap(snapshot.inputs, report.getInputs());
+		initMap(snapshot.outputs, report.getOutputs());
+		return snapshot;
+	}
+
+	private void initMap(ArrayList<PortMapping> snapshot, Map<String, ?> report) {
+		for (String port : report.keySet())
+			snapshot.add(data(port, report.get(port)));
+	}
+
+	private static final String DEFAULT_PROPERTY_STRING = "";
+
+	private PortMapping data(String name, Object o) {
+		String loc;
+		if (o instanceof DataLocation) {
+			DataLocation d = (DataLocation) o;
+			loc = d.getDataServiceURI() + "#" + d.getDataID();
+		} else {
+			Data d = (Data) o;
+			loc = null;
+			if (d.hasReferences()) {
+				try {
+					loc = d.getReferences().iterator().next().getURI()
+							.toString();
+				} catch (Exception e) {
+				}
+			}
+			if (loc == null) {
+				DataLocation dl = d.getDataService().getDataLocation(d);
+				loc = dl.getDataServiceURI() + "#" + dl.getDataID();
+			}
+		}
+		return new PortMapping(name, URI.create(loc));
+	}
+
+	private ProcessorReportDocument getReport(ProcessorReport report) {
+		ProcessorReportDocument snapshot = init(new ProcessorReportDocument(),
+				report);
+		for (ActivityReport pr : report.getChildReports())
+			snapshot.activity.add(getReport(pr));
+		snapshot.jobsQueued = report.getJobsStarted();
+		snapshot.jobsStarted = report.getJobsStarted();
+		snapshot.jobsCompleted = report.getJobsCompleted();
+		snapshot.jobsErrored = report.getJobsCompletedWithErrors();
+		for (String key : report.getPropertyKeys()) {
+			Object value = report.getProperty(key);
+			if (value instanceof String || value instanceof Number)
+				snapshot.properties.add(new Property(key, value.toString()));
+			else
+				snapshot.properties.add(new Property(key,
+						DEFAULT_PROPERTY_STRING));
+		}
+		return snapshot;
+	}
+
+	private ActivityReportDocument getReport(ActivityReport report) {
+		ActivityReportDocument snapshot = init(new ActivityReportDocument(),
+				report);
+		for (WorkflowReport pr : report.getChildReports())
+			snapshot.workflow.add(getReport(pr));
+		initMap(snapshot.inputs, report.getInputs());
+		initMap(snapshot.outputs, report.getOutputs());
+		return snapshot;
+	}
+
+	@Override
+	public WorkflowReportDocument getWorkflowReport() {
+		return getReport(delegated.getWorkflowReport());
+	}
+}
+
+// ExecutionDelegate.java:[96,2]
+// initMap(java.util.ArrayList<org.taverna.server.execution_delegate.RemoteExecution.PortMapping>,java.util.Map<java.lang.String,uk.org.taverna.platform.data.api.Data>)
+// in org.taverna.server.execution_delegate.ExecutionDelegate cannot be applied
+// to
+// (java.util.ArrayList<org.taverna.server.execution_delegate.RemoteExecution.PortMapping>,java.util.Map<java.lang.String,uk.org.taverna.platform.data.api.DataLocation>)
diff --git a/server-execution-delegate/src/main/java/org/taverna/server/execution_delegate/RemoteExecution.java b/server-execution-delegate/src/main/java/org/taverna/server/execution_delegate/RemoteExecution.java
new file mode 100644
index 0000000..b8510df
--- /dev/null
+++ b/server-execution-delegate/src/main/java/org/taverna/server/execution_delegate/RemoteExecution.java
@@ -0,0 +1,188 @@
+package org.taverna.server.execution_delegate;
+
+import static javax.xml.bind.annotation.XmlAccessType.NONE;
+
+import java.net.URI;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+/**
+ * Interface for a single execution of a Taverna workflow in a remote process.
+ * 
+ * @author David Withers
+ */
+public interface RemoteExecution extends Remote {
+
+	/**
+	 * Returns the identifier for this execution.
+	 * 
+	 * @return the identifier for this execution
+	 */
+	String getID() throws RemoteException;
+
+	/**
+	 * Returns the <code>WorkflowReport</code> for the execution.
+	 * 
+	 * @return the <code>WorkflowReport</code> for the execution
+	 */
+	WorkflowReportDocument getWorkflowReport() throws RemoteException;
+
+	/**
+	 * Deletes the execution.
+	 */
+	void delete() throws RemoteException;
+
+	/**
+	 * Starts the execution.
+	 */
+	void start() throws RemoteException;
+
+	/**
+	 * Pauses the execution.
+	 */
+	void pause() throws RemoteException;
+
+	/**
+	 * Resumes a paused execution.
+	 */
+	void resume() throws RemoteException;
+
+	/**
+	 * Cancels the execution.
+	 */
+	void cancel() throws RemoteException;
+
+	@XmlType(name = "Report", propOrder = { "state", "created", "started",
+			"completed", "failed", "cancelled", "paused", "resumed" })
+	@XmlSeeAlso({ WorkflowReportDocument.class, ProcessorReportDocument.class,
+			ActivityReportDocument.class })
+	@XmlAccessorType(NONE)
+	public static abstract class ReportDocument {
+		@XmlAttribute
+		public String subject;
+		@XmlElement
+		public String state;
+		@XmlElement(required = true)
+		@XmlSchemaType(name = "dateTime")
+		public XMLGregorianCalendar created;
+		@XmlElement
+		@XmlSchemaType(name = "dateTime")
+		public XMLGregorianCalendar started;
+		@XmlElement
+		@XmlSchemaType(name = "dateTime")
+		public XMLGregorianCalendar completed;
+		@XmlElement
+		@XmlSchemaType(name = "dateTime")
+		public XMLGregorianCalendar failed;
+		@XmlElement
+		@XmlSchemaType(name = "dateTime")
+		public XMLGregorianCalendar cancelled;
+		@XmlElement
+		@XmlSchemaType(name = "dateTime")
+		public List<XMLGregorianCalendar> paused;
+		@XmlElement
+		@XmlSchemaType(name = "dateTime")
+		public List<XMLGregorianCalendar> resumed;
+	}
+
+	@XmlType(name = "WorkflowReport", propOrder = { "processor", "inputs",
+			"outputs" })
+	@XmlRootElement(name = "workflowReport")
+	@XmlAccessorType(NONE)
+	public static class WorkflowReportDocument extends ReportDocument {
+		@XmlElement(name = "processor")
+		public ArrayList<ProcessorReportDocument> processor = new ArrayList<ProcessorReportDocument>();
+		@XmlElement(name = "input")
+		public ArrayList<PortMapping> inputs = new ArrayList<PortMapping>();
+		@XmlElement(name = "output")
+		public ArrayList<PortMapping> outputs = new ArrayList<PortMapping>();
+	}
+
+	@XmlType(name = "ProcessorReport", propOrder = { "jobsQueued",
+			"jobsStarted", "jobsCompleted", "jobsErrored", "properties",
+			"activity" })
+	@XmlAccessorType(NONE)
+	public static class ProcessorReportDocument extends ReportDocument {
+		@XmlElement(name = "jobsQueued", required = true)
+		public int jobsQueued;
+		@XmlElement(name = "jobsStarted", required = true)
+		public int jobsStarted;
+		@XmlElement(name = "jobsCompleted", required = true)
+		public int jobsCompleted;
+		@XmlElement(name = "jobsErrored", required = true)
+		public int jobsErrored;
+		@XmlElement(name = "property")
+		@XmlElementWrapper(name = "properties")
+		public ArrayList<Property> properties = new ArrayList<Property>();
+		@XmlElement(name = "activity")
+		public ArrayList<ActivityReportDocument> activity = new ArrayList<ActivityReportDocument>();
+
+		@XmlType(name = "ProcessorProperty")
+		@XmlAccessorType(NONE)
+		public static class Property {
+			@XmlAttribute(name = "key", required = true)
+			String key;
+			@XmlValue
+			String value;
+
+			public Property() {
+			}
+
+			public String getKey() {
+				return key;
+			}
+
+			public String getValue() {
+				return value;
+			}
+
+			Property(String key, String value) {
+				this.key = key;
+				this.value = value;
+			}
+		}
+	}
+
+	@XmlType(name = "ActivityReport", propOrder = { "workflow", "inputs",
+			"outputs" })
+	@XmlAccessorType(NONE)
+	public static class ActivityReportDocument extends ReportDocument {
+		@XmlElement(name = "workflow")
+		public ArrayList<WorkflowReportDocument> workflow = new ArrayList<WorkflowReportDocument>();
+		@XmlElement(name = "input")
+		public ArrayList<PortMapping> inputs = new ArrayList<PortMapping>();
+		@XmlElement(name = "output")
+		public ArrayList<PortMapping> outputs = new ArrayList<PortMapping>();
+	}
+
+	@XmlType(name = "PortMapping")
+	@XmlAccessorType(NONE)
+	public static class PortMapping {
+		public PortMapping() {
+		}
+
+		PortMapping(String port, URI data) {
+			this.name = port;
+			this.reference = data;
+		}
+
+		@XmlAttribute(name = "name")
+		public String name;
+		@XmlAttribute(name = "ref")
+		@XmlSchemaType(name = "anyURI")
+		public URI reference;
+	}
+}
diff --git a/server-execution-delegate/src/test/java/SerializationTest.java b/server-execution-delegate/src/test/java/SerializationTest.java
new file mode 100644
index 0000000..c4bf0b9
--- /dev/null
+++ b/server-execution-delegate/src/test/java/SerializationTest.java
@@ -0,0 +1,54 @@
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.SchemaOutputResolver;
+import javax.xml.transform.Result;
+import javax.xml.transform.stream.StreamResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.taverna.server.execution_delegate.RemoteExecution.WorkflowReportDocument;
+
+public class SerializationTest {
+	private static final boolean PRINT = true;
+	SchemaOutputResolver sink;
+	StringWriter schema;
+
+	String schema() {
+		return schema.toString();
+	}
+
+	@Before
+	public void init() {
+		schema = new StringWriter();
+		sink = new SchemaOutputResolver() {
+			@Override
+			public Result createOutput(String namespaceUri,
+					String suggestedFileName) throws IOException {
+				StreamResult sr = new StreamResult(schema);
+				sr.setSystemId("/dev/null");
+				return sr;
+			}
+		};
+		assertEquals("", schema());
+	}
+
+	@Test
+	public void testSchemaGeneration() throws JAXBException, IOException {
+		JAXBContext.newInstance(WorkflowReportDocument.class).generateSchema(
+				sink);
+		assertFalse("generated schema must be non-empty", schema().isEmpty());
+		assertTrue(
+				"generated schema must define workflowReport element",
+				schema().contains(
+						"<xs:element name=\"workflowReport\" type=\"WorkflowReport\"/>\n"));
+		if (PRINT)
+			System.out.print(schema());
+	}
+}
diff --git a/server-port-description/pom.xml b/server-port-description/pom.xml
index 2382504..44a9d50 100644
--- a/server-port-description/pom.xml
+++ b/server-port-description/pom.xml
@@ -3,7 +3,7 @@
 	<parent>
 		<artifactId>server</artifactId>
 		<groupId>uk.org.taverna.server</groupId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 	</parent>
 	<artifactId>server-port-description</artifactId>
 	<name>Workflow Port Descriptor Types</name>
diff --git a/server-rmidaemon/pom.xml b/server-rmidaemon/pom.xml
index 0e76a90..c2282b0 100644
--- a/server-rmidaemon/pom.xml
+++ b/server-rmidaemon/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>uk.org.taverna.server</groupId>
 		<artifactId>server</artifactId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 	</parent>
 	<artifactId>server-rmidaemon</artifactId>
 	<name>RMI registry daemon</name>
diff --git a/server-runinterface/pom.xml b/server-runinterface/pom.xml
index d828299..696487a 100644
--- a/server-runinterface/pom.xml
+++ b/server-runinterface/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>uk.org.taverna.server</groupId>
 		<artifactId>server</artifactId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 		<relativePath>..</relativePath>
 	</parent>
 	<scm>
diff --git a/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java b/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
index fb3af6d..eec4ab5 100644
--- a/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
+++ b/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
@@ -38,7 +38,7 @@
 	 *             If anything goes wrong with the communication.
 	 */
 	@Nonnull
-	RemoteSingleRun make(@Nonnull String workflow, @Nonnull String creator,
+	RemoteSingleRun make(@Nonnull byte[] workflow, @Nonnull String creator,
 			@Nullable UsageRecordReceiver usageRecordReceiver,
 			@Nullable UUID masterID) throws RemoteException;
 
diff --git a/server-unix-forker/pom.xml b/server-unix-forker/pom.xml
index bf228a2..cdbce7b 100644
--- a/server-unix-forker/pom.xml
+++ b/server-unix-forker/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>server</artifactId>
 		<groupId>uk.org.taverna.server</groupId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 	</parent>
 	<artifactId>server-unix-forker</artifactId>
 	<scm>
diff --git a/server-usagerecord/pom.xml b/server-usagerecord/pom.xml
index e654df7..5352779 100644
--- a/server-usagerecord/pom.xml
+++ b/server-usagerecord/pom.xml
@@ -3,7 +3,7 @@
 	<parent>
 		<artifactId>server</artifactId>
 		<groupId>uk.org.taverna.server</groupId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 		<relativePath>..</relativePath>
 	</parent>
 	<artifactId>server-usagerecord</artifactId>
diff --git a/server-webapp/pom.xml b/server-webapp/pom.xml
index 719da86..15f1eb4 100644
--- a/server-webapp/pom.xml
+++ b/server-webapp/pom.xml
@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>uk.org.taverna.server</groupId>
 		<artifactId>server</artifactId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 		<relativePath>..</relativePath>
 	</parent>
 	<scm>
@@ -20,11 +20,13 @@
 		<version.spring-security>3.1.4.RELEASE</version.spring-security>
 		<version.asm>3.3.1</version.asm>
 		<version.smack>3.2.1</version.smack>
-		<version.commandline>2.5.0</version.commandline>
+		<!--<version.commandline>3.0.1-SNAPSHOT</version.commandline>-->
+		<version.commandline>3.0.0</version.commandline>
 		<edition.commandline>enterprise</edition.commandline>
 		<version.jdoapi>3.0.1</version.jdoapi>
 		<forker.module>server-unix-forker</forker.module>
 		<util.dir>${project.build.directory}/${project.build.finalName}/WEB-INF/classes/util</util.dir>
+		<scufl2.version>0.9.2</scufl2.version>
 		<cmdline.dir>${util.dir}/taverna-commandline-${edition.commandline}-${version.commandline}</cmdline.dir>
 	</properties>
 
@@ -289,6 +291,28 @@
 			<version>1.3.4</version>
 			<scope>runtime</scope>
 		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-t2flow</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-rdfxml</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-compress</artifactId>
+			<version>1.4.1</version>
+		</dependency>
 	</dependencies>
 
 	<dependencyManagement>
@@ -573,6 +597,7 @@
 									<groupId>net.sf.taverna.t2.taverna-commandline</groupId>
 									<artifactId>taverna-commandline-${edition.commandline}</artifactId>
 									<version>${version.commandline}</version>
+									<classifier>bin</classifier>
 									<type>zip</type>
 									<classifier>bin</classifier>
 									<outputDirectory>${util.dir}</outputDirectory>
diff --git a/server-webapp/src/build/resources/datanucleus-log.properties b/server-webapp/src/build/resources/datanucleus-log.properties
deleted file mode 100644
index 3249455..0000000
--- a/server-webapp/src/build/resources/datanucleus-log.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-handlers=java.util.logging.ConsoleHandler 
-DataNucleus.General.level=fine 
-DataNucleus.Enhancer.level=WARN 
-java.util.logging.ConsoleHandler.level=WARN
-java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 
diff --git a/server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java b/server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
index 8b4b947..f5259bd 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
@@ -8,29 +8,21 @@
 import static eu.medsea.util.MimeUtil.getMimeType;
 import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
 import static javax.ws.rs.core.UriBuilder.fromUri;
-import static javax.xml.xpath.XPathConstants.NODE;
-import static javax.xml.xpath.XPathConstants.NODESET;
 import static org.apache.commons.logging.LogFactory.getLog;
 import static org.taverna.server.master.common.Uri.secure;
 
 import java.io.ByteArrayInputStream;
-import java.util.ArrayList;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Set;
 
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpression;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
 
 import org.apache.commons.logging.Log;
 import org.springframework.beans.factory.annotation.Required;
-import org.springframework.util.xml.SimpleNamespaceContext;
 import org.taverna.server.master.exceptions.FilesystemAccessException;
 import org.taverna.server.master.exceptions.NoDirectoryEntryException;
 import org.taverna.server.master.interfaces.Directory;
@@ -49,8 +41,11 @@
 import org.taverna.server.port_description.ListValue;
 import org.taverna.server.port_description.OutputDescription;
 import org.taverna.server.port_description.OutputDescription.OutputPort;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
 
 /**
  * A class that is used to build descriptions of the contents of a workflow
@@ -60,67 +55,8 @@
  */
 public class ContentsDescriptorBuilder {
 	private Log log = getLog("Taverna.Server.Webapp");
-	/** Namespace for use when pulling apart a .t2flow document. */
-	private static final String T2FLOW_NS = "http://taverna.sf.net/2008/xml/t2flow";
-	/** Prefix for t2flow namespace. */
-	private static final String T2FLOW_PFX = "t2";
-
 	private FilenameUtils fileUtils;
 	private UriBuilderFactory uriBuilderFactory;
-	private XPathExpression inputPorts;
-	private XPathExpression outputPorts;
-	private XPathExpression portName;
-	private XPathExpression portDepth;
-	private XPathExpression dataflow;
-
-	public ContentsDescriptorBuilder() throws XPathExpressionException {
-		XPath xp = XPathFactory.newInstance().newXPath();
-		SimpleNamespaceContext ctxt = new SimpleNamespaceContext();
-		ctxt.bindNamespaceUri(T2FLOW_PFX, T2FLOW_NS);
-		xp.setNamespaceContext(ctxt);
-
-		dataflow = xp.compile("//t2:dataflow[1]");
-		inputPorts = xp.compile("./t2:inputPorts/t2:port");
-		outputPorts = xp.compile("./t2:outputPorts/t2:port");
-		portName = xp.compile("./t2:name/text()");
-		portDepth = xp.compile("./t2:depth/text()");
-	}
-
-	private Element dataflow(Element root) throws XPathExpressionException {
-		return (Element) dataflow.evaluate(root, NODE);
-	}
-
-	private List<Element> inputPorts(Element dataflow)
-			throws XPathExpressionException {
-		List<Element> result = new ArrayList<>();
-		if (dataflow == null)
-			return result;
-		NodeList nl = (NodeList) inputPorts.evaluate(dataflow, NODESET);
-		// Wrap as a list so we can iterate over it <sigh>
-		for (int i = 0; i < nl.getLength(); i++)
-			result.add((Element) nl.item(i));
-		return result;
-	}
-
-	private List<Element> outputPorts(Element dataflow)
-			throws XPathExpressionException {
-		List<Element> result = new ArrayList<>();
-		if (dataflow == null)
-			return result;
-		NodeList nl = (NodeList) outputPorts.evaluate(dataflow, NODESET);
-		// Wrap as a list so we can iterate over it <sigh>
-		for (int i = 0; i < nl.getLength(); i++)
-			result.add((Element) nl.item(i));
-		return result;
-	}
-
-	private String portName(Element port) throws XPathExpressionException {
-		return portName.evaluate(port);
-	}
-
-	private String portDepth(Element port) throws XPathExpressionException {
-		return portDepth.evaluate(port);
-	}
 
 	@Required
 	public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
@@ -134,50 +70,13 @@
 
 	// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 
-	private Element fillInFromWorkflow(TavernaRun run, UriBuilder ub,
-			AbstractPortDescription portDesc) throws XPathExpressionException {
-		Element elem = run.getWorkflow().getWorkflowRoot();
-		portDesc.fillInBaseData(elem.getAttribute("id"), run.getId(),
-				ub.build());
-		return dataflow(elem);
-	}
-
-	/**
-	 * Build the contents description.
-	 * 
-	 * @param run
-	 *            The workflow run this is talking about.
-	 * @param dataflow
-	 *            The dataflow element of the T2flow document.
-	 * @param ub
-	 *            How to build URIs.
-	 * @param descriptor
-	 *            The descriptor to modify.
-	 * @param expected
-	 *            The list of outputs that are <i>expected</i> to be produced;
-	 *            they might not actually produce anything though.
-	 * @throws NoDirectoryEntryException
-	 * @throws FilesystemAccessException
-	 * @throws XPathExpressionException
-	 */
-	private void constructPorts(TavernaRun run, Element dataflow,
-			UriBuilder ub, OutputDescription descriptor)
-			throws FilesystemAccessException, NoDirectoryEntryException,
-			XPathExpressionException {
-		Collection<DirectoryEntry> outs = null;
-		try {
-			outs = fileUtils.getDirectory(run, "out").getContents();
-		} catch (FilesystemAccessException | NoDirectoryEntryException e) {
-			log.warn("unexpected failure in construction of output descriptor",
-					e);
-		}
-		for (Element output : outputPorts(dataflow)) {
-			OutputPort p = descriptor.addPort(portName(output));
-			if (outs != null) {
-				p.output = constructValue(outs, ub, p.name);
-				p.depth = computeDepth(p.output);
-			}
-		}
+	private Workflow fillInFromWorkflow(TavernaRun run, UriBuilder ub,
+			AbstractPortDescription portDesc) throws IOException {
+		WorkflowBundle bundle = run.getWorkflow().getScufl2Workflow();
+		bundle.getMainWorkflow().getInputPorts();
+		portDesc.fillInBaseData(bundle.getMainWorkflow()
+				.getWorkflowIdentifier().toString(), run.getId(), ub.build());
+		return bundle.getMainWorkflow();
 	}
 
 	/**
@@ -335,12 +234,20 @@
 		OutputDescription descriptor = new OutputDescription();
 		try {
 			UriBuilder ub = getRunUriBuilder(run, ui);
-			Element dataflow = fillInFromWorkflow(run, ub, descriptor);
-			if (dataflow == null || run.getOutputBaclavaFile() != null)
-				return descriptor;
-			constructPorts(run, dataflow, ub.path("wd"), descriptor);
-		} catch (XPathExpressionException e) {
-			log.info("failure in XPath evaluation", e);
+			Workflow dataflow = fillInFromWorkflow(run, ub, descriptor);
+			Collection<DirectoryEntry> outs = null;
+			ub = ub.path("wd/{path}");
+			for (OutputWorkflowPort output : dataflow.getOutputPorts()) {
+				OutputPort p = descriptor.addPort(output.getName());
+				if (run.getOutputBaclavaFile() == null) {
+					if (outs == null)
+						outs = fileUtils.getDirectory(run, "out").getContents();
+					p.output = constructValue(outs, ub, p.name);
+					p.depth = computeDepth(p.output);
+				}
+			}
+		} catch (IOException e) {
+			log.info("failure in conversion to .scufl2", e);
 		}
 		return descriptor;
 	}
@@ -366,19 +273,19 @@
 		InputDescription desc = new InputDescription();
 		try {
 			UriBuilder ub = getRunUriBuilder(run, ui);
-			Element dataflow = fillInFromWorkflow(run, ub, desc);
+			Workflow workflow = fillInFromWorkflow(run, ub, desc);
 			ub = ub.path("input/{name}");
-			for (Element port : inputPorts(dataflow)) {
-				InputPort in = desc.addPort(portName(port));
+			for (InputWorkflowPort port : workflow.getInputPorts()) {
+				InputPort in = desc.addPort(port.getName());
 				in.href = ub.build(in.name);
 				try {
-					in.depth = Integer.valueOf(portDepth(port));
+					in.depth = port.getDepth();
 				} catch (NumberFormatException ex) {
 					in.depth = null;
 				}
 			}
-		} catch (XPathExpressionException e) {
-			log.info("failure in XPath evaluation", e);
+		} catch (IOException e) {
+			log.info("failure in conversion to .scufl2", e);
 		}
 		return desc;
 	}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java b/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java
index cc7d08a..48969fa 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java
@@ -343,6 +343,7 @@
 			List<URI> referenceList, UriInfo ui)
 			throws NoDirectoryEntryException, NoUpdateException,
 			FilesystemAccessException {
+		support.permitUpdate(run);
 		if (referenceList.isEmpty() || referenceList.size() > 1)
 			return status(422).entity("URI list must have single URI in it")
 					.build();
diff --git a/server-webapp/src/main/java/org/taverna/server/master/RunREST.java b/server-webapp/src/main/java/org/taverna/server/master/RunREST.java
index a8cffec..563a822 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/RunREST.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/RunREST.java
@@ -31,6 +31,7 @@
 import org.ogf.usage.JobUsageRecord;
 import org.springframework.beans.factory.annotation.Required;
 import org.taverna.server.master.api.RunBean;
+import org.taverna.server.master.common.ProfileList;
 import org.taverna.server.master.common.Status;
 import org.taverna.server.master.common.Workflow;
 import org.taverna.server.master.exceptions.BadStateChangeException;
@@ -179,6 +180,19 @@
 
 	@Override
 	@CallCounted
+	public String getMainProfileName() {
+		String name = run.getWorkflow().getMainProfileName();
+		return (name == null ? "" : name);
+	}
+
+	@Override
+	@CallCounted
+	public ProfileList getProfiles() {
+		return support.getProfileDescriptor(run.getWorkflow());
+	}
+
+	@Override
+	@CallCounted
 	@PerfLogged
 	@RolesAllowed({ USER, SELF })
 	public DirectoryREST getWorkingDirectory() {
@@ -401,6 +415,12 @@
 
 	@Override
 	@CallCounted
+	public Response profileOptions() {
+		return opt();
+	}
+
+	@Override
+	@CallCounted
 	public Response expiryOptions() {
 		return opt("PUT");
 	}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/TavernaServer.java b/server-webapp/src/main/java/org/taverna/server/master/TavernaServer.java
index 62daf7d..0f98da6 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/TavernaServer.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/TavernaServer.java
@@ -60,8 +60,10 @@
 import org.taverna.server.master.api.TavernaServerBean;
 import org.taverna.server.master.common.Capability;
 import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.DirEntryReference;
 import org.taverna.server.master.common.InputDescription;
 import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
 import org.taverna.server.master.common.RunReference;
 import org.taverna.server.master.common.Status;
 import org.taverna.server.master.common.Trust;
@@ -101,6 +103,7 @@
 import org.taverna.server.master.soap.FileContents;
 import org.taverna.server.master.soap.PermissionList;
 import org.taverna.server.master.soap.TavernaServerSOAP;
+import org.taverna.server.master.soap.WrappedWorkflow;
 import org.taverna.server.master.soap.ZippedDirectory;
 import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
 import org.taverna.server.master.utils.FilenameUtils;
@@ -380,6 +383,23 @@
 	@CallCounted
 	@PerfLogged
 	@RolesAllowed(USER)
+	public RunReference submitWorkflowMTOM(WrappedWorkflow workflow)
+			throws NoUpdateException {
+		Workflow wf;
+		try {
+			wf = workflow.getWorkflow();
+		} catch (IOException e) {
+			throw new NoCreateException(e.getMessage(), e);
+		}
+		checkCreatePolicy(wf);
+		String name = support.buildWorkflow(wf);
+		return new RunReference(name, getRunUriBuilder());
+	}
+
+	@Override
+	@CallCounted
+	@PerfLogged
+	@RolesAllowed(USER)
 	public RunReference submitWorkflowByURI(URI workflowURI)
 			throws NoCreateException {
 		checkCreatePolicy(workflowURI);
@@ -465,6 +485,27 @@
 	@CallCounted
 	@PerfLogged
 	@RolesAllowed(USER)
+	public WrappedWorkflow getRunWorkflowMTOM(String runName)
+			throws UnknownRunException {
+		WrappedWorkflow ww = new WrappedWorkflow();
+		ww.setWorkflow(support.getRun(runName).getWorkflow());
+		return ww;
+	}
+
+	@Override
+	@CallCounted
+	@PerfLogged
+	@RolesAllowed(USER)
+	public ProfileList getRunWorkflowProfiles(String runName)
+			throws UnknownRunException {
+		return support.getProfileDescriptor(support.getRun(runName)
+				.getWorkflow());
+	}
+
+	@Override
+	@CallCounted
+	@PerfLogged
+	@RolesAllowed(USER)
 	public Date getRunExpiry(String runName) throws UnknownRunException {
 		return support.getRun(runName).getExpiry();
 	}
@@ -956,6 +997,25 @@
 	@CallCounted
 	@PerfLogged
 	@RolesAllowed(USER)
+	public void setRunFileContentsFromURI(String runName,
+			DirEntryReference file, URI reference)
+			throws UnknownRunException, NoUpdateException,
+			FilesystemAccessException, NoDirectoryEntryException {
+		TavernaRun run = support.getRun(runName);
+		support.permitUpdate(run);
+		File f = fileUtils.getFile(run, file);
+		try {
+			support.copyDataToFile(reference, f);
+		} catch (IOException e) {
+			throw new FilesystemAccessException(
+					"problem transferring data from URI", e);
+		}
+	}
+
+	@Override
+	@CallCounted
+	@PerfLogged
+	@RolesAllowed(USER)
 	public void setRunFileContentsMTOM(String runName, FileContents newContents)
 			throws UnknownRunException, NoUpdateException,
 			FilesystemAccessException, NoDirectoryEntryException {
diff --git a/server-webapp/src/main/java/org/taverna/server/master/TavernaServerSupport.java b/server-webapp/src/main/java/org/taverna/server/master/TavernaServerSupport.java
index c5c219b..ce36bd3 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/TavernaServerSupport.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/TavernaServerSupport.java
@@ -52,6 +52,7 @@
 import org.taverna.server.master.api.TavernaServerBean;
 import org.taverna.server.master.common.Capability;
 import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
 import org.taverna.server.master.common.VersionedElement;
 import org.taverna.server.master.common.Workflow;
 import org.taverna.server.master.common.version.Version;
@@ -79,6 +80,8 @@
 import org.taverna.server.master.utils.InvocationCounter;
 import org.taverna.server.master.utils.UsernamePrincipal;
 
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
 /**
  * Web application support utilities.
  * 
@@ -861,6 +864,7 @@
 		long total = 0;
 		try {
 			byte[] buffer = new byte[TRANSFER_SIZE];
+			boolean first = true;
 			while (true) {
 				int len = stream.read(buffer);
 				if (len < 0)
@@ -870,19 +874,47 @@
 					log.debug("read " + len
 							+ " bytes from source stream (total: " + total
 							+ ") bound for " + name);
-				if (len == buffer.length)
-					file.appendContents(buffer);
-				else {
+				if (len == buffer.length) {
+					if (first)
+						file.setContents(buffer);
+					else
+						file.appendContents(buffer);
+				} else {
 					byte[] newBuf = new byte[len];
 					System.arraycopy(buffer, 0, newBuf, 0, len);
-					file.appendContents(newBuf);
+					if (first)
+						file.setContents(newBuf);
+					else
+						file.appendContents(newBuf);
 				}
+				first = false;
 			}
 		} catch (IOException exn) {
 			throw new FilesystemAccessException("failed to transfer bytes", exn);
 		}
 	}
 
+	/**
+	 * Build a description of the profiles supported by a workflow. Note that we
+	 * expect the set of profiles to be fairly small.
+	 * 
+	 * @param workflow
+	 *            The workflow to describe the profiles of.
+	 * @return The descriptor (which might be empty).
+	 */
+	public ProfileList getProfileDescriptor(Workflow workflow) {
+		ProfileList result = new ProfileList();
+		String main = workflow.getMainProfileName();
+		for (Profile p : workflow.getProfiles()) {
+			ProfileList.Info i = new ProfileList.Info();
+			i.name = p.getName();
+			if (main != null && main.equals(i.name))
+				i.main = true;
+			result.profile.add(i);
+		}
+		return result;
+	}
+
 	public boolean getAllowStartWorkflowRuns() {
 		return runFactory.isAllowingRunsToStart();
 	}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/common/ProfileList.java b/server-webapp/src/main/java/org/taverna/server/master/common/ProfileList.java
new file mode 100644
index 0000000..69c3622
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/common/ProfileList.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 The University of Manchester
+ * 
+ * See the file "LICENSE.txt" for license terms.
+ */
+package org.taverna.server.master.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * Description of the profiles that can apply to a workflow.
+ * 
+ * @author Donal K. Fellows
+ */
+@XmlRootElement(name = "profiles")
+@XmlType(name = "ProfileList")
+public class ProfileList {
+	public List<ProfileList.Info> profile = new ArrayList<ProfileList.Info>();
+
+	/**
+	 * Description of a single workflow profile.
+	 * 
+	 * @author Donal Fellows
+	 */
+	@XmlRootElement(name = "profile")
+	@XmlType(name = "Profile")
+	public static class Info {
+		@XmlValue
+		public String name;
+		/**
+		 * Whether this is the main profile.
+		 */
+		@XmlAttribute(name = "isMain")
+		public Boolean main;
+	}
+}
\ No newline at end of file
diff --git a/server-webapp/src/main/java/org/taverna/server/master/common/Workflow.java b/server-webapp/src/main/java/org/taverna/server/master/common/Workflow.java
index 9aa3669..ba6a68c 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/common/Workflow.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/common/Workflow.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010-2011 The University of Manchester
+ * Copyright (C) 2010-2012 The University of Manchester
  * 
  * See the file "LICENSE" for license terms.
  */
@@ -7,8 +7,12 @@
 
 import static javax.xml.bind.Marshaller.JAXB_ENCODING;
 import static javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT;
+import static javax.xml.bind.annotation.XmlAccessType.NONE;
 import static org.apache.commons.logging.LogFactory.getLog;
-import static org.taverna.server.master.common.Namespaces.T2FLOW;
+import static org.taverna.server.master.rest.handler.Scufl2DocumentHandler.SCUFL2;
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW;
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_NS;
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_ROOTNAME;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -22,39 +26,57 @@
 import java.io.Serializable;
 import java.io.StringReader;
 import java.io.StringWriter;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.InflaterInputStream;
+import java.net.URL;
 
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAnyElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
+import javax.xml.parsers.DocumentBuilderFactory;
 
+import org.taverna.server.master.rest.handler.Scufl2DocumentHandler;
+import org.taverna.server.master.rest.handler.T2FlowDocumentHandler;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.io.WriterException;
+import uk.org.taverna.scufl2.api.profiles.Profile;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 /**
- * Encapsulation of a T2flow document.
+ * Encapsulation of a T2flow or Scufl2 document.
  * 
- * @author dkf
+ * @author Donal K. Fellows
  */
 @XmlRootElement(name = "workflow")
 @XmlType(name = "Workflow")
-public class Workflow implements Serializable,Externalizable {
-	/**
-	 * Literal document.
-	 */
+@XmlAccessorType(NONE)
+public class Workflow implements Serializable, Externalizable {
+	/** Literal document, if present. */
 	@XmlAnyElement(lax = true)
-	public Element[] content;
+	private Element content;
+	/** SCUFL2 bundle, if present. */
+	@XmlTransient
+	private WorkflowBundle bundle;
+	/** Which came first, the bundle or the t2flow document. */
+	@XmlTransient
+	private boolean isBundleFirst;
 
 	private static Marshaller marshaller;
 	private static Unmarshaller unmarshaller;
-	private final static String ENCODING = "UTF-8"; 
+	private final static String ENCODING = "UTF-8";
+	private final static WorkflowBundleIO io;
 	static {
 		try {
 			JAXBContext context = JAXBContext.newInstance(Workflow.class);
@@ -67,14 +89,160 @@
 					"failed to build JAXB context for working with "
 							+ Workflow.class, e);
 		}
+		io = new WorkflowBundleIO();
 	}
 
+	public enum ContentType {
+		T2FLOW(T2FlowDocumentHandler.T2FLOW), SCUFL2(
+				Scufl2DocumentHandler.SCUFL2);
+		private String type;
+
+		ContentType(String type) {
+			this.type = type;
+		}
+
+		public String getContentType() {
+			return type;
+		}
+	}
+
+	public Workflow() {
+	}
+
+	public Workflow(Element element) {
+		this.content = element;
+		this.isBundleFirst = false;
+	}
+
+	public Workflow(WorkflowBundle bundle) {
+		this.bundle = bundle;
+		this.isBundleFirst = true;
+	}
+
+	public Workflow(URL url) throws ReaderException, IOException {
+		this(io.readBundle(url, null));
+	}
+
+	/**
+	 * What content type would this workflow "prefer" to be?
+	 */
+	public ContentType getPreferredContentType() {
+		if (isBundleFirst)
+			return ContentType.SCUFL2;
+		else
+			return ContentType.T2FLOW;
+	}
+
+	/**
+	 * Retrieves the workflow as a SCUFL2 document, converting it if necessary.
+	 * 
+	 * @return The SCUFL2 document.
+	 * @throws IOException
+	 *             If anything goes wrong.
+	 */
+	public WorkflowBundle getScufl2Workflow() throws IOException {
+		try {
+			if (bundle == null)
+				bundle = io.readBundle(new ByteArrayInputStream(getAsT2Flow()),
+						T2FLOW);
+			return bundle;
+		} catch (IOException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new IOException("problem when converting to SCUFL2", e);
+		}
+	}
+
+	/**
+	 * Get the bytes of the serialized SCUFL2 workflow.
+	 * 
+	 * @return Array of bytes.
+	 * @throws IOException
+	 *             If serialization fails.
+	 * @throws WriterException
+	 *             If conversion fails.
+	 */
+	public byte[] getScufl2Bytes() throws IOException, WriterException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		io.writeBundle(getScufl2Workflow(), baos, SCUFL2);
+		return baos.toByteArray();
+	}
+
+	/**
+	 * Retrieves the workflow as a T2Flow document, converting it if necessary.
+	 * 
+	 * @return The T2Flow document.
+	 * @throws IOException
+	 *             If anything goes wrong.
+	 */
+	public Element getT2flowWorkflow() throws IOException {
+		try {
+			if (content != null)
+				return content;
+			ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			io.writeBundle(bundle, baos, T2FLOW);
+			Document doc;
+			try {
+				DocumentBuilderFactory dbf = DocumentBuilderFactory
+						.newInstance();
+				dbf.setNamespaceAware(true);
+				doc = dbf.newDocumentBuilder().parse(
+						new ByteArrayInputStream(baos.toByteArray()));
+			} catch (SAXException e) {
+				throw new IOException("failed to convert to DOM tree", e);
+			}
+			Element e = doc.getDocumentElement();
+			if (e.getNamespaceURI().equals(T2FLOW_NS)
+					&& e.getNodeName().equals(T2FLOW_ROOTNAME))
+				return content = e;
+			throw new IOException(
+					"unexpected element when converting to T2Flow: {"
+							+ e.getNamespaceURI() + "}" + e.getNodeName());
+		} catch (IOException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new IOException("problem when converting to SCUFL2", e);
+		}
+	}
+
+	/**
+	 * @return The name of the main workflow profile, or <tt>null</tt> if there
+	 *         is none.
+	 */
+	public String getMainProfileName() {
+		try {
+			return getScufl2Workflow().getMainProfile().getName();
+		} catch (IOException e) {
+			return null;
+		}
+	}
+
+	/**
+	 * @return The set of profiles supported over this workflow.
+	 */
+	public NamedSet<Profile> getProfiles() {
+		try {
+			return getScufl2Workflow().getProfiles();
+		} catch (IOException e) {
+			return new NamedSet<Profile>();
+		}
+	}
+
+	/**
+	 * Convert from marshalled form.
+	 * 
+	 * @throws JAXBException
+	 *             If the conversion fails.
+	 */
 	public static Workflow unmarshal(String representation)
 			throws JAXBException {
 		StringReader sr = new StringReader(representation);
 		return (Workflow) unmarshaller.unmarshal(sr);
 	}
 
+	/**
+	 * Convert to marshalled form.
+	 */
 	public String marshal() throws JAXBException {
 		StringWriter sw = new StringWriter();
 		marshaller.marshal(this, sw);
@@ -85,35 +253,69 @@
 	public void readExternal(ObjectInput in) throws IOException,
 			ClassNotFoundException {
 		try {
-			int len = in.readInt();
-			byte[] bytes = new byte[len];
-			in.readFully(bytes);
-			try (Reader r = new InputStreamReader(new InflaterInputStream(
-					new ByteArrayInputStream(bytes)), ENCODING)) {
-				this.content = ((Workflow) unmarshaller.unmarshal(r)).content;
-			}
+			ByteArrayInputStream bytes = readbytes(in);
+			if (bytes != null)
+				try (Reader r = new InputStreamReader(bytes, ENCODING)) {
+					content = ((Workflow) unmarshaller.unmarshal(r)).content;
+				}
+			bytes = readbytes(in);
+			if (bytes != null)
+				bundle = io.readBundle(bytes, SCUFL2);
+			isBundleFirst = in.readBoolean();
 			return;
 		} catch (JAXBException e) {
 			throw new IOException("failed to unmarshal", e);
 		} catch (ClassCastException e) {
 			throw new IOException("bizarre result of unmarshalling", e);
+		} catch (ReaderException e) {
+			throw new IOException("failed to unmarshal", e);
 		}
 	}
 
+	private byte[] getAsT2Flow() throws IOException, JAXBException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		OutputStreamWriter w = new OutputStreamWriter(baos, ENCODING);
+		marshaller.marshal(this, w);
+		w.close();
+		return baos.toByteArray();
+	}
+
+	private byte[] getAsScufl2() throws IOException, WriterException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		io.writeBundle(bundle, baos, SCUFL2);
+		baos.close();
+		return baos.toByteArray();
+	}
+
 	@Override
 	public void writeExternal(ObjectOutput out) throws IOException {
 		try {
-			ByteArrayOutputStream baos = new ByteArrayOutputStream();
-			try (OutputStreamWriter w = new OutputStreamWriter(
-					new DeflaterOutputStream(baos), ENCODING)) {
-				marshaller.marshal(this, w);
-			}
-			byte[] bytes = baos.toByteArray();
-			out.writeInt(bytes.length);
-			out.write(bytes);
+			writebytes(out, (content != null) ? getAsT2Flow() : null);
 		} catch (JAXBException e) {
-			throw new IOException("failed to marshal", e);
+			throw new IOException("failed to marshal t2flow", e);
 		}
+		try {
+			writebytes(out, (bundle != null) ? getAsScufl2() : null);
+		} catch (WriterException e) {
+			throw new IOException("failed to marshal scufl2", e);
+		}
+		out.writeBoolean(isBundleFirst);
+	}
+
+	private ByteArrayInputStream readbytes(ObjectInput in) throws IOException {
+		int len = in.readInt();
+		if (len > 0) {
+			byte[] bytes = new byte[len];
+			in.readFully(bytes);
+			return new ByteArrayInputStream(bytes);
+		}
+		return null;
+	}
+
+	private void writebytes(ObjectOutput out, byte[] data) throws IOException {
+		out.writeInt(data == null ? 0 : data.length);
+		if (data != null && data.length > 0)
+			out.write(data);
 	}
 
 	/**
@@ -125,13 +327,7 @@
 	 * @return The looked up element, or <tt>null</tt> if it doesn't exist.
 	 */
 	private Element getEl(String... name) {
-		Element el = null;
-		for (Element e : content)
-			if (e.getNamespaceURI().equals(T2FLOW)
-					&& e.getLocalName().equals(name[0])) {
-				el = e;
-				break;
-			}
+		Element el = content;
 		boolean skip = true;
 		for (String n : name) {
 			if (skip) {
@@ -140,7 +336,7 @@
 			}
 			if (el == null)
 				return null;
-			NodeList nl = el.getElementsByTagNameNS(T2FLOW, n);
+			NodeList nl = el.getElementsByTagNameNS(T2FLOW_NS, n);
 			if (nl.getLength() == 0)
 				return null;
 			Node node = nl.item(0);
diff --git a/server-webapp/src/main/java/org/taverna/server/master/localworker/AbstractRemoteRunFactory.java b/server-webapp/src/main/java/org/taverna/server/master/localworker/AbstractRemoteRunFactory.java
index f1dec3c..fc7f881 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/localworker/AbstractRemoteRunFactory.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/localworker/AbstractRemoteRunFactory.java
@@ -59,6 +59,8 @@
 import org.taverna.server.master.worker.RemoteRunDelegate;
 import org.taverna.server.master.worker.RunFactoryConfiguration;
 
+import uk.org.taverna.scufl2.api.io.WriterException;
+
 /**
  * Bridge to remote runs via RMI.
  * 
@@ -387,8 +389,14 @@
 	 * @throws JAXBException
 	 *             If serialization fails.
 	 */
-	protected String serializeWorkflow(Workflow workflow) throws JAXBException {
-		return workflow.marshal();
+	protected byte[] serializeWorkflow(Workflow workflow) throws JAXBException {
+		try {
+			return workflow.getScufl2Bytes();
+		} catch (IOException e) {
+			throw new JAXBException("problem converting to scufl2", e);
+		} catch (WriterException e) {
+			throw new JAXBException("problem converting to scufl2", e);
+		}
 	}
 
 	private void acceptUsageRecord(String usageRecord) {
diff --git a/server-webapp/src/main/java/org/taverna/server/master/localworker/ForkRunFactory.java b/server-webapp/src/main/java/org/taverna/server/master/localworker/ForkRunFactory.java
index b8746bc..b67e121 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/localworker/ForkRunFactory.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/localworker/ForkRunFactory.java
@@ -282,7 +282,7 @@
 	 *             If anything fails (communications error, etc.)
 	 */
 	private RemoteSingleRun getRealRun(@Nonnull UsernamePrincipal creator,
-			@Nonnull String wf, UUID id) throws RemoteException {
+			@Nonnull byte[] wf, UUID id) throws RemoteException {
 		@Nonnull
 		String globaluser = "Unknown Person";
 		if (creator != null)
@@ -297,7 +297,7 @@
 	protected RemoteSingleRun getRealRun(UsernamePrincipal creator,
 			Workflow workflow, UUID id) throws Exception {
 		@Nonnull
-		String wf = serializeWorkflow(workflow);
+		byte[] wf = serializeWorkflow(workflow);
 		for (int i = 0; i < 3; i++) {
 			initFactory();
 			try {
diff --git a/server-webapp/src/main/java/org/taverna/server/master/localworker/IdAwareForkRunFactory.java b/server-webapp/src/main/java/org/taverna/server/master/localworker/IdAwareForkRunFactory.java
index 6679039..e449373 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/localworker/IdAwareForkRunFactory.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/localworker/IdAwareForkRunFactory.java
@@ -269,7 +269,7 @@
 	 *             If anything fails (communications error, etc.)
 	 */
 	private RemoteSingleRun getRealRun(@Nonnull UsernamePrincipal creator,
-			@Nonnull String username, @Nonnull String wf, UUID id)
+			@Nonnull String username, @Nonnull byte[] wf, UUID id)
 			throws RemoteException {
 		String globaluser = "Unknown Person";
 		if (creator != null)
@@ -283,7 +283,7 @@
 	@Override
 	protected RemoteSingleRun getRealRun(UsernamePrincipal creator,
 			Workflow workflow, UUID id) throws Exception {
-		String wf = serializeWorkflow(workflow);
+		byte[] wf = serializeWorkflow(workflow);
 		String username = mapper == null ? null : mapper
 				.getUsernameForPrincipal(creator);
 		if (username == null)
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerREST.java b/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerREST.java
index 1e9f4a2..62158bb 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerREST.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerREST.java
@@ -19,6 +19,7 @@
 import static org.taverna.server.master.rest.TavernaServerREST.PathNames.POL_RUN_LIMIT;
 import static org.taverna.server.master.rest.TavernaServerREST.PathNames.ROOT;
 import static org.taverna.server.master.rest.TavernaServerREST.PathNames.RUNS;
+import static org.taverna.server.master.rest.handler.Scufl2DocumentHandler.SCUFL2;
 import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW;
 
 import java.net.URI;
@@ -41,7 +42,9 @@
 import javax.ws.rs.core.UriInfo;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSchemaType;
 import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
 
 import org.apache.abdera.model.Entry;
 import org.apache.abdera.model.Feed;
@@ -59,7 +62,7 @@
 import org.taverna.server.master.soap.TavernaServerSOAP;
 
 /**
- * The REST service interface to Taverna 2.5.4 Server.
+ * The REST service interface to Taverna 3 Server.
  * 
  * @author Donal Fellows
  * @see TavernaServerSOAP
@@ -117,7 +120,7 @@
 	 */
 	@POST
 	@Path(RUNS)
-	@Consumes({ T2FLOW, XML })
+	@Consumes({ T2FLOW, SCUFL2, XML })
 	@RolesAllowed(USER)
 	@Description("Accepts (or not) a request to create a new run executing "
 			+ "the given workflow.")
@@ -129,16 +132,15 @@
 	 * Accepts (or not) a request to create a new run executing the workflow at
 	 * the given location.
 	 * 
-	 * @param referenceList
-	 *            The URI to workflow document to execute.
+	 * @param workflowReference
+	 *            The wrapped URI to workflow document to execute.
 	 * @param ui
 	 *            About the URI being POSTed to.
 	 * @return A response to the POST describing what was created.
 	 * @throws NoUpdateException
 	 *             If the POST failed.
-	 * @throws NoCreateException
-	 *             If the workflow couldn't be read into the server or the
-	 *             engine rejects it.
+	 * @throw NoCreateException If the workflow couldn't be read into the server
+	 *        or the engine rejects it.
 	 */
 	@POST
 	@Path(RUNS)
@@ -586,4 +588,17 @@
 		@Nonnull
 		Entry getEvent(@Nonnull @PathParam("id") String id);
 	}
+
+	/**
+	 * A reference to a workflow hosted on some public HTTP server.
+	 * 
+	 * @author Donal Fellows
+	 */
+	@XmlRootElement(name = "workflowurl")
+	@XmlType(name = "WorkflowReference")
+	public static class WorkflowReference {
+		@XmlValue
+		@XmlSchemaType(name = "anyURI")
+		public URI url;
+	}
 }
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerRunREST.java b/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerRunREST.java
index 536c455..2b328fd 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerRunREST.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/TavernaServerRunREST.java
@@ -8,6 +8,7 @@
 import static javax.ws.rs.core.UriBuilder.fromUri;
 import static org.joda.time.format.ISODateTimeFormat.basicDateTime;
 import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.rest.handler.Scufl2DocumentHandler.SCUFL2;
 import static org.taverna.server.master.interaction.InteractionFeedSupport.FEED_URL_DIR;
 import static org.taverna.server.master.rest.ContentTypes.JSON;
 import static org.taverna.server.master.rest.ContentTypes.ROBUNDLE;
@@ -20,6 +21,7 @@
 import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.LOG;
 import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.NAME;
 import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.OUT;
+import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.PROFILE;
 import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.ROOT;
 import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.RUNBUNDLE;
 import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.SEC;
@@ -62,6 +64,7 @@
 import org.apache.cxf.jaxrs.model.wadl.Description;
 import org.joda.time.format.DateTimeFormatter;
 import org.taverna.server.master.common.Namespaces;
+import org.taverna.server.master.common.ProfileList;
 import org.taverna.server.master.common.Status;
 import org.taverna.server.master.common.Uri;
 import org.taverna.server.master.common.VersionedElement;
@@ -125,7 +128,7 @@
 	 */
 	@GET
 	@Path(WF)
-	@Produces({ T2FLOW, XML, JSON })
+	@Produces({ T2FLOW, SCUFL2, XML, JSON })
 	@Description("Gives the workflow document used to create the workflow run.")
 	@Nonnull
 	public Workflow getWorkflow();
@@ -168,6 +171,40 @@
 	Response nameOptions();
 
 	/**
+	 * Produces the name of the workflow's main profile.
+	 * 
+	 * @return The main profile name, or the empty string if there is no such
+	 *         profile.
+	 */
+	@GET
+	@Path(PROFILE)
+	@Produces(TEXT)
+	@Description("Gives the name of the workflow's main profile, or the empty string if none is defined.")
+	@Nonnull
+	String getMainProfileName();
+
+	/**
+	 * Get a description of the profiles supported by the workflow document used
+	 * to create this run.
+	 * 
+	 * @return A description of the supported profiles.
+	 */
+	@GET
+	@Path(PROFILE)
+	@Produces({ XML, JSON })
+	@Description("Describes what profiles exist on the workflow.")
+	@Nonnull
+	ProfileList getProfiles();
+
+	/** Produce the workflow profile HTTP operations. */
+	@OPTIONS
+	@Path(PROFILE)
+	@Description("Produces the description of the operations on the run's "
+			+ "profile.")
+	@Nonnull
+	Response profileOptions();
+
+	/**
 	 * Returns a resource that represents the workflow run's security
 	 * properties. These may only be accessed by the owner.
 	 * 
@@ -571,6 +608,7 @@
 		public static final String STATUS = "status";
 		public static final String IN = "input";
 		public static final String OUT = "output";
+		public static final String PROFILE = "profile";
 		public static final String LISTEN = "listeners";
 		public static final String SEC = "security";
 		public static final String STDOUT = "stdout";
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/Scufl2DocumentHandler.java b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/Scufl2DocumentHandler.java
new file mode 100644
index 0000000..edeac63
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/Scufl2DocumentHandler.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011 The University of Manchester
+ * 
+ * See the file "LICENSE.txt" for license terms.
+ */
+package org.taverna.server.master.rest.handler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.taverna.server.master.common.Workflow;
+
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.io.WriterException;
+
+/**
+ * Handler that allows a .scufl2 document to be read from and written to a REST
+ * message directly.
+ * 
+ * @author Donal Fellows
+ */
+@Provider
+public class Scufl2DocumentHandler implements MessageBodyReader<Workflow>,
+		MessageBodyWriter<Workflow> {
+	private static final MediaType SCUFL2_TYPE = new MediaType("application",
+			"vnd.taverna.scufl2.workflow-bundle");
+	public static final String SCUFL2 = "application/vnd.taverna.scufl2.workflow-bundle";
+	private WorkflowBundleIO io = new WorkflowBundleIO();
+
+	@Override
+	public boolean isReadable(Class<?> type, Type genericType,
+			Annotation[] annotations, MediaType mediaType) {
+		if (type.isAssignableFrom(Workflow.class))
+			return mediaType.isCompatible(SCUFL2_TYPE);
+		return false;
+	}
+
+	@Override
+	public Workflow readFrom(Class<Workflow> type, Type genericType,
+			Annotation[] annotations, MediaType mediaType,
+			MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+			throws IOException, WebApplicationException {
+		try {
+			return new Workflow(io.readBundle(entityStream, SCUFL2));
+		} catch (ReaderException e) {
+			throw new WebApplicationException(e, 403);
+		}
+	}
+
+	@Override
+	public boolean isWriteable(Class<?> type, Type genericType,
+			Annotation[] annotations, MediaType mediaType) {
+		if (Workflow.class.isAssignableFrom(type))
+			return mediaType.isCompatible(SCUFL2_TYPE);
+		return false;
+	}
+
+	@Override
+	public long getSize(Workflow workflow, Class<?> type, Type genericType,
+			Annotation[] annotations, MediaType mediaType) {
+		return -1;
+	}
+
+	@Override
+	public void writeTo(Workflow workflow, Class<?> type, Type genericType,
+			Annotation[] annotations, MediaType mediaType,
+			MultivaluedMap<String, Object> httpHeaders,
+			OutputStream entityStream) throws IOException,
+			WebApplicationException {
+		try {
+			io.writeBundle(workflow.getScufl2Workflow(), entityStream, SCUFL2);
+		} catch (WriterException e) {
+			throw new WebApplicationException(e);
+		}
+	}
+}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/T2FlowDocumentHandler.java b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/T2FlowDocumentHandler.java
index 78ee822..4227d80 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/T2FlowDocumentHandler.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/T2FlowDocumentHandler.java
@@ -28,7 +28,6 @@
 
 import org.taverna.server.master.common.Workflow;
 import org.w3c.dom.Document;
-import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
 /**
@@ -43,8 +42,8 @@
 	private static final MediaType T2FLOW_TYPE = new MediaType("application",
 			"vnd.taverna.t2flow+xml");
 	public static final String T2FLOW = "application/vnd.taverna.t2flow+xml";
-	private static final String T2FLOW_ROOTNAME = "workflow";
-	private static final String T2FLOW_NS = "http://taverna.sf.net/2008/xml/t2flow";
+	public static final String T2FLOW_ROOTNAME = "workflow";
+	public static final String T2FLOW_NS = "http://taverna.sf.net/2008/xml/t2flow";
 	private DocumentBuilderFactory db;
 	private TransformerFactory transformer;
 
@@ -76,8 +75,7 @@
 		} catch (ParserConfigurationException e) {
 			throw new WebApplicationException(e);
 		}
-		Workflow workflow = new Workflow();
-		workflow.content = new Element[] { doc.getDocumentElement() };
+		Workflow workflow = new Workflow(doc.getDocumentElement());
 		if (doc.getDocumentElement().getNamespaceURI().equals(T2FLOW_NS)
 				&& doc.getDocumentElement().getNodeName()
 						.equals(T2FLOW_ROOTNAME))
@@ -109,7 +107,7 @@
 			WebApplicationException {
 		try {
 			transformer.newTransformer().transform(
-					new DOMSource(workflow.content[0]),
+					new DOMSource(workflow.getT2flowWorkflow()),
 					new StreamResult(entityStream));
 		} catch (TransformerException e) {
 			if (e.getCause() != null && e.getCause() instanceof IOException)
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/URIListHandler.java b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/URIListHandler.java
index 378eee8..a90a229 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/URIListHandler.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/URIListHandler.java
@@ -118,4 +118,4 @@
 		}
 		w.flush();
 	}
-}
\ No newline at end of file
+}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/soap/TavernaServerSOAP.java b/server-webapp/src/main/java/org/taverna/server/master/soap/TavernaServerSOAP.java
index 72aca5e..e175bf8 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/soap/TavernaServerSOAP.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/soap/TavernaServerSOAP.java
@@ -23,8 +23,10 @@
 import org.ogf.usage.JobUsageRecord;
 import org.taverna.server.master.common.Capability;
 import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.DirEntryReference;
 import org.taverna.server.master.common.InputDescription;
 import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
 import org.taverna.server.master.common.RunReference;
 import org.taverna.server.master.common.Status;
 import org.taverna.server.master.common.Trust;
@@ -45,7 +47,7 @@
 import org.taverna.server.port_description.OutputDescription;
 
 /**
- * The SOAP service interface to Taverna 2.5.4 Server.
+ * The SOAP service interface to Taverna 3 Server.
  * 
  * @author Donal Fellows
  * @see TavernaServerREST
@@ -68,7 +70,22 @@
 	@WSDLDocumentation("Make a run for a particular workflow.")
 	RunReference submitWorkflow(
 			@WebParam(name = "workflow") @XmlElement(required = true) Workflow workflow)
-			throws NoUpdateException, NoCreateException;
+					throws NoUpdateException, NoCreateException;
+
+	/**
+	 * Make a run for a particular workflow.
+	 * 
+	 * @param workflow
+	 *            The workflow to instantiate.
+	 * @return Annotated handle for created run.
+	 * @throws NoUpdateException
+	 * @throws NoCreateException
+	 */
+	@WebResult(name = "Run")
+	@WSDLDocumentation("Make a run for a particular workflow.")
+	RunReference submitWorkflowMTOM(
+			@WebParam(name = "workflow") @XmlElement(required = true) WrappedWorkflow workflow)
+			throws NoUpdateException;
 
 	/**
 	 * Make a run for a particular workflow, where that workflow will be
@@ -81,7 +98,8 @@
 	 * @throws NoCreateException
 	 */
 	@WebResult(name = "Run")
-	@WSDLDocumentation("Make a run for a particular workflow where that workflow is given by publicly readable URI.")
+	@WSDLDocumentation("Make a run for a particular workflow where that "
+			+ "workflow is given by publicly readable URI.")
 	RunReference submitWorkflowByURI(
 			@WebParam(name = "workflowURI") @XmlElement(required = true) URI workflowURI)
 			throws NoCreateException, NoUpdateException;
@@ -179,6 +197,39 @@
 			throws UnknownRunException;
 
 	/**
+	 * Get the workflow document used to create the given run.
+	 * 
+	 * @param runName
+	 *            The handle of the run.
+	 * @return The workflow document.
+	 * @throws UnknownRunException
+	 *             If the server doesn't know about the run or if the user is
+	 *             not permitted to see it.
+	 */
+	@WebResult(name = "CreationWorkflow")
+	@WSDLDocumentation("Get the workflow document used to create the given run.")
+	WrappedWorkflow getRunWorkflowMTOM(
+			@WebParam(name = "runName") String runName)
+			throws UnknownRunException;
+
+	/**
+	 * Get a description of the profiles supported by the workflow document used
+	 * to create the given run.
+	 * 
+	 * @param runName
+	 *            The handle of the run.
+	 * @return A description of the supported profiles.
+	 * @throws UnknownRunException
+	 *             If the server doesn't know about the run or if the user is
+	 *             not permitted to see it.
+	 */
+	@WebResult(name = "Profiles")
+	@WSDLDocumentation("Get a description of the profiles supported by the workflow document used to create the given run.")
+	ProfileList getRunWorkflowProfiles(
+			@WebParam(name = "runName") String runName)
+			throws UnknownRunException;
+
+	/**
 	 * Get the descriptive name of the workflow run. The descriptive name
 	 * carries no deep information.
 	 * 
@@ -1281,6 +1332,37 @@
 			FilesystemAccessException, NoDirectoryEntryException;
 
 	/**
+	 * Set the contents of a file under the run's working directory to the
+	 * contents of a publicly readable URI. Runs do not share working
+	 * directories.
+	 * 
+	 * @param runName
+	 *            The handle of the run.
+	 * @param file
+	 *            The name of the file to update; the main working directory is
+	 *            <tt>/</tt> and <tt>..</tt> is always disallowed.
+	 * @param reference
+	 *            The publicly readable URI whose contents are to become the
+	 *            literal bytes of the file's contents.
+	 * @throws UnknownRunException
+	 *             If the server doesn't know about the run or if the user is
+	 *             not permitted to see it.
+	 * @throws NoUpdateException
+	 *             If the user is not allowed to make modifications to the run.
+	 * @throws FilesystemAccessException
+	 *             If some assumption is violated (e.g., writing the contents of
+	 *             a directory).
+	 * @throws NoDirectoryEntryException
+	 *             If the file doesn't exist.
+	 */
+	@WSDLDocumentation("Set the contents of a file under the run's working directory from the contents of a publicly readable URI.")
+	void setRunFileContentsFromURI(@WebParam(name = "runName") String runName,
+			@WebParam(name = "fileName") DirEntryReference file,
+			@WebParam(name = "contents") URI reference)
+			throws UnknownRunException, NoUpdateException,
+			FilesystemAccessException, NoDirectoryEntryException;
+
+	/**
 	 * Get the length of any file (in bytes) at/under the run's working
 	 * directory. Runs do not share working directories.
 	 * 
diff --git a/server-webapp/src/main/java/org/taverna/server/master/soap/WrappedWorkflow.java b/server-webapp/src/main/java/org/taverna/server/master/soap/WrappedWorkflow.java
new file mode 100644
index 0000000..cd06115
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/soap/WrappedWorkflow.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 The University of Manchester
+ * 
+ * See the file "LICENSE.txt" for license terms.
+ */
+package org.taverna.server.master.soap;
+
+import static javax.xml.bind.annotation.XmlAccessType.NONE;
+import static org.apache.commons.io.IOUtils.closeQuietly;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlMimeType;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+import org.taverna.server.master.common.Workflow;
+
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.io.WriterException;
+
+/**
+ * An MTOM-capable description of how to transfer the contents of a file.
+ * 
+ * @author Donal Fellows
+ */
+@XmlType(name = "WorkflowReference")
+@XmlAccessorType(NONE)
+public class WrappedWorkflow {
+	@XmlMimeType("application/octet-stream")
+	// JAXB bug: must be this
+	public DataHandler workflowData;
+	Workflow workflow;
+
+	/**
+	 * Initialize the contents of this descriptor from the given file and
+	 * content type.
+	 * 
+	 * @param workflow
+	 *            The workflow that is to be reported.
+	 */
+	public void setWorkflow(Workflow workflow) {
+		workflowData = new DataHandler(new WorkflowSource(workflow));
+	}
+
+	@XmlTransient
+	public Workflow getWorkflow() throws IOException {
+		if (workflow != null)
+			return workflow;
+		try {
+			return new Workflow(new WorkflowBundleIO().readBundle(
+					workflowData.getInputStream(), null));
+		} catch (ReaderException e) {
+			throw new IOException("problem converting to scufl2 bundle", e);
+		}
+	}
+}
+
+/**
+ * A data source that knows how to deliver a workflow.
+ * 
+ * @author Donal Fellows
+ */
+class WorkflowSource implements DataSource {
+	WorkflowSource(Workflow workflow) {
+		this.wf = workflow;
+		this.io = new WorkflowBundleIO();
+	}
+
+	Workflow wf;
+	final WorkflowBundleIO io;
+
+	@Override
+	public String getContentType() {
+		return wf.getPreferredContentType().getContentType();
+	}
+
+	@Override
+	public String getName() {
+		switch (wf.getPreferredContentType()) {
+		case SCUFL2:
+			return "workflow.scufl2";
+		case T2FLOW:
+			return "workflow.t2flow";
+		default:
+			return "workflow";
+		}
+	}
+
+	@Override
+	public InputStream getInputStream() throws IOException {
+		PipedInputStream is = new PipedInputStream();
+		final OutputStream os = new PipedOutputStream(is);
+		new Worker() {
+			@Override
+			public void doWork() throws WriterException, IOException {
+				io.writeBundle(wf.getScufl2Workflow(), os, wf
+						.getPreferredContentType().getContentType());
+			}
+
+			@Override
+			public void doneWork() {
+				closeQuietly(os);
+			}
+		};
+		return is;
+	}
+
+	@Override
+	public OutputStream getOutputStream() throws IOException {
+		final PipedInputStream is = new PipedInputStream();
+		OutputStream os = new PipedOutputStream(is);
+		new Worker() {
+			@Override
+			public void doWork() throws IOException, ReaderException {
+				wf = new Workflow(io.readBundle(is, null));
+			}
+
+			@Override
+			public void doneWork() {
+				closeQuietly(is);
+			}
+		};
+		return os;
+	}
+
+	static abstract class Worker extends Thread {
+		public Worker() {
+			setDaemon(true);
+			start();
+		}
+
+		public abstract void doWork() throws Exception;
+
+		public abstract void doneWork();
+
+		@Override
+		public void run() {
+			try {
+				doWork();
+			} catch (Exception e) {
+				// do nothing.
+			} finally {
+				doneWork();
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java b/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
new file mode 100644
index 0000000..96cdc6d
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
@@ -0,0 +1,65 @@
+package org.taverna.server.master.utils;
+
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.apache.cxf.common.util.UrlUtils.parseQueryString;
+import static org.apache.cxf.message.Message.HTTP_REQUEST_METHOD;
+import static org.apache.cxf.message.Message.QUERY_STRING;
+import static org.apache.cxf.message.Message.REQUEST_URL;
+import static org.apache.cxf.phase.Phase.READ;
+
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.cxf.binding.soap.interceptor.EndpointSelectionInterceptor;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+
+
+/**
+ * Thunk for TAVSERV-293.
+ * 
+ * @author Donal Fellows (based on work by Daniel Hagen)
+ */
+public class WSDLHeadOptionsInterceptor extends
+		AbstractPhaseInterceptor<Message> {
+	public static final Log log = getLog("Taverna.Server.Utils");
+
+	public WSDLHeadOptionsInterceptor() {
+		super(READ);
+		getAfter().add(EndpointSelectionInterceptor.class.getName());
+	}
+
+	@Override
+	public void handleMessage(Message message) throws Fault {
+		String method = (String) message.get(HTTP_REQUEST_METHOD);
+		String query = (String) message.get(QUERY_STRING);
+
+		if (("HEAD".equals(method) || "OPTIONS".equals(method))
+				&& query != null && !query.trim().isEmpty()
+				&& isRecognizedQuery(query)) {
+			log.debug("adjusting message request method " + method + " for "
+					+ message.get(REQUEST_URL) + " to GET");
+			message.put(HTTP_REQUEST_METHOD, "GET");
+		}
+	}
+
+	/*
+	 * Stolen from http://permalink.gmane.org/gmane.comp.apache.cxf.user/20037
+	 * which is itself in turn stolen from
+	 * org.apache.cxf.frontend.WSDLGetInterceptor.isRecognizedQuery
+	 */
+	/**
+	 * Is this a query for WSDL or XSD relating to it?
+	 * 
+	 * @param query
+	 *            The query string to check
+	 * @return If the query is one to handle.
+	 * @see org.apache.cxf.frontend.WSDLGetInterceptor#isRecognizedQuery(Map,String,String,org.apache.cxf.service.model.EndpointInfo)
+	 *      WSDLGetInterceptor
+	 */
+	private boolean isRecognizedQuery(String query) {
+		Map<String, String> map = parseQueryString(query);
+		return map.containsKey("wsdl") || map.containsKey("xsd");
+	}
+}
diff --git a/server-webapp/src/main/replacementscripts/executeworkflow.sh b/server-webapp/src/main/replacementscripts/executeworkflow.sh
index 8885cb1..10d73b2 100644
--- a/server-webapp/src/main/replacementscripts/executeworkflow.sh
+++ b/server-webapp/src/main/replacementscripts/executeworkflow.sh
@@ -8,7 +8,7 @@
 
 ## Parse the command line to extract the pieces to move around to before or
 ## after the JAR filename...
-pre=
+pre=-Djava.awt.headless=true 
 post=
 for arg
 do
@@ -40,17 +40,17 @@
 if test -x "$JAVA_HOME/bin/java"; then
     javabin="$JAVA_HOME/bin/java"
 fi
-RAVEN_APPHOME_PROP= 
-if test x != "x$RAVEN_APPHOME"; then
-    RAVEN_APPHOME_PROP="-Draven.launcher.app.home=$RAVEN_APPHOME"
+APPHOME_PROP= 
+if test x != "x$TAVERNA_APPHOME"; then
+    APPHOME_PROP="-Dtaverna.app.home=$TAVERNA_APPHOME"
 fi
 RUNID_PROP= 
 if test x != "x$TAVERNA_RUN_ID"; then
     RUNID_PROP="-Dtaverna.runid=$TAVERNA_RUN_ID"
 fi
-INTERACTION_PROPS= 
+INTERACTION_PROPS=-Dtaverna.interaction.ignore_requests=true
 if test x != "x$INTERACTION_HOST"; then
-    INTERACTION_PROPS="-Dtaverna.interaction.host=$INTERACTION_HOST"
+    INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.host=$INTERACTION_HOST"
     INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.port=$INTERACTION_PORT"
     INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.webdav_path=$INTERACTION_WEBDAV"
     INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.feed_path=$INTERACTION_FEED"
@@ -63,14 +63,9 @@
 
 echo "pid:$$"
 exec "$javabin" $memlimit $permsize \
-  "-Dcom.sun.net.ssl.enableECC=false" \
-  "-Djsse.enableSNIExtension=false" \
-  "-Draven.profile=file://$taverna_home/conf/current-profile.xml" \
-  "-Dtaverna.startup=$taverna_home" $RAVEN_APPHOME_PROP $RUNID_PROP \
-  $INTERACTION_PROPS $pre \
-  -Djava.system.class.loader=net.sf.taverna.raven.prelauncher.BootstrapClassLoader \
-  -Draven.launcher.app.main=$MainClass \
-  -Draven.launcher.show_splashscreen=false \
-  -Djava.awt.headless=true -Dtaverna.interaction.ignore_requests=true \
-  -jar "$taverna_home/lib/"prelauncher-*.jar \
-  "$@"
+  "-Dlog4j.configuration=file://$taverna_home/conf/log4j.properties " \
+  "-Djava.util.logging.config.file=$taverna_home/conf/logging.properties " \
+  "-Dtaverna.app.startup=$taverna_home" -Dtaverna.interaction.ignore_requests=true \
+  $APPHOME_PROP $RUNID_PROP $INTERACTION_PROPS -Djava.awt.headless=true $pre \
+  -jar "$taverna_home/lib/taverna-command-line-0.1.1.jar" \
+  ${1+"$@"}
diff --git a/server-webapp/src/main/resources/welcome.html b/server-webapp/src/main/resources/welcome.html
index 81b3b06..f80da4a 100644
--- a/server-webapp/src/main/resources/welcome.html
+++ b/server-webapp/src/main/resources/welcome.html
@@ -6,6 +6,13 @@
 </head>
 <body>
 <h1>Taverna Server %{VERSION}</h1>
+<div style="text-align;center">
+	<p>
+		<i>Note that this is a pre-release version. Significant known
+			issues remain open and it is not guaranteed that the service API
+			will be stable.</i>
+	</p>
+</div>
 <p>For a full list of operations, see the <a
     href="%{BASEURL}/services">service listing</a> generated by Apache
 CXF, which indicates where to access the WSDL and WADL descriptions of
diff --git a/server-webapp/src/main/webapp/WEB-INF/providers.xml b/server-webapp/src/main/webapp/WEB-INF/providers.xml
index 0bb03eb..43531ab 100644
--- a/server-webapp/src/main/webapp/WEB-INF/providers.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/providers.xml
@@ -88,6 +88,7 @@
 
 	<bean id="Provider.RuntimeExceptionRemapping" class="org.taverna.server.master.utils.RuntimeExceptionWrapper" />
 	<bean id="MessagingProvider.ZipStream" class="org.taverna.server.master.rest.handler.ZipStreamHandler" />
+	<bean id="MessagingProvider.URIList" class="org.taverna.server.master.rest.handler.URIListHandler" />
 	<bean id="Interceptor.FlushThreadLocalCache"
 		class="org.taverna.server.master.utils.FlushThreadLocalCacheInterceptor"
 		lazy-init="false">
diff --git a/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml b/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml
index a22664a..22e74b3 100644
--- a/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml
@@ -92,7 +92,8 @@
 			<ref bean="MessagingProvider.InputStream" />
 			<ref bean="MessagingProvider.T2flow" />
 			<ref bean="MessagingProvider.Permission" />
-			<ref bean="MessagingProvider.ZipStream"/>
+			<ref bean="MessagingProvider.URIList" />
+			<ref bean="MessagingProvider.ZipStream" />
 			<ref bean="jsonProvider" />
 			<ref bean="atomEntryHandler" />
 			<ref bean="atomFeedHandler" />
@@ -161,6 +162,14 @@
 		<security:authentication-provider ref="authProvider" />
 	</security:authentication-manager>
 
+	<bean id="WSDLHeadOptionsInterceptor"
+		class="org.taverna.server.master.utils.WSDLHeadOptionsInterceptor" />
+	<cxf:bus>
+		<cxf:inInterceptors>
+			<ref bean="WSDLHeadOptionsInterceptor" />
+		</cxf:inInterceptors>
+	</cxf:bus>
+
 	<aop:aspectj-autoproxy proxy-target-class="true" />
 	<security:global-method-security
 		jsr250-annotations="enabled" />
diff --git a/server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java b/server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java
index 3b6f75d..79c026b 100644
--- a/server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java
+++ b/server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java
@@ -27,6 +27,7 @@
 import org.taverna.server.master.common.DirEntryReference;
 import org.taverna.server.master.common.InputDescription;
 import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
 import org.taverna.server.master.common.RunReference;
 import org.taverna.server.master.common.Status;
 import org.taverna.server.master.common.Trust;
@@ -245,6 +246,11 @@
 	}
 
 	@Test
+	public void testJAXBForProfileList() throws Exception {
+		testJAXB(ProfileList.class);
+	}
+
+	@Test
 	public void testJAXBForDirEntry() throws Exception {
 		testJAXB(DirEntry.class);
 	}
@@ -276,7 +282,7 @@
 				TavernaServerSecurityREST.Descriptor.class,
 				TavernaServerSecurityREST.PermissionDescription.class,
 				TavernaServerSecurityREST.PermissionsDescription.class,
-				Capability.class, CapabilityList.class);
+				ProfileList.class, Capability.class, CapabilityList.class);
 	}
 
 	@Test
@@ -285,7 +291,7 @@
 				Permission.class, PermissionList.class,
 				PermissionList.SinglePermissionMapping.class,
 				RunReference.class, Status.class, Trust.class, Uri.class,
-				Workflow.class, Capability.class);
+				ProfileList.class, Workflow.class, Capability.class);
 	}
 
 	@Test
diff --git a/server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java b/server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java
index 4d49d17..0450317 100644
--- a/server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java
+++ b/server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java
@@ -1,5 +1,8 @@
 package org.taverna.server.master;
 
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_NS;
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_ROOTNAME;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -13,6 +16,7 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.taverna.server.master.common.Workflow;
+import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -21,15 +25,16 @@
 	public void testWorkflowSerialization()
 			throws ParserConfigurationException, IOException,
 			ClassNotFoundException {
-		Workflow w = new Workflow();
-		w.content = new Element[1];
 		DocumentBuilder db = DocumentBuilderFactory.newInstance()
 				.newDocumentBuilder();
 		Document doc = db.getDOMImplementation().createDocument(null, null,
 				null);
-		w.content[0] = doc.createElement("foo");
-		w.content[0].setTextContent("bar");
-		w.content[0].setAttribute("xyz", "abc");
+		Element workflow = doc.createElementNS(T2FLOW_NS, T2FLOW_ROOTNAME);
+		Element foo = doc.createElementNS("urn:foo:bar", "pqr:foo");
+		foo.setTextContent("bar");
+		foo.setAttribute("xyz", "abc");
+		workflow.appendChild(foo);
+		Workflow w = new Workflow(workflow);
 
 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
 		try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
@@ -45,12 +50,19 @@
 		Assert.assertNotNull(o);
 		Assert.assertEquals(w.getClass(), o.getClass());
 		Workflow w2 = (Workflow) o;
-		Assert.assertNotNull(w2.content);
-		Assert.assertEquals(1, w2.content.length);
-		Element e = w2.content[0];
-		Assert.assertEquals("foo", e.getTagName());
+		Assert.assertNotNull(w2.getT2flowWorkflow());
+		Element e = w2.getT2flowWorkflow();
+		Assert.assertEquals(T2FLOW_ROOTNAME, e.getLocalName());
+		Assert.assertEquals(T2FLOW_NS, e.getNamespaceURI());
+		e = (Element) e.getFirstChild();
+		Assert.assertEquals("foo", e.getLocalName());
+		Assert.assertEquals("pqr", e.getPrefix());
+		Assert.assertEquals("urn:foo:bar", e.getNamespaceURI());
 		Assert.assertEquals("bar", e.getTextContent());
 		Assert.assertEquals(1, e.getChildNodes().getLength());
+		// WARNING: These are dependent on how namespaces are encoded!
+		Assert.assertEquals(2, e.getAttributes().getLength());
+		Assert.assertEquals("xyz", ((Attr) e.getAttributes().item(1)).getLocalName());
 		Assert.assertEquals("abc", e.getAttribute("xyz"));
 	}
 }
diff --git a/server-worker/pom.xml b/server-worker/pom.xml
index df2ada8..70239a2 100644
--- a/server-worker/pom.xml
+++ b/server-worker/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>uk.org.taverna.server</groupId>
 		<artifactId>server</artifactId>
-		<version>2.5.5-SNAPSHOT</version>
+		<version>3.0-SNAPSHOT</version>
 		<relativePath>..</relativePath>
 	</parent>
 	<scm>
@@ -15,6 +15,7 @@
 
 	<properties>
 		<workerMainClass>org.taverna.server.localworker.impl.TavernaRunManager</workerMainClass>
+		<scufl2.version>0.9.2</scufl2.version>
 	</properties>
 
 	<dependencies>
@@ -41,6 +42,23 @@
 			<groupId>commons-io</groupId>
 			<artifactId>commons-io</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-t2flow</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-rdfxml</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>runtime</scope>
+		</dependency>
 	</dependencies>
 
 	<build>
diff --git a/server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java b/server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java
index 7b9f012..c513ed8 100644
--- a/server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java
+++ b/server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java
@@ -71,7 +71,7 @@
 	 *             If any of quite a large number of things goes wrong.
 	 */
 	boolean initWorker(LocalWorker local, String executeWorkflowCommand,
-			String workflow, File workingDir, File inputBaclavaFile,
+			byte[] workflow, File workingDir, File inputBaclavaFile,
 			Map<String, File> inputRealFiles, Map<String, String> inputValues,
 			Map<String, String> inputDelimiters, File outputBaclavaFile,
 			File contextDirectory, char[] keystorePassword,
diff --git a/server-worker/src/main/java/org/taverna/server/localworker/impl/LocalWorker.java b/server-worker/src/main/java/org/taverna/server/localworker/impl/LocalWorker.java
index 29755c5..adf3ea7 100644
--- a/server-worker/src/main/java/org/taverna/server/localworker/impl/LocalWorker.java
+++ b/server-worker/src/main/java/org/taverna/server/localworker/impl/LocalWorker.java
@@ -97,7 +97,7 @@
 	/** What to use to run a workflow engine. */
 	private final String executeWorkflowCommand;
 	/** What workflow to run. */
-	private final String workflow;
+	private final byte[] workflow;
 	/** The remote access object for the working directory. */
 	private final DirectoryDelegate baseDir;
 	/** What inputs to pass as files. */
@@ -196,7 +196,7 @@
 	 * @throws ImplementationException
 	 *             If something goes wrong during local setup.
 	 */
-	protected LocalWorker(String executeWorkflowCommand, String workflow,
+	protected LocalWorker(String executeWorkflowCommand, byte[] workflow,
 			UsageRecordReceiver urReceiver, UUID id,
 			Map<String, String> seedEnvironment, List<String> javaParams,
 			WorkerFactory workerFactory) throws RemoteException,
diff --git a/server-worker/src/main/java/org/taverna/server/localworker/impl/TavernaRunManager.java b/server-worker/src/main/java/org/taverna/server/localworker/impl/TavernaRunManager.java
index a4c9a7e..03ee69d 100644
--- a/server-worker/src/main/java/org/taverna/server/localworker/impl/TavernaRunManager.java
+++ b/server-worker/src/main/java/org/taverna/server/localworker/impl/TavernaRunManager.java
@@ -19,8 +19,8 @@
 import static org.taverna.server.localworker.api.Constants.SEC_POLICY_PROP;
 import static org.taverna.server.localworker.api.Constants.UNSECURE_PROP;
 
-import java.io.StringReader;
-import java.io.StringWriter;
+import java.io.ByteArrayInputStream;
+import java.net.URI;
 import java.rmi.RMISecurityManager;
 import java.rmi.RemoteException;
 import java.rmi.registry.Registry;
@@ -31,12 +31,6 @@
 import java.util.Map;
 import java.util.UUID;
 
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.ws.Holder;
-
 import org.taverna.server.localworker.api.RunAccounting;
 import org.taverna.server.localworker.api.Worker;
 import org.taverna.server.localworker.api.WorkerFactory;
@@ -45,9 +39,8 @@
 import org.taverna.server.localworker.server.UsageRecordReceiver;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
+
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
 
 /**
  * The registered factory for runs, this class is responsible for constructing
@@ -57,20 +50,19 @@
  * @author Donal Fellows
  * @see LocalWorker
  */
-@java.lang.SuppressWarnings("serial")
+@SuppressWarnings("serial")
 public class TavernaRunManager extends UnicastRemoteObject implements
 		RemoteRunFactory, RunAccounting, WorkerFactory {
-	DocumentBuilderFactory dbf;
-	TransformerFactory tf;
 	String command;
+	Map<String, String> seedEnvironment = new HashMap<>();
+	List<String> javaInitParams = new ArrayList<>();
+	private WorkflowBundleIO io;
+	private int activeRuns = 0;
 	// Hacks!
 	public static String interactionHost;
 	public static String interactionPort;
 	public static String interactionWebdavPath;
 	public static String interactionFeedPath;
-	Map<String, String> seedEnvironment = new HashMap<>();
-	List<String> javaInitParams = new ArrayList<>();
-	private int activeRuns = 0;
 
 	/**
 	 * How to get the actual workflow document from the XML document that it is
@@ -97,55 +89,18 @@
 	 */
 	public TavernaRunManager(String command) throws RemoteException {
 		this.command = command;
-		this.dbf = DocumentBuilderFactory.newInstance();
-		this.dbf.setNamespaceAware(true);
-		this.dbf.setCoalescing(true);
-		this.tf = TransformerFactory.newInstance();
-	}
-
-	/**
-	 * Do the unwrapping of a workflow to extract the contents of the file to
-	 * feed into the Taverna core.
-	 * 
-	 * @param workflow
-	 *            The string containing the workflow to extract.
-	 * @param wfid
-	 *            A place to store the extracted workflow ID.
-	 * @return The extracted workflow description.
-	 * @throws RemoteException
-	 *             If anything goes wrong.
-	 */
-	private String unwrapWorkflow(String workflow, Holder<String> wfid)
-			throws RemoteException {
-		StringReader sr = new StringReader(workflow);
-		StringWriter sw = new StringWriter();
-		try {
-			Document doc = dbf.newDocumentBuilder().parse(new InputSource(sr));
-			// Try to extract the t2flow's ID.
-			NodeList nl = doc.getElementsByTagNameNS(
-					"http://taverna.sf.net/2008/xml/t2flow", "dataflow");
-			if (nl.getLength() > 0) {
-				Node n = nl.item(0).getAttributes().getNamedItem("id");
-				if (n != null)
-					wfid.value = n.getTextContent();
-			}
-			tf.newTransformer().transform(new DOMSource(unwrapWorkflow(doc)),
-					new StreamResult(sw));
-			return sw.toString();
-		} catch (Exception e) {
-			throw new RemoteException("failed to extract contained workflow", e);
-		}
+		this.io = new WorkflowBundleIO();
 	}
 
 	@Override
-	public RemoteSingleRun make(String workflow, String creator,
+	public RemoteSingleRun make(byte[] workflow, String creator,
 			UsageRecordReceiver urReceiver, UUID id) throws RemoteException {
 		if (creator == null)
 			throw new RemoteException("no creator");
 		try {
-			Holder<String> wfid = new Holder<>("???");
-			workflow = unwrapWorkflow(workflow, wfid);
-			out.println("Creating run from workflow <" + wfid.value + "> for <"
+			URI wfid = io.readBundle(new ByteArrayInputStream(workflow), null)
+					.getMainWorkflow().getWorkflowIdentifier();
+			out.println("Creating run from workflow <" + wfid + "> for <"
 					+ creator + ">");
 			return new LocalWorker(command, workflow, urReceiver, id,
 					seedEnvironment, javaInitParams, this);
@@ -235,6 +190,7 @@
 		for (int i = 1; i < args.length - 1; i++)
 			man.addArgument(args[i]);
 		registry = getRegistry(LOCALHOST);
+
 		registry.bind(factoryName, man);
 		getRuntime().addShutdownHook(new Thread() {
 			@Override
diff --git a/server-worker/src/main/java/org/taverna/server/localworker/impl/WorkerCore.java b/server-worker/src/main/java/org/taverna/server/localworker/impl/WorkerCore.java
index c91fdcc..1a6cff8 100644
--- a/server-worker/src/main/java/org/taverna/server/localworker/impl/WorkerCore.java
+++ b/server-worker/src/main/java/org/taverna/server/localworker/impl/WorkerCore.java
@@ -44,6 +44,7 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -200,19 +201,22 @@
 	 *             If any of quite a large number of things goes wrong.
 	 */
 	@Override
-	public boolean initWorker(@Nonnull final LocalWorker local,
+	public boolean initWorker(
+			@Nonnull final LocalWorker local,
 			@Nonnull final String executeWorkflowCommand,
-			@Nonnull final String workflow, @Nonnull final File workingDir,
+			@Nonnull final byte[] workflow,
+			@Nonnull final File workingDir,
 			@Nullable final File inputBaclava,
 			@Nonnull final Map<String, File> inputFiles,
-			@Nonnull final Map<String, String> inputValues,
+			@Nonnull final Map<String, String> inputValues, 
 			@Nonnull final Map<String, String> inputDelimiters,
 			@Nullable final File outputBaclava,
-			@Nonnull final File securityDir, @Nullable final char[] password,
+			@Nonnull final File securityDir,
+			@Nullable final char[] password,
 			final boolean generateProvenance,
 			@Nonnull final Map<String, String> environment,
-			@Nonnull final String token, @Nonnull final List<String> runtime)
-			throws IOException {
+			@Nullable final String token,
+			@Nonnull final List<String> runtime) throws IOException {
 		try {
 			new TimingOutTask() {
 				@Override
@@ -291,7 +295,7 @@
 	 */
 	@Nonnull
 	ProcessBuilder createProcessBuilder(@Nonnull LocalWorker local,
-			@Nonnull String executeWorkflowCommand, @Nonnull String workflow,
+			@Nonnull String executeWorkflowCommand, @Nonnull byte[] workflow,
 			@Nonnull File workingDir, @Nullable File inputBaclava,
 			@Nonnull Map<String, File> inputFiles,
 			@Nonnull Map<String, String> inputValues,
@@ -407,11 +411,10 @@
 		}
 
 		// Add an argument holding the workflow
-		workflowFile = createTempFile(".wf_", ".t2flow", workingDir);
-		write(workflowFile, workflow, "UTF-8");
-		if (!workflowFile.exists())
-			throw new IOException("failed to instantiate workflow file at "
-					+ workflowFile);
+		File tmp = createTempFile(".wf_", ".scufl2", workingDir);
+		try (OutputStream os = new FileOutputStream(tmp)) {
+			os.write(workflow);
+		}
 		pb.command().add(workflowFile.getAbsolutePath());
 
 		// Indicate what working directory to use
@@ -430,7 +433,7 @@
 		env.put("PATH", new File(System.getProperty("java.home"), "bin")
 				+ pathSeparator + env.get("PATH"));
 		// Patch the environment to deal with TAVSERV-189
-		env.put("RAVEN_APPHOME", workingDir.getCanonicalPath());
+		env.put("TAVERNA_APPHOME", workingDir.getCanonicalPath());
 		// Patch the environment to deal with TAVSERV-224
 		env.put("TAVERNA_RUN_ID", token);
 		if (interactionHost != null || local.interactionFeedURL != null
diff --git a/server-worker/src/test/java/org/taverna/server/localworker/impl/LocalWorkerTest.java b/server-worker/src/test/java/org/taverna/server/localworker/impl/LocalWorkerTest.java
index ef92cc5..7bcd92e 100644
--- a/server-worker/src/test/java/org/taverna/server/localworker/impl/LocalWorkerTest.java
+++ b/server-worker/src/test/java/org/taverna/server/localworker/impl/LocalWorkerTest.java
@@ -90,7 +90,7 @@
 
 		@Override
 		public boolean initWorker(LocalWorker local,
-				String executeWorkflowCommand, String workflow,
+				String executeWorkflowCommand, byte[] workflow,
 				File workingDir, File inputBaclava,
 				Map<String, File> inputFiles, Map<String, String> inputValues,
 				Map<String, String> delimiters, File outputBaclava, File cmdir,
@@ -98,7 +98,7 @@
 				String id, List<String> conf) throws Exception {
 			events.add("init[");
 			events.add(executeWorkflowCommand);
-			events.add(workflow);
+			events.add(new String(workflow, "UTF-8"));
 			int dirLen = workingDir.getName().length();
 			events.add(Integer.toString(dirLen));
 			events.add(inputBaclava == null ? "<null>" : inputBaclava
@@ -154,7 +154,7 @@
 
 	@Before
 	public void setUp() throws Exception {
-		lw = new LocalWorker("XWC", "WF", null, randomUUID(),
+		lw = new LocalWorker("XWC", "WF".getBytes("UTF-8"), null, randomUUID(),
 				new HashMap<String, String>(), new ArrayList<String>(), factory);
 		events = new ArrayList<>();
 		returnThisStatus = RemoteStatus.Operating;
@@ -548,4 +548,4 @@
 						"{bar=<null>, foo=foofile}",
 						"{bar=barvalue, foo=null}", "boo", "]", "kill"), events);
 	}
-}
\ No newline at end of file
+}