http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
new file mode 100644
index 0000000..642a6d6
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
@@ -0,0 +1,411 @@
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import static org.springframework.jmx.support.MetricType.COUNTER;
+import static org.springframework.jmx.support.MetricType.GAUGE;
+import static org.taverna.server.master.TavernaServer.JMX_ROOT;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.annotation.Order;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedMetric;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.taverna.server.master.factories.ConfigurableRunFactory;
+import org.taverna.server.master.localworker.LocalWorkerState;
+
+@ManagedResource(objectName = JMX_ROOT + "Factory", description = "The factory 
for runs.")
+public abstract class RunFactoryConfiguration implements 
ConfigurableRunFactory {
+       protected Log log = LogFactory.getLog("Taverna.Server.Worker");
+       protected LocalWorkerState state;
+       protected RunDBSupport runDB;
+       private int totalRuns = 0;
+
+       @PreDestroy
+       void closeLog() {
+               log = null;
+       }
+
+       @Autowired(required = true)
+       @Order(0)
+       void setState(LocalWorkerState state) {
+               this.state = state;
+       }
+
+       @Autowired(required = true)
+       @Order(0)
+       void setRunDB(RunDBSupport runDB) {
+               this.runDB = runDB;
+       }
+
+       /**
+        * Drop any current references to the registry of runs, and kill off 
that
+        * process.
+        */
+       protected abstract void reinitRegistry();
+
+       /**
+        * Drop any current references to the run factory subprocess and kill it
+        * off.
+        */
+       protected abstract void reinitFactory();
+
+       /** Count the number of operating runs. */
+       protected abstract int operatingCount() throws Exception;
+
+       protected final synchronized void incrementRunCount() {
+               totalRuns++;
+       }
+
+       @Override
+       @ManagedAttribute(description = "Whether it is allowed to start a run 
executing.", currencyTimeLimit = 30)
+       public final boolean isAllowingRunsToStart() {
+               try {
+                       return state.getOperatingLimit() > getOperatingCount();
+               } catch (Exception e) {
+                       log.info("failed to get operating run count", e);
+                       return false;
+               }
+       }
+
+       @Override
+       @ManagedAttribute(description = "The host holding the RMI registry to 
communicate via.")
+       public final String getRegistryHost() {
+               return state.getRegistryHost();
+       }
+
+       @Override
+       @ManagedAttribute(description = "The host holding the RMI registry to 
communicate via.")
+       public final void setRegistryHost(String host) {
+               state.setRegistryHost(host);
+               reinitRegistry();
+               reinitFactory();
+       }
+
+       @Override
+       @ManagedAttribute(description = "The port number of the RMI registry. 
Should not normally be set.")
+       public final int getRegistryPort() {
+               return state.getRegistryPort();
+       }
+
+       @Override
+       @ManagedAttribute(description = "The port number of the RMI registry. 
Should not normally be set.")
+       public final void setRegistryPort(int port) {
+               state.setRegistryPort(port);
+               reinitRegistry();
+               reinitFactory();
+       }
+
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "What JAR do we use to start the RMI 
registry process?")
+       public final String getRmiRegistryJar() {
+               return state.getRegistryJar();
+       }
+
+       @Override
+       @ManagedAttribute(description = "What JAR do we use to start the RMI 
registry process?")
+       public final void setRmiRegistryJar(String rmiRegistryJar) {
+               state.setRegistryJar(rmiRegistryJar);
+               reinitRegistry();
+               reinitFactory();
+       }
+
+       @Override
+       @ManagedAttribute(description = "The maximum number of simultaneous 
runs supported by the server.", currencyTimeLimit = 300)
+       public final int getMaxRuns() {
+               return state.getMaxRuns();
+       }
+
+       @Override
+       @ManagedAttribute(description = "The maximum number of simultaneous 
runs supported by the server.", currencyTimeLimit = 300)
+       public final void setMaxRuns(int maxRuns) {
+               state.setMaxRuns(maxRuns);
+       }
+
+       /** @return How many minutes should a workflow live by default? */
+       @Override
+       @ManagedAttribute(description = "How many minutes should a workflow 
live by default?", currencyTimeLimit = 300)
+       public final int getDefaultLifetime() {
+               return state.getDefaultLifetime();
+       }
+
+       /**
+        * Set how long a workflow should live by default.
+        * 
+        * @param defaultLifetime
+        *            Default lifetime, in minutes.
+        */
+       @Override
+       @ManagedAttribute(description = "How many minutes should a workflow 
live by default?", currencyTimeLimit = 300)
+       public final void setDefaultLifetime(int defaultLifetime) {
+               state.setDefaultLifetime(defaultLifetime);
+       }
+
+       /**
+        * @return How many milliseconds to wait between checks to see if a 
worker
+        *         process has registered.
+        */
+       @Override
+       @ManagedAttribute(description = "How many milliseconds to wait between 
checks to see if a worker process has registered.", currencyTimeLimit = 300)
+       public final int getSleepTime() {
+               return state.getSleepMS();
+       }
+
+       /**
+        * @param sleepTime
+        *            How many milliseconds to wait between checks to see if a
+        *            worker process has registered.
+        */
+       @Override
+       @ManagedAttribute(description = "How many milliseconds to wait between 
checks to see if a worker process has registered.", currencyTimeLimit = 300)
+       public final void setSleepTime(int sleepTime) {
+               state.setSleepMS(sleepTime);
+       }
+
+       /**
+        * @return How many seconds to wait for a worker process to register 
itself.
+        */
+       @Override
+       @ManagedAttribute(description = "How many seconds to wait for a worker 
process to register itself.", currencyTimeLimit = 300)
+       public final int getWaitSeconds() {
+               return state.getWaitSeconds();
+       }
+
+       /**
+        * @param seconds
+        *            How many seconds to wait for a worker process to register
+        *            itself.
+        */
+       @Override
+       @ManagedAttribute(description = "How many seconds to wait for a worker 
process to register itself.", currencyTimeLimit = 300)
+       public final void setWaitSeconds(int seconds) {
+               state.setWaitSeconds(seconds);
+       }
+
+       /** @return The script to run to start running a workflow. */
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "The script to run to start running a 
workflow.", currencyTimeLimit = 300)
+       public final String getExecuteWorkflowScript() {
+               return state.getExecuteWorkflowScript();
+       }
+
+       /**
+        * @param executeWorkflowScript
+        *            The script to run to start running a workflow.
+        */
+       @Override
+       @ManagedAttribute(description = "The script to run to start running a 
workflow.", currencyTimeLimit = 300)
+       public final void setExecuteWorkflowScript(String 
executeWorkflowScript) {
+               state.setExecuteWorkflowScript(executeWorkflowScript);
+               reinitFactory();
+       }
+
+       /** @return The location of the JAR implementing the server worker 
processes. */
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "The location of the JAR implementing 
the server worker processes.")
+       public final String getServerWorkerJar() {
+               return state.getServerWorkerJar();
+       }
+
+       /**
+        * @param serverWorkerJar
+        *            The location of the JAR implementing the server worker
+        *            processes.
+        */
+       @Override
+       @ManagedAttribute(description = "The location of the JAR implementing 
the server worker processes.")
+       public final void setServerWorkerJar(String serverWorkerJar) {
+               state.setServerWorkerJar(serverWorkerJar);
+               reinitFactory();
+       }
+
+       /** @return The list of additional arguments used to make a worker 
process. */
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "The list of additional arguments used 
to make a worker process.", currencyTimeLimit = 300)
+       public final String[] getExtraArguments() {
+               return state.getExtraArgs();
+       }
+
+       /**
+        * @param extraArguments
+        *            The list of additional arguments used to make a worker
+        *            process.
+        */
+       @Override
+       @ManagedAttribute(description = "The list of additional arguments used 
to make a worker process.", currencyTimeLimit = 300)
+       public final void setExtraArguments(@Nonnull String[] extraArguments) {
+               state.setExtraArgs(extraArguments);
+               reinitFactory();
+       }
+
+       /** @return Which java executable to run. */
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "Which java executable to run.", 
currencyTimeLimit = 300)
+       public final String getJavaBinary() {
+               return state.getJavaBinary();
+       }
+
+       /**
+        * @param javaBinary
+        *            Which java executable to run.
+        */
+       @Override
+       @ManagedAttribute(description = "Which java executable to run.", 
currencyTimeLimit = 300)
+       public final void setJavaBinary(@Nonnull String javaBinary) {
+               state.setJavaBinary(javaBinary);
+               reinitFactory();
+       }
+
+       /**
+        * @return A file containing a password to use when running a program as
+        *         another user (e.g., with sudo).
+        */
+       @Nullable
+       @Override
+       @ManagedAttribute(description = "A file containing a password to use 
when running a program as another user (e.g., with sudo).", currencyTimeLimit = 
300)
+       public final String getPasswordFile() {
+               return state.getPasswordFile();
+       }
+
+       /**
+        * @param passwordFile
+        *            A file containing a password to use when running a 
program as
+        *            another user (e.g., with sudo).
+        */
+       @Override
+       @ManagedAttribute(description = "A file containing a password to use 
when running a program as another user (e.g., with sudo).", currencyTimeLimit = 
300)
+       public final void setPasswordFile(@Nullable String passwordFile) {
+               state.setPasswordFile(passwordFile);
+               reinitFactory();
+       }
+
+       /**
+        * @return The location of the JAR implementing the secure-fork process.
+        */
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "The location of the JAR implementing 
the secure-fork process.", currencyTimeLimit = 300)
+       public final String getServerForkerJar() {
+               return state.getServerForkerJar();
+       }
+
+       /**
+        * @param serverForkerJar
+        *            The location of the JAR implementing the secure-fork 
process.
+        */
+       @Override
+       @ManagedAttribute(description = "The location of the JAR implementing 
the secure-fork process.", currencyTimeLimit = 300)
+       public final void setServerForkerJar(String forkerJarFilename) {
+               state.setServerForkerJar(forkerJarFilename);
+               reinitFactory();
+       }
+
+       /**
+        * @return How many times has a workflow run been spawned by this 
engine.
+        *         Restarts reset this counter.
+        */
+       @Override
+       @ManagedMetric(description = "How many times has a workflow run been 
spawned by this engine.", currencyTimeLimit = 10, metricType = COUNTER, 
category = "throughput")
+       public final synchronized int getTotalRuns() {
+               return totalRuns;
+       }
+
+       /**
+        * @return How many checks were done for the worker process the last 
time a
+        *         spawn was tried.
+        */
+       @Override
+       @ManagedAttribute(description = "How many checks were done for the 
worker process the last time a spawn was tried.", currencyTimeLimit = 60)
+       public abstract int getLastStartupCheckCount();
+
+       @Nonnull
+       @Override
+       @ManagedAttribute(description = "The names of the current runs.", 
currencyTimeLimit = 5)
+       public final String[] getCurrentRunNames() {
+               List<String> names = runDB.listRunNames();
+               return names.toArray(new String[names.size()]);
+       }
+
+       @Override
+       @ManagedAttribute(description = "What the factory subprocess's main RMI 
interface is registered as.", currencyTimeLimit = 60)
+       public abstract String getFactoryProcessName();
+
+       /**
+        * @return What was the exit code from the last time the factory 
subprocess
+        *         was killed?
+        */
+       @Override
+       @ManagedAttribute(description = "What was the exit code from the last 
time the factory subprocess was killed?")
+       public abstract Integer getLastExitCode();
+
+       /**
+        * @return The mapping of user names to RMI factory IDs.
+        */
+       @Override
+       @ManagedAttribute(description = "The mapping of user names to RMI 
factory IDs.", currencyTimeLimit = 60)
+       public abstract String[] getFactoryProcessMapping();
+
+       @Override
+       @ManagedAttribute(description = "The maximum number of simultaneous 
operating runs supported by the server.", currencyTimeLimit = 300)
+       public final void setOperatingLimit(int operatingLimit) {
+               state.setOperatingLimit(operatingLimit);
+       }
+
+       @Override
+       @ManagedAttribute(description = "The maximum number of simultaneous 
operating runs supported by the server.", currencyTimeLimit = 300)
+       public final int getOperatingLimit() {
+               return state.getOperatingLimit();
+       }
+
+       /**
+        * @return A count of the number of runs believed to actually be in the
+        *         {@linkplain 
uk.org.taverna.server.master.common.Status#Operating
+        *         operating} state.
+        * @throws Exception
+        *             If anything goes wrong.
+        */
+       @Override
+       @ManagedMetric(description = "How many workflow runs are currently 
actually executing.", currencyTimeLimit = 10, metricType = GAUGE, category = 
"throughput")
+       public final int getOperatingCount() throws Exception {
+               return operatingCount();
+       }
+
+       @Override
+       @ManagedAttribute(description="Whether to tell a workflow to generate 
provenance bundles by default.")
+       public final void setGenerateProvenance(boolean genProv) {
+               state.setGenerateProvenance(genProv);
+       }
+
+       @Override
+       @ManagedAttribute(description="Whether to tell a workflow to generate 
provenance bundles by default.")
+       public final boolean getGenerateProvenance() {
+               return state.getGenerateProvenance();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
new file mode 100644
index 0000000..bb76f85
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
@@ -0,0 +1,662 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import static java.lang.String.format;
+import static java.util.Arrays.fill;
+import static java.util.UUID.randomUUID;
+import static 
org.taverna.server.master.defaults.Default.CERTIFICATE_FIELD_NAMES;
+import static org.taverna.server.master.defaults.Default.CERTIFICATE_TYPE;
+import static 
org.taverna.server.master.defaults.Default.CREDENTIAL_FILE_SIZE_LIMIT;
+import static 
org.taverna.server.master.identity.WorkflowInternalAuthProvider.PREFIX;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.rmi.RemoteException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.security.auth.x500.X500Principal;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriBuilder;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.taverna.server.localworker.remote.ImplementationException;
+import org.taverna.server.localworker.remote.RemoteSecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+/**
+ * Implementation of a security context.
+ * 
+ * @author Donal Fellows
+ */
+public abstract class SecurityContextDelegate implements 
TavernaSecurityContext {
+       Log log = LogFactory.getLog("Taverna.Server.Worker");
+       private final UsernamePrincipal owner;
+       private final List<Credential> credentials = new ArrayList<>();
+       private final List<Trust> trusted = new ArrayList<>();
+       private final RemoteRunDelegate run;
+       private final Object lock = new Object();
+       final SecurityContextFactory factory;
+
+       private transient Keystore keystore;
+       private transient Map<URI, String> uriToAliasMap;
+
+       /**
+        * Initialise the context delegate.
+        * 
+        * @param run
+        *            What workflow run is this for?
+        * @param owner
+        *            Who owns the workflow run?
+        * @param factory
+        *            What class built this object?
+        */
+       protected SecurityContextDelegate(RemoteRunDelegate run,
+                       UsernamePrincipal owner, SecurityContextFactory 
factory) {
+               this.run = run;
+               this.owner = owner;
+               this.factory = factory;
+       }
+
+       @Override
+       public SecurityContextFactory getFactory() {
+               return factory;
+       }
+
+       @Override
+       public UsernamePrincipal getOwner() {
+               return owner;
+       }
+
+       @Override
+       public Credential[] getCredentials() {
+               synchronized (lock) {
+                       return credentials.toArray(new 
Credential[credentials.size()]);
+               }
+       }
+
+       /**
+        * Get the human-readable name of a principal.
+        * 
+        * @param principal
+        *            The principal being decoded.
+        * @return A name.
+        */
+       protected final String getPrincipalName(X500Principal principal) {
+               return factory.x500Utils.getName(principal, 
CERTIFICATE_FIELD_NAMES);
+       }
+
+       /**
+        * Cause the current state to be flushed to the database.
+        */
+       protected final void flushToDB() {
+               factory.db.flushToDisk(run);
+       }
+
+       @Override
+       public void addCredential(Credential toAdd) {
+               synchronized (lock) {
+                       int idx = credentials.indexOf(toAdd);
+                       if (idx != -1)
+                               credentials.set(idx, toAdd);
+                       else
+                               credentials.add(toAdd);
+                       flushToDB();
+               }
+       }
+
+       @Override
+       public void deleteCredential(Credential toDelete) {
+               synchronized (lock) {
+                       credentials.remove(toDelete);
+                       flushToDB();
+               }
+       }
+
+       @Override
+       public Trust[] getTrusted() {
+               synchronized (lock) {
+                       return trusted.toArray(new Trust[trusted.size()]);
+               }
+       }
+
+       @Override
+       public void addTrusted(Trust toAdd) {
+               synchronized (lock) {
+                       int idx = trusted.indexOf(toAdd);
+                       if (idx != -1)
+                               trusted.set(idx, toAdd);
+                       else
+                               trusted.add(toAdd);
+                       flushToDB();
+               }
+       }
+
+       @Override
+       public void deleteTrusted(Trust toDelete) {
+               synchronized (lock) {
+                       trusted.remove(toDelete);
+                       flushToDB();
+               }
+       }
+
+       @Override
+       public abstract void validateCredential(Credential c)
+                       throws InvalidCredentialException;
+
+       @Override
+       public void validateTrusted(Trust t) throws InvalidCredentialException {
+               InputStream contentsAsStream;
+               if (t.certificateBytes != null && t.certificateBytes.length > 
0) {
+                       contentsAsStream = new 
ByteArrayInputStream(t.certificateBytes);
+                       t.certificateFile = null;
+               } else if (t.certificateFile == null
+                               || t.certificateFile.trim().isEmpty())
+                       throw new InvalidCredentialException(
+                                       "absent or empty certificateFile");
+               else {
+                       contentsAsStream = contents(t.certificateFile);
+                       t.certificateBytes = null;
+               }
+               t.serverName = null;
+               if (t.fileType == null || t.fileType.trim().isEmpty())
+                       t.fileType = CERTIFICATE_TYPE;
+               t.fileType = t.fileType.trim();
+               try {
+                       t.loadedCertificates = 
CertificateFactory.getInstance(t.fileType)
+                                       .generateCertificates(contentsAsStream);
+                       t.serverName = new 
ArrayList<>(t.loadedCertificates.size());
+                       for (Certificate c : t.loadedCertificates)
+                               
t.serverName.add(getPrincipalName(((X509Certificate) c)
+                                               .getSubjectX500Principal()));
+               } catch (CertificateException e) {
+                       throw new InvalidCredentialException(e);
+               } catch (ClassCastException e) {
+                       // Do nothing; truncates the list of server names
+               }
+       }
+
+       @Override
+       public void initializeSecurityFromContext(SecurityContext 
securityContext)
+                       throws Exception {
+               // This is how to get the info from Spring Security
+               Authentication auth = securityContext.getAuthentication();
+               if (auth == null)
+                       return;
+               auth.getPrincipal();
+               // do nothing else in this implementation
+       }
+
+       @Override
+       public void initializeSecurityFromSOAPContext(MessageContext context) {
+               // do nothing in this implementation
+       }
+
+       @Override
+       public void initializeSecurityFromRESTContext(HttpHeaders context) {
+               // do nothing in this implementation
+       }
+
+       private UriBuilder getUB() {
+               return factory.uriSource.getRunUriBuilder(run);
+       }
+
+       private RunDatabaseDAO getDAO() {
+               return ((RunDatabase) factory.db).dao;
+       }
+
+       @Nullable
+       private List<X509Certificate> getCerts(URI uri) throws IOException,
+                       GeneralSecurityException {
+               return factory.certFetcher.getTrustsForURI(uri);
+       }
+
+       private void installLocalPasswordCredential(List<Credential> 
credentials,
+                       List<Trust> trusts) throws InvalidCredentialException, 
IOException,
+                       GeneralSecurityException {
+               Credential.Password pw = new Credential.Password();
+               pw.id = "run:self";
+               pw.username = PREFIX + run.id;
+               pw.password = getDAO().getSecurityToken(run.id);
+               UriBuilder ub = getUB().segment("").fragment(factory.httpRealm);
+               pw.serviceURI = ub.build();
+               validateCredential(pw);
+               log.info("issuing self-referential credential for " + 
pw.serviceURI);
+               credentials.add(pw);
+               List<X509Certificate> myCerts = getCerts(pw.serviceURI);
+               if (myCerts != null && myCerts.size() > 0) {
+                       Trust t = new Trust();
+                       t.loadedCertificates = getCerts(pw.serviceURI);
+                       trusts.add(t);
+               }
+       }
+
+       /**
+        * Builds and transfers a keystore with suitable credentials to the 
back-end
+        * workflow execution engine.
+        * 
+        * @throws GeneralSecurityException
+        *             If the manipulation of the keystore, keys or certificates
+        *             fails.
+        * @throws IOException
+        *             If there are problems building the data (should not 
happen).
+        * @throws RemoteException
+        *             If the conveyancing fails.
+        */
+       @Override
+       public final void conveySecurity() throws GeneralSecurityException,
+                       IOException, ImplementationException {
+               RemoteSecurityContext rc = run.run.getSecurityContext();
+
+               List<Trust> trusted = new ArrayList<>(this.trusted);
+               this.trusted.clear();
+               List<Credential> credentials = new 
ArrayList<>(this.credentials);
+               this.credentials.clear();
+
+               try {
+                       installLocalPasswordCredential(credentials, trusted);
+               } catch (Exception e) {
+                       log.warn("failed to construct local credential: "
+                                       + "interaction service will fail", e);
+               }
+
+               char[] password = null;
+               try {
+                       password = generateNewPassword();
+
+                       log.info("constructing merged keystore");
+                       Truststore truststore = new Truststore(password);
+                       Keystore keystore = new Keystore(password);
+                       Map<URI, String> uriToAliasMap = new HashMap<>();
+                       int trustedCount = 0, keyCount = 0;
+
+                       synchronized (lock) {
+                               try {
+                                       for (Trust t : trusted) {
+                                               if (t == null || 
t.loadedCertificates == null)
+                                                       continue;
+                                               for (Certificate cert : 
t.loadedCertificates)
+                                                       if (cert != null) {
+                                                               
truststore.addCertificate(cert);
+                                                               trustedCount++;
+                                                       }
+                                       }
+
+                                       this.uriToAliasMap = uriToAliasMap;
+                                       this.keystore = keystore;
+                                       for (Credential c : credentials) {
+                                               addCredentialToKeystore(c);
+                                               keyCount++;
+                                       }
+                               } finally {
+                                       this.uriToAliasMap = null;
+                                       this.keystore = null;
+                                       credentials.clear();
+                                       trusted.clear();
+                                       flushToDB();
+                               }
+                       }
+
+                       byte[] trustbytes = null, keybytes = null;
+                       try {
+                               trustbytes = truststore.serialize();
+                               keybytes = keystore.serialize();
+
+                               // Now we've built the security information, 
ship it off...
+
+                               log.info("transfering merged truststore with " 
+ trustedCount
+                                               + " entries");
+                               rc.setTruststore(trustbytes);
+
+                               log.info("transfering merged keystore with " + 
keyCount
+                                               + " entries");
+                               rc.setKeystore(keybytes);
+                       } finally {
+                               if (trustbytes != null)
+                                       fill(trustbytes, (byte) 0);
+                               if (keybytes != null)
+                                       fill(keybytes, (byte) 0);
+                       }
+                       rc.setPassword(password);
+
+                       log.info("transferring serviceURL->alias map with "
+                                       + uriToAliasMap.size() + " entries");
+                       rc.setUriToAliasMap(uriToAliasMap);
+               } finally {
+                       if (password != null)
+                               fill(password, ' ');
+               }
+
+               synchronized (lock) {
+                       conveyExtraSecuritySettings(rc);
+               }
+       }
+
+       /**
+        * Hook that allows additional information to be conveyed to the remote 
run.
+        * 
+        * @param remoteSecurityContext
+        *            The remote resource that information would be passed to.
+        * @throws IOException
+        *             If anything goes wrong with the communication.
+        */
+       protected void conveyExtraSecuritySettings(
+                       RemoteSecurityContext remoteSecurityContext) throws 
IOException {
+               // Does nothing by default; overrideable
+       }
+
+       /**
+        * @return A new password with a reasonable level of randomness.
+        */
+       protected final char[] generateNewPassword() {
+               return randomUUID().toString().toCharArray();
+       }
+
+       /**
+        * Adds a credential to the current keystore.
+        * 
+        * @param alias
+        *            The alias to create within the keystore.
+        * @param c
+        *            The key-pair.
+        * @throws KeyStoreException
+        */
+       protected final void addKeypairToKeystore(String alias, Credential c)
+                       throws KeyStoreException {
+               if (c.loadedKey == null)
+                       throw new KeyStoreException("critical: credential was 
not verified");
+               if (uriToAliasMap.containsKey(c.serviceURI))
+                       log.warn("duplicate URI in alias mapping: " + 
c.serviceURI);
+               keystore.addKey(alias, c.loadedKey, c.loadedTrustChain);
+               uriToAliasMap.put(c.serviceURI, alias);
+       }
+
+       /**
+        * Adds a credential to the current keystore.
+        * 
+        * @param c
+        *            The credential to add.
+        * @throws KeyStoreException
+        */
+       public abstract void addCredentialToKeystore(Credential c)
+                       throws KeyStoreException;
+
+       /**
+        * Read a file up to {@value #FILE_SIZE_LIMIT}kB in size.
+        * 
+        * @param name
+        *            The path name of the file, relative to the context run's
+        *            working directory.
+        * @return A stream of the file's contents.
+        * @throws InvalidCredentialException
+        *             If anything goes wrong.
+        */
+       final InputStream contents(String name) throws 
InvalidCredentialException {
+               try {
+                       File f = (File) factory.fileUtils.getDirEntry(run, 
name);
+                       long size = f.getSize();
+                       if (size > CREDENTIAL_FILE_SIZE_LIMIT * 1024)
+                               throw new 
InvalidCredentialException(CREDENTIAL_FILE_SIZE_LIMIT
+                                               + "kB limit hit");
+                       return new ByteArrayInputStream(f.getContents(0, (int) 
size));
+               } catch (NoDirectoryEntryException | FilesystemAccessException 
e) {
+                       throw new InvalidCredentialException(e);
+               } catch (ClassCastException e) {
+                       throw new InvalidCredentialException("not a file", e);
+               }
+       }
+
+       @Override
+       public Set<String> getPermittedDestroyers() {
+               return run.getDestroyers();
+       }
+
+       @Override
+       public void setPermittedDestroyers(Set<String> destroyers) {
+               run.setDestroyers(destroyers);
+       }
+
+       @Override
+       public Set<String> getPermittedUpdaters() {
+               return run.getWriters();
+       }
+
+       @Override
+       public void setPermittedUpdaters(Set<String> updaters) {
+               run.setWriters(updaters);
+       }
+
+       @Override
+       public Set<String> getPermittedReaders() {
+               return run.getReaders();
+       }
+
+       @Override
+       public void setPermittedReaders(Set<String> readers) {
+               run.setReaders(readers);
+       }
+
+       /**
+        * Reinstall the credentials and the trust extracted from serialization 
to
+        * the database.
+        * 
+        * @param credentials
+        *            The credentials to reinstall.
+        * @param trust
+        *            The trusted certificates to reinstall.
+        */
+       void setCredentialsAndTrust(Credential[] credentials, Trust[] trust) {
+               synchronized (lock) {
+                       this.credentials.clear();
+                       if (credentials != null)
+                               for (Credential c : credentials)
+                                       try {
+                                               validateCredential(c);
+                                               this.credentials.add(c);
+                                       } catch (InvalidCredentialException e) {
+                                               log.warn("failed to revalidate 
credential: " + c, e);
+                                       }
+                       this.trusted.clear();
+                       if (trust != null)
+                               for (Trust t : trust)
+                                       try {
+                                               validateTrusted(t);
+                                               this.trusted.add(t);
+                                       } catch (InvalidCredentialException e) {
+                                               log.warn("failed to revalidate 
trust assertion: " + t,
+                                                               e);
+                                       }
+               }
+       }
+
+       static class SecurityStore {
+               private KeyStore ks;
+               private char[] password;
+
+               SecurityStore(char[] password) throws GeneralSecurityException {
+                       this.password = password.clone();
+                       ks = KeyStore.getInstance("UBER", "BC");
+                       try {
+                               ks.load(null, this.password);
+                       } catch (IOException e) {
+                               throw new GeneralSecurityException(
+                                               "problem initializing blank 
truststore", e);
+                       }
+               }
+
+               final synchronized void setCertificate(String alias, 
Certificate c)
+                               throws KeyStoreException {
+                       if (ks == null)
+                               throw new IllegalStateException("store already 
written");
+                       ks.setCertificateEntry(alias, c);
+               }
+
+               final synchronized void setKey(String alias, Key key, 
Certificate[] trustChain)
+                               throws KeyStoreException {
+                       if (ks == null)
+                               throw new IllegalStateException("store already 
written");
+                       ks.setKeyEntry(alias, key, password, trustChain);
+               }
+
+               final synchronized byte[] serialize(boolean logIt)
+                               throws GeneralSecurityException {
+                       if (ks == null)
+                               throw new IllegalStateException("store already 
written");
+                       try (ByteArrayOutputStream stream = new 
ByteArrayOutputStream()) {
+                               ks.store(stream, password);
+                               if (logIt)
+                                       
LogFactory.getLog("Taverna.Server.Worker").debug(
+                                                       "serialized UBER/BC 
truststore (size: " + ks.size()
+                                                                       + ") 
with password \""
+                                                                       + new 
String(password) + "\"");
+                               return stream.toByteArray();
+                       } catch (IOException e) {
+                               throw new GeneralSecurityException(
+                                               "problem serializing keystore", 
e);
+                       } finally {
+                               ks = null;
+                               fill(password, ' ');
+                       }
+               }
+
+               @Override
+               protected final void finalize() {
+                       fill(password, ' ');
+                       ks = null;
+               }
+       }
+
+       /**
+        * A trust store that can only be added to or serialized. Only trusted
+        * certificates can be placed in it.
+        * 
+        * @author Donal Fellows
+        */
+       class Truststore extends SecurityStore {
+               Truststore(char[] password) throws GeneralSecurityException {
+                       super(password);
+               }
+
+               /**
+                * Add a trusted certificate to the truststore. No certificates 
can be
+                * added after the truststore is serialized.
+                * 
+                * @param cert
+                *            The certificate (typically belonging to a root 
CA) to add.
+                * @throws KeyStoreException
+                *             If anything goes wrong.
+                */
+               public void addCertificate(Certificate cert) throws 
KeyStoreException {
+                       X509Certificate c = (X509Certificate) cert;
+                       String alias = format("trustedcert#%s#%s#%s",
+                                       
getPrincipalName(c.getSubjectX500Principal()),
+                                       
getPrincipalName(c.getIssuerX500Principal()),
+                                       factory.x500Utils.getSerial(c));
+                       setCertificate(alias, c);
+                       if (log.isDebugEnabled() && factory.logSecurityDetails)
+                               log.debug("added cert with alias \"" + alias + 
"\" of type "
+                                               + 
c.getClass().getCanonicalName());
+               }
+
+               /**
+                * Get the byte serialization of this truststore. This can only 
be
+                * fetched exactly once.
+                * 
+                * @return The serialization.
+                * @throws GeneralSecurityException
+                *             If anything goes wrong.
+                */
+               public byte[] serialize() throws GeneralSecurityException {
+                       return serialize(log.isDebugEnabled() && 
factory.logSecurityDetails);
+               }
+       }
+
+       /**
+        * A key store that can only be added to or serialized. Only keys can be
+        * placed in it.
+        * 
+        * @author Donal Fellows
+        */
+       class Keystore extends SecurityStore {
+               Keystore(char[] password) throws GeneralSecurityException {
+                       super(password);
+               }
+
+               /**
+                * Add a key to the keystore. No keys can be added after the 
keystore is
+                * serialized.
+                * 
+                * @param alias
+                *            The alias of the key.
+                * @param key
+                *            The secret/private key to add.
+                * @param trustChain
+                *            The trusted certificate chain of the key. Should 
be
+                *            <tt>null</tt> for secret keys.
+                * @throws KeyStoreException
+                *             If anything goes wrong.
+                */
+               public void addKey(String alias, Key key, Certificate[] 
trustChain)
+                               throws KeyStoreException {
+                       setKey(alias, key, trustChain);
+                       if (log.isDebugEnabled() && factory.logSecurityDetails)
+                               log.debug("added key with alias \"" + alias + 
"\" of type "
+                                               + 
key.getClass().getCanonicalName());
+               }
+
+               /**
+                * Get the byte serialization of this keystore. This can only 
be fetched
+                * exactly once.
+                * 
+                * @return The serialization.
+                * @throws GeneralSecurityException
+                *             If anything goes wrong.
+                */
+               public byte[] serialize() throws GeneralSecurityException {
+                       return serialize(log.isDebugEnabled() && 
factory.logSecurityDetails);
+               }
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
new file mode 100644
index 0000000..ef29b55
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
@@ -0,0 +1,311 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import static java.lang.String.format;
+import static javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.rmi.RemoteException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.spec.SecretKeySpec;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.taverna.server.localworker.remote.RemoteSecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.utils.UsernamePrincipal;
+import org.taverna.server.master.utils.X500Utils;
+
+/**
+ * Factoring out of the part of the security context handling that actually
+ * deals with the different types of credentials.
+ * 
+ * @author Donal Fellows
+ */
+class SecurityContextDelegateImpl extends SecurityContextDelegate {
+       private static final char USERNAME_PASSWORD_SEPARATOR = '\u0000';
+       private static final String USERNAME_PASSWORD_KEY_ALGORITHM = "DUMMY";
+       /** What passwords are encoded as. */
+       private static final Charset UTF8 = Charset.forName("UTF-8");
+
+       private X500Utils x500Utils;
+
+       /**
+        * Initialise the context delegate.
+        * 
+        * @param run
+        *            What workflow run is this for?
+        * @param owner
+        *            Who owns the workflow run?
+        * @param factory
+        *            What class built this object?
+        */
+       protected SecurityContextDelegateImpl(RemoteRunDelegate run,
+                       UsernamePrincipal owner, SecurityContextFactory 
factory) {
+               super(run, owner, factory);
+               this.x500Utils = factory.x500Utils;
+       }
+
+       @Override
+       public void validateCredential(Credential c)
+                       throws InvalidCredentialException {
+               try {
+                       if (c instanceof Credential.Password)
+                               
validatePasswordCredential((Credential.Password) c);
+                       else if (c instanceof Credential.KeyPair)
+                               validateKeyCredential((Credential.KeyPair) c);
+                       else
+                               throw new InvalidCredentialException("unknown 
credential type");
+               } catch (InvalidCredentialException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new InvalidCredentialException(e);
+               }
+       }
+
+       @Override
+       public void addCredentialToKeystore(Credential c) throws 
KeyStoreException {
+               try {
+                       if (c instanceof Credential.Password)
+                               addUserPassToKeystore((Credential.Password) c);
+                       else if (c instanceof Credential.KeyPair)
+                               addKeypairToKeystore((Credential.KeyPair) c);
+                       else
+                               throw new KeyStoreException("unknown credential 
type");
+               } catch (KeyStoreException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new KeyStoreException(e);
+               }
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+       /**
+        * Tests whether the given username+password credential descriptor is 
valid.
+        * If it is invalid, an exception will be thrown describing what the 
problem
+        * is. Validation mainly consists of listing what the username is.
+        * 
+        * @param passwordDescriptor
+        *            The credential descriptor to validate.
+        * @throws InvalidCredentialException
+        *             If the username is empty. NB: the password may be empty!
+        *             That's legal (if unwise).
+        */
+       protected void validatePasswordCredential(
+                       Credential.Password passwordDescriptor)
+                       throws InvalidCredentialException {
+               if (passwordDescriptor.username == null
+                               || passwordDescriptor.username.trim().isEmpty())
+                       throw new InvalidCredentialException("absent or empty 
username");
+               if (passwordDescriptor.serviceURI == null)
+                       throw new InvalidCredentialException("absent service 
URI");
+               String keyToSave = passwordDescriptor.username
+                               + USERNAME_PASSWORD_SEPARATOR + 
passwordDescriptor.password;
+               passwordDescriptor.loadedKey = encodeKey(keyToSave);
+               passwordDescriptor.loadedTrustChain = null;
+       }
+
+       private static Key encodeKey(String key) {
+               return new SecretKeySpec(key.getBytes(UTF8),
+                               USERNAME_PASSWORD_KEY_ALGORITHM);
+       }
+
+       /**
+        * Adds a username/password credential pair to the current keystore.
+        * 
+        * @param userpassCredential
+        *            The username and password.
+        * @throws KeyStoreException
+        */
+       protected void addUserPassToKeystore(Credential.Password 
userpassCredential)
+                       throws KeyStoreException {
+               String alias = format("password#%s",
+                               userpassCredential.serviceURI.toASCIIString());
+               addKeypairToKeystore(alias, userpassCredential);
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+       /**
+        * Tests whether the given key-pair credential descriptor is valid. If 
it is
+        * invalid, an exception will be thrown describing what the problem is.
+        * 
+        * @param keypairDescriptor
+        *            The descriptor to validate.
+        * @throws InvalidCredentialException
+        *             If the descriptor is invalid
+        * @throws KeyStoreException
+        *             If we don't understand the keystore type or the contents 
of
+        *             the keystore
+        * @throws NoSuchAlgorithmException
+        *             If the keystore is of a known type but we can't 
comprehend
+        *             its security
+        * @throws CertificateException
+        *             If the keystore does not include enough information 
about the
+        *             trust chain of the keypair
+        * @throws UnrecoverableKeyException
+        *             If we can't get the key out of the keystore
+        * @throws IOException
+        *             If we can't read the keystore for prosaic reasons (e.g., 
file
+        *             absent)
+        */
+       protected void validateKeyCredential(Credential.KeyPair 
keypairDescriptor)
+                       throws InvalidCredentialException, KeyStoreException,
+                       NoSuchAlgorithmException, CertificateException, 
IOException,
+                       UnrecoverableKeyException {
+               if (keypairDescriptor.credentialName == null
+                               || 
keypairDescriptor.credentialName.trim().isEmpty())
+                       throw new InvalidCredentialException(
+                                       "absent or empty credentialName");
+
+               InputStream contentsAsStream;
+               if (keypairDescriptor.credentialBytes != null
+                               && keypairDescriptor.credentialBytes.length > 
0) {
+                       contentsAsStream = new ByteArrayInputStream(
+                                       keypairDescriptor.credentialBytes);
+                       keypairDescriptor.credentialFile = null;
+               } else if (keypairDescriptor.credentialFile == null
+                               || 
keypairDescriptor.credentialFile.trim().isEmpty())
+                       throw new InvalidCredentialException(
+                                       "absent or empty credentialFile");
+               else {
+                       contentsAsStream = 
contents(keypairDescriptor.credentialFile);
+                       keypairDescriptor.credentialBytes = new byte[0];
+               }
+               if (keypairDescriptor.fileType == null
+                               || keypairDescriptor.fileType.trim().isEmpty())
+                       keypairDescriptor.fileType = KeyStore.getDefaultType();
+               keypairDescriptor.fileType = keypairDescriptor.fileType.trim();
+
+               KeyStore ks = KeyStore.getInstance(keypairDescriptor.fileType);
+               char[] password = 
keypairDescriptor.unlockPassword.toCharArray();
+               ks.load(contentsAsStream, password);
+
+               try {
+                       keypairDescriptor.loadedKey = ks.getKey(
+                                       keypairDescriptor.credentialName, 
password);
+               } catch (UnrecoverableKeyException ignored) {
+                       keypairDescriptor.loadedKey = ks.getKey(
+                                       keypairDescriptor.credentialName, new 
char[0]);
+               }
+               if (keypairDescriptor.loadedKey == null)
+                       throw new InvalidCredentialException(
+                                       "no such credential in key store");
+               keypairDescriptor.loadedTrustChain = ks
+                               
.getCertificateChain(keypairDescriptor.credentialName);
+               if (keypairDescriptor.loadedTrustChain == null
+                               || keypairDescriptor.loadedTrustChain.length == 
0)
+                       throw new InvalidCredentialException(
+                                       "could not establish trust chain for 
credential");
+       }
+
+       /**
+        * Adds a key-pair to the current keystore.
+        * 
+        * @param c
+        *            The key-pair.
+        * @throws KeyStoreException
+        */
+       protected void addKeypairToKeystore(Credential.KeyPair c)
+                       throws KeyStoreException {
+               X509Certificate subjectCert = (X509Certificate) 
c.loadedTrustChain[0];
+               String alias = format("keypair#%s#%s#%s",
+                               
getPrincipalName(subjectCert.getSubjectX500Principal()),
+                               
getPrincipalName(subjectCert.getIssuerX500Principal()),
+                               x500Utils.getSerial(subjectCert));
+               addKeypairToKeystore(alias, c);
+       }
+}
+
+/**
+ * Special subclass that adds support for HELIO project security tokens.
+ * 
+ * @author Donal Fellows
+ */
+class HelioSecurityContextDelegateImpl extends SecurityContextDelegateImpl {
+       /**
+        * Initialise the context delegate.
+        * 
+        * @param run
+        *            What workflow run is this for?
+        * @param owner
+        *            Who owns the workflow run?
+        * @param factory
+        *            What class built this object?
+        */
+       protected HelioSecurityContextDelegateImpl(RemoteRunDelegate run,
+                       UsernamePrincipal owner, SecurityContextFactory 
factory) {
+               super(run, owner, factory);
+       }
+
+       private Log log = LogFactory.getLog("Taverna.Server.Worker");
+       /** The name of the HTTP header holding the CIS token. */
+       private static final String HELIO_CIS_TOKEN = "X-Helio-CIS";
+       private transient String helioToken;
+
+       @Override
+       public void initializeSecurityFromSOAPContext(MessageContext context) {
+               // does nothing
+               @SuppressWarnings("unchecked")
+               Map<String, List<String>> headers = (Map<String, List<String>>) 
context
+                               .get(HTTP_REQUEST_HEADERS);
+               if (factory.supportHelioToken && 
headers.containsKey(HELIO_CIS_TOKEN))
+                       helioToken = headers.get(HELIO_CIS_TOKEN).get(0);
+       }
+
+       @Override
+       public void initializeSecurityFromRESTContext(HttpHeaders context) {
+               // does nothing
+               MultivaluedMap<String, String> headers = 
context.getRequestHeaders();
+               if (factory.supportHelioToken && 
headers.containsKey(HELIO_CIS_TOKEN))
+                       helioToken = headers.get(HELIO_CIS_TOKEN).get(0);
+       }
+
+       @Override
+       protected void conveyExtraSecuritySettings(RemoteSecurityContext rc)
+                       throws RemoteException {
+               try {
+                       if (factory.supportHelioToken && helioToken != null) {
+                               if (factory.logSecurityDetails)
+                                       log.info("transfering HELIO CIS token: 
" + helioToken);
+                               rc.setHelioToken(helioToken);
+                       }
+               } finally {
+                       helioToken = null;
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
new file mode 100644
index 0000000..1d485d8
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
@@ -0,0 +1,167 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import static java.security.Security.addProvider;
+import static java.security.Security.getProvider;
+import static java.security.Security.removeProvider;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+import java.io.Serializable;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.beans.factory.annotation.Value;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.UriBuilderFactory;
+import org.taverna.server.master.utils.CertificateChainFetcher;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.UsernamePrincipal;
+import org.taverna.server.master.utils.X500Utils;
+
+/**
+ * Singleton factory. Really is a singleton (and is also very trivial); the
+ * singleton-ness is just about limiting the number of instances of this around
+ * even when lots of serialization is going on.
+ * 
+ * @see Serializable
+ * @author Donal Fellows
+ */
+public class SecurityContextFactory implements
+               org.taverna.server.master.interfaces.SecurityContextFactory {
+       private static final long serialVersionUID = 12345678987654321L;
+       private static SecurityContextFactory instance;
+       transient RunDBSupport db;
+       transient FilenameUtils fileUtils;
+       transient X500Utils x500Utils;
+       transient UriBuilderFactory uriSource;
+       transient CertificateChainFetcher certFetcher;
+       transient String httpRealm;
+       private transient PasswordIssuer passwordIssuer;
+       private transient BouncyCastleProvider provider;
+
+       /**
+        * Whether to support HELIO CIS tokens.
+        */
+       @Value("${helio.cis.enableTokenPassing}")
+       boolean supportHelioToken;
+
+       /**
+        * Whether to log the details of security (passwords, etc).
+        */
+       @Value("${log.security.details}")
+       boolean logSecurityDetails;
+
+       private Log log() {
+               return getLog("Taverna.Server.Worker.Security");
+       }
+
+       private void installAsInstance(SecurityContextFactory handle) {
+               instance = handle;
+       }
+
+       @PreDestroy
+       void removeAsSingleton() {
+               installAsInstance(null);
+               try {
+                       if (provider != null)
+                               removeProvider(provider.getName());
+               } catch (SecurityException e) {
+                       log().warn(
+                                       "failed to remove BouncyCastle security 
provider; "
+                                                       + "might be OK if 
configured in environment", e);
+               }
+       }
+
+       @PostConstruct
+       void setAsSingleton() {
+               installAsInstance(this);
+               if (getProvider(PROVIDER_NAME) == null)
+                       try {
+                               provider = new BouncyCastleProvider();
+                               if (addProvider(provider) == -1)
+                                       provider = null;
+                       } catch (SecurityException e) {
+                               log().warn(
+                                               "failed to install BouncyCastle 
security provider; "
+                                                               + "might be OK 
if already configured", e);
+                               provider = null;
+                       }
+       }
+
+       @Required
+       public void setRunDatabase(RunDBSupport db) {
+               this.db = db;
+       }
+
+       @Required
+       public void setCertificateFetcher(CertificateChainFetcher fetcher) {
+               this.certFetcher = fetcher;
+       }
+
+       @Required
+       public void setFilenameConverter(FilenameUtils fileUtils) {
+               this.fileUtils = fileUtils;
+       }
+
+       @Required
+       public void setX500Utils(X500Utils x500Utils) {
+               this.x500Utils = x500Utils;
+       }
+
+       @Required
+       public void setUriSource(UriBuilderFactory uriSource) {
+               this.uriSource = uriSource;
+       }
+
+       @Required
+       public void setHttpRealm(String realm) {
+               this.httpRealm = realm; //${http.realmName}
+       }
+
+       @Required
+       public void setPasswordIssuer(PasswordIssuer issuer) {
+               this.passwordIssuer = issuer;
+       }
+
+       @Override
+       public SecurityContextDelegate create(TavernaRun run,
+                       UsernamePrincipal owner) throws Exception {
+               Log log = log();
+               if (log.isDebugEnabled())
+                       log.debug("constructing security context delegate for " 
+ owner);
+               RemoteRunDelegate rrd = (RemoteRunDelegate) run;
+               return new HelioSecurityContextDelegateImpl(rrd, owner, this);
+       }
+
+       private Object readResolve() {
+               if (instance == null)
+                       installAsInstance(this);
+               return instance;
+       }
+
+       public String issueNewPassword() {
+               return passwordIssuer.issue();
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
new file mode 100644
index 0000000..f9f4d16
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
@@ -0,0 +1,77 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import static org.taverna.server.master.defaults.Default.NOTIFY_MESSAGE_FORMAT;
+
+import java.text.MessageFormat;
+
+import org.springframework.beans.factory.annotation.Required;
+
+/**
+ * Completion notifier that sends messages by email.
+ * 
+ * @author Donal Fellows
+ */
+public class SimpleFormattedCompletionNotifier implements CompletionNotifier {
+       @Required
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       /**
+        * @param subject
+        *            The subject of the notification email.
+        */
+       @Required
+       public void setSubject(String subject) {
+               this.subject = subject;
+       }
+
+       /**
+        * @param messageFormat
+        *            The template for the body of the message to send. 
Parameter #0
+        *            will be substituted with the ID of the job, and parameter 
#1
+        *            will be substituted with the exit code.
+        */
+       public void setMessageFormat(String messageFormat) {
+               this.format = new MessageFormat(messageFormat);
+       }
+
+       private String name;
+       private String subject;
+       private MessageFormat format = new MessageFormat(NOTIFY_MESSAGE_FORMAT);
+
+       @Override
+       public String makeCompletionMessage(String name, RemoteRunDelegate run,
+                       int code) {
+               return format.format(new Object[] { name, code });
+       }
+
+       @Override
+       public String makeMessageSubject(String name, RemoteRunDelegate run,
+                       int code) {
+               return subject;
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
new file mode 100644
index 0000000..a81e610
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
@@ -0,0 +1,121 @@
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import java.io.StringWriter;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.common.version.Version;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.UriBuilderFactory;
+
+public class VelocityCompletionNotifier implements CompletionNotifier {
+       private String subject;
+       private VelocityEngine engine;
+       private Template template;
+       private String name;
+       private String templateName;
+       private UriBuilderFactory ubf;
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       /**
+        * @param subject
+        *            The subject of the notification email.
+        */
+       @Required
+       public void setSubject(String subject) {
+               this.subject = subject;
+       }
+
+       /**
+        * @param engine
+        *            The configured Apache Velocity engine.
+        */
+       @Required
+       public void setVelocityEngine(VelocityEngine engine) {
+               this.engine = engine;
+       }
+
+       /**
+        * @param uriBuilderFactory
+        *            The configured URI builder factory.
+        */
+       @Required
+       public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
+               this.ubf = uriBuilderFactory;
+       }
+
+       /**
+        * @param name
+        *            The name of the template.
+        */
+       @Required
+       public void setName(String name) {
+               this.name = name;
+               this.templateName = getClass().getName() + "_" + name + 
".vtmpl";
+       }
+
+       private Template getTemplate() {
+               if (template == null)
+                       synchronized(this) {
+                               if (template == null)
+                                       template = 
engine.getTemplate(templateName);
+                       }
+               return template;
+       }
+
+       @Override
+       public String makeCompletionMessage(String name, RemoteRunDelegate run,
+                       int code) {
+               VelocityContext ctxt = new VelocityContext();
+               ctxt.put("id", name);
+               ctxt.put("uriBuilder", ubf.getRunUriBuilder(run));
+               ctxt.put("name", run.getName());
+               ctxt.put("creationTime", run.getCreationTimestamp());
+               ctxt.put("startTime", run.getStartTimestamp());
+               ctxt.put("finishTime", run.getFinishTimestamp());
+               ctxt.put("expiryTime", run.getExpiry());
+               ctxt.put("serverVersion", Version.JAVA);
+               for (Listener l : run.getListeners())
+                       if (l.getName().equals("io")) {
+                               for (String p : l.listProperties())
+                                       try {
+                                               ctxt.put("prop_" + p, 
l.getProperty(p));
+                                       } catch (NoListenerException e) {
+                                               // Ignore...
+                                       }
+                               break;
+                       }
+               StringWriter sw = new StringWriter();
+               getTemplate().merge(ctxt, sw);
+               return sw.toString();
+       }
+
+       @Override
+       public String makeMessageSubject(String name, RemoteRunDelegate run,
+                       int code) {
+               return subject;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
new file mode 100644
index 0000000..510c8d0
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
@@ -0,0 +1,216 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+
+import java.net.URI;
+import java.util.List;
+
+import org.taverna.server.master.common.Status;
+
+/**
+ * Profile of the getters and setters in a worker system. Ensures that the
+ * persisted state matches the public view on the state model at least fairly
+ * closely.
+ * 
+ * @author Donal Fellows
+ */
+public interface WorkerModel extends PolicyLimits {
+
+       /**
+        * @param defaultLifetime
+        *            how long a workflow run should live by default, in 
minutes.
+        */
+       public abstract void setDefaultLifetime(int defaultLifetime);
+
+       /**
+        * @return how long a workflow run should live by default, in minutes.
+        */
+       public abstract int getDefaultLifetime();
+
+       /**
+        * @param maxRuns
+        *            the maximum number of extant workflow runs
+        */
+       public abstract void setMaxRuns(int maxRuns);
+
+       /**
+        * @param factoryProcessNamePrefix
+        *            the prefix used for factory processes in RMI
+        */
+       public abstract void setFactoryProcessNamePrefix(
+                       String factoryProcessNamePrefix);
+
+       /**
+        * @return the prefix used for factory processes in RMI
+        */
+       public abstract String getFactoryProcessNamePrefix();
+
+       /**
+        * @param executeWorkflowScript
+        *            the script to run to actually run a workflow
+        */
+       public abstract void setExecuteWorkflowScript(String 
executeWorkflowScript);
+
+       /**
+        * @return the script to run to actually run a workflow
+        */
+       public abstract String getExecuteWorkflowScript();
+
+       /**
+        * @param extraArgs
+        *            the extra arguments to pass into the workflow runner
+        */
+       public abstract void setExtraArgs(String[] extraArgs);
+
+       /**
+        * @return the extra arguments to pass into the workflow runner
+        */
+       public abstract String[] getExtraArgs();
+
+       /**
+        * @param waitSeconds
+        *            the number of seconds to wait for subprocesses to start
+        */
+       public abstract void setWaitSeconds(int waitSeconds);
+
+       /**
+        * @return the number of seconds to wait for subprocesses to start
+        */
+       public abstract int getWaitSeconds();
+
+       /**
+        * @param sleepMS
+        *            milliseconds to wait between polling for a started
+        *            subprocess's status
+        */
+       public abstract void setSleepMS(int sleepMS);
+
+       /**
+        * @return milliseconds to wait between polling for a started 
subprocess's
+        *         status
+        */
+       public abstract int getSleepMS();
+
+       /**
+        * @param serverWorkerJar
+        *            the full path name of the file system access worker
+        *            subprocess's implementation JAR
+        */
+       public abstract void setServerWorkerJar(String serverWorkerJar);
+
+       /**
+        * @return the full path name of the file system access worker 
subprocess's
+        *         implementation JAR
+        */
+       public abstract String getServerWorkerJar();
+
+       /**
+        * @param javaBinary
+        *            the full path name to the Java binary to use
+        */
+       public abstract void setJavaBinary(String javaBinary);
+
+       /**
+        * @return the full path name to the Java binary to use
+        */
+       public abstract String getJavaBinary();
+
+       /**
+        * @param registryPort
+        *            what port is the RMI registry on
+        */
+       public abstract void setRegistryPort(int registryPort);
+
+       /**
+        * @return what port is the RMI registry on
+        */
+       public abstract int getRegistryPort();
+
+       /**
+        * @param registryHost
+        *            what host (network interface) is the RMI registry on
+        */
+       public abstract void setRegistryHost(String registryHost);
+
+       /**
+        * @return what host (network interface) is the RMI registry on
+        */
+       public abstract String getRegistryHost();
+
+       /**
+        * @param serverForkerJar
+        *            the full path name of the impersonation engine's
+        *            implementation JAR
+        */
+       public abstract void setServerForkerJar(String serverForkerJar);
+
+       /**
+        * @return the full path name of the impersonation engine's 
implementation
+        *         JAR
+        */
+       public abstract String getServerForkerJar();
+
+       /**
+        * @param passwordFile
+        *            the full path name of a file containing a password to use 
with
+        *            sudo (or empty for none)
+        */
+       public abstract void setPasswordFile(String passwordFile);
+
+       /**
+        * @return the full path name of a file containing a password to use 
with
+        *         sudo (or empty for none)
+        */
+       public abstract String getPasswordFile();
+
+       /**
+        * @param operatingLimit
+        *            the maximum number of runs in the
+        *            {@linkplain Status#Operating operating} state at once
+        */
+       public abstract void setOperatingLimit(int operatingLimit);
+
+       @Override
+       void setPermittedWorkflowURIs(List<URI> permittedWorkflows);
+
+       /**
+        * @return the full path name of the RMI registry subprocess's
+        *         implementation JAR
+        */
+       String getRegistryJar();
+
+       /**
+        * @param rmiRegistryJar
+        *            the full path name of the RMI registry subprocess's
+        *            implementation JAR
+        */
+       void setRegistryJar(String rmiRegistryJar);
+
+       /**
+        * @return whether a run should generate provenance information by 
default
+        */
+       boolean getGenerateProvenance();
+
+       /**
+        * @param generateProvenance
+        *            whether a run should generate provenance information by
+        *            default
+        */
+       void setGenerateProvenance(boolean generateProvenance);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
new file mode 100644
index 0000000..e96b794
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
@@ -0,0 +1,23 @@
+/*
+ */
+/**
+ * A Taverna Server back-end that works by forking off workflow executors.
+ */
+package org.taverna.server.master.worker;
+/*
+ * 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.
+ */
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
 
b/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
deleted file mode 100644
index 051a037..0000000
--- 
a/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- */
-package org.taverna.server.master;
-/*
- * 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.
- */
-
-import static eu.medsea.util.MimeUtil.getMimeType;
-import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
-import static javax.ws.rs.core.UriBuilder.fromUri;
-import static org.apache.commons.logging.LogFactory.getLog;
-import static org.taverna.server.master.common.Uri.secure;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
-
-import org.apache.commons.logging.Log;
-import org.springframework.beans.factory.annotation.Required;
-import org.taverna.server.master.exceptions.FilesystemAccessException;
-import org.taverna.server.master.exceptions.NoDirectoryEntryException;
-import org.taverna.server.master.interfaces.Directory;
-import org.taverna.server.master.interfaces.DirectoryEntry;
-import org.taverna.server.master.interfaces.File;
-import org.taverna.server.master.interfaces.TavernaRun;
-import org.taverna.server.master.interfaces.UriBuilderFactory;
-import org.taverna.server.master.utils.FilenameUtils;
-import org.taverna.server.port_description.AbsentValue;
-import org.taverna.server.port_description.AbstractPortDescription;
-import org.taverna.server.port_description.AbstractValue;
-import org.taverna.server.port_description.ErrorValue;
-import org.taverna.server.port_description.InputDescription;
-import org.taverna.server.port_description.InputDescription.InputPort;
-import org.taverna.server.port_description.LeafValue;
-import org.taverna.server.port_description.ListValue;
-import org.taverna.server.port_description.OutputDescription;
-import org.taverna.server.port_description.OutputDescription.OutputPort;
-
-import org.apache.taverna.scufl2.api.container.WorkflowBundle;
-import org.apache.taverna.scufl2.api.core.Workflow;
-import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
-import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
-
-/**
- * A class that is used to build descriptions of the contents of a workflow
- * run's filesystem.
- * 
- * @author Donal Fellows
- */
-public class ContentsDescriptorBuilder {
-       private Log log = getLog("Taverna.Server.Webapp");
-       private FilenameUtils fileUtils;
-       private UriBuilderFactory uriBuilderFactory;
-
-       @Required
-       public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
-               this.uriBuilderFactory = uriBuilderFactory;
-       }
-
-       @Required
-       public void setFileUtils(FilenameUtils fileUtils) {
-               this.fileUtils = fileUtils;
-       }
-
-       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
-       private Workflow fillInFromWorkflow(TavernaRun run, UriBuilder ub,
-                       AbstractPortDescription portDesc) throws IOException {
-               WorkflowBundle bundle = run.getWorkflow().getScufl2Workflow();
-               bundle.getMainWorkflow().getInputPorts();
-               portDesc.fillInBaseData(bundle.getMainWorkflow()
-                               .getIdentifier().toString(), run.getId(), 
ub.build());
-               return bundle.getMainWorkflow();
-       }
-
-       /**
-        * Computes the depth of value in a descriptor.
-        * 
-        * @param value
-        *            The value description to characterise.
-        * @return Its depth (i.e., the depth of the port outputting the value) 
or
-        *         <tt>null</tt> if that is impossible to determine.
-        */
-       private Integer computeDepth(AbstractValue value) {
-               if (value instanceof ListValue) {
-                       int mv = 1;
-                       for (AbstractValue v : ((ListValue) value).contents) {
-                               Integer d = computeDepth(v);
-                               if (d != null && mv <= d)
-                                       mv = d + 1;
-                       }
-                       return mv;
-               } else if (value instanceof LeafValue || value instanceof 
ErrorValue)
-                       return 0;
-               else
-                       return null;
-       }
-
-       /**
-        * Build a description of a leaf value.
-        * 
-        * @param file
-        *            The file representing the value.
-        * @return A value descriptor.
-        * @throws FilesystemAccessException
-        *             If anything goes wrong.
-        */
-       private LeafValue constructLeafValue(File file)
-                       throws FilesystemAccessException {
-               LeafValue v = new LeafValue();
-               v.fileName = file.getFullName();
-               v.byteLength = file.getSize();
-               try {
-                       byte[] head = file.getContents(0, 1024);
-                       v.contentType = getMimeType(new 
ByteArrayInputStream(head));
-               } catch (Exception e) {
-                       v.contentType = 
APPLICATION_OCTET_STREAM_TYPE.toString();
-               }
-               return v;
-       }
-
-       /**
-        * Build a description of an error value.
-        * 
-        * @param file
-        *            The file representing the error.
-        * @return A value descriptor.
-        * @throws FilesystemAccessException
-        *             If anything goes wrong.
-        */
-       private ErrorValue constructErrorValue(File file)
-                       throws FilesystemAccessException {
-               ErrorValue v = new ErrorValue();
-               v.fileName = file.getFullName();
-               v.byteLength = file.getSize();
-               return v;
-       }
-
-       /**
-        * Build a description of a list value.
-        * 
-        * @param dir
-        *            The directory representing the list.
-        * @param ub
-        *            The factory for URIs.
-        * @return A value descriptor.
-        * @throws FilesystemAccessException
-        *             If anything goes wrong.
-        */
-       private ListValue constructListValue(Directory dir, UriBuilder ub)
-                       throws FilesystemAccessException {
-               ListValue v = new ListValue();
-               v.length = 0;
-               Set<DirectoryEntry> contents = new HashSet<>(dir.getContents());
-               Iterator<DirectoryEntry> it = contents.iterator();
-               while (it.hasNext())
-                       if (!it.next().getName().matches("^[0-9]+([.].*)?$"))
-                               it.remove();
-               for (int i = 1; !contents.isEmpty(); i++) {
-                       String exact = Integer.toString(i);
-                       AbstractValue subval = constructValue(contents, ub, 
exact);
-                       v.contents.add(subval);
-                       if (!(subval instanceof AbsentValue)) {
-                               v.length = i;
-                               String pfx = i + ".";
-                               for (DirectoryEntry de : contents)
-                                       if (de.getName().equals(exact)
-                                                       || 
de.getName().startsWith(pfx)) {
-                                               contents.remove(de);
-                                               break;
-                                       }
-                       }
-               }
-               return v;
-       }
-
-       /**
-        * Build a value description.
-        * 
-        * @param parentContents
-        *            The contents of the parent directory.
-        * @param ub
-        *            The factory for URIs.
-        * @param name
-        *            The name of the value's file/directory representative.
-        * @return A value descriptor.
-        * @throws FilesystemAccessException
-        *             If anything goes wrong.
-        */
-       private AbstractValue constructValue(
-                       Collection<DirectoryEntry> parentContents, UriBuilder 
ub,
-                       String name) throws FilesystemAccessException {
-               String error = name + ".error";
-               String prefix = name + ".";
-               for (DirectoryEntry entry : parentContents) {
-                       AbstractValue av;
-                       if (entry.getName().equals(error) && entry instanceof 
File) {
-                               av = constructErrorValue((File) entry);
-                       } else if (!entry.getName().equals(name)
-                                       && !entry.getName().startsWith(prefix))
-                               continue;
-                       else if (entry instanceof File)
-                               av = constructLeafValue((File) entry);
-                       else
-                               av = constructListValue((Directory) entry, ub);
-                       String fullPath = 
entry.getFullName().replaceFirst("^/", "");
-                       av.href = ub.clone().path(fullPath).build();
-                       return av;
-               }
-               return new AbsentValue();
-       }
-
-       /**
-        * Construct a description of the outputs of a workflow run.
-        * 
-        * @param run
-        *            The workflow run whose outputs are to be described.
-        * @param ui
-        *            The origin for URIs.
-        * @return The description, which can be serialized to XML.
-        * @throws FilesystemAccessException
-        *             If something goes wrong reading the directories.
-        * @throws NoDirectoryEntryException
-        *             If something goes wrong reading the directories.
-        */
-       public OutputDescription makeOutputDescriptor(TavernaRun run, UriInfo 
ui)
-                       throws FilesystemAccessException, 
NoDirectoryEntryException {
-               OutputDescription descriptor = new OutputDescription();
-               try {
-                       UriBuilder ub = getRunUriBuilder(run, ui);
-                       Workflow dataflow = fillInFromWorkflow(run, ub, 
descriptor);
-                       Collection<DirectoryEntry> outs = null;
-                       ub = ub.path("wd/{path}");
-                       for (OutputWorkflowPort output : 
dataflow.getOutputPorts()) {
-                               OutputPort p = 
descriptor.addPort(output.getName());
-                               if (run.getOutputBaclavaFile() == null) {
-                                       if (outs == null)
-                                               outs = 
fileUtils.getDirectory(run, "out").getContents();
-                                       p.output = constructValue(outs, ub, 
p.name);
-                                       p.depth = computeDepth(p.output);
-                               }
-                       }
-               } catch (IOException e) {
-                       log.info("failure in conversion to .scufl2", e);
-               }
-               return descriptor;
-       }
-
-       private UriBuilder getRunUriBuilder(TavernaRun run, UriInfo ui) {
-               if (ui == null)
-                       return secure(uriBuilderFactory.getRunUriBuilder(run));
-               else
-                       return secure(fromUri(ui.getAbsolutePath().toString()
-                                       .replaceAll("/(out|in)put/?$", "")));
-       }
-
-       /**
-        * Constructs input descriptions.
-        * 
-        * @param run
-        *            The run to build for.
-        * @param ui
-        *            The mechanism for building URIs.
-        * @return The description of the <i>expected</i> inputs of the run.
-        */
-       public InputDescription makeInputDescriptor(TavernaRun run, UriInfo ui) 
{
-               InputDescription desc = new InputDescription();
-               try {
-                       UriBuilder ub = getRunUriBuilder(run, ui);
-                       Workflow workflow = fillInFromWorkflow(run, ub, desc);
-                       ub = ub.path("input/{name}");
-                       for (InputWorkflowPort port : workflow.getInputPorts()) 
{
-                               InputPort in = desc.addPort(port.getName());
-                               in.href = ub.build(in.name);
-                               try {
-                                       in.depth = port.getDepth();
-                               } catch (NumberFormatException ex) {
-                                       in.depth = null;
-                               }
-                       }
-               } catch (IOException e) {
-                       log.info("failure in conversion to .scufl2", e);
-               }
-               return desc;
-       }
-}

Reply via email to