Merge branch '2.5' into taverna-3
diff --git a/pom.xml b/pom.xml
index 1e38f26..fc7b0c9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,7 +97,7 @@
 			</roles>
 		</contributor>
 		<contributor>
-			<name>Stian Soiland</name>
+			<name>Stian Soiland-Reyes</name>
 			<organization>The University of Manchester</organization>
 			<organizationUrl>http://www.manchester.ac.uk/</organizationUrl>
 			<timezone>0</timezone>
@@ -549,11 +549,12 @@
 		<module>server-webapp</module>
 		<module>server-runinterface</module>
 		<module>server-worker</module>
-		<module>server-distribution</module>
 		<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>
 	</modules>
 </project>
diff --git a/server-client/pom.xml b/server-client/pom.xml
new file mode 100644
index 0000000..e62978a
--- /dev/null
+++ b/server-client/pom.xml
@@ -0,0 +1,123 @@
+<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.server</groupId>
+		<artifactId>server</artifactId>
+		<version>3.0-SNAPSHOT</version>
+	</parent>
+	<artifactId>server-client</artifactId>
+	<packaging>bundle</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.jvnet.ws.wadl</groupId>
+			<artifactId>wadl-core</artifactId>
+			<version>1.1.6</version>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.jersey</groupId>
+			<artifactId>jersey-client</artifactId>
+			<version>1.8</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.4</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.tika</groupId>
+			<artifactId>tika-core</artifactId>
+			<version>1.5</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.server</groupId>
+			<artifactId>server-usagerecord</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Export-Package>uk.org.taverna.server.client</Export-Package>
+						<Private-Package>uk.org.taverna.server.client.*</Private-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.7</source>
+					<target>1.7</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.jvnet.ws.wadl</groupId>
+				<artifactId>wadl-client-plugin</artifactId>
+				<version>1.1.6</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>generate</goal>
+						</goals>
+					</execution>
+				</executions>
+				<configuration>
+					<packageName>org.taverna.server.client.wadl</packageName>
+					<includes>*.wadl</includes>
+					<customClassNames>
+						<property>
+							<name>http://example.com/taverna/rest</name>
+							<value>TavernaServer</value>
+						</property>
+					</customClassNames>
+				</configuration>
+			</plugin>
+		</plugins>
+		<pluginManagement>
+			<plugins>
+				<!--This plugin's configuration is used to store Eclipse m2e settings 
+					only. It has no influence on the Maven build itself. -->
+				<plugin>
+					<groupId>org.eclipse.m2e</groupId>
+					<artifactId>lifecycle-mapping</artifactId>
+					<version>1.0.0</version>
+					<configuration>
+						<lifecycleMappingMetadata>
+							<pluginExecutions>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>
+											org.jvnet.ws.wadl
+										</groupId>
+										<artifactId>
+											wadl-client-plugin
+										</artifactId>
+										<versionRange>
+											[1.1.6,)
+										</versionRange>
+										<goals>
+											<goal>generate</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<execute />
+									</action>
+								</pluginExecution>
+							</pluginExecutions>
+						</lifecycleMappingMetadata>
+					</configuration>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+	</build>
+	<inceptionYear>2014</inceptionYear>
+	<name>Server Client OSGi Module</name>
+</project>
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Connected.java b/server-client/src/main/java/uk/org/taverna/server/client/Connected.java
new file mode 100644
index 0000000..263034c
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Connected.java
@@ -0,0 +1,20 @@
+package uk.org.taverna.server.client;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+abstract class Connected {
+	void checkError(ClientResponse response) throws ClientException,
+			ServerException {
+		ClientResponse.Status s = response.getClientResponseStatus();
+		if (s.getStatusCode() == 401)
+			throw new TavernaServer.AuthorizationException("not authorized",
+					null);
+		if (s.getStatusCode() >= 500)
+			throw new TavernaServer.ServerException(s.getReasonPhrase(), null);
+		if (s.getStatusCode() >= 400)
+			throw new TavernaServer.ClientException(s.getReasonPhrase(), null);
+	}
+}
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java b/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java
new file mode 100644
index 0000000..267707d
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java
@@ -0,0 +1,39 @@
+package uk.org.taverna.server.client;
+
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName.Wd.Path2;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public abstract class DirEntry extends Connected {
+	final Path2 handle;
+	final String path;
+	final Run run;
+
+	protected DirEntry(Run run, String path) {
+		this.run = run;
+		this.path = path.replaceFirst("/+$", "");
+		this.handle = run.run.wd().path2(this.path);
+	}
+
+	public void delete() throws ClientException, ServerException {
+		checkError(handle.deleteAsXml(ClientResponse.class));
+	}
+
+	String path(ClientResponse response) throws ClientException, ServerException {
+		checkError(response);
+		String[] bits = response.getLocation().getPath().split("/");
+		return concat(bits[bits.length - 1]);
+	}
+
+	String localName() {
+		String[] bits = path.split("/");
+		return bits[bits.length - 1];
+	}
+
+	String concat(String name) {
+		return path + "/" + name.split("/", 2)[0];
+	}
+}
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Directory.java b/server-client/src/main/java/uk/org/taverna/server/client/Directory.java
new file mode 100644
index 0000000..38dc394
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Directory.java
@@ -0,0 +1,94 @@
+package uk.org.taverna.server.client;
+
+import static java.io.File.createTempFile;
+import static javax.ws.rs.client.Entity.entity;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipFile;
+
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName.Wd;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+import uk.org.taverna.server.client.generic.DirectoryEntry;
+import uk.org.taverna.server.client.generic.DirectoryReference;
+import uk.org.taverna.server.client.generic.FileReference;
+import uk.org.taverna.server.client.rest.DirectoryContents;
+import uk.org.taverna.server.client.rest.MakeDirectory;
+import uk.org.taverna.server.client.rest.UploadFile;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public class Directory extends DirEntry {
+	private final Wd wd;
+
+	Directory(Run run) {
+		super(run, "");
+		this.wd = run.run.wd();
+	}
+
+	Directory(Run run, String path) {
+		super(run, path);
+		this.wd = run.run.wd();
+	}
+
+	public List<DirEntry> list() {
+		List<DirEntry> result = new ArrayList<>();
+		for (DirectoryEntry de : wd.path3(path)
+				.getAsXml(DirectoryContents.class).getDirOrFile())
+			if (de instanceof DirectoryReference)
+				result.add(new Directory(run, de.getValue()));
+			else if (de instanceof FileReference)
+				result.add(new File(run, de.getValue()));
+		return result;
+	}
+
+	public File createFile(String name, byte[] content) throws ClientException,
+			ServerException {
+		UploadFile uf = new UploadFile();
+		uf.setName(name);
+		uf.setValue(content);
+		return new File(run, path(wd.path(path).putAsXml(uf,
+				ClientResponse.class)));
+	}
+
+	public File createFile(String name, java.io.File content)
+			throws ClientException, ServerException {
+		return new File(run, path(wd.path(concat(name)).putOctetStreamAsXml(
+				entity(content, APPLICATION_OCTET_STREAM_TYPE),
+				ClientResponse.class)));
+	}
+
+	public File createFile(String name, URI source) throws ClientException,
+			ServerException {
+		return new File(run, path(wd.path(concat(name)).postTextUriListAsXml(
+				source.toString(), ClientResponse.class)));
+	}
+
+	public Directory createDirectory(String name) throws ClientException,
+			ServerException {
+		MakeDirectory mkdir = new MakeDirectory();
+		mkdir.setName(name);
+		return new Directory(run, path(wd.path(path).putAsXml(mkdir,
+				ClientResponse.class)));
+	}
+
+	public byte[] getZippedContents() {
+		return wd.path3(path).getAsZip(byte[].class);
+	}
+
+	public ZipFile getZip() throws IOException {
+		byte[] contents = getZippedContents();
+		java.io.File tmp = createTempFile(localName(), ".zip");
+		try (OutputStream os = new FileOutputStream(tmp)) {
+			os.write(contents);
+		}
+		return new ZipFile(tmp);
+	}
+}
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/File.java b/server-client/src/main/java/uk/org/taverna/server/client/File.java
new file mode 100644
index 0000000..0287afb
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/File.java
@@ -0,0 +1,95 @@
+package uk.org.taverna.server.client;
+
+import static java.io.File.createTempFile;
+import static javax.ws.rs.client.Entity.entity;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
+import static org.apache.commons.io.IOUtils.copy;
+import static org.apache.tika.mime.MimeTypes.getDefaultMimeTypes;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.apache.tika.mime.MimeTypeException;
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName.Wd;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+public class File extends DirEntry {
+	private final Wd wd;
+
+	File(Run run, String path) {
+		super(run, path);
+		wd = run.run.wd();
+	}
+
+	public InputStream getAsStream() {
+		return wd.path3(path).getAsOctetStream(InputStream.class);
+	}
+
+	public byte[] get() {
+		return wd.path3(path).getAsOctetStream(byte[].class);
+	}
+
+	public String get(Charset encoding) {
+		return new String(wd.path3(path).getAsOctetStream(byte[].class),
+				encoding);
+	}
+
+	public java.io.File getAsFile() throws ClientHandlerException,
+			UniformInterfaceException, IOException, MimeTypeException,
+			ClientException, ServerException {
+		ClientResponse cr = wd.path3(path).getAsOctetStream(
+				ClientResponse.class);
+		checkError(cr);
+		String[] bits = localName().split("[.]");
+		String ext = getDefaultMimeTypes().forName(
+				cr.getHeaders().getFirst("Content-Type")).getExtension();
+		if (ext == null)
+			ext = bits[bits.length - 1];
+		java.io.File tmp = createTempFile(bits[0], ext);
+		try (OutputStream os = new FileOutputStream(tmp);
+				InputStream is = cr.getEntity(InputStream.class)) {
+			copy(is, os);
+		}
+		return tmp;
+	}
+
+	public void setContents(byte[] newContents) throws ClientException,
+			ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(newContents,
+				ClientResponse.class));
+	}
+
+	public void setContents(String newContents) throws ClientException,
+			ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(newContents,
+				ClientResponse.class));
+	}
+
+	public void setContents(String newContents, Charset encoding)
+			throws ClientException, ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(
+				newContents.getBytes(encoding), ClientResponse.class));
+	}
+
+	public void setContents(InputStream newContents) throws ClientException,
+			ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(newContents,
+				ClientResponse.class));
+	}
+
+	public void setContents(java.io.File newContents) throws IOException,
+			ClientException, ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(
+				entity(newContents, APPLICATION_OCTET_STREAM_TYPE),
+				ClientResponse.class));
+	}
+}
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Property.java b/server-client/src/main/java/uk/org/taverna/server/client/Property.java
new file mode 100644
index 0000000..0e6542f
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Property.java
@@ -0,0 +1,18 @@
+package uk.org.taverna.server.client;
+
+public enum Property {
+	STDOUT("stdout"), STDERR("stderr"), EXIT_CODE("exitcode"), READY_TO_NOTIFY(
+			"readyToNotify"), EMAIL("notificationAddress"), USAGE(
+			"usageRecord");
+
+	private String s;
+
+	private Property(String s) {
+		this.s = s;
+	}
+
+	@Override
+	public String toString() {
+		return s;
+	}
+}
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Run.java b/server-client/src/main/java/uk/org/taverna/server/client/Run.java
new file mode 100644
index 0000000..5c6875e
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Run.java
@@ -0,0 +1,215 @@
+package uk.org.taverna.server.client;
+
+import static org.joda.time.format.ISODateTimeFormat.dateTime;
+import static org.joda.time.format.ISODateTimeFormat.dateTimeParser;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+
+import javax.xml.bind.JAXBException;
+
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.ogf.usage.JobUsageRecord;
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName;
+import org.w3c.dom.Element;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+import uk.org.taverna.server.client.generic.KeyPairCredential;
+import uk.org.taverna.server.client.generic.PasswordCredential;
+import uk.org.taverna.server.client.generic.port.InputPort;
+import uk.org.taverna.server.client.generic.port.OutputPort;
+import uk.org.taverna.server.client.rest.InputDescription;
+import uk.org.taverna.server.client.rest.InputDescription.Value;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public class Run extends Connected {
+	RunsRunName run;
+
+	Run(TavernaServer server, String value) {
+		run = server.root.runsRunName(value);
+	}
+
+	public String getName() {
+		return run.name().getAsTextPlain(ClientResponse.class)
+				.getEntity(String.class);
+	}
+
+	public void setName(String name) {
+		run.name().putTextPlain(name, String.class);
+	}
+
+	public Date getExpiry() {
+		return dateTimeParser().parseDateTime(
+				run.expiry().getAsTextPlain(String.class)).toDate();
+	}
+
+	public void setExpiry(Date expiryTimestamp) {
+		run.expiry().putTextPlain(
+				dateTime().print(new DateTime(expiryTimestamp)), String.class);
+	}
+
+	public Date getCreate() {
+		String timestamp = run.createTime().getAsTextPlain(String.class);
+		if (timestamp == null || timestamp.trim().isEmpty())
+			return null;
+		return dateTimeParser().parseDateTime(timestamp).toDate();
+	}
+
+	public Date getStart() {
+		String timestamp = run.startTime().getAsTextPlain(String.class);
+		if (timestamp == null || timestamp.trim().isEmpty())
+			return null;
+		return dateTimeParser().parseDateTime(timestamp).toDate();
+	}
+
+	public Date getFinish() {
+		String timestamp = run.finishTime().getAsTextPlain(String.class);
+		if (timestamp == null || timestamp.trim().isEmpty())
+			return null;
+		return dateTimeParser().parseDateTime(timestamp).toDate();
+	}
+
+	public Status getStatus() {
+		return Status.valueOf(run.status().getAsTextPlain(String.class));
+	}
+
+	public void setStatus(Status status) {
+		run.status().putTextPlain(status, String.class);
+	}
+
+	public void start() {
+		setStatus(Status.Operating);
+	}
+
+	public void kill() {
+		setStatus(Status.Finished);
+	}
+
+	public boolean isRunning() {
+		return getStatus() == Status.Operating;
+	}
+
+	public String getStandardOutput() {
+		return run.stdout().getAsTextPlain(String.class);
+	}
+
+	public String getStandardError() {
+		return run.stderr().getAsTextPlain(String.class);
+	}
+
+	public String getLog() {
+		return run.log().getAsTextPlain(String.class);
+	}
+
+	public Integer getExitCode() {
+		String code = run.listeners().name("io")
+				.propertiesPropertyName("exitCode")
+				.getAsTextPlain(String.class);
+		if (code == null || code.trim().isEmpty())
+			return null;
+		return Integer.parseInt(code);
+	}
+
+	public String getProperty(Property prop) {
+		return run.listeners().name("io")
+				.propertiesPropertyName(prop.toString())
+				.getAsTextPlain(String.class);
+	}
+
+	public void setGenerateRunBundle(boolean generateRunBundle) {
+		run.generateProvenance().putTextPlain(generateRunBundle, String.class);
+	}
+
+	public byte[] getRunBundle() {
+		return run.runBundle().getAsVndWf4everRobundleZip(byte[].class);
+	}
+
+	public List<InputPort> getInputs() {
+		return run.input().expected().getAsInputDescriptionXml().getInput();
+	}
+
+	public List<OutputPort> getOutputs() {
+		return run.output().getAsOutputDescriptionXml().getOutput();
+	}
+
+	public void setInput(String name, String value) {
+		Value v = new Value();
+		v.setValue(value);
+		InputDescription idesc = new InputDescription();
+		idesc.setValue(v);
+		run.input().inputName(name).putXmlAsInputDescription(idesc);
+	}
+
+	public void setInput(String name, String value, char listSeparator) {
+		Value v = new Value();
+		v.setValue(value);
+		InputDescription idesc = new InputDescription();
+		idesc.setValue(v);
+		idesc.setListDelimiter(new String(new char[] { listSeparator }));
+		run.input().inputName(name).putXmlAsInputDescription(idesc);
+	}
+
+	public byte[] getWorkflow() {
+		return run.workflow().getAsVndTavernaT2flowXml(byte[].class);
+	}
+
+	// TODO Consider better ways to do this
+	public Element getInteractionFeed() {
+		return run.interaction().getAsAtomXml(Element.class);
+	}
+
+	public Element getInteractionEntry(String id) {
+		return run.interaction().id(id).getAsAtomXml(Element.class);
+	}
+
+	public JobUsageRecord getUsageRecord() throws JAXBException {
+		return JobUsageRecord.unmarshal(run.usage().getAsXml(Element.class));
+	}
+
+	public Directory getWorkingDirectory() {
+		return new Directory(this);
+	}
+
+	public String getOwner() {
+		return run.security().owner().getAsTextPlain(String.class);
+	}
+
+	// TODO permissions
+
+	public void grantPasswordCredential(URI contextService, String username,
+			String password) throws ClientException, ServerException {
+		PasswordCredential pc = new PasswordCredential();
+		pc.setServiceURI(contextService.toString());
+		pc.setUsername(username);
+		pc.setPassword(password);
+		checkError(run.security().credentials()
+				.postXmlAsOctetStream(pc, ClientResponse.class));
+	}
+
+	public void grantKeyCredential(URI contextService, java.io.File source,
+			String unlockPassword, String aliasEntry) throws IOException,
+			ClientException, ServerException {
+		KeyPairCredential kpc = new KeyPairCredential();
+		kpc.setServiceURI(contextService.toString());
+		try (InputStream in = new FileInputStream(source)) {
+			byte[] buffer = new byte[(int) source.length()];
+			IOUtils.read(in, buffer);
+			kpc.setCredentialBytes(buffer);
+		}
+		if (source.getName().endsWith(".p12"))
+			kpc.setFileType("PKCS12");
+		else
+			kpc.setFileType("JKS");
+		kpc.setCredentialName(aliasEntry);
+		kpc.setUnlockPassword(unlockPassword);
+		checkError(run.security().credentials()
+				.postXmlAsOctetStream(kpc, ClientResponse.class));
+	}
+}
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Status.java b/server-client/src/main/java/uk/org/taverna/server/client/Status.java
new file mode 100644
index 0000000..9c375ad
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Status.java
@@ -0,0 +1,36 @@
+package uk.org.taverna.server.client;
+
+/**
+ * States of a workflow run. They are {@link #Initialized Initialized},
+ * {@link #Operating Operating}, {@link #Stopped Stopped}, and
+ * {@link #Finished Finished}. Conceptually, there is also a
+ * <tt>Destroyed</tt> state, but the workflow run does not exist (and hence
+ * can't have its state queried or set) in that case.
+ * 
+ * @author Donal Fellows
+ */
+public enum Status {
+	/**
+	 * The workflow run has been created, but is not yet running. The run
+	 * will need to be manually moved to {@link #Operating Operating} when
+	 * ready.
+	 */
+	Initialized,
+	/**
+	 * The workflow run is going, reading input, generating output, etc.
+	 * Will eventually either move automatically to {@link #Finished
+	 * Finished} or can be moved manually to {@link #Stopped Stopped} (where
+	 * supported).
+	 */
+	Operating,
+	/**
+	 * The workflow run is paused, and will need to be moved back to
+	 * {@link #Operating Operating} manually.
+	 */
+	Stopped,
+	/**
+	 * The workflow run has ceased; data files will continue to exist until
+	 * the run is destroyed (which may be manual or automatic).
+	 */
+	Finished
+}
\ No newline at end of file
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java
new file mode 100644
index 0000000..7c0dcdd
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java
@@ -0,0 +1,128 @@
+package uk.org.taverna.server.client;
+
+import static java.nio.file.Files.readAllBytes;
+import static org.taverna.server.client.wadl.TavernaServer.createClient;
+import static org.taverna.server.client.wadl.TavernaServer.root;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.taverna.server.client.wadl.TavernaServer.Root;
+
+import uk.org.taverna.server.client.generic.Capability;
+import uk.org.taverna.server.client.generic.TavernaRun;
+import uk.org.taverna.server.client.generic.VersionedElement;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
+
+public class TavernaServer extends Connected {
+	final Root root;
+	private final URI location;
+	private final boolean authenticated;
+
+	TavernaServer(URI serviceRoot) {
+		root = root(createClient(), location = serviceRoot);
+		authenticated = false;
+	}
+
+	TavernaServer(URI serviceRoot, String username, String password) {
+		Client client = createClient();
+		client.addFilter(new HTTPBasicAuthFilter(username, password));
+		authenticated = true;
+		root = root(client, location = serviceRoot);
+	}
+
+	TavernaServer(TavernaServer service, String username, String password) {
+		Client client = createClient();
+		client.addFilter(new HTTPBasicAuthFilter(username, password));
+		authenticated = true;
+		root = root(client, location = service.location);
+		getServerVersionInfo();
+	}
+
+	public TavernaServer upgradeToAuth(String username, String password) {
+		if (authenticated)
+			throw new IllegalStateException("may only upgrade an unauthenticated connection");
+		return new TavernaServer(this, username, password);
+	}
+
+	public List<Capability> getCapabilities() {
+		return root.policy().capabilities().getAsCapabilitiesXml()
+				.getCapability();
+	}
+
+	public int getRunLimit() {
+		return root.policy().runLimit().getAsTextPlain(Integer.class);
+	}
+
+	public int getOperatingLimit() {
+		return root.policy().operatingLimit().getAsTextPlain(Integer.class);
+	}
+
+	public List<String> getPermittedWorkflows() {
+		return root.policy().permittedWorkflows().getAsPermittedWorkflowsXml()
+				.getWorkflow();
+	}
+
+	public List<Run> getExistingRuns() {
+		List<Run> runs = new ArrayList<>();
+		for (TavernaRun run : root.runs().getAsRunListXml().getRun())
+			runs.add(new Run(this, run.getValue()));
+		return runs;
+	}
+
+	public VersionedElement getServerVersionInfo() {
+		return root.getAsServerDescriptionXml();
+	}
+
+	private Run response2run(ClientResponse response) throws ClientException, ServerException {
+		checkError(response);
+		if (response.getClientResponseStatus().getStatusCode() == 201) {
+			String[] path = response.getLocation().getPath().split("/");
+			return new Run(this, path[path.length - 1]);
+		}
+		return null;
+	}
+
+	public Run createWorkflowRun(byte[] t2flowBytes) throws ClientException, ServerException {
+		return response2run(root.runs().postVndTavernaT2flowXmlAsOctetStream(
+				t2flowBytes, ClientResponse.class));
+	}
+
+	public Run createWorkflowRun(File t2flowFile) throws IOException, ClientException, ServerException {
+		return createWorkflowRun(readAllBytes(t2flowFile.toPath()));
+	}
+
+	public Run createWorkflowRun(URI t2flowUri) throws ClientException, ServerException {
+		return response2run(root.runs().postTextUriListAsOctetStream(
+				t2flowUri.toString(), ClientResponse.class));
+	}
+
+
+	public static class ClientException extends Exception {
+		private static final long serialVersionUID = 1L;
+
+		ClientException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
+	public static class AuthorizationException extends ClientException {
+		private static final long serialVersionUID = 1L;
+
+		AuthorizationException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
+	static class ServerException extends Exception {
+		private static final long serialVersionUID = 1L;
+
+		ServerException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
+}
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java
new file mode 100644
index 0000000..b00b075
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java
@@ -0,0 +1,23 @@
+package uk.org.taverna.server.client;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TavernaServerConnectionFactory {
+	private Map<URI, TavernaServer> cache = new HashMap<>();
+
+	public synchronized TavernaServer connectNoAuth(URI uri) {
+		TavernaServer conn = cache.get(uri);
+		if (conn == null)
+			cache.put(uri, conn = new TavernaServer(uri));
+		return conn;
+	}
+
+	public TavernaServer connectAuth(URI uri, String username, String password) {
+		TavernaServer conn = new TavernaServer(uri, username, password);
+		// Force a check of the credentials by getting the server version
+		conn.getServerVersionInfo();
+		return conn;
+	}
+}
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/package-info.java b/server-client/src/main/java/uk/org/taverna/server/client/package-info.java
new file mode 100644
index 0000000..59e809d
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Implementation of a basic client for Taverna Server.
+ * @author Donal Fellows
+ */
+package uk.org.taverna.server.client;
\ No newline at end of file
diff --git a/server-client/src/main/wadl/tavserv.wadl b/server-client/src/main/wadl/tavserv.wadl
new file mode 100644
index 0000000..2ba8fbe
--- /dev/null
+++ b/server-client/src/main/wadl/tavserv.wadl
@@ -0,0 +1,591 @@
+<application xmlns="http://wadl.dev.java.net/2009/02"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:prefix1="http://ns.taverna.org.uk/2010/xml/server/rest/"
+	xmlns:prefix3="http://ns.taverna.org.uk/2010/xml/server/"
+	xmlns:prefix10="http://ns.taverna.org.uk/2010/port/" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" jxb:version="2.1">
+<grammars>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:admin="http://ns.taverna.org.uk/2010/xml/server/admin/" xmlns:feed="http://ns.taverna.org.uk/2010/xml/server/feed/" xmlns:port="http://ns.taverna.org.uk/2010/port/" xmlns:tns="http://ns.taverna.org.uk/2010/xml/server/rest/" xmlns:ts="http://ns.taverna.org.uk/2010/xml/server/" xmlns:ts-rest="http://ns.taverna.org.uk/2010/xml/server/rest/" xmlns:ts-soap="http://ns.taverna.org.uk/2010/xml/server/soap/" xmlns:xlink="http://www.w3.org/1999/xlink" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://ns.taverna.org.uk/2010/xml/server/rest/" jxb:version="2.1">
+    <xs:annotation>
+    	<xs:appinfo>
+			<jxb:schemaBindings>
+				<jxb:package name="uk.org.taverna.server.client.rest" />
+			</jxb:schemaBindings>
+    	</xs:appinfo>
+    </xs:annotation>
+    <xs:import namespace="http://ns.taverna.org.uk/2010/xml/server/"/>
+    <xs:import namespace="http://www.w3.org/1999/xlink"/>
+    <xs:element name="capabilities">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="unbounded" minOccurs="0" name="capability" type="ts:Capability"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="credential" type="ts-rest:Credential"/>
+    <xs:element name="credentials" type="ts-rest:credentialList"/>
+    <xs:element name="directoryContents" type="ts-rest:DirectoryContents"/>
+    <xs:element name="enabledNotificationFabrics">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="unbounded" minOccurs="0" name="notifier" type="xs:string"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="filesystemOperation" type="ts-rest:FilesystemCreationOperation"/>
+    <xs:element name="listenerDefinition" type="ts-rest:ListenerDefinition"/>
+    <xs:element name="listenerDescription" type="ts-rest:ListenerDescription"/>
+    <xs:element name="listeners">
+        <xs:complexType>
+            <xs:complexContent>
+                <xs:extension base="ts:VersionedElement">
+                    <xs:sequence>
+                        <xs:element maxOccurs="unbounded" minOccurs="0" name="listener" type="ts-rest:ListenerDescription"/>
+                    </xs:sequence>
+                </xs:extension>
+            </xs:complexContent>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="mkdir" type="ts-rest:MakeDirectory"/>
+    <xs:element name="permissionUpdate" type="ts-rest:permissionDescription"/>
+    <xs:element name="permissionsDescriptor" type="ts-rest:permissionsDescription"/>
+    <xs:element name="permittedListeners">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="unbounded" minOccurs="0" name="type" type="xs:string"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="permittedWorkflows">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="unbounded" minOccurs="0" name="workflow" type="xs:string"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="policyDescription">
+        <xs:complexType>
+            <xs:complexContent>
+                <xs:extension base="ts:VersionedElement">
+                    <xs:sequence>
+                        <xs:element minOccurs="0" name="runLimit" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="operatingLimit" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="permittedWorkflows" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="permittedListenerTypes" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="enabledNotificationFabrics" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="capabilities" type="ts:Location"/>
+                    </xs:sequence>
+                </xs:extension>
+            </xs:complexContent>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="properties">
+        <xs:complexType>
+            <xs:complexContent>
+                <xs:extension base="ts:VersionedElement">
+                    <xs:sequence>
+                        <xs:element maxOccurs="unbounded" minOccurs="0" name="property" type="ts-rest:PropertyDescription"/>
+                    </xs:sequence>
+                </xs:extension>
+            </xs:complexContent>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="runDescription">
+        <xs:complexType>
+            <xs:complexContent>
+                <xs:extension base="ts:VersionedElement">
+                    <xs:sequence>
+                        <xs:element minOccurs="0" name="expiry">
+                            <xs:complexType>
+                                <xs:simpleContent>
+                                    <xs:extension base="xs:string">
+                                        <xs:attribute ref="xlink:href"/>
+                                    </xs:extension>
+                                </xs:simpleContent>
+                            </xs:complexType>
+                        </xs:element>
+                        <xs:element minOccurs="0" name="creationWorkflow" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="createTime" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="startTime" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="finishTime" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="status" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="workingDirectory" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="inputs" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="output" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="securityContext" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="listeners">
+                            <xs:complexType>
+                                <xs:complexContent>
+                                    <xs:extension base="ts:Location">
+                                        <xs:sequence>
+                                            <xs:element maxOccurs="unbounded" minOccurs="0" name="listener" nillable="true" type="ts:Location"/>
+                                        </xs:sequence>
+                                    </xs:extension>
+                                </xs:complexContent>
+                            </xs:complexType>
+                        </xs:element>
+                        <xs:element minOccurs="0" name="interaction" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="name" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="stdout" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="stderr" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="usage" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="log" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="run-bundle" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="generate-provenance" type="ts:Location"/>
+                    </xs:sequence>
+                    <xs:attribute ref="ts-rest:owner"/>
+                </xs:extension>
+            </xs:complexContent>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="runInput" type="ts-rest:InputDescription"/>
+    <xs:element name="runInputs" type="ts-rest:TavernaRunInputs"/>
+    <xs:element name="runList">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="unbounded" minOccurs="0" name="run" type="ts:TavernaRun"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="securityDescriptor" type="ts-rest:SecurityDescriptor"/>
+    <xs:element name="serverDescription">
+        <xs:complexType>
+            <xs:complexContent>
+                <xs:extension base="ts:VersionedElement">
+                    <xs:sequence>
+                        <xs:element minOccurs="0" name="runs" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="policy" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="feed" type="ts:Location"/>
+                        <xs:element minOccurs="0" name="interactionFeed" type="ts:Location"/>
+                    </xs:sequence>
+                </xs:extension>
+            </xs:complexContent>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="trustedIdentities" type="ts-rest:trustList"/>
+    <xs:element name="upload" type="ts-rest:UploadFile"/>
+    <xs:element name="userPermission" type="ts-rest:linkedPermissionDescription"/>
+    <xs:complexType name="InputDescription">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:choice minOccurs="0">
+                        <xs:element name="file">
+                            <xs:complexType>
+                                <xs:simpleContent>
+                                    <xs:extension base="ts-rest:InputContents"/>
+                                </xs:simpleContent>
+                            </xs:complexType>
+                        </xs:element>
+                        <xs:element name="reference">
+                            <xs:complexType>
+                                <xs:simpleContent>
+                                    <xs:extension base="ts-rest:InputContents"/>
+                                </xs:simpleContent>
+                            </xs:complexType>
+                        </xs:element>
+                        <xs:element name="value">
+                            <xs:complexType>
+                                <xs:simpleContent>
+                                    <xs:extension base="ts-rest:InputContents"/>
+                                </xs:simpleContent>
+                            </xs:complexType>
+                        </xs:element>
+                    </xs:choice>
+                </xs:sequence>
+                <xs:attribute ref="ts-rest:name"/>
+                <xs:attribute ref="ts-rest:descriptorRef"/>
+                <xs:attribute ref="ts-rest:listDelimiter"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:simpleType name="InputContents">
+        <xs:restriction base="xs:string"/>
+    </xs:simpleType>
+    <xs:complexType name="ListenerDescription">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:element minOccurs="0" name="configuration" type="ts:Location"/>
+                    <xs:element minOccurs="0" name="properties">
+                        <xs:complexType>
+                            <xs:sequence>
+                                <xs:element maxOccurs="unbounded" minOccurs="0" name="property" type="ts-rest:PropertyDescription"/>
+                            </xs:sequence>
+                        </xs:complexType>
+                    </xs:element>
+                </xs:sequence>
+                <xs:attribute ref="xlink:href"/>
+                <xs:attribute ref="ts-rest:name"/>
+                <xs:attribute ref="ts-rest:type"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="PropertyDescription">
+        <xs:complexContent>
+            <xs:extension base="ts:Location">
+                <xs:sequence/>
+                <xs:attribute ref="ts-rest:name"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="TavernaRunInputs">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:element minOccurs="0" name="expected" type="ts:Location"/>
+                    <xs:element minOccurs="0" name="baclava" type="ts:Location"/>
+                    <xs:element maxOccurs="unbounded" minOccurs="0" name="input" nillable="true" type="ts:Location"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="Credential">
+        <xs:choice>
+            <xs:element ref="ts:keypair"/>
+            <xs:element ref="ts:userpass"/>
+        </xs:choice>
+    </xs:complexType>
+    <xs:complexType name="DirectoryContents">
+        <xs:sequence>
+            <xs:choice maxOccurs="unbounded" minOccurs="0">
+                <xs:element ref="ts:dir"/>
+                <xs:element ref="ts:file"/>
+            </xs:choice>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType name="FilesystemCreationOperation">
+        <xs:simpleContent>
+            <xs:extension base="xs:base64Binary">
+                <xs:attribute ref="ts-rest:name"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="MakeDirectory">
+        <xs:simpleContent>
+            <xs:extension base="ts-rest:FilesystemCreationOperation"/>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="UploadFile">
+        <xs:simpleContent>
+            <xs:extension base="ts-rest:FilesystemCreationOperation"/>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="permissionsDescription">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:element maxOccurs="unbounded" minOccurs="0" name="permission" type="ts-rest:linkedPermissionDescription"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="linkedPermissionDescription">
+        <xs:complexContent>
+            <xs:extension base="ts:Location">
+                <xs:sequence>
+                    <xs:element minOccurs="0" name="userName" type="xs:string"/>
+                    <xs:element minOccurs="0" name="permission" type="ts:Permission"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="trustList">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:element maxOccurs="unbounded" minOccurs="0" name="trust" type="ts:TrustDescriptor"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="permissionDescription">
+        <xs:sequence>
+            <xs:element minOccurs="0" name="userName" type="xs:string"/>
+            <xs:element minOccurs="0" name="permission" type="ts:Permission"/>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="SecurityDescriptor">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:element minOccurs="0" name="owner" type="xs:string"/>
+                    <xs:element minOccurs="0" name="permissions" type="ts:Location"/>
+                    <xs:element minOccurs="0" name="credentials" type="ts-rest:CredentialCollection"/>
+                    <xs:element minOccurs="0" name="trusts" type="ts-rest:TrustCollection"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="CredentialCollection">
+        <xs:sequence>
+            <xs:element maxOccurs="unbounded" minOccurs="0" ref="ts-rest:credential"/>
+        </xs:sequence>
+        <xs:attribute ref="xlink:href"/>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="TrustCollection">
+        <xs:sequence>
+            <xs:element maxOccurs="unbounded" minOccurs="0" name="trust" type="ts:TrustDescriptor"/>
+        </xs:sequence>
+        <xs:attribute ref="xlink:href"/>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="credentialList">
+        <xs:complexContent>
+            <xs:extension base="ts:VersionedElement">
+                <xs:sequence>
+                    <xs:element maxOccurs="unbounded" minOccurs="0" ref="ts-rest:credential"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="ListenerDefinition">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute ref="ts-rest:type"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:attribute name="descriptorRef" type="xs:anyURI"/>
+    <xs:attribute name="listDelimiter" type="xs:string"/>
+    <xs:attribute name="name" type="xs:string"/>
+    <xs:attribute name="owner" type="xs:string"/>
+    <xs:attribute name="type" type="xs:string"/>
+</xs:schema>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/1999/xlink" attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://www.w3.org/1999/xlink" jxb:version="2.1">
+    <xs:attribute name="href" type="xs:string"/>
+</xs:schema>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:port="http://ns.taverna.org.uk/2010/port/" xmlns:run="http://ns.taverna.org.uk/2010/run/" xmlns:tns="http://ns.taverna.org.uk/2010/port/" xmlns:xlink="http://www.w3.org/1999/xlink" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://ns.taverna.org.uk/2010/port/" jxb:version="2.1">
+    <xs:annotation>
+    	<xs:appinfo>
+			<jxb:schemaBindings>
+				<jxb:package name="uk.org.taverna.server.client.generic.port" />
+			</jxb:schemaBindings>
+    	</xs:appinfo>
+    </xs:annotation>
+    <xs:import namespace="http://www.w3.org/1999/xlink"/>
+    <xs:element name="inputDescription" type="port:inputDescription"/>
+    <xs:element name="workflowOutputs" type="port:outputDescription"/>
+    <xs:complexType name="outputDescription">
+        <xs:complexContent>
+            <xs:extension base="port:PortDescription">
+                <xs:sequence>
+                    <xs:element maxOccurs="unbounded" minOccurs="0" name="output" type="port:OutputPort"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType abstract="true" name="PortDescription">
+        <xs:sequence/>
+        <xs:attribute ref="port:workflowId"/>
+        <xs:attribute ref="port:workflowRun"/>
+        <xs:attribute ref="port:workflowRunId"/>
+    </xs:complexType>
+    <xs:complexType name="OutputPort">
+        <xs:complexContent>
+            <xs:extension base="port:Port">
+                <xs:choice>
+                    <xs:element name="value" type="port:LeafValue"/>
+                    <xs:element name="list" type="port:ListValue"/>
+                    <xs:element name="error" type="port:ErrorValue"/>
+                    <xs:element name="absent" type="port:AbsentValue"/>
+                </xs:choice>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="Port">
+        <xs:sequence/>
+        <xs:attribute ref="port:name" use="required"/>
+        <xs:attribute ref="port:depth"/>
+    </xs:complexType>
+    <xs:complexType name="LeafValue">
+        <xs:complexContent>
+            <xs:extension base="port:Value">
+                <xs:sequence/>
+                <xs:attribute ref="port:contentFile"/>
+                <xs:attribute ref="port:contentType"/>
+                <xs:attribute ref="port:contentByteLength"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType abstract="true" name="Value">
+        <xs:sequence/>
+        <xs:attribute ref="xlink:href"/>
+    </xs:complexType>
+    <xs:complexType name="ErrorValue">
+        <xs:complexContent>
+            <xs:extension base="port:Value">
+                <xs:sequence/>
+                <xs:attribute ref="port:depth"/>
+                <xs:attribute ref="port:errorFile"/>
+                <xs:attribute ref="port:errorByteLength"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="ListValue">
+        <xs:complexContent>
+            <xs:extension base="port:Value">
+                <xs:sequence>
+                    <xs:choice maxOccurs="unbounded" minOccurs="0">
+                        <xs:element name="value" type="port:LeafValue"/>
+                        <xs:element name="list" type="port:ListValue"/>
+                        <xs:element name="error" type="port:ErrorValue"/>
+                        <xs:element name="absent" type="port:AbsentValue"/>
+                    </xs:choice>
+                </xs:sequence>
+                <xs:attribute ref="port:length"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="AbsentValue">
+        <xs:complexContent>
+            <xs:extension base="port:Value">
+                <xs:sequence/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="inputDescription">
+        <xs:complexContent>
+            <xs:extension base="port:PortDescription">
+                <xs:sequence>
+                    <xs:element maxOccurs="unbounded" minOccurs="0" name="input" type="port:InputPort"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="InputPort">
+        <xs:complexContent>
+            <xs:extension base="port:Port">
+                <xs:sequence/>
+                <xs:attribute ref="xlink:href"/>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:attribute name="contentByteLength" type="xs:long"/>
+    <xs:attribute name="contentFile" type="xs:string"/>
+    <xs:attribute name="contentType" type="xs:string"/>
+    <xs:attribute name="depth" type="xs:int"/>
+    <xs:attribute name="errorByteLength" type="xs:long"/>
+    <xs:attribute name="errorFile" type="xs:string"/>
+    <xs:attribute name="length" type="xs:int"/>
+    <xs:attribute name="name" type="xs:ID"/>
+    <xs:attribute name="workflowId" type="xs:string"/>
+    <xs:attribute name="workflowRun" type="xs:anyURI"/>
+    <xs:attribute name="workflowRunId" type="xs:string"/>
+</xs:schema>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:admin="http://ns.taverna.org.uk/2010/xml/server/admin/" xmlns:feed="http://ns.taverna.org.uk/2010/xml/server/feed/" xmlns:tns="http://ns.taverna.org.uk/2010/xml/server/" xmlns:ts="http://ns.taverna.org.uk/2010/xml/server/" xmlns:ts-rest="http://ns.taverna.org.uk/2010/xml/server/rest/" xmlns:ts-soap="http://ns.taverna.org.uk/2010/xml/server/soap/" xmlns:xlink="http://www.w3.org/1999/xlink" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://ns.taverna.org.uk/2010/xml/server/" jxb:version="2.1">
+    <xs:annotation>
+    	<xs:appinfo>
+			<jxb:schemaBindings>
+				<jxb:package name="uk.org.taverna.server.client.generic" />
+			</jxb:schemaBindings>
+    	</xs:appinfo>
+    </xs:annotation>
+    <xs:import namespace="http://www.w3.org/1999/xlink"/>
+    <xs:element name="capability" type="ts:Capability"/>
+    <xs:element name="dir" type="ts:DirectoryReference"/>
+    <xs:element name="file" type="ts:FileReference"/>
+    <xs:element name="keypair" type="ts:KeyPairCredential"/>
+    <xs:element name="runReference" type="ts:TavernaRun"/>
+    <xs:element name="trustedIdentity" type="ts:TrustDescriptor"/>
+    <xs:element name="userpass" type="ts:PasswordCredential"/>
+    <xs:element name="workflow" type="ts:Workflow"/>
+    <xs:complexType abstract="true" name="VersionedElement">
+        <xs:sequence/>
+        <xs:attribute ref="ts:serverVersion"/>
+        <xs:attribute ref="ts:serverRevision"/>
+        <xs:attribute ref="ts:serverBuildTimestamp"/>
+    </xs:complexType>
+    <xs:complexType name="Location">
+        <xs:sequence/>
+        <xs:attribute ref="xlink:href"/>
+    </xs:complexType>
+    <xs:complexType name="Capability">
+        <xs:sequence/>
+        <xs:attribute ref="ts:capability"/>
+        <xs:attribute ref="ts:version"/>
+    </xs:complexType>
+    <xs:complexType name="KeyPairCredential">
+        <xs:complexContent>
+            <xs:extension base="ts:CredentialDescriptor">
+                <xs:sequence>
+                    <xs:element name="credentialName" type="xs:string"/>
+                    <xs:element minOccurs="0" name="credentialFile" type="xs:string"/>
+                    <xs:element minOccurs="0" name="fileType" type="xs:string"/>
+                    <xs:element minOccurs="0" name="unlockPassword" type="xs:string"/>
+                    <xs:element minOccurs="0" name="credentialBytes" type="xs:base64Binary"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType abstract="true" name="CredentialDescriptor">
+        <xs:sequence>
+            <xs:element minOccurs="0" name="serviceURI" type="xs:anyURI"/>
+        </xs:sequence>
+        <xs:attribute ref="xlink:href"/>
+    </xs:complexType>
+    <xs:complexType name="PasswordCredential">
+        <xs:complexContent>
+            <xs:extension base="ts:CredentialDescriptor">
+                <xs:sequence>
+                    <xs:element name="username" type="xs:string"/>
+                    <xs:element name="password" type="xs:string"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+    <xs:complexType name="DirectoryEntry">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute ref="xlink:href"/>
+                <xs:attribute ref="ts:name"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="DirectoryReference">
+        <xs:simpleContent>
+            <xs:extension base="ts:DirectoryEntry"/>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="FileReference">
+        <xs:simpleContent>
+            <xs:extension base="ts:DirectoryEntry"/>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="TavernaRun">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute ref="xlink:href"/>
+                <xs:attribute ref="ts:serverVersion"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="Workflow">
+        <xs:sequence>
+            <xs:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="lax"/>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType final="extension restriction" name="TrustDescriptor">
+        <xs:sequence>
+            <xs:element minOccurs="0" name="certificateFile" type="xs:string"/>
+            <xs:element minOccurs="0" name="fileType" type="xs:string"/>
+            <xs:element minOccurs="0" name="certificateBytes" type="xs:base64Binary"/>
+            <xs:element maxOccurs="unbounded" minOccurs="0" name="serverName" type="xs:string"/>
+        </xs:sequence>
+        <xs:attribute ref="xlink:href"/>
+    </xs:complexType>
+    <xs:simpleType name="Permission">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="none"/>
+            <xs:enumeration value="read"/>
+            <xs:enumeration value="update"/>
+            <xs:enumeration value="destroy"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:attribute name="capability" type="xs:anyURI"/>
+    <xs:attribute name="name" type="xs:string"/>
+    <xs:attribute name="serverBuildTimestamp" type="xs:string"/>
+    <xs:attribute name="serverRevision" type="xs:string"/>
+    <xs:attribute name="serverVersion" type="xs:string"/>
+    <xs:attribute name="version" type="xs:string"/>
+</xs:schema>
+</grammars><resources base="http://example.com/taverna/rest"><resource path="/"><method name="GET"><doc>Produces the description of the service.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:serverDescription"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the service.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><resource path="runs"><method name="GET"><doc>Produces a list of all runs visible to the user.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:runList"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the operations on the collection of runs.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Accepts (or not) a request to create a new run executing the given workflow.</doc><request><representation mediaType="application/vnd.taverna.t2flow+xml" element="prefix3:workflow"><doc>Accepts (or not) a request to create a new run executing the given workflow.</doc></representation><representation mediaType="application/xml" element="prefix3:workflow"><doc>Accepts (or not) a request to create a new run executing the given workflow.</doc></representation></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Accepts a URL to a workflow to download and run. The URL must be hosted on a publicly-accessible service.</doc><request><representation mediaType="text/uri-list"><doc>Accepts a URL to a workflow to download and run. The URL must be hosted on a publicly-accessible service.</doc></representation></request><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/policy"><method name="GET"><doc>Describe the parts of this policy.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:policyDescription"></representation><representation mediaType="application/json"></representation></response></method><resource path="/capabilities"><method name="GET"><doc>Gets a description of the capabilities supported by this installation of Taverna Server.</doc><response><representation mediaType="application/xml" element="prefix1:capabilities"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/enabledNotificationFabrics"><method name="GET"><doc>Gets the list of supported, enabled notification fabrics. Each corresponds (approximately) to a protocol, e.g., email.</doc><response><representation mediaType="application/xml" element="prefix1:enabledNotificationFabrics"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/operatingLimit"><method name="GET"><doc>Gets the maximum number of simultaneously operating runs that the user may have. Note that this is often a global limit; it does not represent a promise that a particular user may be able to have that many operating runs at once.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:int"><doc>Gets the maximum number of simultaneously operating runs that the user may have. Note that this is often a global limit; it does not represent a promise that a particular user may be able to have that many operating runs at once.</doc></param></representation></response></method></resource><resource path="/permittedListenerTypes"><method name="GET"><doc>Gets the list of permitted event listener types.</doc><response><representation mediaType="application/xml" element="prefix1:permittedListeners"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/permittedWorkflows"><method name="GET"><doc>Gets the list of permitted workflows.</doc><response><representation mediaType="application/xml" element="prefix1:permittedWorkflows"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/runLimit"><method name="GET"><doc>Gets the maximum number of simultaneous runs in any state that the user may create.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:int"><doc>Gets the maximum number of simultaneous runs in any state that the user may create.</doc></param></representation></response></method></resource></resource><resource path="/runs/{runName}"><doc>This represents how a Taverna Server workflow run looks to a RESTful API.</doc><param name="runName" style="template" type="xs:string"/><method name="DELETE"><doc>Deletes a workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="GET"><doc>Describes a workflow run.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:runDescription"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><resource path="/createTime"><method name="GET"><doc>Gives the time when the workflow run was first submitted to the server.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the time when the workflow run was first submitted to the server.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run create time.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/expiry"><method name="GET"><doc>Gives the time when the workflow run becomes eligible for automatic deletion.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the time when the workflow run becomes eligible for automatic deletion.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run expiry.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Sets the time when the workflow run becomes eligible for automatic deletion.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:string"><doc>Sets the time when the workflow run becomes eligible for automatic deletion.</doc></param></representation></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Sets the time when the workflow run becomes eligible for automatic deletion.</doc></param></representation></response></method></resource><resource path="/finishTime"><method name="GET"><doc>Gives the time when the workflow run was first detected as finished, or an empty string if it has not yet finished (including if it has never started).</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the time when the workflow run was first detected as finished, or an empty string if it has not yet finished (including if it has never started).</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run finish time.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/generate-provenance"><method name="GET"><doc>Whether to create the run bundle for the workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:boolean"><doc>Whether to create the run bundle for the workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Whether to create the run bundle for the workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Whether to create the run bundle for the workflow run.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:boolean"><doc>Whether to create the run bundle for the workflow run.</doc></param></representation></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:boolean"><doc>Whether to create the run bundle for the workflow run.</doc></param></representation></response></method></resource><resource path="/log"><method name="GET"><doc>Return the log for the workflow run.</doc><response><representation mediaType="text/plain"></representation></response></method><method name="OPTIONS"><doc>Return the log for the workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/name"><method name="GET"><doc>Gives the descriptive name of the workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the descriptive name of the workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the operations on the run&apos;s descriptive name.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Set the descriptive name of the workflow run. Note that this value may be arbitrarily truncated by the implementation.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:string"><doc>Set the descriptive name of the workflow run. Note that this value may be arbitrarily truncated by the implementation.</doc></param></representation></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Set the descriptive name of the workflow run. Note that this value may be arbitrarily truncated by the implementation.</doc></param></representation></response></method></resource><resource path="/output"><method name="GET"><doc>Gives the Baclava file where output will be written; empty means use multiple simple files in the out directory.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the Baclava file where output will be written; empty means use multiple simple files in the out directory.</doc></param></representation></response></method><method name="GET"><doc>Gives a description of the outputs, as currently understood</doc><request></request><response><representation mediaType="application/xml" element="prefix10:workflowOutputs"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run output.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Sets the Baclava file where output will be written; empty means use multiple simple files in the out directory.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:string"><doc>Sets the Baclava file where output will be written; empty means use multiple simple files in the out directory.</doc></param></representation></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Sets the Baclava file where output will be written; empty means use multiple simple files in the out directory.</doc></param></representation></response></method></resource><resource path="/run-bundle"><method name="GET"><doc>Return the run bundle for the workflow run.</doc><response><representation mediaType="application/vnd.wf4ever.robundle+zip"></representation></response></method><method name="OPTIONS"><doc>Return the run bundle for the workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/startTime"><method name="GET"><doc>Gives the time when the workflow run was started, or an empty string if the run has not yet started.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the time when the workflow run was started, or an empty string if the run has not yet started.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run start time.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/status"><method name="GET"><doc>Gives the current status of the workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the current status of the workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run status.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Attempts to update the status of the workflow run.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:string"><doc>Attempts to update the status of the workflow run.</doc></param></representation></request><response><representation mediaType="text/plain"></representation></response></method></resource><resource path="/stderr"><method name="GET"><doc>Return the stderr for the workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Return the stderr for the workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Return the stderr for the workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/stdout"><method name="GET"><doc>Return the stdout for the workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Return the stdout for the workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Return the stdout for the workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/usage"><method name="GET"><doc>Return the usage record for the workflow run.</doc><response><representation mediaType="application/xml"></representation></response></method><method name="OPTIONS"><doc>Return the usage record for the workflow run.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/workflow"><method name="GET"><doc>Gives the workflow document used to create the workflow run.</doc><response><representation mediaType="application/vnd.taverna.t2flow+xml" element="prefix3:workflow"></representation><representation mediaType="application/xml" element="prefix3:workflow"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run workflow.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/input"><doc>This represents how a Taverna Server workflow run&apos;s inputs looks to a RESTful API.</doc><method name="GET"><doc>Describe the sub-URIs of this resource.</doc><response><representation mediaType="application/xml" element="prefix1:runInputs"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run&apos;s inputs&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><resource path="/baclava"><method name="GET"><doc>Gives the Baclava file describing the inputs, or empty if individual files are used.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the Baclava file describing the inputs, or empty if individual files are used.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the inputs&apos; baclava operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Sets the Baclava file describing the inputs.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:string"><doc>Sets the Baclava file describing the inputs.</doc></param></representation></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Sets the Baclava file describing the inputs.</doc></param></representation></response></method></resource><resource path="/expected"><method name="GET"><doc>Describe the expected inputs of this workflow run.</doc><response><representation mediaType="application/xml" element="prefix10:inputDescription"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the expected inputs&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/input/{name}"><param name="name" style="template" type="xs:string"/><method name="GET"><doc>Gives a description of what is used to supply a particular input.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:runInput"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the one input&apos;s operations.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Sets the source for a particular input port.</doc><request><representation mediaType="application/xml" element="prefix1:runInput"><doc>Sets the source for a particular input port.</doc></representation><representation mediaType="application/json"><doc>Sets the source for a particular input port.</doc></representation></request><response><representation mediaType="application/xml" element="prefix1:runInput"></representation><representation mediaType="application/json"></representation></response></method></resource></resource><resource path="/interaction"><method name="GET"><doc>Get the feed document for this ATOM feed.</doc><response><representation mediaType="application/atom+xml"></representation></response></method><method name="OPTIONS"><doc>Describes what HTTP operations are supported on the feed.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Adds an entry to this ATOM feed.</doc><request><representation mediaType="application/atom+xml"><doc>Adds an entry to this ATOM feed.</doc></representation></request><response><representation mediaType="application/atom+xml"></representation></response></method><resource path="/{id}"><param name="id" style="template" type="xs:string"/><method name="DELETE"><doc>Deletes an entry from this ATOM feed.</doc><request></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Deletes an entry from this ATOM feed.</doc></param></representation></response></method><method name="GET"><doc>Get the entry with a particular ID within this ATOM feed.</doc><request></request><response><representation mediaType="application/atom+xml"></representation></response></method><method name="OPTIONS"><doc>Describes what HTTP operations are supported on an entry.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method></resource></resource><resource path="/listeners"><doc>This represents all the event listeners attached to a workflow run.</doc><method name="GET"><doc>Get the listeners installed in the workflow run.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:listeners"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run listeners&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Add a new event listener to the named workflow run.</doc><request><representation mediaType="application/xml" element="prefix1:listenerDefinition"><doc>Add a new event listener to the named workflow run.</doc></representation><representation mediaType="application/json"><doc>Add a new event listener to the named workflow run.</doc></representation></request><response><representation mediaType="application/octet-stream"></representation></response></method><resource path="/{name}"><doc>This represents a single event listener attached to a workflow run.</doc><param name="name" style="template" type="xs:string"/><method name="GET"><doc>Get the description of this listener.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:listenerDescription"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run listener&apos;s operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><resource path="/configuration"><method name="GET"><doc>Get the configuration for the given event listener that is attached to a workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Get the configuration for the given event listener that is attached to a workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run listener&apos;s configuration&apos;s operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/properties"><method name="GET"><doc>Get the list of properties supported by a given event listener attached to a workflow run.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:properties"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run listener&apos;s properties&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/properties/{propertyName}"><doc>This represents a single property attached of an event listener.</doc><param name="propertyName" style="template" type="xs:string"/><method name="GET"><doc>Get the value of the particular property of an event listener attached to a workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Get the value of the particular property of an event listener attached to a workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run listener&apos;s property&apos;s operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Set the value of the particular property of an event listener attached to a workflow run.</doc><request><representation mediaType="text/plain"><param name="request" style="plain" type="xs:string"><doc>Set the value of the particular property of an event listener attached to a workflow run.</doc></param></representation></request><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Set the value of the particular property of an event listener attached to a workflow run.</doc></param></representation></response></method></resource></resource></resource><resource path="/security"><doc>Manages the security of the workflow run. In general, only the owner of a run may access this resource.</doc><method name="GET"><doc>Gives a description of the security information supported by the workflow run.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:securityDescriptor"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run security.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><resource path="/credentials"><method name="DELETE"><doc>Deletes all credentials.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="GET"><doc>Gives a list of credentials supplied to this workflow run.</doc><response><representation mediaType="application/xml" element="prefix1:credentials"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run credentials&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Creates a new credential.</doc><request><representation mediaType="application/xml" element="prefix1:credential"><doc>Creates a new credential.</doc></representation><representation mediaType="application/json"><doc>Creates a new credential.</doc></representation></request><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/credentials/{id}"><param name="id" style="template" type="xs:string"/><method name="DELETE"><doc>Deletes a particular credential.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="GET"><doc>Describes a particular credential.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:credential"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run credential&apos;s operations.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Updates a particular credential.</doc><request><representation mediaType="application/xml" element="prefix1:credential"><doc>Updates a particular credential.</doc></representation><representation mediaType="application/json"><doc>Updates a particular credential.</doc></representation></request><response><representation mediaType="application/xml" element="prefix1:credential"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/owner"><method name="GET"><doc>Gives the identity of who owns the workflow run.</doc><response><representation mediaType="text/plain"><param name="result" style="plain" type="xs:string"><doc>Gives the identity of who owns the workflow run.</doc></param></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run owner.</doc><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/permissions"><method name="GET"><doc>Gives a list of all non-default permissions associated with the enclosing workflow run. By default, nobody has any access at all except for the owner of the run.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:permissionsDescriptor"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run permissions&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Creates a new assignment of permissions to a particular user.</doc><request><representation mediaType="application/xml" element="prefix1:permissionUpdate"><doc>Creates a new assignment of permissions to a particular user.</doc></representation><representation mediaType="application/json"><doc>Creates a new assignment of permissions to a particular user.</doc></representation></request><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/permissions/{id}"><param name="id" style="template" type="xs:string"/><method name="DELETE"><doc>Deletes (by resetting to default) the permissions associated with a particular user.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="GET"><doc>Describes the permission granted to a particular user.</doc><request></request><response><representation mediaType="text/plain"></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run permission&apos;s operations.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Updates the permissions granted to a particular user.</doc><request><representation mediaType="text/plain"><doc>Updates the permissions granted to a particular user.</doc></representation></request><response><representation mediaType="text/plain"></representation></response></method></resource><resource path="/trusts"><method name="DELETE"><doc>Deletes all trusted identities.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="GET"><doc>Gives a list of trusted identities supplied to this workflow run.</doc><response><representation mediaType="application/xml" element="prefix1:trustedIdentities"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the run trusted certificates&apos; operations.</doc><response><representation mediaType="application/octet-stream"></representation></response></method><method name="POST"><doc>Adds a new trusted identity.</doc><request><representation mediaType="application/xml" element="prefix3:trustedIdentity"><doc>Adds a new trusted identity.</doc></representation><representation mediaType="application/json"><doc>Adds a new trusted identity.</doc></representation></request><response><representation mediaType="application/octet-stream"></representation></response></method></resource><resource path="/trusts/{id}"><param name="id" style="template" type="xs:string"/><method name="DELETE"><doc>Deletes a particular trusted identity.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="GET"><doc>Describes a particular trusted identity.</doc><request></request><response><representation mediaType="application/xml" element="prefix3:trustedIdentity"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of one run trusted certificate&apos;s operations.</doc><request></request><response><representation mediaType="application/octet-stream"></representation></response></method><method name="PUT"><doc>Updates a particular trusted identity.</doc><request><representation mediaType="application/xml" element="prefix3:trustedIdentity"><doc>Updates a particular trusted identity.</doc></representation><representation mediaType="application/json"><doc>Updates a particular trusted identity.</doc></representation></request><response><representation mediaType="application/xml" element="prefix3:trustedIdentity"></representation><representation mediaType="application/json"></representation></response></method></resource></resource><resource path="/wd"><doc>Representation of how a workflow run&apos;s working directory tree looks.</doc><method name="GET"><doc>Describes the working directory of the workflow run.</doc><request></request><response><representation mediaType="application/xml" element="prefix1:directoryContents"></representation><representation mediaType="application/json"></representation></response></method><resource path="/{path:(.*)}"><param name="path" style="template" repeating="true"/><method name="POST"><doc>Creates or updates a file in a particular location beneath the working directory of the workflow run with the contents of a publicly readable URL.</doc><request><representation mediaType="text/uri-list"><doc>Creates or updates a file in a particular location beneath the working directory of the workflow run with the contents of a publicly readable URL.</doc></representation></request><response><representation mediaType="application/xml"></representation><representation mediaType="application/json"></representation></response></method><method name="PUT"><doc>Creates or updates a file in a particular location beneath the working directory of the workflow run.</doc><request><representation mediaType="application/octet-stream"><doc>Creates or updates a file in a particular location beneath the working directory of the workflow run.</doc></representation><representation mediaType="*/*"><doc>Creates or updates a file in a particular location beneath the working directory of the workflow run.</doc></representation></request><response><representation mediaType="application/xml"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/{path:.*}"><param name="path" style="template" repeating="true"/><method name="DELETE"><doc>Deletes a file or directory that is in or below the working directory of a workflow run.</doc><request></request><response><representation mediaType="application/xml"></representation><representation mediaType="application/json"></representation></response></method><method name="OPTIONS"><doc>Produces the description of the files/directories&apos; baclava operations.</doc><request></request><response><representation mediaType="application/xml"></representation><representation mediaType="application/json"></representation></response></method><method name="POST"><doc>Creates a directory in the filesystem beneath the working directory of the workflow run, or creates or updates a file&apos;s contents, where that file is in or below the working directory of a workflow run.</doc><request><representation mediaType="application/xml" element="prefix1:filesystemOperation"><doc>Creates a directory in the filesystem beneath the working directory of the workflow run, or creates or updates a file&apos;s contents, where that file is in or below the working directory of a workflow run.</doc></representation><representation mediaType="application/json"><doc>Creates a directory in the filesystem beneath the working directory of the workflow run, or creates or updates a file&apos;s contents, where that file is in or below the working directory of a workflow run.</doc></representation></request><response><representation mediaType="application/xml"></representation><representation mediaType="application/json"></representation></response></method></resource><resource path="/{path:.+}"><param name="path" style="template" repeating="true"/><method name="GET"><doc>Gives a description of the named entity in or beneath the working directory of the workflow run (either a Directory or File).</doc><request></request><response><representation mediaType="application/xml"></representation><representation mediaType="application/json"></representation><representation mediaType="application/octet-stream"></representation><representation mediaType="application/zip"></representation><representation mediaType="*/*"></representation></response></method></resource></resource></resource></resource></resources></application>
\ No newline at end of file
diff --git a/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java b/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
index 19c2bcd..fa68b81 100644
--- a/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
+++ b/server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
@@ -221,11 +221,15 @@
 	 *            The location used for pushing web pages to support the feed.
 	 *            If <tt>null</tt>, a default from the factory will be used
 	 *            instead.
+	 * @param publishUrlBase
+	 *            Where to <i>actually</i> publish to, if this needs to be
+	 *            different from the location presented in the published HTML
+	 *            and Feed entries. Necessary in complex network scenarios.
 	 * @throws RemoteException
 	 *             If anything goes wrong with the communication.
 	 */
 	void setInteractionServiceDetails(@Nonnull URL interactionFeed,
-			@Nonnull URL webdavPath) throws RemoteException;
+			@Nonnull URL webdavPath, @Nullable URL publishUrlBase) throws RemoteException;
 
 	/**
 	 * A do-nothing method, used to check the general reachability of the
diff --git a/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java b/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
index cf7799a..d12d3d8 100644
--- a/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
+++ b/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
@@ -19,6 +19,7 @@
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.datatype.DatatypeConfigurationException;
 import javax.xml.datatype.DatatypeFactory;
+import javax.xml.transform.dom.DOMSource;
 
 import org.ogf.usage.v1_0.Charge;
 import org.ogf.usage.v1_0.ConsumableResourceType;
@@ -45,6 +46,7 @@
 import org.ogf.usage.v1_0.TimeInstant;
 import org.ogf.usage.v1_0.UserIdentity;
 import org.ogf.usage.v1_0.WallDuration;
+import org.w3c.dom.Element;
 
 @XmlRootElement(name = "UsageRecord", namespace = "http://schema.ogf.org/urf/2003/09/urf")
 public class JobUsageRecord extends org.ogf.usage.v1_0.UsageRecordType {
@@ -277,10 +279,25 @@
 		return writer.toString();
 	}
 
+	private static JAXBContext context;
+	static {
+		try {
+			context = JAXBContext.newInstance(JobUsageRecord.class);
+		} catch (JAXBException e) {
+			throw new RuntimeException("failed to handle JAXB annotated class",
+					e);
+		}
+	}
+
 	public static JobUsageRecord unmarshal(String s) throws JAXBException {
-		StringReader reader = new StringReader(s);
-		return (JobUsageRecord) JAXBContext.newInstance(JobUsageRecord.class)
-				.createUnmarshaller().unmarshal(reader);
+		return (JobUsageRecord) context.createUnmarshaller().unmarshal(
+				new StringReader(s));
+	}
+
+	public static JobUsageRecord unmarshal(Element elem) throws JAXBException {
+		return context.createUnmarshaller()
+				.unmarshal(new DOMSource(elem), JobUsageRecord.class)
+				.getValue();
 	}
 
 	// TODO: Add signing support
diff --git a/server-webapp/pom.xml b/server-webapp/pom.xml
index f56b9c5..15f1eb4 100644
--- a/server-webapp/pom.xml
+++ b/server-webapp/pom.xml
@@ -520,6 +520,19 @@
 										<ignore />
 									</action>
 								</pluginExecution>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>net.alchim31.maven</groupId>
+										<artifactId>yuicompressor-maven-plugin</artifactId>
+										<versionRange>[1.0.0,)</versionRange>
+										<goals>
+											<goal>compress</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<execute/>
+									</action>
+								</pluginExecution>
 							</pluginExecutions>
 						</lifecycleMappingMetadata>
 					</configuration>
@@ -664,6 +677,14 @@
 			</plugin>
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-war-plugin</artifactId>
+				<version>2.4</version>
+				<configuration>
+					<webXml>src/main/webapp/WEB-INF/web-sec.xml</webXml>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-eclipse-plugin</artifactId>
 				<configuration>
 					<additionalProjectnatures>
@@ -735,6 +756,24 @@
 				</configuration>
 			</plugin>
 			<plugin>
+				<groupId>net.alchim31.maven</groupId>
+				<artifactId>yuicompressor-maven-plugin</artifactId>
+				<version>1.4.0</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>compress</goal>
+						</goals>
+					</execution>
+				</executions>
+				<configuration>
+					<excludes>
+						<exclude>**/*.min.js</exclude>
+					</excludes>
+					<nosuffix>true</nosuffix>
+				</configuration>
+			</plugin>
+			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-resources-plugin</artifactId>
 				<executions>
@@ -810,5 +849,33 @@
 				<forker.module>server-win-forker</forker.module>
 			</properties>
 		</profile>
+		<profile>
+			<id>nosec</id>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-war-plugin</artifactId>
+						<configuration>
+							<webXml>src/main/webapp/WEB-INF/web-nosec.xml</webXml>
+						</configuration>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+		<profile>
+			<id>partsec</id>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-war-plugin</artifactId>
+						<configuration>
+							<webXml>src/main/webapp/WEB-INF/web-partsec.xml</webXml>
+						</configuration>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
 	</profiles>
 </project>
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 ab43827..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,7 +8,7 @@
 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 org.taverna.server.master.TavernaServerSupport.log;
+import static org.apache.commons.logging.LogFactory.getLog;
 import static org.taverna.server.master.common.Uri.secure;
 
 import java.io.ByteArrayInputStream;
@@ -21,6 +21,7 @@
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
+import org.apache.commons.logging.Log;
 import org.springframework.beans.factory.annotation.Required;
 import org.taverna.server.master.exceptions.FilesystemAccessException;
 import org.taverna.server.master.exceptions.NoDirectoryEntryException;
@@ -53,6 +54,7 @@
  * @author Donal Fellows
  */
 public class ContentsDescriptorBuilder {
+	private Log log = getLog("Taverna.Server.Webapp");
 	private FilenameUtils fileUtils;
 	private UriBuilderFactory uriBuilderFactory;
 
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 5bd6f30..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
@@ -11,7 +11,7 @@
 import static javax.ws.rs.core.Response.ok;
 import static javax.ws.rs.core.Response.seeOther;
 import static javax.ws.rs.core.Response.status;
-import static org.taverna.server.master.TavernaServer.log;
+import static org.apache.commons.logging.LogFactory.getLog;
 import static org.taverna.server.master.api.ContentTypes.APPLICATION_ZIP_TYPE;
 import static org.taverna.server.master.api.ContentTypes.DIRECTORY_VARIANTS;
 import static org.taverna.server.master.api.ContentTypes.INITIAL_FILE_VARIANTS;
@@ -37,6 +37,7 @@
 import javax.ws.rs.core.Variant;
 import javax.xml.ws.Holder;
 
+import org.apache.commons.logging.Log;
 import org.springframework.beans.factory.annotation.Required;
 import org.taverna.server.master.api.DirectoryBean;
 import org.taverna.server.master.exceptions.FilesystemAccessException;
@@ -61,6 +62,7 @@
  * @author Donal Fellows
  */
 class DirectoryREST implements TavernaServerDirectoryREST, DirectoryBean {
+	private Log log = getLog("Taverna.Server.Webapp");
 	private TavernaServerSupport support;
 	private TavernaRun run;
 	private FilenameUtils fileUtils;
diff --git a/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java b/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java
index 2822de3..3e983a9 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java
@@ -5,11 +5,12 @@
  */
 package org.taverna.server.master;
 
-import static org.taverna.server.master.TavernaServer.log;
+import static org.apache.commons.logging.LogFactory.getLog;
 import static org.taverna.server.master.utils.RestUtils.opt;
 
 import javax.ws.rs.core.Response;
 
+import org.apache.commons.logging.Log;
 import org.taverna.server.master.api.ListenerPropertyBean;
 import org.taverna.server.master.exceptions.NoListenerException;
 import org.taverna.server.master.exceptions.NoUpdateException;
@@ -26,6 +27,7 @@
  */
 class ListenerPropertyREST implements TavernaServerListenersREST.Property,
 		ListenerPropertyBean {
+	private Log log = getLog("Taverna.Server.Webapp");
 	private TavernaServerSupport support;
 	private Listener listen;
 	private String propertyName;
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 eb7b38e..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
@@ -10,9 +10,9 @@
 import static javax.ws.rs.core.Response.noContent;
 import static javax.ws.rs.core.Response.ok;
 import static javax.ws.rs.core.Response.status;
+import static org.apache.commons.logging.LogFactory.getLog;
 import static org.joda.time.format.ISODateTimeFormat.dateTime;
 import static org.joda.time.format.ISODateTimeFormat.dateTimeParser;
-import static org.taverna.server.master.TavernaServer.log;
 import static org.taverna.server.master.common.Roles.SELF;
 import static org.taverna.server.master.common.Roles.USER;
 import static org.taverna.server.master.common.Status.Initialized;
@@ -26,6 +26,7 @@
 import javax.ws.rs.core.UriInfo;
 import javax.xml.bind.JAXBException;
 
+import org.apache.commons.logging.Log;
 import org.joda.time.DateTime;
 import org.ogf.usage.JobUsageRecord;
 import org.springframework.beans.factory.annotation.Required;
@@ -58,6 +59,7 @@
  * @author Donal Fellows
  */
 abstract class RunREST implements TavernaServerRunREST, RunBean {
+	private Log log = getLog("Taverna.Server.Webapp");
 	private String runName;
 	private TavernaRun run;
 	private TavernaServerSupport support;
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 7c83c75..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
@@ -128,7 +128,7 @@
 			+ Version.JAVA + ",name=";
 
 	/** The logger for the server framework. */
-	public static Log log = getLog("Taverna.Server.Webapp");
+	public Log log = getLog("Taverna.Server.Webapp");
 
 	@PreDestroy
 	void closeLog() {
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 5c6d938..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
@@ -91,7 +91,7 @@
 		+ Version.JAVA + " web-application interface.")
 public class TavernaServerSupport {
 	/** The main webapp log. */
-	public static Log log = getLog("Taverna.Server.Webapp");
+	private Log log = getLog("Taverna.Server.Webapp");
 	private Log accessLog = getLog("Taverna.Server.Webapp.Access");;
 	/** Bean used to log counts of external calls. */
 	private InvocationCounter counter;
diff --git a/server-webapp/src/main/java/org/taverna/server/master/identity/UserStore.java b/server-webapp/src/main/java/org/taverna/server/master/identity/UserStore.java
index fdd615b..054d932 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/identity/UserStore.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/identity/UserStore.java
@@ -50,7 +50,7 @@
 public class UserStore extends JDOSupport<User> implements UserDetailsService,
 		UserStoreAPI {
 	/** The logger for the user store. */
-	private static Log log = getLog("Taverna.Server.UserDB");
+	private transient Log log = getLog("Taverna.Server.UserDB");
 
 	public UserStore() {
 		super(User.class);
diff --git a/server-webapp/src/main/java/org/taverna/server/master/identity/WorkflowInternalAuthProvider.java b/server-webapp/src/main/java/org/taverna/server/master/identity/WorkflowInternalAuthProvider.java
index d8361e5..9219a60 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/identity/WorkflowInternalAuthProvider.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/identity/WorkflowInternalAuthProvider.java
@@ -54,7 +54,7 @@
  */
 public class WorkflowInternalAuthProvider extends
 		AbstractUserDetailsAuthenticationProvider {
-	private static Log log = LogFactory.getLog("Taverna.Server.UserDB");
+	private Log log = LogFactory.getLog("Taverna.Server.UserDB");
 	private static final boolean logDecisions = true;
 	public static final String PREFIX = "wfrun_";
 	private RunDatabaseDAO dao;
@@ -265,7 +265,7 @@
 	}
 
 	public static class WorkflowSelfIDMapper implements LocalIdentityMapper {
-		private static Log log = LogFactory.getLog("Taverna.Server.UserDB");
+		private Log log = LogFactory.getLog("Taverna.Server.UserDB");
 		private RunStore runStore;
 
 		@PreDestroy
diff --git a/server-webapp/src/main/java/org/taverna/server/master/interaction/InteractionFeedSupport.java b/server-webapp/src/main/java/org/taverna/server/master/interaction/InteractionFeedSupport.java
index 3a9808b..99e1d99 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/interaction/InteractionFeedSupport.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/interaction/InteractionFeedSupport.java
@@ -5,18 +5,31 @@
  */
 package org.taverna.server.master.interaction;
 
+import static java.lang.management.ManagementFactory.getPlatformMBeanServer;
 import static java.util.Collections.reverse;
+import static javax.management.Query.attr;
+import static javax.management.Query.match;
+import static javax.management.Query.value;
+import static org.apache.commons.logging.LogFactory.getLog;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.annotation.Nullable;
+import javax.annotation.PostConstruct;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
 import org.apache.abdera.Abdera;
 import org.apache.abdera.factory.Factory;
 import org.apache.abdera.i18n.iri.IRI;
@@ -97,6 +110,28 @@
 		this.uriBuilder = uriBuilder;
 	}
 
+	private final Map<String, URL> endPoints = new HashMap<>();
+
+	@PostConstruct
+	void determinePorts() {
+		try {
+			MBeanServer mbs = getPlatformMBeanServer();
+			for (ObjectName obj : mbs.queryNames(new ObjectName(
+					"*:type=Connector,*"),
+					match(attr("protocol"), value("HTTP/1.1")))) {
+				String scheme = mbs.getAttribute(obj, "scheme").toString();
+				String port = obj.getKeyProperty("port");
+				endPoints.put(scheme, new URL(scheme + "://localhost:" + port));
+			}
+			getLog(getClass()).info(
+					"installed feed port publication mapping for "
+							+ endPoints.keySet());
+		} catch (Exception e) {
+			getLog(getClass()).error(
+					"failure in determining local port mapping", e);
+		}
+	}
+	
 	/**
 	 * @param run
 	 *            The workflow run that defines which feed we are operating on.
@@ -106,6 +141,13 @@
 		return uriBuilder.getRunUriBuilder(run).path(FEED_URL_DIR).build();
 	}
 
+	@Nullable
+	public URL getLocalFeedBase(URI feedURI) {
+		if (feedURI == null)
+			return null;
+		return endPoints.get(feedURI.getScheme());
+	}
+
 	/**
 	 * @param run
 	 *            The workflow run that defines which feed we are operating on.
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 d2c9c91..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
@@ -17,6 +17,7 @@
 
 import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.net.URI;
 import java.net.URL;
 import java.rmi.MarshalledObject;
 import java.rmi.RMISecurityManager;
@@ -29,14 +30,14 @@
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.annotation.Resource;
 import javax.xml.bind.JAXBException;
 
 import org.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Required;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.task.TaskExecutor;
 import org.springframework.jmx.export.annotation.ManagedResource;
 import org.taverna.server.localworker.remote.RemoteRunFactory;
 import org.taverna.server.localworker.remote.RemoteSingleRun;
@@ -142,6 +143,19 @@
 		}
 	}
 
+	protected static final Process launchSubprocess(ProcessBuilder b)
+			throws IOException {
+		Thread t = Thread.currentThread();
+		ClassLoader ccl = t.getContextClassLoader();
+		try {
+			t.setContextClassLoader(null);
+			return b.start();
+		} finally {
+			t.setContextClassLoader(ccl);
+		}
+	}
+
+	/** Get a handle to a new instance of the RMI registry. */
 	private Registry makeRegistry(int port) throws RemoteException {
 		ProcessBuilder p = new ProcessBuilder(getJavaBinary());
 		p.command().add("-jar");
@@ -149,7 +163,7 @@
 		p.command().add(Integer.toString(port));
 		p.command().add(Boolean.toString(rmiLocalhostOnly));
 		try {
-			Process proc = p.start();
+			Process proc = launchSubprocess(p);
 			Thread.sleep(getSleepTime());
 			try {
 				if (proc.exitValue() == 0)
@@ -240,7 +254,6 @@
 	public static final String SECURITY_POLICY_FILE = "security.policy";
 	private SecurityContextFactory securityFactory;
 	UsageRecordRecorder usageRecordSink;
-	TaskExecutor urProcessorPool;
 	private EventDAO masterEventFeed;
 
 	@Autowired(required = true)
@@ -258,12 +271,6 @@
 		this.usageRecordSink = usageRecordSink;
 	}
 
-	@Resource(name = "URThreads")
-	@Required
-	void setURProcessorPool(TaskExecutor urProcessorPool) {
-		this.urProcessorPool = urProcessorPool;
-	}
-
 	/**
 	 * Configures the Java security model. Not currently used, as it is
 	 * viciously difficult to get right!
@@ -337,10 +344,16 @@
 					state.getDefaultLifetime(), runDB, id,
 					state.getGenerateProvenance(), this);
 			run.setSecurityContext(securityFactory.create(run, creator));
-			URL feedUrl = interactionFeedSupport.getFeedURI(run).toURL();
+			@Nonnull
+			URI feed = interactionFeedSupport.getFeedURI(run);
+			@Nonnull
+			URL feedUrl = feed.toURL();
+			@Nonnull
 			URL webdavUrl = baseurifactory.getRunUriBuilder(run)
 					.path(DIR + "/interactions").build().toURL();
-			rsr.setInteractionServiceDetails(feedUrl, webdavUrl);
+			@Nullable
+			URL pub = interactionFeedSupport.getLocalFeedBase(feed);
+			rsr.setInteractionServiceDetails(feedUrl, webdavUrl, pub);
 			return run;
 		} catch (NoCreateException e) {
 			log.warn("failed to build run instance", e);
@@ -386,6 +399,12 @@
 		}
 	}
 
+	private void acceptUsageRecord(String usageRecord) {
+		if (usageRecordSink != null)
+			usageRecordSink.storeUsageRecord(usageRecord);
+		runDB.checkForFinishNow();
+	}
+
 	/**
 	 * Make a Remote object that can act as a consumer for usage records.
 	 * 
@@ -403,20 +422,8 @@
 				}
 
 				@Override
-				public void acceptUsageRecord(final String usageRecord) {
-					if (usageRecordSink != null && urProcessorPool != null)
-						urProcessorPool.execute(new Runnable() {
-							@Override
-							public void run() {
-								usageRecordSink.storeUsageRecord(usageRecord);
-							}
-						});
-					urProcessorPool.execute(new Runnable() {
-						@Override
-						public void run() {
-							runDB.checkForFinishNow();
-						}
-					});
+				public void acceptUsageRecord(String usageRecord) {
+					AbstractRemoteRunFactory.this.acceptUsageRecord(usageRecord);
 				}
 			}
 			return new URReceiver();
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 dccfeac..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
@@ -10,13 +10,9 @@
 import static java.util.Arrays.asList;
 import static java.util.Calendar.SECOND;
 import static java.util.UUID.randomUUID;
-import static org.apache.commons.logging.LogFactory.getLog;
 import static org.taverna.server.master.TavernaServer.JMX_ROOT;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.rmi.ConnectException;
 import java.rmi.ConnectIOException;
 import java.rmi.NotBoundException;
@@ -26,9 +22,9 @@
 
 import javax.annotation.Nonnull;
 import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import javax.xml.bind.JAXBException;
 
-import org.apache.commons.logging.Log;
 import org.springframework.jmx.export.annotation.ManagedAttribute;
 import org.springframework.jmx.export.annotation.ManagedResource;
 import org.taverna.server.localworker.remote.RemoteRunFactory;
@@ -145,15 +141,20 @@
 
 		// Spawn the subprocess
 		log.info("about to create subprocess: " + p.command());
-		factoryProcess = p.start();
-		Thread logger = new Thread(new OutputLogger(factoryProcessName,
-				factoryProcess), factoryProcessName + ".Logger");
-		logger.setDaemon(true);
-		logger.start();
-		Thread logger2 = new Thread(new ErrorLogger(factoryProcessName,
-				factoryProcess), factoryProcessName + ".Logger");
-		logger2.setDaemon(true);
-		logger2.start();
+		
+		factoryProcess = launchSubprocess(p);
+		outlog = new StreamLogger("FactoryStdout", factoryProcess.getInputStream()) {
+			@Override
+			protected void write(String msg) {
+				log.info(msg);
+			}
+		};
+		errlog = new StreamLogger("FactoryStderr", factoryProcess.getErrorStream()) {
+			@Override
+			protected void write(String msg) {
+				log.info(msg);
+			}
+		};
 
 		// Wait for the subprocess to register itself in the RMI registry
 		Calendar deadline = Calendar.getInstance();
@@ -192,6 +193,17 @@
 		throw lastException;
 	}
 
+	private StreamLogger outlog, errlog;
+
+	private void stopLoggers() {
+		if (outlog != null)
+			outlog.stop();
+		outlog = null;
+		if (errlog != null)
+			errlog.stop();
+		errlog = null;
+	}
+
 	private RemoteRunFactory getRemoteFactoryHandle(String name)
 			throws RemoteException, NotBoundException {
 		log.info("about to look up resource called " + name);
@@ -207,81 +219,10 @@
 		return rrf;
 	}
 
-	private static class OutputLogger implements Runnable {
-		private final Log log;
-
-		OutputLogger(String name, Process process) {
-			log = getLog("Taverna.Server.LocalWorker." + name);
-			this.uniqueName = name;
-			this.br = new BufferedReader(new InputStreamReader(
-					process.getInputStream()));
-		}
-
-		private String uniqueName;
-		private BufferedReader br;
-
-		@Override
-		public void run() {
-			try {
-				String line;
-				while (true) {
-					line = br.readLine();
-					if (line == null)
-						break;
-					log.info(uniqueName + " subprocess output: " + line);
-				}
-			} catch (IOException e) {
-				// Do nothing...
-			} catch (Exception e) {
-				log.warn("failure in reading from " + uniqueName, e);
-			} finally {
-				try {
-					br.close();
-				} catch (Throwable e) {
-				}
-			}
-		}
-	}
-
-	private static class ErrorLogger implements Runnable {
-		private final Log log;
-
-		ErrorLogger(String name, Process process) {
-			log = getLog("Taverna.Server.LocalWorker." + name);
-			this.uniqueName = name;
-			this.br = new BufferedReader(new InputStreamReader(
-					process.getErrorStream()));
-		}
-
-		private String uniqueName;
-		private BufferedReader br;
-
-		@Override
-		public void run() {
-			try {
-				String line;
-				while (true) {
-					line = br.readLine();
-					if (line == null)
-						break;
-					log.info(uniqueName + " subprocess error: " + line);
-				}
-			} catch (IOException e) {
-				// Do nothing...
-			} catch (Exception e) {
-				log.warn("failure in reading from " + uniqueName, e);
-			} finally {
-				try {
-					br.close();
-				} catch (Throwable e) {
-				}
-			}
-		}
-	}
-
 	/**
 	 * Destroys the subprocess that manufactures runs.
 	 */
+	@PreDestroy
 	public void killFactory() {
 		if (factory != null) {
 			log.info("requesting shutdown of " + factoryProcessName);
@@ -315,6 +256,7 @@
 				}
 			} finally {
 				factoryProcess = null;
+				stopLoggers();
 			}
 			if (code > 128) {
 				log.info(factoryProcessName + " died with signal="
@@ -327,12 +269,6 @@
 		}
 	}
 
-	@Override
-	protected void finalize() throws Throwable {
-		killFactory();
-		super.finalize();
-	}
-
 	/**
 	 * The real core of the run builder, factored out from its reliability
 	 * support.
@@ -347,6 +283,7 @@
 	 */
 	private RemoteSingleRun getRealRun(@Nonnull UsernamePrincipal creator,
 			@Nonnull byte[] wf, UUID id) throws RemoteException {
+		@Nonnull
 		String globaluser = "Unknown Person";
 		if (creator != null)
 			globaluser = creator.getName();
@@ -359,6 +296,7 @@
 	@Override
 	protected RemoteSingleRun getRealRun(UsernamePrincipal creator,
 			Workflow workflow, UUID id) throws Exception {
+		@Nonnull
 		byte[] wf = serializeWorkflow(workflow);
 		for (int i = 0; i < 3; i++) {
 			initFactory();
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 1115f25..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
@@ -10,15 +10,12 @@
 import static java.util.Arrays.asList;
 import static java.util.Calendar.SECOND;
 import static java.util.UUID.randomUUID;
-import static org.apache.commons.logging.LogFactory.getLog;
 import static org.taverna.server.master.TavernaServer.JMX_ROOT;
+import static org.taverna.server.master.localworker.AbstractRemoteRunFactory.launchSubprocess;
 
-import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.rmi.ConnectException;
@@ -335,72 +332,6 @@
 	}
 }
 
-abstract class StreamLogger {
-	protected final Log log;
-
-	protected StreamLogger(String name, InputStream is) {
-		log = getLog("Taverna.Server.LocalWorker." + name);
-		final String uniqueName = name;
-		final BufferedReader br = new BufferedReader(new InputStreamReader(is));
-		Thread t = new Thread(new Runnable() {
-			@Override
-			public void run() {
-				String line;
-				try {
-					while ((line = br.readLine()) != null)
-						if (!line.isEmpty())
-							write(line);
-				} catch (IOException e) {
-					// Do nothing...
-				} catch (Exception e) {
-					log.warn("failure in reading from " + uniqueName, e);
-				} finally {
-					try {
-						br.close();
-					} catch (Throwable e) {
-					}
-				}
-			}
-		}, name + ".StreamLogger");
-		t.setDaemon(true);
-		t.start();
-	}
-
-	/**
-	 * Write a line read from the subproces to the log.
-	 * <p>
-	 * This needs to be implemented by subclasses in order for the log to be
-	 * correctly written with the class name.
-	 * 
-	 * @param msg
-	 *            The message to write. Guaranteed to have no newline characters
-	 *            in it and to be non-empty.
-	 */
-	protected abstract void write(String msg);
-}
-
-class StdOut extends StreamLogger {
-	StdOut(Process process) {
-		super("forker", process.getInputStream());
-	}
-
-	@Override
-	protected void write(String msg) {
-		log.info(msg);
-	}
-}
-
-class StdErr extends StreamLogger {
-	StdErr(Process process) {
-		super("forker", process.getErrorStream());
-	}
-
-	@Override
-	protected void write(String msg) {
-		log.info(msg);
-	}
-}
-
 /**
  * The connector that handles the secure fork process itself.
  * 
@@ -414,6 +345,7 @@
 	private Integer lastExitCode;
 	private Log log;
 	private LocalWorkerState state;
+	private StreamLogger out, err;
 
 	/**
 	 * Construct the command to run the meta-factory process.
@@ -452,12 +384,22 @@
 		// Spawn the subprocess
 		log.info("about to create subprocess: " + p.command());
 		log.info("subprocess directory: " + p.directory());
-		process = p.start();
+		process = launchSubprocess(p);
 		channel = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
 				process.getOutputStream())), true);
 		// Log the responses
-		new StdOut(process);
-		new StdErr(process);
+		out = new StreamLogger("ForkedStdout", process.getInputStream()) {
+			@Override
+			protected void write(String msg) {
+				log.info(msg);
+			}
+		};
+		err = new StreamLogger("ForkedStderr", process.getErrorStream()) {
+			@Override
+			protected void write(String msg) {
+				log.info(msg);
+			}
+		};
 	}
 
 	@Override
@@ -495,6 +437,8 @@
 		} finally {
 			process = null;
 			channel = null;
+			out.stop();
+			err.stop();
 		}
 	}
 
diff --git a/server-webapp/src/main/java/org/taverna/server/master/localworker/StreamLogger.java b/server-webapp/src/main/java/org/taverna/server/master/localworker/StreamLogger.java
new file mode 100644
index 0000000..f361e17
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/localworker/StreamLogger.java
@@ -0,0 +1,62 @@
+package org.taverna.server.master.localworker;
+
+import static java.lang.Thread.interrupted;
+import static org.apache.commons.logging.LogFactory.getLog;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.apache.commons.logging.Log;
+
+abstract class StreamLogger {
+	protected final Log log;
+	private Thread t;
+	private InputStream in;
+
+	protected StreamLogger(final String name, InputStream is) {
+		log = getLog("Taverna.Server.LocalWorker." + name);
+		in = is;
+		t = new Thread(new Runnable() {
+			@Override
+			public void run() {
+				try (BufferedReader br = new BufferedReader(
+						new InputStreamReader(in))) {
+					String line;
+					while (!interrupted() && (line = br.readLine()) != null)
+						if (!line.isEmpty())
+							write(line);
+				} catch (IOException e) {
+					// Do nothing...
+				} catch (Exception e) {
+					log.warn("failure in reading from " + name, e);
+				}
+			}
+		}, name + ".StreamLogger");
+		t.setContextClassLoader(null);
+		t.setDaemon(true);
+		t.start();
+	}
+
+	/**
+	 * Write a line read from the subprocess to the log.
+	 * <p>
+	 * This needs to be implemented by subclasses in order for the log to be
+	 * correctly written with the class name.
+	 * 
+	 * @param msg
+	 *            The message to write. Guaranteed to have no newline characters
+	 *            in it and to be non-empty.
+	 */
+	protected abstract void write(String msg);
+
+	public void stop() {
+		log.info("trying to close down " + t.getName());
+		t.interrupt();
+		try {
+			in.close();
+		} catch (IOException e) {
+		}
+	}
+}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/notification/atom/EventDAO.java b/server-webapp/src/main/java/org/taverna/server/master/notification/atom/EventDAO.java
index ac33133..56f25ff 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/notification/atom/EventDAO.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/notification/atom/EventDAO.java
@@ -5,6 +5,10 @@
  */
 package org.taverna.server.master.notification.atom;
 
+import static java.lang.Thread.interrupted;
+import static java.lang.Thread.sleep;
+import static java.util.Arrays.asList;
+
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Date;
@@ -13,6 +17,7 @@
 import java.util.concurrent.BlockingQueue;
 
 import javax.annotation.Nonnull;
+import javax.annotation.PreDestroy;
 import javax.jdo.annotations.PersistenceAware;
 
 import org.apache.commons.logging.Log;
@@ -155,25 +160,41 @@
 				messageSubject, messageContent));
 	}
 
+	private Thread eventDaemon;
+	private boolean shuttingDown = false;
+
 	@Required
 	public void setSelf(final EventDAO dao) {
-		Thread t = new Thread(new Runnable() {
+		eventDaemon = new Thread(new Runnable() {
 			@Override
 			public void run() {
 				try {
-					while (true) {
-						ArrayList<Event> e = new ArrayList<>();
-						e.add(insertQueue.take());
-						insertQueue.drainTo(e);
-						dao.storeEvents(e);
-						Thread.sleep(5000);
+					while (!shuttingDown && !interrupted()) {
+						transferEvents(dao, new ArrayList<Event>(
+								asList(insertQueue.take())));
+						sleep(5000);
 					}
 				} catch (InterruptedException e) {
+				} finally {
+					transferEvents(dao, new ArrayList<Event>());
 				}
 			}
-		});
-		t.setDaemon(true);
-		t.start();
+		}, "ATOM event daemon");
+		eventDaemon.setContextClassLoader(null);
+		eventDaemon.setDaemon(true);
+		eventDaemon.start();
+	}
+
+	private void transferEvents(EventDAO dao, List<Event> e) {
+		insertQueue.drainTo(e);
+		dao.storeEvents(e);
+	}
+
+	@PreDestroy
+	void stopDaemon() {
+		shuttingDown = true;
+		if (eventDaemon != null)
+			eventDaemon.interrupt();
 	}
 
 	@WithinSingleTransaction
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/FileMessageHandler.java b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/FileMessageHandler.java
index f181a61..0aeb816 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/FileMessageHandler.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/FileMessageHandler.java
@@ -5,7 +5,7 @@
  */
 package org.taverna.server.master.rest.handler;
 
-import static org.taverna.server.master.TavernaServer.log;
+import static org.apache.commons.logging.LogFactory.getLog;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -18,6 +18,7 @@
 import javax.ws.rs.ext.MessageBodyWriter;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.commons.logging.Log;
 import org.taverna.server.master.exceptions.FilesystemAccessException;
 import org.taverna.server.master.interfaces.File;
 
@@ -28,6 +29,7 @@
  */
 @Provider
 public class FileMessageHandler implements MessageBodyWriter<File> {
+	private Log log = getLog("Taverna.Server.Webapp");
 	/** How much to pull from the worker in one read. */
 	private int maxChunkSize;
 
diff --git a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/HandlerCore.java b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/HandlerCore.java
index efaf47d..0e3fb51 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/rest/handler/HandlerCore.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/rest/handler/HandlerCore.java
@@ -7,10 +7,11 @@
 
 import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
 import static javax.ws.rs.core.Response.status;
-import static org.taverna.server.master.TavernaServer.log;
+import static org.apache.commons.logging.LogFactory.getLog;
 
 import javax.ws.rs.core.Response;
 
+import org.apache.commons.logging.Log;
 import org.taverna.server.master.api.ManagementModel;
 
 /**
@@ -20,6 +21,7 @@
  * @author Donal Fellows
  */
 public class HandlerCore {
+	private Log log = getLog("Taverna.Server.Webapp");
 	private ManagementModel managementModel;
 
 	/**
diff --git a/server-webapp/src/main/java/org/taverna/server/master/usage/UsageRecordRecorder.java b/server-webapp/src/main/java/org/taverna/server/master/usage/UsageRecordRecorder.java
index 16f8689..12de304 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/usage/UsageRecordRecorder.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/usage/UsageRecordRecorder.java
@@ -5,7 +5,7 @@
  */
 package org.taverna.server.master.usage;
 
-import static org.taverna.server.master.TavernaServer.log;
+import static org.apache.commons.logging.LogFactory.getLog;
 
 import java.io.FileWriter;
 import java.io.IOException;
@@ -17,6 +17,7 @@
 import javax.annotation.PreDestroy;
 import javax.xml.bind.JAXBException;
 
+import org.apache.commons.logging.Log;
 import org.ogf.usage.JobUsageRecord;
 import org.springframework.beans.factory.annotation.Required;
 import org.taverna.server.master.api.ManagementModel;
@@ -30,6 +31,7 @@
  * @author Donal Fellows
  */
 public class UsageRecordRecorder extends JDOSupport<UsageRecord> {
+	private Log log = getLog("Taverna.Server.Webapp");
 	public UsageRecordRecorder() {
 		super(UsageRecord.class);
 	}
@@ -153,10 +155,8 @@
 		return result;
 	}
 
-	@Override
 	@PreDestroy
 	public void close() {
-		super.close();
 		if (writer != null)
 			writer.close();
 	}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java b/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java
index f94d497..c27502f 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java
@@ -165,8 +165,20 @@
 			return null;
 		synchronized (this) {
 			if (!cache.containsKey(uri)) {
+				int port = uri.getPort();
+				if (port == -1)
+					switch (uri.getScheme()) {
+					case "http":
+						port = 80;
+						break;
+					case "https":
+						port = 443;
+						break;
+					default:
+						return null;
+					}
 				X509Certificate[] chain = getCertificateChainForService(
-						uri.getHost(), uri.getPort());
+						uri.getHost(), port);
 				if (chain != null)
 					cache.put(uri, unmodifiableList(asList(chain)));
 				else
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java b/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java
index 1b41ab7..f8b39e3 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java
@@ -1,5 +1,6 @@
 package org.taverna.server.master.utils;
 
+import java.io.EOFException;
 import java.io.IOException;
 import java.io.Writer;
 
@@ -26,30 +27,42 @@
 }
 
 class DBLog extends Writer {
-	Log log = LogFactory.getLog("Taverna.Server.Database");
-	StringBuilder sb = new StringBuilder();
+	private Log log = LogFactory.getLog("Taverna.Server.Database");
+	private StringBuilder sb = new StringBuilder();
+	private boolean closed = false;
 
 	@Override
 	public void write(char[] cbuf, int off, int len) throws IOException {
+		if (closed)
+			throw new EOFException();
 		if (!log.isInfoEnabled())
 			return;
 		sb.append(cbuf, off, len);
-		while (true) {
-			int idx = sb.indexOf("\n");
+		while (!closed) {
+			int idx = sb.indexOf("\n"), realIdx = idx;
 			if (idx < 0)
 				break;
-			if (idx > 0 && sb.charAt(idx - 1) == '\r')
+			char ch;
+			while (idx > 0 && ((ch = sb.charAt(idx - 1)) == '\r' || ch == ' ' || ch == '\t'))
 				idx--;
-			log.info(sb.substring(0, idx));
-			sb.delete(0, idx + 1);
+			if (idx > 0)
+				log.info(sb.substring(0, idx));
+			sb.delete(0, realIdx + 1);
 		}
 	}
 
 	@Override
 	public void flush() throws IOException {
+		if (sb.length() > 0) {
+			log.info(sb.toString());
+			sb = new StringBuilder();
+		}
 	}
 
 	@Override
 	public void close() throws IOException {
+		flush();
+		closed = true;
+		sb = null;
 	}
 }
\ No newline at end of file
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java b/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java
index 9bfe8b4..ba9ec81 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java
@@ -9,8 +9,10 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import static org.apache.commons.logging.LogFactory.getLog;
 
+import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.WeakHashMap;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -37,7 +39,7 @@
  */
 public abstract class JDOSupport<T> {
 	private Class<T> contextClass;
-	PersistenceManager pm;
+	private PersistenceManagerBuilder pmb;
 
 	/**
 	 * Instantiate this class, supplying it a handle to the class that will be
@@ -51,18 +53,19 @@
 	}
 
 	/**
-	 * @param persistenceManagerFactory
+	 * @param persistenceManagerBuilder
 	 *            The JDO engine to use for managing persistence.
 	 */
 	@Required
-	public void setPersistenceManagerFactory(
-			PersistenceManagerFactory persistenceManagerFactory) {
-		pm = persistenceManagerFactory.getPersistenceManagerProxy();
+	public void setPersistenceManagerBuilder(
+			PersistenceManagerBuilder persistenceManagerBuilder) {
+		pmb = persistenceManagerBuilder;
 	}
 
-	@PreDestroy
-	public void close() {
-		pm.close();
+	private PersistenceManager pm() {
+		if (isPersistent())
+			return pmb.getPersistenceManager();
+		return null;
 	}
 
 	/**
@@ -72,7 +75,7 @@
 	 * @return Whether there is a persistence manager installed.
 	 */
 	protected boolean isPersistent() {
-		return pm != null;
+		return pmb != null;
 	}
 
 	/**
@@ -84,7 +87,7 @@
 	 */
 	@Nonnull
 	protected Query query(@Nonnull String filter) {
-		return pm.newQuery(contextClass, filter);
+		return pm().newQuery(contextClass, filter);
 	}
 
 	/**
@@ -98,7 +101,7 @@
 	 */
 	@Nonnull
 	protected Query namedQuery(@Nonnull String name) {
-		return pm.newNamedQuery(contextClass, name);
+		return pm().newNamedQuery(contextClass, name);
 	}
 
 	/**
@@ -113,7 +116,7 @@
 	protected T persist(@Nullable T value) {
 		if (value == null)
 			return null;
-		return pm.makePersistent(value);
+		return pm().makePersistent(value);
 	}
 
 	/**
@@ -128,7 +131,7 @@
 	protected T detach(@Nullable T value) {
 		if (value == null)
 			return null;
-		return pm.detachCopy(value);
+		return pm().detachCopy(value);
 	}
 
 	/**
@@ -141,7 +144,7 @@
 	@Nullable
 	protected T getById(Object id) {
 		try {
-			return pm.getObjectById(contextClass, id);
+			return pm().getObjectById(contextClass, id);
 		} catch (Exception e) {
 			return null;
 		}
@@ -155,7 +158,7 @@
 	 */
 	protected void delete(@Nullable T value) {
 		if (value != null)
-			pm.deletePersistent(value);
+			pm().deletePersistent(value);
 	}
 
 	/**
@@ -173,9 +176,9 @@
 		Object applyTransaction(ProceedingJoinPoint pjp, JDOSupport<?> support)
 				throws Throwable {
 			synchronized (lock) {
+				PersistenceManager pm = support.pm();
 				int id = ++txid;
-				Transaction tx = support.pm == null ? null : support.pm
-						.currentTransaction();
+				Transaction tx = (pm == null) ? null : pm.currentTransaction();
 				if (tx != null && tx.isActive())
 					tx = null;
 				if (tx != null) {
@@ -217,6 +220,51 @@
 	 */
 	@Target(METHOD)
 	@Retention(RUNTIME)
+	@Documented
 	public @interface WithinSingleTransaction {
 	}
+
+	/**
+	 * Manages {@linkplain PersistenceManager persistence managers} in a way
+	 * that doesn't cause problems when the web application is unloaded.
+	 * 
+	 * @author Donal Fellows
+	 */
+	public static class PersistenceManagerBuilder {
+		private PersistenceManagerFactory pmf;
+		private WeakHashMap<Thread, PersistenceManager> cache = new WeakHashMap<>();
+
+		/**
+		 * @param persistenceManagerFactory
+		 *            The JDO engine to use for managing persistence.
+		 */
+		@Required
+		public void setPersistenceManagerFactory(
+				PersistenceManagerFactory persistenceManagerFactory) {
+			pmf = persistenceManagerFactory;
+		}
+
+		@Nonnull
+		public PersistenceManager getPersistenceManager() {
+			if (cache == null)
+				return pmf.getPersistenceManager();
+			Thread t = Thread.currentThread();
+			PersistenceManager pm = cache.get(t);
+			if (pm == null && pmf != null) {
+				pm = pmf.getPersistenceManager();
+				cache.put(t, pm);
+			}
+			return pm;
+		}
+
+		@PreDestroy
+		void clearThreadCache() {
+			WeakHashMap<Thread, PersistenceManager> cache = this.cache;
+			this.cache = null;
+			for (PersistenceManager pm : cache.values())
+				if (pm != null)
+					pm.close();
+			cache.clear();
+		}
+	}
 }
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java b/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java
index 5abb18f..a8aa937 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java
@@ -44,9 +44,9 @@
 		ddl.append(item);
 		if (!item.endsWith("\n"))
 			ddl.append('\n');
-		timeout = currentTimeMillis() + 15000;
+		timeout = currentTimeMillis() + 5000;
 		if (timer == null)
-			(timer = new Thread(new Runnable() {
+			timer = new OneShotThread("DDL logger timeout", new Runnable() {
 				@Override
 				public void run() {
 					try {
@@ -57,7 +57,7 @@
 					}
 					logDDL();
 				}
-			})).start();
+			});
 	}
 
 	/**
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java b/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java
new file mode 100644
index 0000000..68b813d
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java
@@ -0,0 +1,10 @@
+package org.taverna.server.master.utils;
+
+public class OneShotThread extends Thread {
+	public OneShotThread(String name, Runnable target) {
+		super(target, name);
+		setContextClassLoader(null);
+		setDaemon(true);
+		start();
+	}
+}
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java b/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java
index ca1f19f..03fc749 100644
--- a/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java
@@ -5,12 +5,17 @@
  */
 package org.taverna.server.master.utils;
 
+import static java.lang.Thread.currentThread;
+import static java.sql.DriverManager.deregisterDriver;
+import static java.sql.DriverManager.getDrivers;
 import static org.taverna.server.master.utils.Contextualizer.ROOT_PLACEHOLDER;
 
 import java.io.PrintWriter;
 import java.sql.Connection;
+import java.sql.Driver;
 import java.sql.DriverManager;
 import java.sql.SQLException;
+import java.util.Enumeration;
 
 import javax.annotation.PreDestroy;
 
@@ -49,6 +54,7 @@
 	private void doInit() {
 		synchronized (this) {
 			if (!init) {
+				setDriverClassLoader(currentThread().getContextClassLoader());
 				String url = getUrl();
 				if (url.contains(ROOT_PLACEHOLDER)) {
 					String newurl = ctxt.contextualize(url);
@@ -108,5 +114,21 @@
 			// Expected; ignore it
 		}
 		log = null;
+		dropDriver();
+	}
+
+	private void dropDriver() {
+		Enumeration<Driver> drivers = getDrivers();
+		while (drivers.hasMoreElements()) {
+			Driver d = drivers.nextElement();
+			if (d.getClass().getClassLoader() == getDriverClassLoader()
+					&& d.getClass().getName().equals(getDriverClassName())) {
+				try {
+					deregisterDriver(d);
+				} catch (SQLException e) {
+				}
+				break;
+			}
+		}
 	}
 }
diff --git a/server-webapp/src/main/replacementscripts/executeworkflow.sh b/server-webapp/src/main/replacementscripts/executeworkflow.sh
index e698aa1..10d73b2 100644
--- a/server-webapp/src/main/replacementscripts/executeworkflow.sh
+++ b/server-webapp/src/main/replacementscripts/executeworkflow.sh
@@ -54,6 +54,9 @@
     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"
+    if test x != "x$INTERACTION_PUBLISH"; then
+    	INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.publishAddressOverride=$INTERACTION_PUBLISH"
+    fi
 fi
 
 MainClass=net.sf.taverna.t2.commandline.CommandLineLauncher
@@ -62,7 +65,7 @@
 exec "$javabin" $memlimit $permsize \
   "-Dlog4j.configuration=file://$taverna_home/conf/log4j.properties " \
   "-Djava.util.logging.config.file=$taverna_home/conf/logging.properties " \
-  "-Dtaverna.app.startup=$taverna_home" \
-  $APPHOME_PROP $RUNID_PROP $INTERACTION_PROPS $pre \
+  "-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/admin.html b/server-webapp/src/main/resources/admin.html
index 8ebbd7e..a80a783 100644
--- a/server-webapp/src/main/resources/admin.html
+++ b/server-webapp/src/main/resources/admin.html
@@ -94,7 +94,7 @@
 <br>
 <textarea title="Workflow URIs to limit execution to." rows="5" cols="60" id="workflows"></textarea>
 <p>
-<button id="saveWorkflows">Save</button> <button id="refreshWorkflows">Refresh</button>
+<button id="saveWorkflows">Save</button> <button id="refreshWorkflows">Refresh</button> <button id="emptyWorkflows">Empty URIs list</button>
 </div>
 
 <div id="t-usage">
diff --git a/server-webapp/src/main/resources/static/admin.js b/server-webapp/src/main/resources/static/admin.js
index c2b4406..c2c41bf 100644
--- a/server-webapp/src/main/resources/static/admin.js
+++ b/server-webapp/src/main/resources/static/admin.js
@@ -194,8 +194,9 @@
 
 /** How to get the list of permitted workflows; called on demand */
 function refreshWorkflows() {
-	var wftable = $("#workflows"), wfbut = $("#saveWorkflows"), wfref = $("#refreshWorkflows");
+	var wftable = $("#workflows"), wfbut = $("#saveWorkflows"), wfbut1 = $("#emptyWorkflows"), wfref = $("#refreshWorkflows");
 	wfbut.button("disable");
+	wfbut1.button("disable");
 	wfref.button("disable");
 	getJSON(where("permittedWorkflowURIs"), function(data) {
 		var s = "";
@@ -204,19 +205,32 @@
 		});
 		wftable.val($.trim(s));
 		wfbut.button("enable");
+		wfbut1.button("enable");
 		wfref.button("enable");
 	});
 }
 /** How to set the list of permitted workflows; called when the user clicks */
 function saveWorkflows() {
-	var wftable = $("#workflows"), wfbut = $("#saveWorkflows");
+	var wftable = $("#workflows"), wfbut = $("#saveWorkflows"), wfbut1 = $("#emptyWorkflows");
 	var xml = NodeAll("stringList", "string", wftable.val().split("\n"));
 	wfbut.button("disable");
+	wfbut1.button("disable");
 	putXML(where("permittedWorkflowURIs"), xml, function() {
 		refreshWorkflows();
 	});
 }
 
+/** How to empty the list of permitted workflows; called when the user clicks */
+function emptyWorkflows() {
+        var wftable = $("#workflows"), wfbut = $("#saveWorkflows"), wfbut1 = $("#emptyWorkflows");
+        var xml = NodeAll("stringList", "string", "");
+        wfbut.button("disable");
+        wfbut1.button("disable");
+        putXML(where("permittedWorkflowURIs"), xml, function() {
+                refreshWorkflows();
+        });
+}
+
 /** How to update the table of users; called on demand */
 function refreshUsers() {
 	var usertable = $("#userList");
@@ -497,6 +511,12 @@
 		refreshWorkflows();
 		event.preventDefault();
 	});
+	$("#emptyWorkflows").button({
+                disabled : true
+        }).click(function(event) {
+                emptyWorkflows();
+                event.preventDefault();
+        });
 
 	// Make the link to the list of usage records point correctly
 	// Original plan called for browsable table, but that's too slow
diff --git a/server-webapp/src/main/webapp/WEB-INF/beans.xml b/server-webapp/src/main/webapp/WEB-INF/beans.xml
index af0d8cf..73990a7 100644
--- a/server-webapp/src/main/webapp/WEB-INF/beans.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/beans.xml
@@ -62,7 +62,7 @@
 		<property name="logIncomingWorkflows" value="${default.logworkflows}" />
 		<property name="allowNewWorkflowRuns" value="${default.permitsubmit}" />
 		<property name="logOutgoingExceptions" value="${default.logexceptions}" />
-		<property name="persistenceManagerFactory" ref="pmf" />
+		<property name="persistenceManagerBuilder" ref="pmb" />
 		<property name="self" ref="webapp.state" />
 	</bean>
 
@@ -179,7 +179,7 @@
 	<bean id="passwordEncoder"
 		class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
 	<bean class="org.taverna.server.master.identity.UserStore" id="userStore">
-		<property name="persistenceManagerFactory" ref="pmf" />
+		<property name="persistenceManagerBuilder" ref="pmb" />
 		<property name="baselineUserProperties">
 			<util:properties location="/WEB-INF/security/users.properties" />
 		</property>
@@ -192,24 +192,28 @@
 		<props> <prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop> 
 		<prop key="hibernate.hbm2ddl.auto">create</prop> </props> </property> <property 
 		name="annotatedClasses"> <list> </list> </property> </bean> -->
-	<bean id="pmf" class="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"
-		destroy-method="close">
-		<property name="connectionFactory" ref="dataSource" />
-		<property name="nontransactionalRead" value="true" />
-		<property name="persistenceProperties">
-			<props>
-				<prop key="datanucleus.storeManagerType">rdbms</prop>
-				<prop key="datanucleus.autoCreateTables">true</prop>
-				<prop key="datanucleus.autoCreateTables">true</prop>
-				<prop key="datanucleus.validateTables">true</prop>
-				<prop key="datanucleus.autoCreateColumns">true</prop>
-				<prop key="datanucleus.autoCreateConstraints">true</prop>
-				<prop key="datanucleus.validateConstraints">true</prop>
-				<prop key="datanucleus.autoCreateSchema">true</prop>
-				<prop key="datanucleus.PersistenceUnitName">TavernaServer</prop>
-				<prop key="datanucleus.rdbms.datastoreAdapterClassName"
-					>org.taverna.server.master.utils.LoggingDerbyAdapter</prop>
-			</props>
+	<bean id="pmb" class="org.taverna.server.master.utils.JDOSupport.PersistenceManagerBuilder">
+		<property name="persistenceManagerFactory">
+			<bean id="pmf" class="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"
+				destroy-method="close">
+				<property name="connectionFactory" ref="dataSource" />
+				<property name="nontransactionalRead" value="true" />
+				<property name="persistenceProperties">
+					<props>
+						<prop key="datanucleus.storeManagerType">rdbms</prop>
+						<prop key="datanucleus.autoCreateTables">true</prop>
+						<prop key="datanucleus.autoCreateTables">true</prop>
+						<prop key="datanucleus.validateTables">true</prop>
+						<prop key="datanucleus.autoCreateColumns">true</prop>
+						<prop key="datanucleus.autoCreateConstraints">true</prop>
+						<prop key="datanucleus.validateConstraints">true</prop>
+						<prop key="datanucleus.autoCreateSchema">true</prop>
+						<prop key="datanucleus.PersistenceUnitName">TavernaServer</prop>
+						<prop key="datanucleus.rdbms.datastoreAdapterClassName"
+							>org.taverna.server.master.utils.LoggingDerbyAdapter</prop>
+					</props>
+				</property>
+			</bean>
 		</property>
 	</bean>
 	<bean id="transactionAspect"
@@ -245,7 +249,7 @@
 	<bean id="usageRecordSink" class="org.taverna.server.master.usage.UsageRecordRecorder">
 		<property name="state" ref="webapp.state" />
 		<property name="contextualizer" ref="contextualizer" />
-		<property name="persistenceManagerFactory" ref="pmf" />
+		<property name="persistenceManagerBuilder" ref="pmb" />
 		<property name="self" ref="usageRecordSink" />
 		<property name="disableDB" value="${usage.disableDB}" />
 		<property name="logFile" value="${usage.logFile}" />
@@ -269,7 +273,7 @@
 		/> <property name="usageRecordSink" ref="usageRecordSink" /> <property name="URProcessorPool" 
 		ref="URThreads" /> </bean> -->
 
-	<task:executor id="URThreads" pool-size="${pool.size}" />
+	<!-- <task:executor id="URThreads" pool-size="${pool.size}" /> -->
 
 	<bean id="worker.securityContext"
 		class="org.taverna.server.master.worker.SecurityContextFactory">
@@ -326,7 +330,7 @@
 				the subprocess to complete its registration.
 			</description>
 		</property>
-		<property name="persistenceManagerFactory" ref="pmf" />
+		<property name="persistenceManagerBuilder" ref="pmb" />
 		<!-- <property name="javaBinary"> <description>The name of the java executable 
 			used to run the server worker. Defaults to the executable used to run the 
 			hosting environment.</description> </property> -->
@@ -388,7 +392,7 @@
 			The implementation of the catalog of workflow runs
 			supported by the localworker run engine.
 		</description>
-		<property name="persistenceManagerFactory" ref="pmf" />
+		<property name="persistenceManagerBuilder" ref="pmb" />
 		<property name="facade" ref="worker.rundb" />
 	</bean>
 	<task:scheduled-tasks scheduler="taskScheduler">
@@ -451,7 +455,7 @@
 
 	<bean id="dispatch.atom" class="org.taverna.server.master.notification.atom.EventDAO">
 		<property name="expiryAgeDays" value="${atom.lifespan}" />
-		<property name="persistenceManagerFactory" ref="pmf" />
+		<property name="persistenceManagerBuilder" ref="pmb" />
 		<property name="uriBuilderFactory" ref="feed" />
 		<property name="self" ref="dispatch.atom" />
 	</bean>
diff --git a/server-webapp/src/main/webapp/WEB-INF/web.xml b/server-webapp/src/main/webapp/WEB-INF/web-nosec.xml
similarity index 95%
copy from server-webapp/src/main/webapp/WEB-INF/web.xml
copy to server-webapp/src/main/webapp/WEB-INF/web-nosec.xml
index 335a5d3..d6c6ee7 100644
--- a/server-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/web-nosec.xml
@@ -10,8 +10,7 @@
 	<description>This is the front-end engine for Taverna 2.5.4 Server.</description>
 	<context-param>
 		<param-name>contextConfigLocation</param-name>
-		<param-value>WEB-INF/secure.xml</param-value>
-		<!-- <param-value>WEB-INF/insecure.xml</param-value> -->
+		<param-value>WEB-INF/insecure.xml</param-value>
 		<description>Where Spring is to load its bean definitions from. DO NOT
 			CHANGE WITHOUT CONSULTING DOCUMENTATION.</description>
 	</context-param>
diff --git a/server-webapp/src/main/webapp/WEB-INF/web.xml b/server-webapp/src/main/webapp/WEB-INF/web-partsec.xml
similarity index 95%
copy from server-webapp/src/main/webapp/WEB-INF/web.xml
copy to server-webapp/src/main/webapp/WEB-INF/web-partsec.xml
index 335a5d3..a2b545e 100644
--- a/server-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/web-partsec.xml
@@ -10,8 +10,7 @@
 	<description>This is the front-end engine for Taverna 2.5.4 Server.</description>
 	<context-param>
 		<param-name>contextConfigLocation</param-name>
-		<param-value>WEB-INF/secure.xml</param-value>
-		<!-- <param-value>WEB-INF/insecure.xml</param-value> -->
+		<param-value>WEB-INF/partsecure.xml</param-value>
 		<description>Where Spring is to load its bean definitions from. DO NOT
 			CHANGE WITHOUT CONSULTING DOCUMENTATION.</description>
 	</context-param>
diff --git a/server-webapp/src/main/webapp/WEB-INF/web.xml b/server-webapp/src/main/webapp/WEB-INF/web-sec.xml
similarity index 97%
rename from server-webapp/src/main/webapp/WEB-INF/web.xml
rename to server-webapp/src/main/webapp/WEB-INF/web-sec.xml
index 335a5d3..9a5395a 100644
--- a/server-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/web-sec.xml
@@ -11,7 +11,6 @@
 	<context-param>
 		<param-name>contextConfigLocation</param-name>
 		<param-value>WEB-INF/secure.xml</param-value>
-		<!-- <param-value>WEB-INF/insecure.xml</param-value> -->
 		<description>Where Spring is to load its bean definitions from. DO NOT
 			CHANGE WITHOUT CONSULTING DOCUMENTATION.</description>
 	</context-param>
diff --git a/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml b/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml
index 1b979b4..22e74b3 100644
--- a/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml
+++ b/server-webapp/src/main/webapp/WEB-INF/webappBeans.xml
@@ -97,6 +97,9 @@
 			<ref bean="jsonProvider" />
 			<ref bean="atomEntryHandler" />
 			<ref bean="atomFeedHandler" />
+			<bean class="org.apache.cxf.jaxrs.model.wadl.WadlGenerator">
+				<property name="addResourceAndMethodIds" value="true"/>
+			</bean>
 		</jaxrs:providers>
 		<jaxrs:outInterceptors>
 			<ref bean="Interceptor.FlushThreadLocalCache" />
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 05f9b1b..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
@@ -166,6 +166,7 @@
 	List<String> runtimeSettings = new ArrayList<>();
 	URL interactionFeedURL;
 	URL webdavURL;
+	URL publishURL;//FIXME
 	private boolean doProvenance = true;
 
 	// ----------------------- METHODS -----------------------
@@ -755,9 +756,10 @@
 	}
 
 	@Override
-	public void setInteractionServiceDetails(URL feed, URL webdav) {
+	public void setInteractionServiceDetails(URL feed, URL webdav, URL publish) {
 		interactionFeedURL = feed;
 		webdavURL = webdav;
+		publishURL = publish;
 	}
 
 	@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 7a24cd5..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
@@ -54,6 +54,8 @@
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.rmi.RemoteException;
 import java.rmi.server.UnicastRemoteObject;
@@ -442,6 +444,9 @@
 			env.put("INTERACTION_WEBDAV",
 					local.webdavURL != null ? local.webdavURL.getPath()
 							: interactionWebdavPath);
+			String pub = makeInterPublish(local.publishURL);
+			if (pub != null && !pub.isEmpty())
+				env.put("INTERACTION_PUBLISH", pub);
 		}
 		return pb;
 	}
@@ -464,6 +469,23 @@
 	}
 
 	@Nullable
+	private static String makeInterPublish(@Nullable URL url)
+			throws IOException {
+		if (url == null)
+			return null;
+		try {
+			URI uri = url.toURI();
+			int port = uri.getPort();
+			if (port == -1)
+				return uri.getScheme() + "://" + uri.getHost();
+			else
+				return uri.getScheme() + "://" + uri.getHost() + ":" + port;
+		} catch (URISyntaxException e) {
+			throw new IOException("problem constructing publication url", e);
+		}
+	}
+
+	@Nullable
 	private static String makeInterPath(@Nullable URL url) {
 		if (url == null)
 			return interactionFeedPath;