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
+ */
[email protected]("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());
+       }
+}

Reply via email to