You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 16:37:02 UTC

[02/49] incubator-taverna-server git commit: taverna-* module names

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd b/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd
new file mode 100644
index 0000000..a485e30
--- /dev/null
+++ b/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd
@@ -0,0 +1,305 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- persistence.xml schema -->
+<xsd:schema targetNamespace="http://java.sun.com/xml/ns/persistence" 
+  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+  xmlns:persistence="http://java.sun.com/xml/ns/persistence"
+  elementFormDefault="qualified" 
+  attributeFormDefault="unqualified" 
+  version="1.0">
+
+  <xsd:annotation>
+    <xsd:documentation>
+      @(#)persistence_1_0.xsd  1.0  Feb 9 2006
+    </xsd:documentation>
+  </xsd:annotation>
+
+  <xsd:annotation>
+    <xsd:documentation>
+
+      DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+      Copyright 2005-2007 Sun Microsystems, Inc. All rights reserved.
+
+      The contents of this file are subject to the terms of either the
+      GNU General Public License Version 2 only ("GPL") or the Common
+      Development and Distribution License("CDDL") (collectively, the
+      "License").  You may not use this file except in compliance with
+      the License. You can obtain a copy of the License at
+      https://glassfish.dev.java.net/public/CDDL+GPL.html or
+      glassfish/bootstrap/legal/LICENSE.txt.  See the License for the
+      specific language governing permissions and limitations under the
+      License.
+
+      When distributing the software, include this License Header
+      Notice in each file and include the License file at
+      glassfish/bootstrap/legal/LICENSE.txt.  Sun designates this
+      particular file as subject to the "Classpath" exception as
+      provided by Sun in the GPL Version 2 section of the License file
+      that accompanied this code.  If applicable, add the following
+      below the License Header, with the fields enclosed by brackets []
+      replaced by your own identifying information:
+      "Portions Copyrighted [year] [name of copyright owner]"
+
+      Contributor(s):
+
+      If you wish your version of this file to be governed by only the
+      CDDL or only the GPL Version 2, indicate your decision by adding
+      "[Contributor] elects to include this software in this
+      distribution under the [CDDL or GPL Version 2] license."  If you
+      don't indicate a single choice of license, a recipient has the
+      option to distribute your version of this file under either the
+      CDDL, the GPL Version 2 or to extend the choice of license to its
+      licensees as provided above.  However, if you add GPL Version 2
+      code and therefore, elected the GPL Version 2 license, then the
+      option applies only if the new code is made subject to such
+      option by the copyright holder.
+
+    </xsd:documentation>
+  </xsd:annotation>
+
+   <xsd:annotation>
+     <xsd:documentation><![CDATA[
+
+     This is the XML Schema for the persistence configuration file.
+     The file must be named "META-INF/persistence.xml" in the 
+     persistence archive.
+     Persistence configuration files must indicate
+     the persistence schema by using the persistence namespace:
+
+     http://java.sun.com/xml/ns/persistence
+
+     and indicate the version of the schema by
+     using the version element as shown below:
+
+      <persistence xmlns="http://java.sun.com/xml/ns/persistence"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
+          http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+        version="1.0">
+          ...
+      </persistence>
+
+    ]]></xsd:documentation>
+  </xsd:annotation>
+
+  <xsd:simpleType name="versionType">
+    <xsd:restriction base="xsd:token">
+      <xsd:pattern value="[0-9]+(\.[0-9]+)*"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+  <!-- **************************************************** -->
+
+  <xsd:element name="persistence">
+    <xsd:complexType>
+      <xsd:sequence>
+
+        <!-- **************************************************** -->
+
+        <xsd:element name="persistence-unit" 
+                     minOccurs="0" maxOccurs="unbounded">
+          <xsd:complexType>
+            <xsd:annotation>
+              <xsd:documentation>
+
+                Configuration of a persistence unit.
+
+              </xsd:documentation>
+            </xsd:annotation>
+            <xsd:sequence>
+
+            <!-- **************************************************** -->
+
+              <xsd:element name="description" type="xsd:string" 
+                           minOccurs="0">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    Textual description of this persistence unit.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="provider" type="xsd:string" 
+                           minOccurs="0">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    Provider class that supplies EntityManagers for this 
+                    persistence unit.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="jta-data-source" type="xsd:string" 
+                           minOccurs="0">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    The container-specific name of the JTA datasource to use.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="non-jta-data-source" type="xsd:string" 
+                           minOccurs="0">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    The container-specific name of a non-JTA datasource to use.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="mapping-file" type="xsd:string" 
+                           minOccurs="0" maxOccurs="unbounded">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    File containing mapping information. Loaded as a resource 
+                    by the persistence provider.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="jar-file" type="xsd:string" 
+                           minOccurs="0" maxOccurs="unbounded">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    Jar file that should be scanned for entities. 
+                    Not applicable to Java SE persistence units.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="class" type="xsd:string" 
+                           minOccurs="0" maxOccurs="unbounded">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    Class to scan for annotations.  It should be annotated 
+                    with either @Entity, @Embeddable or @MappedSuperclass.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="exclude-unlisted-classes" type="xsd:boolean" 
+                           default="false" minOccurs="0">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    When set to true then only listed classes and jars will 
+                    be scanned for persistent classes, otherwise the enclosing 
+                    jar or directory will also be scanned. Not applicable to 
+                    Java SE persistence units.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+              </xsd:element>
+
+              <!-- **************************************************** -->
+
+              <xsd:element name="properties" minOccurs="0">
+                <xsd:annotation>
+                  <xsd:documentation>
+
+                    A list of vendor-specific properties.
+
+                  </xsd:documentation>
+                </xsd:annotation>
+                <xsd:complexType>
+                  <xsd:sequence>
+                    <xsd:element name="property" 
+                                 minOccurs="0" maxOccurs="unbounded">
+                      <xsd:annotation>
+                        <xsd:documentation>
+                          A name-value pair.
+                        </xsd:documentation>
+                      </xsd:annotation>
+                      <xsd:complexType>
+                        <xsd:attribute name="name" type="xsd:string" 
+                                       use="required"/>
+                        <xsd:attribute name="value" type="xsd:string" 
+                                       use="required"/>
+                      </xsd:complexType>
+                    </xsd:element>
+                  </xsd:sequence>
+                </xsd:complexType>
+              </xsd:element>
+
+            </xsd:sequence>
+
+            <!-- **************************************************** -->
+
+            <xsd:attribute name="name" type="xsd:string" use="required">
+              <xsd:annotation>
+                <xsd:documentation>
+
+                  Name used in code to reference this persistence unit.
+
+                </xsd:documentation>
+              </xsd:annotation>
+            </xsd:attribute>
+
+            <!-- **************************************************** -->
+
+            <xsd:attribute name="transaction-type" 
+                           type="persistence:persistence-unit-transaction-type">
+              <xsd:annotation>
+                <xsd:documentation>
+
+                  Type of transactions used by EntityManagers from this 
+                  persistence unit.
+
+                </xsd:documentation>
+              </xsd:annotation>
+            </xsd:attribute>
+
+          </xsd:complexType>
+        </xsd:element>
+      </xsd:sequence>
+      <xsd:attribute name="version" type="persistence:versionType" 
+                     fixed="1.0" use="required"/>
+    </xsd:complexType>
+  </xsd:element>
+
+  <!-- **************************************************** -->
+
+  <xsd:simpleType name="persistence-unit-transaction-type">
+    <xsd:annotation>
+      <xsd:documentation>
+
+        public enum TransactionType { JTA, RESOURCE_LOCAL };
+
+      </xsd:documentation>
+    </xsd:annotation>
+    <xsd:restriction base="xsd:token">
+      <xsd:enumeration value="JTA"/>
+      <xsd:enumeration value="RESOURCE_LOCAL"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+</xsd:schema>
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java
new file mode 100644
index 0000000..79c026b
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2010-2012 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Arrays;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.SchemaOutputResolver;
+import javax.xml.transform.Result;
+import javax.xml.transform.stream.StreamResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.taverna.server.master.admin.Admin;
+import org.taverna.server.master.common.Credential.KeyPair;
+import org.taverna.server.master.common.Credential.Password;
+import org.taverna.server.master.common.Capability;
+import org.taverna.server.master.common.DirEntryReference;
+import org.taverna.server.master.common.InputDescription;
+import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
+import org.taverna.server.master.common.RunReference;
+import org.taverna.server.master.common.Status;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.common.Uri;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.rest.DirectoryContents;
+import org.taverna.server.master.rest.ListenerDefinition;
+import org.taverna.server.master.rest.MakeOrUpdateDirEntry;
+import org.taverna.server.master.rest.TavernaServerInputREST.InDesc;
+import org.taverna.server.master.rest.TavernaServerInputREST.InputsDescriptor;
+import org.taverna.server.master.rest.TavernaServerListenersREST.ListenerDescription;
+import org.taverna.server.master.rest.TavernaServerListenersREST.Listeners;
+import org.taverna.server.master.rest.TavernaServerListenersREST.Properties;
+import org.taverna.server.master.rest.TavernaServerListenersREST.PropertyDescription;
+import org.taverna.server.master.rest.TavernaServerREST.EnabledNotificationFabrics;
+import org.taverna.server.master.rest.TavernaServerREST.PermittedListeners;
+import org.taverna.server.master.rest.TavernaServerREST.PermittedWorkflows;
+import org.taverna.server.master.rest.TavernaServerREST.PolicyView.CapabilityList;
+import org.taverna.server.master.rest.TavernaServerREST.PolicyView.PolicyDescription;
+import org.taverna.server.master.rest.TavernaServerREST.RunList;
+import org.taverna.server.master.rest.TavernaServerREST.ServerDescription;
+import org.taverna.server.master.rest.TavernaServerRunREST.RunDescription;
+import org.taverna.server.master.rest.TavernaServerSecurityREST;
+import org.taverna.server.master.rest.TavernaServerSecurityREST.CredentialHolder;
+import org.taverna.server.master.soap.DirEntry;
+import org.taverna.server.master.soap.FileContents;
+import org.taverna.server.master.soap.PermissionList;
+
+/**
+ * This test file ensures that the JAXB bindings will work once deployed instead
+ * of mysteriously failing in service.
+ * 
+ * @author Donal Fellows
+ */
+public class JaxbSanityTest {
+	SchemaOutputResolver sink;
+	StringWriter schema;
+
+	String schema() {
+		return schema.toString();
+	}
+
+	@Before
+	public void init() {
+		schema = new StringWriter();
+		sink = new SchemaOutputResolver() {
+			@Override
+			public Result createOutput(String namespaceUri,
+					String suggestedFileName) throws IOException {
+				StreamResult sr = new StreamResult(schema);
+				sr.setSystemId("/dev/null");
+				return sr;
+			}
+		};
+		assertEquals("", schema());
+	}
+
+	private boolean printSchema = false;
+
+	private void testJAXB(Class<?>... classes) throws Exception {
+		JAXBContext.newInstance(classes).generateSchema(sink);
+		if (printSchema)
+			System.out.println(schema());
+		assertTrue(schema().length() > 0);
+	}
+
+	@Test
+	public void testJAXBForDirEntryReference() throws Exception {
+		JAXBContext.newInstance(DirEntryReference.class).generateSchema(sink);
+		assertTrue(schema().length() > 0);
+	}
+
+	@Test
+	public void testJAXBForInputDescription() throws Exception {
+		testJAXB(InputDescription.class);
+	}
+
+	@Test
+	public void testJAXBForRunReference() throws Exception {
+		testJAXB(RunReference.class);
+	}
+
+	@Test
+	public void testJAXBForWorkflow() throws Exception {
+		testJAXB(Workflow.class);
+	}
+
+	@Test
+	public void testJAXBForStatus() throws Exception {
+		testJAXB(Status.class);
+	}
+
+	@Test
+	public void testJAXBForUri() throws Exception {
+		testJAXB(Uri.class);
+	}
+
+	@Test
+	public void testJAXBForDirectoryContents() throws Exception {
+		testJAXB(DirectoryContents.class);
+	}
+
+	@Test
+	public void testJAXBForListenerDefinition() throws Exception {
+		testJAXB(ListenerDefinition.class);
+	}
+
+	@Test
+	public void testJAXBForMakeOrUpdateDirEntry() throws Exception {
+		testJAXB(MakeOrUpdateDirEntry.class);
+	}
+
+	@Test
+	public void testJAXBForInDesc() throws Exception {
+		testJAXB(InDesc.class);
+	}
+
+	@Test
+	public void testJAXBForInputsDescriptor() throws Exception {
+		testJAXB(InputsDescriptor.class);
+	}
+
+	@Test
+	public void testJAXBForListenerDescription() throws Exception {
+		testJAXB(ListenerDescription.class);
+	}
+
+	@Test
+	public void testJAXBForListeners() throws Exception {
+		testJAXB(Listeners.class);
+	}
+
+	@Test
+	public void testJAXBForProperties() throws Exception {
+		testJAXB(Properties.class);
+	}
+
+	@Test
+	public void testJAXBForPropertyDescription() throws Exception {
+		testJAXB(PropertyDescription.class);
+	}
+
+	@Test
+	public void testJAXBForPermittedListeners() throws Exception {
+		testJAXB(PermittedListeners.class);
+	}
+
+	@Test
+	public void testJAXBForPermittedWorkflows() throws Exception {
+		testJAXB(PermittedWorkflows.class);
+	}
+
+	@Test
+	public void testJAXBForEnabledNotifiers() throws Exception {
+		testJAXB(EnabledNotificationFabrics.class);
+	}
+
+	@Test
+	public void testJAXBForServerDescription() throws Exception {
+		testJAXB(ServerDescription.class);
+	}
+
+	@Test
+	public void testJAXBForRunDescription() throws Exception {
+		testJAXB(RunDescription.class);
+	}
+
+	@Test
+	public void testJAXBForRunList() throws Exception {
+		testJAXB(RunList.class);
+	}
+
+	@Test
+	public void testJAXBForPolicyDescription() throws Exception {
+		testJAXB(PolicyDescription.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityCredential() throws Exception {
+		testJAXB(CredentialHolder.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityCredentialList() throws Exception {
+		testJAXB(TavernaServerSecurityREST.CredentialList.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityTrust() throws Exception {
+		testJAXB(Trust.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityTrustList() throws Exception {
+		testJAXB(TavernaServerSecurityREST.TrustList.class);
+	}
+
+	@Test
+	public void testJAXBForPermission() throws Exception {
+		testJAXB(Permission.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityPermissionDescription() throws Exception {
+		testJAXB(TavernaServerSecurityREST.PermissionDescription.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityPermissionsDescription() throws Exception {
+		testJAXB(TavernaServerSecurityREST.PermissionsDescription.class);
+	}
+
+	@Test
+	public void testJAXBForSecurityDescriptor() throws Exception {
+		testJAXB(TavernaServerSecurityREST.Descriptor.class);
+	}
+
+	@Test
+	public void testJAXBForProfileList() throws Exception {
+		testJAXB(ProfileList.class);
+	}
+
+	@Test
+	public void testJAXBForDirEntry() throws Exception {
+		testJAXB(DirEntry.class);
+	}
+
+	@Test
+	public void testJAXBForCapability() throws Exception {
+		testJAXB(Capability.class);
+	}
+
+	@Test
+	public void testJAXBForCapabilityList() throws Exception {
+		testJAXB(CapabilityList.class);
+	}
+
+	@Test
+	public void testJAXBForEverythingREST() throws Exception {
+		testJAXB(DirEntryReference.class, InputDescription.class,
+				RunReference.class, Workflow.class, Status.class,
+				DirectoryContents.class, InDesc.class,
+				ListenerDefinition.class, MakeOrUpdateDirEntry.class,
+				InputsDescriptor.class, ListenerDescription.class,
+				Listeners.class, Properties.class, PropertyDescription.class,
+				PermittedListeners.class, PermittedWorkflows.class,
+				EnabledNotificationFabrics.class, ServerDescription.class,
+				RunDescription.class, Uri.class, RunList.class,
+				PolicyDescription.class, CredentialHolder.class, Trust.class,
+				TavernaServerSecurityREST.CredentialList.class,
+				TavernaServerSecurityREST.TrustList.class, Permission.class,
+				TavernaServerSecurityREST.Descriptor.class,
+				TavernaServerSecurityREST.PermissionDescription.class,
+				TavernaServerSecurityREST.PermissionsDescription.class,
+				ProfileList.class, Capability.class, CapabilityList.class);
+	}
+
+	@Test
+	public void testJAXBForEverythingSOAP() throws Exception {
+		testJAXB(DirEntry.class, FileContents.class, InputDescription.class,
+				Permission.class, PermissionList.class,
+				PermissionList.SinglePermissionMapping.class,
+				RunReference.class, Status.class, Trust.class, Uri.class,
+				ProfileList.class, Workflow.class, Capability.class);
+	}
+
+	@Test
+	public void testUserPassSerializeDeserialize() throws Exception {
+		JAXBContext c = JAXBContext.newInstance(CredentialHolder.class);
+
+		Password password = new Password();
+		password.username = "foo";
+		password.password = "bar";
+
+		// Serialize
+		StringWriter sw = new StringWriter();
+		CredentialHolder credIn = new CredentialHolder(password);
+		c.createMarshaller().marshal(credIn, sw);
+
+		// Deserialize
+		StringReader sr = new StringReader(sw.toString());
+		Object credOutObj = c.createUnmarshaller().unmarshal(sr);
+
+		// Test value-equivalence
+		assertEquals(credIn.getClass(), credOutObj.getClass());
+		CredentialHolder credOut = (CredentialHolder) credOutObj;
+		assertEquals(credIn.credential.getClass(),
+				credOut.credential.getClass());
+		assertEquals(credIn.getUserpass().username,
+				credOut.getUserpass().username);
+		assertEquals(credIn.getUserpass().password,
+				credOut.getUserpass().password);
+	}
+
+	@Test
+	public void testKeypairSerializeDeserialize() throws Exception {
+		JAXBContext c = JAXBContext.newInstance(CredentialHolder.class);
+
+		KeyPair keypair = new KeyPair();
+		keypair.credentialName = "foo";
+		keypair.credentialBytes = new byte[] { 1, 2, 3 };
+
+		// Serialize
+		StringWriter sw = new StringWriter();
+		CredentialHolder credIn = new CredentialHolder(keypair);
+		c.createMarshaller().marshal(credIn, sw);
+
+		// Deserialize
+		StringReader sr = new StringReader(sw.toString());
+		Object credOutObj = c.createUnmarshaller().unmarshal(sr);
+
+		// Test value-equivalence
+		assertEquals(credIn.getClass(), credOutObj.getClass());
+		CredentialHolder credOut = (CredentialHolder) credOutObj;
+		assertEquals(credIn.credential.getClass(),
+				credOut.credential.getClass());
+		assertEquals(credIn.getKeypair().credentialName,
+				credOut.getKeypair().credentialName);
+		assertTrue(Arrays.equals(credIn.getKeypair().credentialBytes,
+				credOut.getKeypair().credentialBytes));
+	}
+
+	@Test
+	public void testJAXBforAdmininstration() throws Exception {
+		testJAXB(Admin.AdminDescription.class);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java
new file mode 100644
index 0000000..9665cf0
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java
@@ -0,0 +1,246 @@
+package org.taverna.server.master;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.taverna.server.master.api.ManagementModel;
+import org.taverna.server.master.common.RunReference;
+import org.taverna.server.master.exceptions.BadPropertyValueException;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.mocks.ExampleRun;
+import org.taverna.server.master.mocks.MockPolicy;
+import org.taverna.server.master.mocks.SimpleListenerFactory;
+import org.taverna.server.master.mocks.SimpleNonpersistentRunStore;
+
+public class TavernaServerImplTest {
+	private TavernaServer server;
+	private MockPolicy policy;
+	private SimpleNonpersistentRunStore store;
+	@java.lang.SuppressWarnings("unused")
+	private ExampleRun.Builder runFactory;
+	private SimpleListenerFactory lFactory;
+	private TavernaServerSupport support;
+
+	private String lrunname;
+	private String lrunconf;
+
+	Listener makeListener(TavernaRun run, final String config) {
+		lrunname = run.toString();
+		lrunconf = config;
+		return new Listener() {
+			@Override
+			public String getConfiguration() {
+				return config;
+			}
+
+			@Override
+			public String getName() {
+				return "bar";
+			}
+
+			@Override
+			public String getProperty(String propName)
+					throws NoListenerException {
+				throw new NoListenerException();
+			}
+
+			@Override
+			public String getType() {
+				return "foo";
+			}
+
+			@Override
+			public String[] listProperties() {
+				return new String[0];
+			}
+
+			@Override
+			public void setProperty(String propName, String value)
+					throws NoListenerException, BadPropertyValueException {
+				throw new NoListenerException();
+			}
+		};
+	}
+
+	@Before
+	public void wireup() throws Exception {
+		// Wire everything up; ought to be done with Spring, but this works...
+		server = new TavernaServer() {
+			@Override
+			protected RunREST makeRunInterface() {
+				return new RunREST() {
+					@Override
+					protected ListenersREST makeListenersInterface() {
+						return new ListenersREST() {
+							@Override
+							protected SingleListenerREST makeListenerInterface() {
+								return new SingleListenerREST() {
+									@Override
+									protected ListenerPropertyREST makePropertyInterface() {
+										return new ListenerPropertyREST() {
+										};
+									}
+								};
+							}
+						};
+					}
+
+					@Override
+					protected RunSecurityREST makeSecurityInterface() {
+						return new RunSecurityREST() {
+						};
+					}
+
+					@Override
+					protected DirectoryREST makeDirectoryInterface() {
+						return new DirectoryREST() {
+						};
+					}
+
+					@Override
+					protected InputREST makeInputInterface() {
+						return new InputREST() {
+						};
+					}
+
+					@Override
+					protected InteractionFeed makeInteractionFeed() {
+						return null; // TODO...
+					}
+				};
+			}
+
+			@Override
+			public PolicyView getPolicyDescription() {
+				return new PolicyREST();
+			}
+		};
+		support = new TavernaServerSupport();
+		server.setSupport(support);
+		support.setWebapp(server);
+		support.setLogGetPrincipalFailures(false);
+		support.setStateModel(new ManagementModel() {
+			@Override
+			public boolean getAllowNewWorkflowRuns() {
+				return true;
+			}
+
+			@Override
+			public boolean getLogIncomingWorkflows() {
+				return false;
+			}
+
+			@Override
+			public boolean getLogOutgoingExceptions() {
+				return false;
+			}
+
+			@Override
+			public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) {
+			}
+
+			@Override
+			public void setLogIncomingWorkflows(boolean logIncomingWorkflows) {
+			}
+
+			@Override
+			public void setLogOutgoingExceptions(boolean logOutgoingExceptions) {
+			}
+
+			@Override
+			public String getUsageRecordLogFile() {
+				return null;
+			}
+
+			@Override
+			public void setUsageRecordLogFile(String usageRecordLogFile) {
+			}
+		});
+		server.setPolicy(policy = new MockPolicy());
+		support.setPolicy(policy);
+		server.setRunStore(store = new SimpleNonpersistentRunStore());
+		support.setRunStore(store);
+		store.setPolicy(policy);
+		support.setRunFactory(runFactory = new ExampleRun.Builder(1));
+		support.setListenerFactory(lFactory = new SimpleListenerFactory());
+		lFactory.setBuilders(singletonMap(
+				"foo",
+				(SimpleListenerFactory.Builder) new SimpleListenerFactory.Builder() {
+					@Override
+					public Listener build(TavernaRun run, String configuration)
+							throws NoListenerException {
+						return makeListener(run, configuration);
+					}
+				}));
+	}
+
+	@Test
+	public void defaults1() {
+		assertNotNull(server);
+	}
+
+	@Test
+	public void defaults2() {
+		assertEquals(10, server.getServerMaxRuns());
+	}
+
+	@Test
+	public void defaults3() {
+		assertEquals(1, server.getServerListeners().length);
+	}
+
+	@Test
+	public void defaults4() {
+		assertNotNull(support.getPrincipal());
+	}
+
+	@Test
+	public void serverAsksPolicyForMaxRuns() {
+		int oldmax = policy.maxruns;
+		try {
+			policy.maxruns = 1;
+			assertEquals(1, server.getServerMaxRuns());
+		} finally {
+			policy.maxruns = oldmax;
+		}
+	}
+
+	@Test
+	public void makeAndKillARun() throws NoUpdateException, UnknownRunException {
+		RunReference rr = server.submitWorkflow(null);
+		assertNotNull(rr);
+		assertNotNull(rr.name);
+		server.destroyRun(rr.name);
+	}
+
+	@Test
+	public void makeListenKillRun() throws Exception {
+		RunReference run = server.submitWorkflow(null);
+		try {
+			lrunname = lrunconf = null;
+			assertEquals(asList("foo"), asList(server.getServerListeners()));
+			String l = server.addRunListener(run.name, "foo", "foobar");
+			assertEquals("bar", l);
+			assertEquals("foobar", lrunconf);
+			assertEquals(lrunname, support.getRun(run.name).toString());
+			assertEquals(asList("default", "bar"),
+					asList(server.getRunListeners(run.name)));
+			assertEquals(0,
+					server.getRunListenerProperties(run.name, "bar").length);
+		} finally {
+			try {
+				server.destroyRun(run.name);
+			} catch (Exception e) {
+				// Ignore
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java
new file mode 100644
index 0000000..0450317
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java
@@ -0,0 +1,68 @@
+package org.taverna.server.master;
+
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_NS;
+import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_ROOTNAME;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.taverna.server.master.common.Workflow;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class WorkflowSerializationTest {
+	@Test
+	public void testWorkflowSerialization()
+			throws ParserConfigurationException, IOException,
+			ClassNotFoundException {
+		DocumentBuilder db = DocumentBuilderFactory.newInstance()
+				.newDocumentBuilder();
+		Document doc = db.getDOMImplementation().createDocument(null, null,
+				null);
+		Element workflow = doc.createElementNS(T2FLOW_NS, T2FLOW_ROOTNAME);
+		Element foo = doc.createElementNS("urn:foo:bar", "pqr:foo");
+		foo.setTextContent("bar");
+		foo.setAttribute("xyz", "abc");
+		workflow.appendChild(foo);
+		Workflow w = new Workflow(workflow);
+
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+			oos.writeObject(w);
+		}
+
+		Object o;
+		try (ObjectInputStream ois = new ObjectInputStream(
+				new ByteArrayInputStream(baos.toByteArray()))) {
+			o = ois.readObject();
+		}
+
+		Assert.assertNotNull(o);
+		Assert.assertEquals(w.getClass(), o.getClass());
+		Workflow w2 = (Workflow) o;
+		Assert.assertNotNull(w2.getT2flowWorkflow());
+		Element e = w2.getT2flowWorkflow();
+		Assert.assertEquals(T2FLOW_ROOTNAME, e.getLocalName());
+		Assert.assertEquals(T2FLOW_NS, e.getNamespaceURI());
+		e = (Element) e.getFirstChild();
+		Assert.assertEquals("foo", e.getLocalName());
+		Assert.assertEquals("pqr", e.getPrefix());
+		Assert.assertEquals("urn:foo:bar", e.getNamespaceURI());
+		Assert.assertEquals("bar", e.getTextContent());
+		Assert.assertEquals(1, e.getChildNodes().getLength());
+		// WARNING: These are dependent on how namespaces are encoded!
+		Assert.assertEquals(2, e.getAttributes().getLength());
+		Assert.assertEquals("xyz", ((Attr) e.getAttributes().item(1)).getLocalName());
+		Assert.assertEquals("abc", e.getAttribute("xyz"));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java
new file mode 100644
index 0000000..a2a0791
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.mocks;
+
+import static java.util.Calendar.MINUTE;
+import static java.util.Collections.unmodifiableList;
+import static java.util.UUID.randomUUID;
+import static org.taverna.server.master.common.Status.Initialized;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.xml.ws.handler.MessageContext;
+
+import org.springframework.security.core.context.SecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.Status;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.factories.RunFactory;
+import org.taverna.server.master.interfaces.Directory;
+import org.taverna.server.master.interfaces.Input;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.SecurityContextFactory;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+@SuppressWarnings("serial")
+public class ExampleRun implements TavernaRun, TavernaSecurityContext {
+	String id;
+	List<Listener> listeners;
+	Workflow workflow;
+	Status status;
+	Date expiry;
+	UsernamePrincipal owner;
+	String inputBaclava;
+	String outputBaclava;
+	java.io.File realRoot;
+	List<Input> inputs;
+	String name;
+
+	public ExampleRun(UsernamePrincipal creator, Workflow workflow, Date expiry) {
+		this.id = randomUUID().toString();
+		this.listeners = new ArrayList<>();
+		this.status = Initialized;
+		this.owner = creator;
+		this.workflow = workflow;
+		this.expiry = expiry;
+		this.inputs = new ArrayList<>();
+		listeners.add(new DefaultListener());
+	}
+
+	@Override
+	public void addListener(Listener l) {
+		listeners.add(l);
+	}
+
+	@Override
+	public void destroy() {
+		// This does nothing...
+	}
+
+	@Override
+	public Date getExpiry() {
+		return expiry;
+	}
+
+	@Override
+	public List<Listener> getListeners() {
+		return listeners;
+	}
+
+	@Override
+	public TavernaSecurityContext getSecurityContext() {
+		return this;
+	}
+
+	@Override
+	public Status getStatus() {
+		return status;
+	}
+
+	@Override
+	public Workflow getWorkflow() {
+		return workflow;
+	}
+
+	@Override
+	public Directory getWorkingDirectory() {
+		// LATER: Implement this!
+		throw new UnsupportedOperationException("not yet implemented");
+	}
+
+	@Override
+	public void setExpiry(Date d) {
+		if (d.after(new Date()))
+			this.expiry = d;
+	}
+
+	@Override
+	public String setStatus(Status s) {
+		this.status = s;
+		return null;
+	}
+
+	@Override
+	public UsernamePrincipal getOwner() {
+		return owner;
+	}
+
+	public static class Builder implements RunFactory {
+		private int lifetime;
+
+		public Builder(int initialLifetimeMinutes) {
+			this.lifetime = initialLifetimeMinutes;
+		}
+
+		@Override
+		public TavernaRun create(UsernamePrincipal creator, Workflow workflow) {
+			Calendar c = GregorianCalendar.getInstance();
+			c.add(MINUTE, lifetime);
+			return new ExampleRun(creator, workflow, c.getTime());
+		}
+
+		@Override
+		public boolean isAllowingRunsToStart() {
+			return true;
+		}
+	}
+
+	static final String[] emptyArray = new String[0];
+
+	class DefaultListener implements Listener {
+		@Override
+		public String getConfiguration() {
+			return "";
+		}
+
+		@Override
+		public String getName() {
+			return "default";
+		}
+
+		@Override
+		public String getType() {
+			return "default";
+		}
+
+		@Override
+		public String[] listProperties() {
+			return emptyArray;
+		}
+
+		@Override
+		public String getProperty(String propName) throws NoListenerException {
+			throw new NoListenerException("no such property");
+		}
+
+		@Override
+		public void setProperty(String propName, String value)
+				throws NoListenerException {
+			throw new NoListenerException("no such property");
+		}
+	}
+
+	@Override
+	public String getInputBaclavaFile() {
+		return inputBaclava;
+	}
+
+	@Override
+	public List<Input> getInputs() {
+		return unmodifiableList(inputs);
+	}
+
+	@Override
+	public String getOutputBaclavaFile() {
+		return outputBaclava;
+	}
+
+	class ExampleInput implements Input {
+		public String name;
+		public String file;
+		public String value;
+		public String delim;
+
+		public ExampleInput(String name) {
+			this.name = name;
+		}
+
+		@Override
+		public String getFile() {
+			return file;
+		}
+
+		@Override
+		public String getName() {
+			return name;
+		}
+
+		@Override
+		public String getValue() {
+			return value;
+		}
+
+		@Override
+		public void setFile(String file) throws FilesystemAccessException,
+				BadStateChangeException {
+			if (status != Status.Initialized)
+				throw new BadStateChangeException();
+			checkBadFilename(file);
+			this.file = file;
+			this.value = null;
+			inputBaclava = null;
+		}
+
+		@Override
+		public void setValue(String value) throws BadStateChangeException {
+			if (status != Status.Initialized)
+				throw new BadStateChangeException();
+			this.value = value;
+			this.file = null;
+			inputBaclava = null;
+		}
+
+		void reset() {
+			this.file = null;
+			this.value = null;
+		}
+
+		@Override
+		public String getDelimiter() {
+			return delim;
+		}
+
+		@Override
+		public void setDelimiter(String delimiter)
+				throws BadStateChangeException {
+			if (status != Status.Initialized)
+				throw new BadStateChangeException();
+			if (delimiter == null)
+				delim = null;
+			else
+				delim = delimiter.substring(0, 1);
+		}
+	}
+
+	@Override
+	public Input makeInput(String name) throws BadStateChangeException {
+		if (status != Status.Initialized)
+			throw new BadStateChangeException();
+		Input i = new ExampleInput(name);
+		inputs.add(i);
+		return i;
+	}
+
+	static void checkBadFilename(String filename)
+			throws FilesystemAccessException {
+		if (filename.startsWith("/"))
+			throw new FilesystemAccessException("filename may not be absolute");
+		if (Arrays.asList(filename.split("/")).contains(".."))
+			throw new FilesystemAccessException(
+					"filename may not refer to parent");
+	}
+
+	@Override
+	public void setInputBaclavaFile(String filename)
+			throws FilesystemAccessException, BadStateChangeException {
+		if (status != Status.Initialized)
+			throw new BadStateChangeException();
+		checkBadFilename(filename);
+		inputBaclava = filename;
+		for (Input i : inputs)
+			((ExampleInput) i).reset();
+	}
+
+	@Override
+	public void setOutputBaclavaFile(String filename)
+			throws FilesystemAccessException, BadStateChangeException {
+		if (status != Status.Initialized)
+			throw new BadStateChangeException();
+		if (filename != null)
+			checkBadFilename(filename);
+		outputBaclava = filename;
+	}
+
+	private Date created = new Date();
+	@Override
+	public Date getCreationTimestamp() {
+		return created;
+	}
+
+	@Override
+	public Date getFinishTimestamp() {
+		return null;
+	}
+
+	@Override
+	public Date getStartTimestamp() {
+		return null;
+	}
+
+	@Override
+	public Credential[] getCredentials() {
+		return new Credential[0];
+	}
+
+	@Override
+	public void addCredential(Credential toAdd) {
+	}
+
+	@Override
+	public void deleteCredential(Credential toDelete) {
+	}
+
+	@Override
+	public Trust[] getTrusted() {
+		return new Trust[0];
+	}
+
+	@Override
+	public void addTrusted(Trust toAdd) {
+	}
+
+	@Override
+	public void deleteTrusted(Trust toDelete) {
+	}
+
+	@Override
+	public void validateCredential(Credential c)
+			throws InvalidCredentialException {
+	}
+
+	@Override
+	public void validateTrusted(Trust t) throws InvalidCredentialException {
+	}
+
+	@Override
+	public void initializeSecurityFromSOAPContext(MessageContext context) {
+		// Do nothing
+	}
+
+	@Override
+	public void initializeSecurityFromRESTContext(HttpHeaders headers) {
+		// Do nothing
+	}
+
+	@Override
+	public void conveySecurity() throws GeneralSecurityException, IOException {
+		// Do nothing
+	}
+
+	@Override
+	public SecurityContextFactory getFactory() {
+		return null;
+	}
+
+	private Set<String> destroyers = new HashSet<String>();
+	private Set<String> updaters = new HashSet<String>();
+	private Set<String> readers = new HashSet<String>();
+	@Override
+	public Set<String> getPermittedDestroyers() {
+		return destroyers;
+	}
+
+	@Override
+	public void setPermittedDestroyers(Set<String> destroyers) {
+		this.destroyers = destroyers;
+		updaters.addAll(destroyers);
+		readers.addAll(destroyers);
+	}
+
+	@Override
+	public Set<String> getPermittedUpdaters() {
+		return updaters;
+	}
+
+	@Override
+	public void setPermittedUpdaters(Set<String> updaters) {
+		this.updaters = updaters;
+		this.updaters.addAll(destroyers);
+		readers.addAll(updaters);
+	}
+
+	@Override
+	public Set<String> getPermittedReaders() {
+		return readers;
+	}
+
+	@Override
+	public void setPermittedReaders(Set<String> readers) {
+		this.readers = readers;
+		this.readers.addAll(destroyers);
+		this.readers.addAll(updaters);
+	}
+
+	@Override
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public void initializeSecurityFromContext(SecurityContext securityContext)
+			throws Exception {
+		// Do nothing
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public void setName(String name) {
+		this.name = (name.length() > 5 ? name.substring(0, 5) : name);
+	}
+
+	@Override
+	public void ping() throws UnknownRunException {
+		// Do nothing
+	}
+
+	@Override
+	public boolean getGenerateProvenance() {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	@Override
+	public void setGenerateProvenance(boolean generateProvenance) {
+		// TODO Auto-generated method stub
+		
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java
new file mode 100644
index 0000000..81dd08c
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java
@@ -0,0 +1,59 @@
+package org.taverna.server.master.mocks;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.exceptions.NoCreateException;
+import org.taverna.server.master.exceptions.NoDestroyException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+public class MockPolicy extends SimpleServerPolicy {
+	public MockPolicy() {
+		super();
+		super.setCleanerInterval(30);
+	}
+
+	public int maxruns = 10;
+	Integer usermaxruns;
+	Set<TavernaRun> denyaccess = new HashSet<>();
+	boolean exnOnUpdate, exnOnCreate, exnOnDelete;
+
+	@Override
+	public int getMaxRuns() {
+		return maxruns;
+	}
+
+	@Override
+	public Integer getMaxRuns(UsernamePrincipal user) {
+		return usermaxruns;
+	}
+
+	@Override
+	public boolean permitAccess(UsernamePrincipal user, TavernaRun run) {
+		return !denyaccess.contains(run);
+	}
+
+	@Override
+	public void permitCreate(UsernamePrincipal user, Workflow workflow)
+			throws NoCreateException {
+		if (this.exnOnCreate)
+			throw new NoCreateException();
+	}
+
+	@Override
+	public void permitDestroy(UsernamePrincipal user, TavernaRun run)
+			throws NoDestroyException {
+		if (this.exnOnDelete)
+			throw new NoDestroyException();
+	}
+
+	@Override
+	public void permitUpdate(UsernamePrincipal user, TavernaRun run)
+			throws NoUpdateException {
+		if (this.exnOnUpdate)
+			throw new NoUpdateException();
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java
new file mode 100644
index 0000000..d864214
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java
@@ -0,0 +1,64 @@
+package org.taverna.server.master.mocks;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.factories.ListenerFactory;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+
+/**
+ * A factory for event listener. The factory is configured using Spring.
+ * 
+ * @author Donal Fellows
+ */
+public class SimpleListenerFactory implements ListenerFactory {
+	private Map<String, Builder> builders = new HashMap<>();
+
+	public void setBuilders(Map<String, Builder> builders) {
+		this.builders = builders;
+	}
+
+	@Override
+	public List<String> getSupportedListenerTypes() {
+		return new ArrayList<>(builders.keySet());
+	}
+
+	@Override
+	public Listener makeListener(TavernaRun run, String listenerType,
+			String configuration) throws NoListenerException {
+		Builder b = builders.get(listenerType);
+		if (b == null)
+			throw new NoListenerException("no such listener type");
+		Listener l = b.build(run, configuration);
+		run.addListener(l);
+		return l;
+	}
+
+	/**
+	 * How to actually construct a listener.
+	 * 
+	 * @author Donal Fellows
+	 */
+	public interface Builder {
+		/**
+		 * Make an event listener attached to a run.
+		 * 
+		 * @param run
+		 *            The run to attach to.
+		 * @param configuration
+		 *            A user-specified configuration document. The constructed
+		 *            listener <i>should</i> process this configuration document
+		 *            and be able to return it to the user when requested.
+		 * @return The listener object.
+		 * @throws NoListenerException
+		 *             If the listener construction failed or the
+		 *             <b>configuration</b> document was bad in some way.
+		 */
+		public Listener build(TavernaRun run, String configuration)
+				throws NoListenerException;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java
new file mode 100644
index 0000000..a3751e4
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java
@@ -0,0 +1,151 @@
+package org.taverna.server.master.mocks;
+
+import java.lang.ref.WeakReference;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.taverna.server.master.exceptions.NoDestroyException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.interfaces.Policy;
+import org.taverna.server.master.interfaces.RunStore;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+/**
+ * Example of a store for Taverna Workflow Runs.
+ * 
+ * @author Donal Fellows
+ */
+public class SimpleNonpersistentRunStore implements RunStore {
+	private Map<String, TavernaRun> store = new HashMap<>();
+	private Object lock = new Object();
+
+	Timer timer;
+	private CleanerTask cleaner;
+
+	/**
+	 * The connection to the main policy store. Suitable for wiring up with
+	 * Spring.
+	 * 
+	 * @param p
+	 *            The policy to connect to.
+	 */
+	public void setPolicy(SimpleServerPolicy p) {
+		p.store = this;
+		cleanerIntervalUpdated(p.getCleanerInterval());
+	}
+
+	public SimpleNonpersistentRunStore() {
+		timer = new Timer("SimpleNonpersistentRunStore.CleanerTimer", true);
+		cleanerIntervalUpdated(300);
+	}
+
+	@Override
+	protected void finalize() {
+		timer.cancel();
+	}
+
+	/**
+	 * Remove and destroy all runs that are expired at the moment that this
+	 * method starts.
+	 */
+	void clean() {
+		Date now = new Date();
+		synchronized (lock) {
+			// Use an iterator so we have access to its remove() method...
+			Iterator<TavernaRun> i = store.values().iterator();
+			while (i.hasNext()) {
+				TavernaRun w = i.next();
+				if (w.getExpiry().before(now)) {
+					i.remove();
+					try {
+						w.destroy();
+					} catch (NoDestroyException e) {
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Reconfigure the cleaner task's call interval. This is called internally
+	 * and from the Policy when the interval is set there.
+	 * 
+	 * @param intervalInSeconds
+	 *            How long between runs of the cleaner task, in seconds.
+	 */
+	void cleanerIntervalUpdated(int intervalInSeconds) {
+		if (cleaner != null)
+			cleaner.cancel();
+		cleaner = new CleanerTask(this, intervalInSeconds);
+	}
+
+	@Override
+	public TavernaRun getRun(UsernamePrincipal user, Policy p, String uuid)
+			throws UnknownRunException {
+		synchronized (lock) {
+			TavernaRun w = store.get(uuid);
+			if (w == null || !p.permitAccess(user, w))
+				throw new UnknownRunException();
+			return w;
+		}
+	}
+
+	@Override
+	public TavernaRun getRun(String uuid) throws UnknownRunException {
+		synchronized (lock) {
+			TavernaRun w = store.get(uuid);
+			if (w == null)
+				throw new UnknownRunException();
+			return w;
+		}
+	}
+
+	@Override
+	public Map<String, TavernaRun> listRuns(UsernamePrincipal user, Policy p) {
+		Map<String, TavernaRun> filtered = new HashMap<>();
+		synchronized (lock) {
+			for (Map.Entry<String, TavernaRun> entry : store.entrySet())
+				if (p.permitAccess(user, entry.getValue()))
+					filtered.put(entry.getKey(), entry.getValue());
+		}
+		return filtered;
+	}
+
+	@Override
+	public String registerRun(TavernaRun run) {
+		synchronized (lock) {
+			store.put(run.getId(), run);
+			return run.getId();
+		}
+	}
+
+	@Override
+	public void unregisterRun(String uuid) {
+		synchronized (lock) {
+			store.remove(uuid);
+		}
+	}
+}
+
+class CleanerTask extends TimerTask {
+	WeakReference<SimpleNonpersistentRunStore> store;
+
+	CleanerTask(SimpleNonpersistentRunStore store, int interval) {
+		this.store = new WeakReference<>(store);
+		int tms = interval * 1000;
+		store.timer.scheduleAtFixedRate(this, tms, tms);
+	}
+
+	@Override
+	public void run() {
+		SimpleNonpersistentRunStore s = store.get();
+		if (s != null) {
+			s.clean();
+		}
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java
new file mode 100644
index 0000000..1a68d2c
--- /dev/null
+++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java
@@ -0,0 +1,110 @@
+package org.taverna.server.master.mocks;
+
+import java.net.URI;
+import java.util.List;
+
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.exceptions.NoCreateException;
+import org.taverna.server.master.exceptions.NoDestroyException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Policy;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+/**
+ * A very simple (and unsafe) security model. The number of runs is configurable
+ * through Spring (or 10 if unconfigured) with no per-user limits supported, all
+ * workflows are permitted, and all identified users may create a workflow run.
+ * Any user may read off information about any run, but only its owner may
+ * modify or destroy it.
+ * <p>
+ * Note that this is a <i>Policy Enforcement Point</i> for access control to
+ * individual workflows.
+ * 
+ * @author Donal Fellows
+ */
+public class SimpleServerPolicy implements Policy {
+	private int maxRuns = 10;
+	private int cleanerInterval;
+	SimpleNonpersistentRunStore store;
+
+	public void setMaxRuns(int maxRuns) {
+		this.maxRuns = maxRuns;
+	}
+
+	@Override
+	public int getMaxRuns() {
+		return maxRuns;
+	}
+
+	@Override
+	public Integer getMaxRuns(UsernamePrincipal p) {
+		return null; // No per-user limits
+	}
+
+	public int getCleanerInterval() {
+		return cleanerInterval;
+	}
+
+	/**
+	 * Sets how often the store of workflow runs will try to clean out expired
+	 * runs.
+	 * 
+	 * @param intervalInSeconds
+	 */
+	public void setCleanerInterval(int intervalInSeconds) {
+		cleanerInterval = intervalInSeconds;
+		if (store != null)
+			store.cleanerIntervalUpdated(intervalInSeconds);
+	}
+
+	@Override
+	public boolean permitAccess(UsernamePrincipal p, TavernaRun run) {
+		// No secrets here!
+		return true;
+	}
+
+	@Override
+	public void permitCreate(UsernamePrincipal p, Workflow workflow)
+			throws NoCreateException {
+		// Only identified users may create
+		if (p == null)
+			throw new NoCreateException();
+		// Global run count limit enforcement
+		if (store.listRuns(p, this).size() >= maxRuns)
+			throw new NoCreateException();
+		// Per-user run count enforcement would come here
+	}
+
+	@Override
+	public void permitDestroy(UsernamePrincipal p, TavernaRun run)
+			throws NoDestroyException {
+		// Only the creator may destroy
+		if (p == null || !p.equals(run.getSecurityContext().getOwner()))
+			throw new NoDestroyException();
+	}
+
+	@Override
+	public void permitUpdate(UsernamePrincipal p, TavernaRun run)
+			throws NoUpdateException {
+		// Only the creator may change
+		if (p == null || !p.equals(run.getSecurityContext().getOwner()))
+			throw new NoUpdateException();
+	}
+
+	@Override
+	public int getOperatingLimit() {
+		return 1;
+	}
+
+	@Override
+	public List<URI> listPermittedWorkflowURIs(UsernamePrincipal user) {
+		return null;
+	}
+
+	@Override
+	public void setPermittedWorkflowURIs(UsernamePrincipal user,
+			List<URI> permitted) {
+		// Ignore
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/resources/example.xml
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/resources/example.xml b/taverna-server-webapp/src/test/resources/example.xml
new file mode 100644
index 0000000..14cc242
--- /dev/null
+++ b/taverna-server-webapp/src/test/resources/example.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+	<bean id="policy" class="org.taverna.server.master.mocks.SimpleServerPolicy"
+		lazy-init="false" scope="singleton">
+		<property name="maxRuns" value="1">
+			<description>
+				Limit on total number of simultaneous runs.
+			</description>
+		</property>
+		<property name="cleanerInterval" value="300">
+			<description>
+				Time between trying to delete expired runs, in seconds.
+			</description>
+		</property>
+	</bean>
+
+	<bean id="runFactory" class="org.taverna.server.master.mocks.ExampleRun$Builder">
+		<constructor-arg type="int" value="10" /> <!-- "initialLifetimeMinutes" -->
+	</bean>
+
+	<bean id="runCatalog" scope="singleton"
+		class="org.taverna.server.master.mocks.SimpleNonpersistentRunStore">
+		<property name="policy" ref="policy" />
+	</bean>
+
+	<bean id="listenerFactory" class="org.taverna.server.master.mocks.SimpleListenerFactory">
+		<property name="builders">
+			<description>
+				This map describes how to build each type of supported
+				event listener that is not installed by default. Any site policy for
+				a listeners should be installed using its properties, as shown. The
+				"key" is the type, the "class" is the builder for actual instances
+				(which must be an instance of
+				org.taverna.server.master.factories.SimpleListenerFactory.Builder)
+				and any policies and installation-specific configurations are
+				characterised by properties such as "sitePolicy" below.
+			</description>
+			<map>
+				<!--						<entry key="exampleListener">-->
+				<!--
+					<bean
+					class="org.taverna.server.master.example.ExampleListener$Builder">
+				-->
+				<!--								<property name="sitePolicy">-->
+				<!--									<value>Just an example!</value>-->
+				<!--								</property>-->
+				<!--							</bean>-->
+				<!--						</entry>-->
+			</map>
+		</property>
+	</bean>
+</beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/test/resources/log4j.properties b/taverna-server-webapp/src/test/resources/log4j.properties
new file mode 100644
index 0000000..6707f55
--- /dev/null
+++ b/taverna-server-webapp/src/test/resources/log4j.properties
@@ -0,0 +1,4 @@
+log4j.rootLogger=info, R 
+log4j.appender.R=org.apache.log4j.ConsoleAppender
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} %C{1} - %m%n
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-server-worker/pom.xml b/taverna-server-worker/pom.xml
new file mode 100644
index 0000000..8006c6d
--- /dev/null
+++ b/taverna-server-worker/pom.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.server</groupId>
+		<artifactId>taverna-server</artifactId>
+		<version>3.1.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-server-worker</artifactId>
+	<name>Apache Taverna Server Workflow Executor/File System Access Process Implementation</name>
+	<description>This is the implementation of the factory process that is started up by the web application to manage executing Taverna Workflow executeworkflow.sh calls. Also provides per-user access to filestore.</description>
+
+	<properties>
+		<workerMainClass>org.taverna.server.localworker.impl.TavernaRunManager</workerMainClass>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-server-runinterface</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-server-usagerecord</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.language</groupId>
+			<artifactId>taverna-scufl2-api</artifactId>
+			<version>${taverna.language.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.language</groupId>
+			<artifactId>taverna-scufl2-t2flow</artifactId>
+			<version>${taverna.language.version}</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.language</groupId>
+			<artifactId>taverna-scufl2-wfbundle</artifactId>
+			<version>${taverna.language.version}</version>
+			<scope>runtime</scope>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<configuration>
+					<descriptorRefs>
+						<descriptorRef>jar-with-dependencies</descriptorRef>
+					</descriptorRefs>
+					<archive>
+						<manifest>
+							<mainClass>${workerMainClass}</mainClass>
+						</manifest>
+					</archive>
+				</configuration>
+				<executions>
+					<execution>
+						<id>make-assembly</id>
+						<phase>package</phase>
+						<goals>
+							<goal>single</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+		<pluginManagement>
+			<plugins>
+				<plugin>
+					<groupId>org.eclipse.m2e</groupId>
+					<artifactId>lifecycle-mapping</artifactId>
+					<version>1.0.0</version>
+					<configuration>
+						<lifecycleMappingMetadata>
+							<pluginExecutions>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>org.apache.maven.plugins</groupId>
+										<artifactId>maven-assembly-plugin</artifactId>
+                                    	<versionRange>[2.0,)</versionRange>
+                                    	<goals>
+                                    		<goal>single</goal>
+                                    	</goals>
+									</pluginExecutionFilter>
+									<action>
+										<execute />
+									</action>
+								</pluginExecution>
+							</pluginExecutions>
+						</lifecycleMappingMetadata>
+					</configuration>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+	</build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF b/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..5e94951
--- /dev/null
+++ b/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path: 
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java
new file mode 100644
index 0000000..600913a
--- /dev/null
+++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.localworker.api;
+
+import static java.nio.charset.Charset.defaultCharset;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * The defaults associated with this worker, together with various other
+ * constants.
+ * 
+ * @author Donal Fellows
+ */
+public abstract class Constants {
+	/**
+	 * Subdirectories of the working directory to create by default.
+	 */
+	public static final String[] SUBDIR_LIST = { "conf", "externaltool", "feed",
+			"interactions", "lib", "logs", "plugins", "repository", "var" };
+
+	/** The name of the default encoding for characters on this machine. */
+	public static final String SYSTEM_ENCODING = defaultCharset().name();
+
+	/**
+	 * Password to use to encrypt security information. This default is <7 chars
+	 * to work even without Unlimited Strength JCE.
+	 */
+	public static final char[] KEYSTORE_PASSWORD = { 'c', 'h', 'a', 'n', 'g', 'e' };
+
+	/**
+	 * The name of the directory (in the home directory) where security settings
+	 * will be written.
+	 */
+	public static final String SECURITY_DIR_NAME = ".taverna-server-security";
+
+	/** The name of the file that will be the created keystore. */
+	public static final String KEYSTORE_FILE = "t2keystore.ubr";
+
+	/** The name of the file that will be the created truststore. */
+	public static final String TRUSTSTORE_FILE = "t2truststore.ubr";
+
+	/**
+	 * The name of the file that contains the password to unlock the keystore
+	 * and truststore.
+	 */
+	public static final String PASSWORD_FILE = "password.txt";
+
+	// --------- UNUSED ---------
+	// /**
+	// * The name of the file that contains the mapping from URIs to keystore
+	// * aliases.
+	// */
+	// public static final String URI_ALIAS_MAP = "urlmap.txt";
+
+	/**
+	 * Used to instruct the Taverna credential manager to use a non-default
+	 * location for user credentials.
+	 */
+	public static final String CREDENTIAL_MANAGER_DIRECTORY = "-cmdir";
+
+	/**
+	 * Used to instruct the Taverna credential manager to take its master
+	 * password from standard input.
+	 */
+	public static final String CREDENTIAL_MANAGER_PASSWORD = "-cmpassword";
+
+	/**
+	 * Name of environment variable used to pass HELIO security tokens to
+	 * workflows.
+	 */
+	// This technique is known to be insecure; bite me.
+	public static final String HELIO_TOKEN_NAME = "HELIO_CIS_TOKEN";
+
+	/**
+	 * The name of the standard listener, which is installed by default.
+	 */
+	public static final String DEFAULT_LISTENER_NAME = "io";
+
+	/**
+	 * Time to wait for the subprocess to wait, in milliseconds.
+	 */
+	public static final int START_WAIT_TIME = 1500;
+
+	/**
+	 * Time to wait for success or failure of a death-causing activity (i.e.,
+	 * sending a signal).
+	 */
+	public static final int DEATH_TIME = 333;
+
+	/**
+	 * The name of the file (in this code's resources) that provides the default
+	 * security policy that we use.
+	 */
+	public static final String SECURITY_POLICY_FILE = "security.policy";
+
+	/**
+	 * The Java property holding security policy info.
+	 */
+	public static final String SEC_POLICY_PROP = "java.security.policy";
+	/**
+	 * The Java property to set to make this code not try to enforce security
+	 * policy.
+	 */
+	public static final String UNSECURE_PROP = "taverna.suppressrestrictions.rmi";
+	/**
+	 * The Java property that holds the name of the host name to enforce.
+	 */
+	public static final String RMI_HOST_PROP = "java.rmi.server.hostname";
+	/**
+	 * The default hostname to require in secure mode. This is the
+	 * <i>resolved</i> version of "localhost".
+	 */
+	public static final String LOCALHOST;
+	static {
+		String h = "127.0.0.1"; // fallback
+		try {
+			h = InetAddress.getByName("localhost").getHostAddress();
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+		} finally {
+			LOCALHOST = h;
+		}
+	}
+
+	/**
+	 * Time to wait during closing down this process. In milliseconds.
+	 */
+	public static final int DEATH_DELAY = 500;
+	/**
+	 * The name of the property describing where shared directories should be
+	 * located.
+	 */
+	public static final String SHARED_DIR_PROP = "taverna.sharedDirectory";
+
+	public static final String TIME = "/usr/bin/time";
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java
new file mode 100644
index 0000000..3896c06
--- /dev/null
+++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.localworker.api;
+
+/**
+ * 
+ * @author Donal Fellows
+ */
+public interface RunAccounting {
+	/**
+	 * Logs that a run has started executing.
+	 */
+	void runStarted();
+
+	/**
+	 * Logs that a run has finished executing.
+	 */
+	void runCeased();
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java
new file mode 100644
index 0000000..c513ed8
--- /dev/null
+++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2010-2012 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.localworker.api;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import org.taverna.server.localworker.impl.LocalWorker;
+import org.taverna.server.localworker.remote.ImplementationException;
+import org.taverna.server.localworker.remote.RemoteListener;
+import org.taverna.server.localworker.remote.RemoteStatus;
+import org.taverna.server.localworker.server.UsageRecordReceiver;
+
+/**
+ * The interface between the connectivity layer and the thunk to the
+ * subprocesses.
+ * 
+ * @author Donal Fellows
+ */
+public interface Worker {
+	/**
+	 * Fire up the workflow. This causes a transition into the operating state.
+	 * 
+	 * @param local
+	 *            The reference to the factory class for this worker.
+	 * @param executeWorkflowCommand
+	 *            The command to run to execute the workflow.
+	 * @param workflow
+	 *            The workflow document to execute.
+	 * @param workingDir
+	 *            What directory to use as the working directory.
+	 * @param inputBaclavaFile
+	 *            The baclava file to use for inputs, or <tt>null</tt> to use
+	 *            the other <b>input*</b> arguments' values.
+	 * @param inputRealFiles
+	 *            A mapping of input names to files that supply them. Note that
+	 *            we assume that nothing mapped here will be mapped in
+	 *            <b>inputValues</b>.
+	 * @param inputValues
+	 *            A mapping of input names to values to supply to them. Note
+	 *            that we assume that nothing mapped here will be mapped in
+	 *            <b>inputFiles</b>.
+	 * @param inputDelimiters
+	 *            A mapping of input names to characters used to split them into
+	 *            lists.
+	 * @param outputBaclavaFile
+	 *            What baclava file to write the output from the workflow into,
+	 *            or <tt>null</tt> to have it written into the <tt>out</tt>
+	 *            subdirectory.
+	 * @param contextDirectory
+	 *            The directory containing the keystore and truststore. <i>Must
+	 *            not be <tt>null</tt>.</i>
+	 * @param keystorePassword
+	 *            The password to the keystore and truststore. <i>Must not be
+	 *            <tt>null</tt>.</i>
+	 * @param generateProvenance
+	 *            Whether to generate a run bundle containing provenance data.
+	 * @param environment
+	 *            Any environment variables that need to be added to the
+	 *            invokation.
+	 * @param masterToken
+	 *            The internal name of the workflow run.
+	 * @param runtimeSettings
+	 *            List of configuration details for the forked runtime.
+	 * @return Whether a successful start happened.
+	 * @throws Exception
+	 *             If any of quite a large number of things goes wrong.
+	 */
+	boolean initWorker(LocalWorker local, String executeWorkflowCommand,
+			byte[] workflow, File workingDir, File inputBaclavaFile,
+			Map<String, File> inputRealFiles, Map<String, String> inputValues,
+			Map<String, String> inputDelimiters, File outputBaclavaFile,
+			File contextDirectory, char[] keystorePassword,
+			boolean generateProvenance, Map<String, String> environment,
+			String masterToken, List<String> runtimeSettings) throws Exception;
+
+	/**
+	 * Kills off the subprocess if it exists and is alive.
+	 * 
+	 * @throws Exception
+	 *             if anything goes badly wrong when the worker is being killed
+	 *             off.
+	 */
+	void killWorker() throws Exception;
+
+	/**
+	 * Move the worker out of the stopped state and back to operating.
+	 * 
+	 * @throws Exception
+	 *             if it fails (which it always does; operation currently
+	 *             unsupported).
+	 */
+	void startWorker() throws Exception;
+
+	/**
+	 * Move the worker into the stopped state from the operating state.
+	 * 
+	 * @throws Exception
+	 *             if it fails (which it always does; operation currently
+	 *             unsupported).
+	 */
+	void stopWorker() throws Exception;
+
+	/**
+	 * @return The status of the workflow run. Note that this can be an
+	 *         expensive operation.
+	 */
+	RemoteStatus getWorkerStatus();
+
+	/**
+	 * @return The listener that is registered by default, in addition to all
+	 *         those that are explicitly registered by the user.
+	 */
+	RemoteListener getDefaultListener();
+
+	/**
+	 * @param receiver
+	 *            The destination where any final usage records are to be
+	 *            written in order to log them back to the server.
+	 */
+	void setURReceiver(UsageRecordReceiver receiver);
+
+	/**
+	 * Arrange for the deletion of any resources created during worker process
+	 * construction. Guaranteed to be the last thing done before finalization.
+	 * 
+	 * @throws ImplementationException
+	 *             If anything goes wrong.
+	 */
+	void deleteLocalResources() throws ImplementationException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java
new file mode 100644
index 0000000..4ce13a7
--- /dev/null
+++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java
@@ -0,0 +1,18 @@
+package org.taverna.server.localworker.api;
+
+
+/**
+ * Class that manufactures instances of {@link Worker}.
+ * 
+ * @author Donal Fellows
+ */
+public interface WorkerFactory {
+	/**
+	 * Create an instance of the low-level worker class.
+	 * 
+	 * @return The worker object.
+	 * @throws Exception
+	 *             If anything goes wrong.
+	 */
+	Worker makeInstance() throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java
new file mode 100644
index 0000000..1692856
--- /dev/null
+++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.localworker.impl;
+
+import static org.apache.commons.io.FileUtils.forceDelete;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+import static org.apache.commons.io.FileUtils.touch;
+import static org.taverna.server.localworker.impl.utils.FilenameVerifier.getValidatedNewFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.collections.MapIterator;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.taverna.server.localworker.remote.RemoteDirectory;
+import org.taverna.server.localworker.remote.RemoteDirectoryEntry;
+import org.taverna.server.localworker.remote.RemoteFile;
+
+/**
+ * This class acts as a remote-aware delegate for the workflow run's working
+ * directory and its subdirectories.
+ * 
+ * @author Donal Fellows
+ * @see FileDelegate
+ */
+@SuppressWarnings("serial")
+public class DirectoryDelegate extends UnicastRemoteObject implements
+		RemoteDirectory {
+	private File dir;
+	private DirectoryDelegate parent;
+	private ReferenceMap localCache;
+
+	/**
+	 * @param dir
+	 * @param parent
+	 * @throws RemoteException
+	 *             If registration of the directory fails.
+	 */
+	public DirectoryDelegate(@Nonnull File dir,
+			@Nonnull DirectoryDelegate parent) throws RemoteException {
+		super();
+		this.localCache = new ReferenceMap();
+		this.dir = dir;
+		this.parent = parent;
+	}
+
+	@Override
+	public Collection<RemoteDirectoryEntry> getContents()
+			throws RemoteException {
+		List<RemoteDirectoryEntry> result = new ArrayList<>();
+		for (String s : dir.list()) {
+			if (s.equals(".") || s.equals(".."))
+				continue;
+			File f = new File(dir, s);
+			RemoteDirectoryEntry entry;
+			synchronized (localCache) {
+				entry = (RemoteDirectoryEntry) localCache.get(s);
+				if (f.isDirectory()) {
+					if (entry == null || !(entry instanceof DirectoryDelegate)) {
+						entry = new DirectoryDelegate(f, this);
+						localCache.put(s, entry);
+					}
+				} else if (f.isFile()) {
+					if (entry == null || !(entry instanceof FileDelegate)) {
+						entry = new FileDelegate(f, this);
+						localCache.put(s, entry);
+					}
+				} else {
+					// not file or dir; skip...
+					continue;
+				}
+			}
+			result.add(entry);
+		}
+		return result;
+	}
+
+	@Override
+	public RemoteFile makeEmptyFile(String name) throws IOException {
+		File f = getValidatedNewFile(dir, name);
+		touch(f);
+		FileDelegate delegate = new FileDelegate(f, this);
+		synchronized (localCache) {
+			localCache.put(name, delegate);
+		}
+		return delegate;
+	}
+
+	@Override
+	public RemoteDirectory makeSubdirectory(String name) throws IOException {
+		File f = getValidatedNewFile(dir, name);
+		forceMkdir(f);
+		DirectoryDelegate delegate = new DirectoryDelegate(f, this);
+		synchronized (localCache) {
+			localCache.put(name, delegate);
+		}
+		return delegate;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public void destroy() throws IOException {
+		if (parent == null)
+			throw new IOException("tried to destroy main job working directory");
+		Collection<RemoteDirectoryEntry> values;
+		synchronized (localCache) {
+			values = new ArrayList<>(localCache.values());
+		}
+		for (RemoteDirectoryEntry obj : values) {
+			if (obj == null)
+				continue;
+			try {
+				obj.destroy();
+			} catch (IOException e) {
+			}
+		}
+		forceDelete(dir);
+		parent.forgetEntry(this);
+	}
+
+	@Override
+	public RemoteDirectory getContainingDirectory() {
+		return parent;
+	}
+
+	void forgetEntry(@Nonnull RemoteDirectoryEntry entry) {
+		synchronized (localCache) {
+			MapIterator i = localCache.mapIterator();
+			while (i.hasNext()) {
+				Object key = i.next();
+				if (entry == i.getValue()) {
+					localCache.remove(key);
+					break;
+				}
+			}
+		}
+	}
+
+	@Override
+	public String getName() {
+		if (parent == null)
+			return "";
+		return dir.getName();
+	}
+
+	@Override
+	public Date getModificationDate() throws RemoteException {
+		return new Date(dir.lastModified());
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java
----------------------------------------------------------------------
diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java
new file mode 100644
index 0000000..7e47af9
--- /dev/null
+++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.localworker.impl;
+
+import static java.lang.System.arraycopy;
+import static java.net.InetAddress.getLocalHost;
+import static org.apache.commons.io.FileUtils.copyFile;
+import static org.apache.commons.io.FileUtils.forceDelete;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.taverna.server.localworker.remote.RemoteDirectory;
+import org.taverna.server.localworker.remote.RemoteFile;
+
+/**
+ * This class acts as a remote-aware delegate for the files in a workflow run's
+ * working directory and its subdirectories.
+ * 
+ * @author Donal Fellows
+ * @see DirectoryDelegate
+ */
+@java.lang.SuppressWarnings("serial")
+public class FileDelegate extends UnicastRemoteObject implements RemoteFile {
+	private File file;
+	private DirectoryDelegate parent;
+
+	/**
+	 * @param file
+	 * @param parent
+	 * @throws RemoteException
+	 *             If registration of the file fails.
+	 */
+	public FileDelegate(@Nonnull File file, @Nonnull DirectoryDelegate parent)
+			throws RemoteException {
+		super();
+		this.file = file;
+		this.parent = parent;
+	}
+
+	@Override
+	public byte[] getContents(int offset, int length) throws IOException {
+		if (length == -1)
+			length = (int) (file.length() - offset);
+		if (length < 0 || length > 1024 * 64)
+			length = 1024 * 64;
+		byte[] buffer = new byte[length];
+		int read;
+		try (FileInputStream fis = new FileInputStream(file)) {
+			if (offset > 0 && fis.skip(offset) != offset)
+				throw new IOException("did not move to correct offset in file");
+			read = fis.read(buffer);
+		}
+		if (read <= 0)
+			return new byte[0];
+		if (read < buffer.length) {
+			byte[] shortened = new byte[read];
+			arraycopy(buffer, 0, shortened, 0, read);
+			return shortened;
+		}
+		return buffer;
+	}
+
+	@Override
+	public long getSize() {
+		return file.length();
+	}
+
+	@Override
+	public void setContents(byte[] data) throws IOException {
+		try (FileOutputStream fos = new FileOutputStream(file)) {
+			fos.write(data);
+		}
+	}
+
+	@Override
+	public void appendContents(byte[] data) throws IOException {
+		try (FileOutputStream fos = new FileOutputStream(file, true)) {
+			fos.write(data);
+		}
+	}
+
+	@Override
+	public void destroy() throws IOException {
+		forceDelete(file);
+		parent.forgetEntry(this);
+		parent = null;
+	}
+
+	@Override
+	public RemoteDirectory getContainingDirectory() {
+		return parent;
+	}
+
+	@Override
+	public String getName() {
+		return file.getName();
+	}
+
+	@Override
+	public void copy(RemoteFile sourceFile) throws RemoteException, IOException {
+		String sourceHost = sourceFile.getNativeHost();
+		if (!getNativeHost().equals(sourceHost)) {
+			throw new IOException(
+					"cross-system copy not implemented; cannot copy from "
+							+ sourceHost + " to " + getNativeHost());
+		}
+		// Must copy; cannot count on other file to stay unmodified
+		copyFile(new File(sourceFile.getNativeName()), file);
+	}
+
+	@Override
+	public String getNativeName() {
+		return file.getAbsolutePath();
+	}
+
+	@Override
+	public String getNativeHost() {
+		try {
+			return getLocalHost().getHostAddress();
+		} catch (UnknownHostException e) {
+			throw new RuntimeException(
+					"unexpected failure to resolve local host address", e);
+		}
+	}
+
+	@Override
+	public Date getModificationDate() throws RemoteException {
+		return new Date(file.lastModified());
+	}
+}