http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
deleted file mode 100644
index bc91f0e..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- */
-package org.taverna.server.localworker.remote;
-/*
- * 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.rmi.Remote;
-import java.rmi.RemoteException;
-import java.util.UUID;
-
-import org.taverna.server.localworker.server.UsageRecordReceiver;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * The main RMI-enabled interface for creating runs.
- * 
- * @author Donal Fellows
- */
-public interface RemoteRunFactory extends Remote {
-       /**
-        * Makes a workflow run that will process a particular workflow 
document.
-        * 
-        * @param workflow
-        *            The (serialised) workflow to instantiate as a run.
-        * @param creator
-        *            Who is this run created for?
-        * @param usageRecordReceiver
-        *            Where to write any usage records. May be <tt>null</tt> to
-        *            cause them to not be written.
-        * @param masterID
-        *            The UUID of the run to use, or <tt>null</tt> if the 
execution
-        *            engine is to manufacture a new one for itself.
-        * @return A remote handle for the run.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       RemoteSingleRun make(@Nonnull byte[] workflow, @Nonnull String creator,
-                       @Nullable UsageRecordReceiver usageRecordReceiver,
-                       @Nullable UUID masterID) throws RemoteException;
-
-       /**
-        * Asks this factory to unregister itself from the registry and cease
-        * operation.
-        * 
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       void shutdown() throws RemoteException;
-
-       /**
-        * Configures the details to use when setting up the workflow run's
-        * connnection to the interaction feed.
-        * 
-        * @param host
-        *            The host where the feed is located.
-        * @param port
-        *            The port where the feed is located.
-        * @param webdavPath
-        *            The path used for pushing web pages into the feed.
-        * @param feedPath
-        *            The path used for reading and writing notifications on the
-        *            feed.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       void setInteractionServiceDetails(@Nonnull String host,
-                       @Nonnull String port, @Nonnull String webdavPath,
-                       @Nonnull String feedPath) throws RemoteException;
-
-       /**
-        * Gets a count of the number of {@linkplain RemoteSingleRun workflow 
runs}
-        * that this factor knows about that are in the
-        * {@link RemoteStatus#Operating Operating} state.
-        * 
-        * @return A count of "running" workflow runs.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       int countOperatingRuns() throws RemoteException;
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java
deleted file mode 100644
index fc4baff..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- */
-package org.taverna.server.localworker.remote;
-/*
- * 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.rmi.Remote;
-import java.rmi.RemoteException;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-
-/**
- * Outline of the security context for a workflow run.
- * 
- * @author Donal Fellows
- */
-public interface RemoteSecurityContext extends Remote {
-       void setKeystore(@Nonnull byte[] keystore) throws RemoteException,
-                       ImplementationException;
-
-       void setPassword(@Nonnull char[] password) throws RemoteException,
-                       ImplementationException;
-
-       void setTruststore(@Nonnull byte[] truststore) throws RemoteException,
-                       ImplementationException;
-
-       void setUriToAliasMap(@Nonnull Map<URI, String> uriToAliasMap)
-                       throws RemoteException;
-
-       void setHelioToken(@Nonnull String helioToken) throws RemoteException;
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
deleted file mode 100644
index e2519f8..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- */
-package org.taverna.server.localworker.remote;
-/*
- * 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.URL;
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.util.Date;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-public interface RemoteSingleRun extends Remote {
-       /**
-        * @return The name of the Baclava file to use for all inputs, or
-        *         <tt>null</tt> if no Baclava file is set.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nullable
-       public String getInputBaclavaFile() throws RemoteException;
-
-       /**
-        * Sets the Baclava file to use for all inputs. This overrides the use 
of
-        * individual inputs.
-        * 
-        * @param filename
-        *            The filename to use. Must not start with a <tt>/</tt> or
-        *            contain any <tt>..</tt> segments. Will be interpreted 
relative
-        *            to the run's working directory.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       public void setInputBaclavaFile(@Nonnull String filename)
-                       throws RemoteException;
-
-       /**
-        * @return The list of input assignments.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public List<RemoteInput> getInputs() throws RemoteException;
-
-       /**
-        * Create an input assignment.
-        * 
-        * @param name
-        *            The name of the port that this will be an input for.
-        * @return The assignment reference.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public RemoteInput makeInput(@Nonnull String name) throws 
RemoteException;
-
-       /**
-        * @return The file (relative to the working directory) to write the 
outputs
-        *         of the run to as a Baclava document, or <tt>null</tt> if 
they are
-        *         to be written to non-Baclava files in a directory called
-        *         <tt>out</tt>.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nullable
-       public String getOutputBaclavaFile() throws RemoteException;
-
-       /**
-        * Sets where the output of the run is to be written to. This will 
cause the
-        * output to be generated as a Baclava document, rather than a 
collection of
-        * individual non-Baclava files in the subdirectory of the working 
directory
-        * called <tt>out</tt>.
-        * 
-        * @param filename
-        *            Where to write the Baclava file (or <tt>null</tt> to 
cause the
-        *            output to be written to individual files); overwrites any
-        *            previous setting of this value.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       public void setOutputBaclavaFile(@Nullable String filename)
-                       throws RemoteException;
-
-       /**
-        * @return The current status of the run.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public RemoteStatus getStatus() throws RemoteException;
-
-       /**
-        * Set the status of the run, which should cause it to move into the 
given
-        * state. This may cause some significant changes.
-        * 
-        * @param s
-        *            The state to try to change to.
-        * @throws IllegalStateTransitionException
-        *             If the requested state change is impossible. (Note that 
it is
-        *             always legal to set the status to the current status.)
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        * @throws ImplementationException
-        *             If something goes horribly wrong on the back end.
-        * @throws StillWorkingOnItException
-        *             If the startup time of the workflow implementation 
exceeds a
-        *             built-in threshold.
-        */
-       public void setStatus(@Nonnull RemoteStatus s)
-                       throws IllegalStateTransitionException, RemoteException,
-                       ImplementationException, StillWorkingOnItException;
-
-       /**
-        * @return When this workflow run was found to have finished, or
-        *         <tt>null</tt> if it has never finished (either still running 
or
-        *         never started).
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nullable
-       public Date getFinishTimestamp() throws RemoteException;
-
-       /**
-        * @return When this workflow run was started, or <tt>null</tt> if it 
has
-        *         never been started.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nullable
-       public Date getStartTimestamp() throws RemoteException;
-
-       /**
-        * @return Handle to the main working directory of the run.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public RemoteDirectory getWorkingDirectory() throws RemoteException;
-
-       /**
-        * @return The list of listener instances attached to the run.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public List<RemoteListener> getListeners() throws RemoteException;
-
-       /**
-        * Add a listener to the run.
-        * 
-        * @param listener
-        *            The listener to add.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        * @throws ImplementationException
-        *             If something goes wrong when adding the listener.
-        */
-       public void addListener(@Nonnull RemoteListener listener)
-                       throws RemoteException, ImplementationException;
-
-       /**
-        * @return The security context structure for this run.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        * @throws ImplementationException
-        *             If something goes wrong when getting the context.
-        */
-       @Nonnull
-       public RemoteSecurityContext getSecurityContext() throws 
RemoteException,
-                       ImplementationException;
-
-       /**
-        * Kill off this run, removing all resources which it consumes.
-        * 
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        * @throws ImplementationException
-        *             If something goes horribly wrong when destroying the run.
-        */
-       public void destroy() throws RemoteException, ImplementationException;
-
-       /**
-        * Get the types of listener supported by this run.
-        * 
-        * @return A list of listener type names.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public List<String> getListenerTypes() throws RemoteException;
-
-       /**
-        * Create a listener that can be attached to this run.
-        * 
-        * @param type
-        *            The type name of the listener to create; it must be one 
of the
-        *            names returned by the {@link #getListenerTypes()} 
operation.
-        * @param configuration
-        *            The configuration document for this listener. The nature 
of
-        *            the contents of this are determined by the type.
-        * @return A handle for the listener.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       @Nonnull
-       public RemoteListener makeListener(@Nonnull String type,
-                       @Nonnull String configuration) throws RemoteException;
-
-       /**
-        * Configures the details to use when setting up the workflow run's
-        * connnection to the interaction feed.
-        * 
-        * @param interactionFeed
-        *            The location of the interaction feed. If <tt>null</tt>,
-        *            defaults from the factory will be used instead.
-        * @param webdavPath
-        *            The location used for pushing web pages to support the 
feed.
-        *            If <tt>null</tt>, a default from the factory will be used
-        *            instead.
-        * @param publishUrlBase
-        *            Where to <i>actually</i> publish to, if this needs to be
-        *            different from the location presented in the published 
HTML
-        *            and Feed entries. Necessary in complex network scenarios.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       void setInteractionServiceDetails(@Nonnull URL interactionFeed,
-                       @Nonnull URL webdavPath, @Nullable URL publishUrlBase) 
throws RemoteException;
-
-       /**
-        * A do-nothing method, used to check the general reachability of the
-        * workflow run.
-        * 
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       void ping() throws RemoteException;
-
-       /**
-        * Sets whether we should generate provenance information from a run.
-        * 
-        * @param generateProvenance
-        *            Boolean flag, true for do the generation. Must be set 
before
-        *            starting the run for this to have an effect.
-        * @throws RemoteException
-        *             If anything goes wrong with the communication.
-        */
-       void setGenerateProvenance(boolean generateProvenance)
-                       throws RemoteException;
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java
deleted file mode 100644
index 89a7cff..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- */
-package org.taverna.server.localworker.remote;
-/*
- * 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.
- */
-
-/**
- * States of a workflow run. They are {@link RemoteStatus#Initialized
- * Initialized}, {@link RemoteStatus#Operating Operating},
- * {@link RemoteStatus#Stopped Stopped}, and {@link RemoteStatus#Finished
- * Finished}. Conceptually, there is also a <tt>Destroyed</tt> state, but the
- * workflow run does not exist (and hence can't have its state queried or set)
- * in that case.
- * 
- * @author Donal Fellows
- */
-public enum RemoteStatus {
-       /**
-        * The workflow run has been created, but is not yet running. The run 
will
-        * need to be manually moved to {@link #Operating} when ready.
-        */
-       Initialized,
-       /**
-        * The workflow run is going, reading input, generating output, etc. 
Will
-        * eventually either move automatically to {@link #Finished} or can be 
moved
-        * manually to {@link #Stopped} (where supported).
-        */
-       Operating,
-       /**
-        * The workflow run is paused, and will need to be moved back to
-        * {@link #Operating} manually.
-        */
-       Stopped,
-       /**
-        * The workflow run has ceased; data files will continue to exist until 
the
-        * run is destroyed (which may be manual or automatic).
-        */
-       Finished
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java
deleted file mode 100644
index a7af96b..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- */
-package org.taverna.server.localworker.remote;
-/*
- * 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.
- */
-
-/**
- * Exception that indicates that the implementation is still working on
- * processing the operation. Note that though this is an exception, it is 
<i>not
- * a failure</i>.
- * 
- * @author Donal Fellows
- */
-@SuppressWarnings("serial")
-public class StillWorkingOnItException extends Exception {
-       public StillWorkingOnItException(String string) {
-               super(string);
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java
deleted file mode 100644
index 6466e2f..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- */
-/**
- * Interfaces exported by worker classes to the server.
- */
-package org.taverna.server.localworker.remote;
-/*
- * 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-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java
deleted file mode 100644
index cc0127e..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- */
-package org.taverna.server.localworker.server;
-/*
- * 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.rmi.Remote;
-import java.rmi.RemoteException;
-
-/**
- * Interface exported by (part of) the webapp to allow processes it creates to
- * push in usage records.
- * 
- * @author Donal Fellows
- */
-public interface UsageRecordReceiver extends Remote {
-       /**
-        * Called to push in a usage record. Note that it is assumed that the 
usage
-        * record already contains all the information required to locate and
-        * process the job; there is no separate handle.
-        * 
-        * @param usageRecord
-        *            The serialised XML of the usage record.
-        * @throws RemoteException
-        *             if anything goes wrong.
-        */
-       void acceptUsageRecord(String usageRecord) throws RemoteException;
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java
 
b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java
deleted file mode 100644
index 584f1ed..0000000
--- 
a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- */
-/**
- * Interfaces exported by the server to worker classes.
- */
-package org.taverna.server.localworker.server;
-/*
- * 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-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java
 
b/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java
new file mode 100644
index 0000000..ebe73f4
--- /dev/null
+++ 
b/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java
@@ -0,0 +1,221 @@
+/*
+ */
+package org.taverna.server.unixforker;
+/*
+ * 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.System.err;
+import static java.lang.System.getProperty;
+import static java.lang.System.in;
+import static java.lang.System.out;
+import static java.util.Arrays.asList;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A simple class that forks off processes when asked to over its standard
+ * input. The one complication is that it forks them off as other users, 
through
+ * the use of the <tt>sudo</tt> utility. It is Unix-specific.
+ * 
+ * @author Donal Fellows
+ */
+public class Forker extends Thread {
+       private static String password;
+       private static BufferedReader br;
+
+       /**
+        * Helper to make reading a password from a file clearer. The password 
must
+        * be the first line of the file.
+        * 
+        * @param passwordFile
+        *            The file to load from.
+        * @throws IOException
+        *             If anything goes wrong.
+        */
+       private static void loadPassword(@Nonnull File passwordFile)
+                       throws IOException {
+               try {
+                       err.println("attempting to load password from " + 
passwordFile);
+                       try (FileReader fr = new FileReader(passwordFile)) {
+                               password = new BufferedReader(fr).readLine();
+                       }
+               } catch (IOException e) {
+                       err.println("failed to read password from file " + 
passwordFile
+                                       + "described in password.file 
property");
+                       throw e;
+               }
+       }
+
+       /**
+        * Initialization code, which runs before the main loop starts 
processing.
+        * 
+        * @param args
+        *            The arguments to the program.
+        * @throws Exception
+        *             If anything goes wrong.
+        */
+       public static void init(String[] args) throws Exception {
+               if (args.length < 1)
+                       throw new IllegalArgumentException(
+                                       "wrong # args: must be \"program 
?argument ...?\"");
+               if (getProperty("password.file") != null)
+                       loadPassword(new File(getProperty("password.file")));
+               if (password == null)
+                       err.println("no password.file property or empty file; "
+                                       + "assuming password-less sudo is 
configured");
+               else
+                       err.println("password is of length " + 
password.length());
+               br = new BufferedReader(new InputStreamReader(in));
+       }
+
+       /**
+        * The body of the main loop of this program.
+        * 
+        * @param args
+        *            The arguments to use when running the other program.
+        * @return Whether to repeat the loop.
+        * @throws Exception
+        *             If anything goes wrong. Note that the loop is repeated 
if an
+        *             exception occurs in it.
+        */
+       public static boolean mainLoopBody(String[] args) throws Exception {
+               String line = br.readLine();
+               if (line == null)
+                       return false;
+               List<String> vals = asList(line.split("[ \t]+"));
+               if (vals.size() != 2) {
+                       out.println("wrong # values: must be \"username 
UUID\"");
+                       return true;
+               }
+               ProcessBuilder pb = new ProcessBuilder();
+               pb.command()
+                               .addAll(asList("sudo", "-u", vals.get(0), "-S", 
"-H", "--"));
+               pb.command().addAll(asList(args));
+               pb.command().add(vals.get(1));
+               Forker f = new Forker(pb);
+               f.setDaemon(true);
+               f.start();
+               return true;
+       }
+
+       /**
+        * The main code for this class, which turns this into an executable
+        * program. Runs the initialisation and then the main loop, in both 
cases
+        * with appropriate error handling.
+        * 
+        * @param args
+        *            Arguments to this program.
+        */
+       public static void main(String... args) {
+               try {
+                       init(args);
+                       while (true) {
+                               try {
+                                       if (!mainLoopBody(args))
+                                               break;
+                               } catch (Exception e) {
+                                       e.printStackTrace(err);
+                                       out.println(e.getClass().getName() + ": 
" + e.getMessage());
+                               }
+                       }
+                       System.exit(0);
+               } catch (Exception e) {
+                       e.printStackTrace(err);
+                       System.exit(1);
+               }
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+       public Forker(ProcessBuilder pb) throws IOException {
+               out.println("Starting subprocess: " + pb.command());
+               final Process p = pb.start();
+               abstract class ProcessAttachedDaemon extends Thread {
+                       public ProcessAttachedDaemon() {
+                               setDaemon(true);
+                               start();
+                       }
+
+                       abstract void act() throws Exception;
+
+                       @Override
+                       public final void run() {
+                               try {
+                                       act();
+                                       p.waitFor();
+                               } catch (InterruptedException e) {
+                                       // Just drop
+                               } catch (Exception e) {
+                                       p.destroy();
+                                       e.printStackTrace(err);
+                               }
+                       }
+               }
+               new ProcessAttachedDaemon() {
+                       @Override
+                       void act() throws Exception {
+                               copyFromSudo("Subprocess(out):", 
p.getInputStream());
+                       }
+               };
+               new ProcessAttachedDaemon() {
+                       @Override
+                       void act() throws Exception {
+                               copyFromSudo("Subprocess(err):", 
p.getErrorStream());
+                       }
+               };
+               new ProcessAttachedDaemon() {
+                       @Override
+                       void act() throws Exception {
+                               interactWithSudo(p.getOutputStream());
+                       }
+               };
+       }
+
+       protected void interactWithSudo(OutputStream os) throws Exception {
+               if (password != null) {
+                       OutputStreamWriter osw = new OutputStreamWriter(os);
+                       osw.write(password + "\n");
+                       osw.flush();
+               }
+               os.close();
+       }
+
+       protected void copyFromSudo(String header, InputStream sudoStream)
+                       throws Exception {
+               int b = '\n';
+               while (true) {
+                       if (b == '\n')
+                               out.print(header);
+                       b = sudoStream.read();
+                       if (b == -1)
+                               break;
+                       out.write(b);
+                       out.flush();
+               }
+               sudoStream.close();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java
 
b/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java
deleted file mode 100644
index ebe73f4..0000000
--- 
a/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- */
-package org.taverna.server.unixforker;
-/*
- * 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.System.err;
-import static java.lang.System.getProperty;
-import static java.lang.System.in;
-import static java.lang.System.out;
-import static java.util.Arrays.asList;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-/**
- * A simple class that forks off processes when asked to over its standard
- * input. The one complication is that it forks them off as other users, 
through
- * the use of the <tt>sudo</tt> utility. It is Unix-specific.
- * 
- * @author Donal Fellows
- */
-public class Forker extends Thread {
-       private static String password;
-       private static BufferedReader br;
-
-       /**
-        * Helper to make reading a password from a file clearer. The password 
must
-        * be the first line of the file.
-        * 
-        * @param passwordFile
-        *            The file to load from.
-        * @throws IOException
-        *             If anything goes wrong.
-        */
-       private static void loadPassword(@Nonnull File passwordFile)
-                       throws IOException {
-               try {
-                       err.println("attempting to load password from " + 
passwordFile);
-                       try (FileReader fr = new FileReader(passwordFile)) {
-                               password = new BufferedReader(fr).readLine();
-                       }
-               } catch (IOException e) {
-                       err.println("failed to read password from file " + 
passwordFile
-                                       + "described in password.file 
property");
-                       throw e;
-               }
-       }
-
-       /**
-        * Initialization code, which runs before the main loop starts 
processing.
-        * 
-        * @param args
-        *            The arguments to the program.
-        * @throws Exception
-        *             If anything goes wrong.
-        */
-       public static void init(String[] args) throws Exception {
-               if (args.length < 1)
-                       throw new IllegalArgumentException(
-                                       "wrong # args: must be \"program 
?argument ...?\"");
-               if (getProperty("password.file") != null)
-                       loadPassword(new File(getProperty("password.file")));
-               if (password == null)
-                       err.println("no password.file property or empty file; "
-                                       + "assuming password-less sudo is 
configured");
-               else
-                       err.println("password is of length " + 
password.length());
-               br = new BufferedReader(new InputStreamReader(in));
-       }
-
-       /**
-        * The body of the main loop of this program.
-        * 
-        * @param args
-        *            The arguments to use when running the other program.
-        * @return Whether to repeat the loop.
-        * @throws Exception
-        *             If anything goes wrong. Note that the loop is repeated 
if an
-        *             exception occurs in it.
-        */
-       public static boolean mainLoopBody(String[] args) throws Exception {
-               String line = br.readLine();
-               if (line == null)
-                       return false;
-               List<String> vals = asList(line.split("[ \t]+"));
-               if (vals.size() != 2) {
-                       out.println("wrong # values: must be \"username 
UUID\"");
-                       return true;
-               }
-               ProcessBuilder pb = new ProcessBuilder();
-               pb.command()
-                               .addAll(asList("sudo", "-u", vals.get(0), "-S", 
"-H", "--"));
-               pb.command().addAll(asList(args));
-               pb.command().add(vals.get(1));
-               Forker f = new Forker(pb);
-               f.setDaemon(true);
-               f.start();
-               return true;
-       }
-
-       /**
-        * The main code for this class, which turns this into an executable
-        * program. Runs the initialisation and then the main loop, in both 
cases
-        * with appropriate error handling.
-        * 
-        * @param args
-        *            Arguments to this program.
-        */
-       public static void main(String... args) {
-               try {
-                       init(args);
-                       while (true) {
-                               try {
-                                       if (!mainLoopBody(args))
-                                               break;
-                               } catch (Exception e) {
-                                       e.printStackTrace(err);
-                                       out.println(e.getClass().getName() + ": 
" + e.getMessage());
-                               }
-                       }
-                       System.exit(0);
-               } catch (Exception e) {
-                       e.printStackTrace(err);
-                       System.exit(1);
-               }
-       }
-
-       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
-       public Forker(ProcessBuilder pb) throws IOException {
-               out.println("Starting subprocess: " + pb.command());
-               final Process p = pb.start();
-               abstract class ProcessAttachedDaemon extends Thread {
-                       public ProcessAttachedDaemon() {
-                               setDaemon(true);
-                               start();
-                       }
-
-                       abstract void act() throws Exception;
-
-                       @Override
-                       public final void run() {
-                               try {
-                                       act();
-                                       p.waitFor();
-                               } catch (InterruptedException e) {
-                                       // Just drop
-                               } catch (Exception e) {
-                                       p.destroy();
-                                       e.printStackTrace(err);
-                               }
-                       }
-               }
-               new ProcessAttachedDaemon() {
-                       @Override
-                       void act() throws Exception {
-                               copyFromSudo("Subprocess(out):", 
p.getInputStream());
-                       }
-               };
-               new ProcessAttachedDaemon() {
-                       @Override
-                       void act() throws Exception {
-                               copyFromSudo("Subprocess(err):", 
p.getErrorStream());
-                       }
-               };
-               new ProcessAttachedDaemon() {
-                       @Override
-                       void act() throws Exception {
-                               interactWithSudo(p.getOutputStream());
-                       }
-               };
-       }
-
-       protected void interactWithSudo(OutputStream os) throws Exception {
-               if (password != null) {
-                       OutputStreamWriter osw = new OutputStreamWriter(os);
-                       osw.write(password + "\n");
-                       osw.flush();
-               }
-               os.close();
-       }
-
-       protected void copyFromSudo(String header, InputStream sudoStream)
-                       throws Exception {
-               int b = '\n';
-               while (true) {
-                       if (b == '\n')
-                               out.print(header);
-                       b = sudoStream.read();
-                       if (b == -1)
-                               break;
-                       out.write(b);
-                       out.flush();
-               }
-               sudoStream.close();
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java
new file mode 100644
index 0000000..051a037
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java
@@ -0,0 +1,305 @@
+/*
+ */
+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;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java
new file mode 100644
index 0000000..b800c1c
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java
@@ -0,0 +1,388 @@
+/*
+ */
+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 javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.ok;
+import static javax.ws.rs.core.Response.seeOther;
+import static javax.ws.rs.core.Response.status;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.taverna.server.master.api.ContentTypes.APPLICATION_ZIP_TYPE;
+import static org.taverna.server.master.api.ContentTypes.DIRECTORY_VARIANTS;
+import static org.taverna.server.master.api.ContentTypes.INITIAL_FILE_VARIANTS;
+import static org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.Variant;
+import javax.xml.ws.Holder;
+
+import org.apache.commons.logging.Log;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.DirectoryBean;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+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.rest.DirectoryContents;
+import org.taverna.server.master.rest.FileSegment;
+import org.taverna.server.master.rest.MakeOrUpdateDirEntry;
+import org.taverna.server.master.rest.MakeOrUpdateDirEntry.MakeDirectory;
+import org.taverna.server.master.rest.TavernaServerDirectoryREST;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful access to the filesystem.
+ * 
+ * @author Donal Fellows
+ */
+class DirectoryREST implements TavernaServerDirectoryREST, DirectoryBean {
+       private Log log = getLog("Taverna.Server.Webapp");
+       private TavernaServerSupport support;
+       private TavernaRun run;
+       private FilenameUtils fileUtils;
+
+       @Override
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       @Required
+       public void setFileUtils(FilenameUtils fileUtils) {
+               this.fileUtils = fileUtils;
+       }
+
+       @Override
+       public DirectoryREST connect(TavernaRun run) {
+               this.run = run;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Response destroyDirectoryEntry(List<PathSegment> path)
+                       throws NoUpdateException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               support.permitUpdate(run);
+               fileUtils.getDirEntry(run, path).destroy();
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public DirectoryContents getDescription(UriInfo ui)
+                       throws FilesystemAccessException {
+               return new DirectoryContents(ui, run.getWorkingDirectory()
+                               .getContents());
+       }
+
+       @Override
+       @CallCounted
+       public Response options(List<PathSegment> path) {
+               return opt("PUT", "POST", "DELETE");
+       }
+
+       /*
+        * // Nasty! This can have several different responses...
+        * 
+        * @Override @CallCounted private Response
+        * getDirectoryOrFileContents(List<PathSegment> path, UriInfo ui, 
Request
+        * req) throws FilesystemAccessException, NoDirectoryEntryException {
+        * 
+        * DirectoryEntry de = fileUtils.getDirEntry(run, path);
+        * 
+        * // How did the user want the result?
+        * 
+        * List<Variant> variants = getVariants(de); Variant v =
+        * req.selectVariant(variants); if (v == null) return
+        * notAcceptable(variants).type(TEXT_PLAIN)
+        * .entity("Do not know what type of response to produce.") .build();
+        * 
+        * // Produce the content to deliver up
+        * 
+        * Object result; if
+        * (v.getMediaType().equals(APPLICATION_OCTET_STREAM_TYPE))
+        * 
+        * // Only for files...
+        * 
+        * result = de; else if (v.getMediaType().equals(APPLICATION_ZIP_TYPE))
+        * 
+        * // Only for directories...
+        * 
+        * result = ((Directory) de).getContentsAsZip(); else
+        * 
+        * // Only for directories... // XML or JSON; let CXF pick what to do
+        * 
+        * result = new DirectoryContents(ui, ((Directory) de).getContents());
+        * return ok(result).type(v.getMediaType()).build();
+        * 
+        * }
+        */
+
+       private boolean matchType(MediaType a, MediaType b) {
+               if (log.isDebugEnabled())
+                       log.debug("comparing " + a.getType() + "/" + 
a.getSubtype()
+                                       + " and " + b.getType() + "/" + 
b.getSubtype());
+               return (a.isWildcardType() || b.isWildcardType() || 
a.getType().equals(
+                               b.getType()))
+                               && (a.isWildcardSubtype() || 
b.isWildcardSubtype() || a
+                                               
.getSubtype().equals(b.getSubtype()));
+       }
+
+       /**
+        * What are we willing to serve up a directory or file as?
+        * 
+        * @param de
+        *            The reference to the object to serve.
+        * @return The variants we can serve it as.
+        * @throws FilesystemAccessException
+        *             If we fail to read data necessary to detection of its 
media
+        *             type.
+        */
+       private List<Variant> getVariants(DirectoryEntry de)
+                       throws FilesystemAccessException {
+               if (de instanceof Directory)
+                       return DIRECTORY_VARIANTS;
+               else if (!(de instanceof File))
+                       throw new FilesystemAccessException("not a directory or 
file!");
+               File f = (File) de;
+               List<Variant> variants = new ArrayList<>(INITIAL_FILE_VARIANTS);
+               String contentType = support.getEstimatedContentType(f);
+               if (!contentType.equals(APPLICATION_OCTET_STREAM)) {
+                       String[] ct = contentType.split("/");
+                       variants.add(0,
+                                       new Variant(new MediaType(ct[0], 
ct[1]), (String) null, null));
+               }
+               return variants;
+       }
+
+       /** How did the user want the result? */
+       private MediaType pickType(HttpHeaders headers, DirectoryEntry de)
+                       throws FilesystemAccessException, 
NegotiationFailedException {
+               List<Variant> variants = getVariants(de);
+               // Manual content negotiation!!! Ugh!
+               for (MediaType mt : headers.getAcceptableMediaTypes())
+                       for (Variant v : variants)
+                               if (matchType(mt, v.getMediaType()))
+                                       return v.getMediaType();
+               throw new NegotiationFailedException(
+                               "Do not know what type of response to 
produce.", variants);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Response getDirectoryOrFileContents(List<PathSegment> path,
+                       UriInfo ui, HttpHeaders headers) throws 
FilesystemAccessException,
+                       NoDirectoryEntryException, NegotiationFailedException {
+               DirectoryEntry de = fileUtils.getDirEntry(run, path);
+
+               // How did the user want the result?
+               MediaType wanted = pickType(headers, de);
+
+               log.info("producing content of type " + wanted);
+               // Produce the content to deliver up
+               Object result;
+               if (de instanceof File) {
+                       // Only for files...
+                       result = de;
+                       List<String> range = headers.getRequestHeader("Range");
+                       if (range != null && range.size() == 1)
+                               return new FileSegment((File) de, range.get(0))
+                                               .toResponse(wanted);
+               } else {
+                       // Only for directories...
+                       Directory d = (Directory) de;
+                       if 
(wanted.getType().equals(APPLICATION_ZIP_TYPE.getType())
+                                       && wanted.getSubtype().equals(
+                                                       
APPLICATION_ZIP_TYPE.getSubtype()))
+                               result = d.getContentsAsZip();
+                       else
+                               // XML or JSON; let CXF pick what to do
+                               result = new DirectoryContents(ui, 
d.getContents());
+               }
+               return ok(result).type(wanted).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Response makeDirectoryOrUpdateFile(List<PathSegment> parent,
+                       MakeOrUpdateDirEntry op, UriInfo ui) throws 
NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               support.permitUpdate(run);
+               DirectoryEntry container = fileUtils.getDirEntry(run, parent);
+               if (!(container instanceof Directory))
+                       throw new FilesystemAccessException("You may not "
+                                       + ((op instanceof MakeDirectory) ? 
"make a subdirectory of"
+                                                       : "place a file in") + 
" a file.");
+               if (op.name == null || op.name.length() == 0)
+                       throw new FilesystemAccessException("missing name 
attribute");
+               Directory d = (Directory) container;
+               UriBuilder ub = secure(ui).path("{name}");
+
+               // Make a directory in the context directory
+
+               if (op instanceof MakeDirectory) {
+                       Directory target = 
d.makeSubdirectory(support.getPrincipal(),
+                                       op.name);
+                       return created(ub.build(target.getName())).build();
+               }
+
+               // Make or set the contents of a file
+
+               File f = null;
+               for (DirectoryEntry e : d.getContents()) {
+                       if (e.getName().equals(op.name)) {
+                               if (e instanceof Directory)
+                                       throw new FilesystemAccessException(
+                                                       "You may not overwrite 
a directory with a file.");
+                               f = (File) e;
+                               break;
+                       }
+               }
+               if (f == null) {
+                       f = d.makeEmptyFile(support.getPrincipal(), op.name);
+                       f.setContents(op.contents);
+                       return created(ub.build(f.getName())).build();
+               }
+               f.setContents(op.contents);
+               return seeOther(ub.build(f.getName())).build();
+       }
+
+       private File getFileForWrite(List<PathSegment> filePath,
+                       Holder<Boolean> isNew) throws FilesystemAccessException,
+                       NoDirectoryEntryException, NoUpdateException {
+               support.permitUpdate(run);
+               if (filePath == null || filePath.size() == 0)
+                       throw new FilesystemAccessException(
+                                       "Cannot create a file that is not in a 
directory.");
+
+               List<PathSegment> dirPath = new ArrayList<>(filePath);
+               String name = dirPath.remove(dirPath.size() - 1).getPath();
+               DirectoryEntry de = fileUtils.getDirEntry(run, dirPath);
+               if (!(de instanceof Directory)) {
+                       throw new FilesystemAccessException(
+                                       "Cannot create a file that is not in a 
directory.");
+               }
+               Directory d = (Directory) de;
+
+               File f = null;
+               isNew.value = false;
+               for (DirectoryEntry e : d.getContents())
+                       if (e.getName().equals(name)) {
+                               if (e instanceof File) {
+                                       f = (File) e;
+                                       break;
+                               }
+                               throw new FilesystemAccessException(
+                                               "Cannot create a file that is 
not in a directory.");
+                       }
+
+               if (f == null) {
+                       f = d.makeEmptyFile(support.getPrincipal(), name);
+                       isNew.value = true;
+               } else
+                       f.setContents(new byte[0]);
+               return f;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Response setFileContents(List<PathSegment> filePath,
+                       InputStream contents, UriInfo ui) throws 
NoDirectoryEntryException,
+                       NoUpdateException, FilesystemAccessException {
+               Holder<Boolean> isNew = new Holder<>(true);
+               support.copyStreamToFile(contents, getFileForWrite(filePath, 
isNew));
+
+               if (isNew.value)
+                       return created(ui.getAbsolutePath()).build();
+               else
+                       return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response setFileContentsFromURL(List<PathSegment> filePath,
+                       List<URI> referenceList, UriInfo ui)
+                       throws NoDirectoryEntryException, NoUpdateException,
+                       FilesystemAccessException {
+               support.permitUpdate(run);
+               if (referenceList.isEmpty() || referenceList.size() > 1)
+                       return status(422).entity("URI list must have single 
URI in it")
+                                       .build();
+               URI uri = referenceList.get(0);
+               try {
+                       uri.toURL();
+               } catch (MalformedURLException e) {
+                       return status(422).entity("URI list must have value URL 
in it")
+                                       .build();
+               }
+               Holder<Boolean> isNew = new Holder<>(true);
+               File f = getFileForWrite(filePath, isNew);
+
+               try {
+                       support.copyDataToFile(uri, f);
+               } catch (MalformedURLException ex) {
+                       // Should not happen; called uri.toURL() successfully 
above
+                       throw new NoUpdateException("failed to parse URI", ex);
+               } catch (IOException ex) {
+                       throw new FilesystemAccessException(
+                                       "failed to transfer data from URI", ex);
+               }
+
+               if (isNew.value)
+                       return created(ui.getAbsolutePath()).build();
+               else
+                       return noContent().build();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java
new file mode 100644
index 0000000..4ccb033
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java
@@ -0,0 +1,84 @@
+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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.interfaces.File;
+
+/**
+ * Simple concatenation of files.
+ * 
+ * @author Donal Fellows
+ */
+public class FileConcatenation implements Iterable<File> {
+       private List<File> files = new ArrayList<>();
+
+       public void add(File f) {
+               files.add(f);
+       }
+
+       public boolean isEmpty() {
+               return files.isEmpty();
+       }
+
+       /**
+        * @return The total length of the files, or -1 if this cannot be
+        *         determined.
+        */
+       public long size() {
+               long size = 0;
+               for (File f : files)
+                       try {
+                               size += f.getSize();
+                       } catch (FilesystemAccessException e) {
+                               // Ignore; shouldn't happen but can't guarantee
+                       }
+               return (size == 0 && !files.isEmpty() ? -1 : size);
+       }
+
+       /**
+        * Get the concatenated files.
+        * 
+        * @param encoding
+        *            The encoding to use.
+        * @return The concatenated files.
+        * @throws UnsupportedEncodingException
+        *             If the encoding doesn't exist.
+        */
+       public String get(String encoding) throws UnsupportedEncodingException {
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();
+               for (File f : files)
+                       try {
+                               baos.write(f.getContents(0, -1));
+                       } catch (FilesystemAccessException | IOException e) {
+                               continue;
+                       }
+               return baos.toString(encoding);
+       }
+
+       @Override
+       public Iterator<File> iterator() {
+               return files.iterator();
+       }
+}
\ 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/InputREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java
new file mode 100644
index 0000000..b9c8f55
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java
@@ -0,0 +1,265 @@
+/*
+ */
+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 java.util.UUID.randomUUID;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.Date;
+
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.InputBean;
+import org.taverna.server.master.common.DirEntryReference;
+import org.taverna.server.master.exceptions.BadInputPortNameException;
+import org.taverna.server.master.exceptions.BadPropertyValueException;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.interfaces.DirectoryEntry;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.Input;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.TavernaServerInputREST;
+import 
org.taverna.server.master.rest.TavernaServerInputREST.InDesc.AbstractContents;
+import org.taverna.server.master.rest.TavernaServerInputREST.InDesc.Reference;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+import org.taverna.server.port_description.InputDescription;
+
+/**
+ * RESTful interface to the input descriptor of a single workflow run.
+ * 
+ * @author Donal Fellows
+ */
+class InputREST implements TavernaServerInputREST, InputBean {
+       private UriInfo ui;
+       private TavernaServerSupport support;
+       private TavernaRun run;
+       private ContentsDescriptorBuilder cdBuilder;
+       private FilenameUtils fileUtils;
+
+       @Override
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       @Required
+       public void setCdBuilder(ContentsDescriptorBuilder cdBuilder) {
+               this.cdBuilder = cdBuilder;
+       }
+
+       @Override
+       @Required
+       public void setFileUtils(FilenameUtils fileUtils) {
+               this.fileUtils = fileUtils;
+       }
+
+       @Override
+       public InputREST connect(TavernaRun run, UriInfo ui) {
+               this.run = run;
+               this.ui = ui;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public InputsDescriptor get() {
+               return new InputsDescriptor(ui, run);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public InputDescription getExpected() {
+               return cdBuilder.makeInputDescriptor(run, ui);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String getBaclavaFile() {
+               String i = run.getInputBaclavaFile();
+               return i == null ? "" : i;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public InDesc getInput(String name, UriInfo ui) throws 
BadInputPortNameException {
+               Input i = support.getInput(run, name);
+               if (i == null)
+                       throw new BadInputPortNameException("unknown input port 
name");
+               return new InDesc(i, ui);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String setBaclavaFile(String filename) throws NoUpdateException,
+                       BadStateChangeException, FilesystemAccessException {
+               support.permitUpdate(run);
+               run.setInputBaclavaFile(filename);
+               String i = run.getInputBaclavaFile();
+               return i == null ? "" : i;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public InDesc setInput(String name, InDesc inputDescriptor, UriInfo ui)
+                       throws NoUpdateException, BadStateChangeException,
+                       FilesystemAccessException, BadInputPortNameException,
+                       BadPropertyValueException {
+               inputDescriptor.descriptorRef = null;
+               AbstractContents ac = inputDescriptor.assignment;
+               if (name == null || name.isEmpty())
+                       throw new BadInputPortNameException("bad input name");
+               if (ac == null)
+                       throw new BadPropertyValueException("no content!");
+               if (inputDescriptor.delimiter != null
+                               && inputDescriptor.delimiter.isEmpty())
+                       inputDescriptor.delimiter = null;
+               if (ac instanceof InDesc.Reference)
+                       return setRemoteInput(name, (InDesc.Reference) ac,
+                                       inputDescriptor.delimiter, ui);
+               if (!(ac instanceof InDesc.File || ac instanceof InDesc.Value))
+                       throw new BadPropertyValueException("unknown content 
type");
+               support.permitUpdate(run);
+               Input i = support.getInput(run, name);
+               if (i == null)
+                       i = run.makeInput(name);
+               if (ac instanceof InDesc.File)
+                       i.setFile(ac.contents);
+               else
+                       i.setValue(ac.contents);
+               i.setDelimiter(inputDescriptor.delimiter);
+               return new InDesc(i, ui);
+       }
+
+       private InDesc setRemoteInput(String name, Reference ref, String 
delimiter,
+                       UriInfo ui) throws BadStateChangeException,
+                       BadPropertyValueException, FilesystemAccessException {
+               URITemplate tmpl = new URITemplate(ui.getBaseUri()
+                               + "/runs/{runName}/wd/{path:.+}");
+               MultivaluedMap<String, String> mvm = new MetadataMap<>();
+               if (!tmpl.match(ref.contents, mvm)) {
+                       throw new BadPropertyValueException(
+                                       "URI in reference does not refer to 
local disk resource");
+               }
+               try {
+                       File from = fileUtils.getFile(
+                                       
support.getRun(mvm.get("runName").get(0)),
+                                       
SyntheticDirectoryEntry.make(mvm.get("path").get(0)));
+                       File to = run.getWorkingDirectory().makeEmptyFile(
+                                       support.getPrincipal(), 
randomUUID().toString());
+
+                       to.copy(from);
+
+                       Input i = support.getInput(run, name);
+                       if (i == null)
+                               i = run.makeInput(name);
+                       i.setFile(to.getFullName());
+                       i.setDelimiter(delimiter);
+                       return new InDesc(i, ui);
+               } catch (UnknownRunException e) {
+                       throw new BadStateChangeException("may not copy from 
that run", e);
+               } catch (NoDirectoryEntryException e) {
+                       throw new BadStateChangeException("source does not 
exist", e);
+               }
+       }
+
+       @Override
+       @CallCounted
+       public Response options() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response expectedOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response baclavaOptions() {
+               return opt("PUT");
+       }
+
+       @Override
+       @CallCounted
+       public Response inputOptions(@PathParam("name") String name) {
+               return opt("PUT");
+       }
+}
+
+/**
+ * A way to create synthetic directory entries, used during deletion.
+ * 
+ * @author Donal Fellows
+ */
+class SyntheticDirectoryEntry implements DirectoryEntry {
+       public static DirEntryReference make(String path) {
+               return DirEntryReference.newInstance(new 
SyntheticDirectoryEntry(path));
+       }
+
+       private SyntheticDirectoryEntry(String p) {
+               this.p = p;
+               this.d = new Date();
+       }
+
+       private String p;
+       private Date d;
+
+       @Override
+       public String getName() {
+               return null;
+       }
+
+       @Override
+       public String getFullName() {
+               return p;
+       }
+
+       @Override
+       public void destroy() {
+       }
+
+       @Override
+       public int compareTo(DirectoryEntry o) {
+               return p.compareTo(o.getFullName());
+       }
+
+       @Override
+       public Date getModificationDate() {
+               return d;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java
new file mode 100644
index 0000000..6dfa3e2
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java
@@ -0,0 +1,120 @@
+/*
+ */
+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 org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.Response;
+
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.model.Feed;
+import org.taverna.server.master.api.FeedBean;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interaction.InteractionFeedSupport;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.InteractionFeedREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * How to connect an interaction feed to the webapp.
+ * 
+ * @author Donal Fellows
+ */
+public class InteractionFeed implements InteractionFeedREST, FeedBean {
+       private InteractionFeedSupport interactionFeed;
+       private TavernaRun run;
+
+       @Override
+       public void setInteractionFeedSupport(InteractionFeedSupport feed) {
+               this.interactionFeed = feed;
+       }
+
+       InteractionFeed connect(TavernaRun run) {
+               this.run = run;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Feed getFeed() throws FilesystemAccessException,
+                       NoDirectoryEntryException {
+               return interactionFeed.getRunFeed(run);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Response addEntry(Entry entry) throws MalformedURLException,
+                       FilesystemAccessException, NoDirectoryEntryException,
+                       NoUpdateException {
+               Entry realEntry = interactionFeed.addRunFeedEntry(run, entry);
+               URI location;
+               try {
+                       location = realEntry.getSelfLink().getHref().toURI();
+               } catch (URISyntaxException e) {
+                       throw new RuntimeException("failed to make URI from 
link?!", e);
+               }
+               return Response.created(location).entity(realEntry)
+                               
.type("application/atom+xml;type=entry").build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public Entry getEntry(String id) throws FilesystemAccessException,
+                       NoDirectoryEntryException {
+               return interactionFeed.getRunFeedEntry(run, id);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public String deleteEntry(String id) throws FilesystemAccessException,
+                       NoDirectoryEntryException, NoUpdateException {
+               interactionFeed.removeRunFeedEntry(run, id);
+               return "entry successfully deleted";
+       }
+
+       @Override
+       @CallCounted
+       public Response feedOptions() {
+               return opt("POST");
+       }
+
+       @Override
+       @CallCounted
+       public Response entryOptions(String id) {
+               return opt("DELETE");
+       }
+}
\ 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/ListenerPropertyREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java
new file mode 100644
index 0000000..26a8982
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java
@@ -0,0 +1,91 @@
+/*
+ */
+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 org.apache.commons.logging.LogFactory.getLog;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.logging.Log;
+import org.taverna.server.master.api.ListenerPropertyBean;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single property of a workflow run.
+ * 
+ * @author Donal Fellows
+ */
+class ListenerPropertyREST implements TavernaServerListenersREST.Property,
+               ListenerPropertyBean {
+       private Log log = getLog("Taverna.Server.Webapp");
+       private TavernaServerSupport support;
+       private Listener listen;
+       private String propertyName;
+       private TavernaRun run;
+
+       @Override
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       public ListenerPropertyREST connect(Listener listen, TavernaRun run,
+                       String propertyName) {
+               this.listen = listen;
+               this.propertyName = propertyName;
+               this.run = run;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String getValue() {
+               try {
+                       return listen.getProperty(propertyName);
+               } catch (NoListenerException e) {
+                       log.error("unexpected exception; property \"" + 
propertyName
+                                       + "\" should exist", e);
+                       return null;
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String setValue(String value) throws NoUpdateException,
+                       NoListenerException {
+               support.permitUpdate(run);
+               listen.setProperty(propertyName, value);
+               return listen.getProperty(propertyName);
+       }
+
+       @Override
+       @CallCounted
+       public Response options() {
+               return opt("PUT");
+       }
+}
\ No newline at end of file

Reply via email to