http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerRunREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerRunREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerRunREST.java
new file mode 100644
index 0000000..ef6ddd4
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerRunREST.java
@@ -0,0 +1,810 @@
+/*
+ */
+package org.taverna.server.master.rest;
+/*
+ * 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.UriBuilder.fromUri;
+import static org.joda.time.format.ISODateTimeFormat.basicDateTime;
+import static org.taverna.server.master.common.Roles.USER;
+import static 
org.taverna.server.master.rest.handler.Scufl2DocumentHandler.SCUFL2;
+import static 
org.taverna.server.master.interaction.InteractionFeedSupport.FEED_URL_DIR;
+import static org.taverna.server.master.rest.ContentTypes.JSON;
+import static org.taverna.server.master.rest.ContentTypes.ROBUNDLE;
+import static org.taverna.server.master.rest.ContentTypes.TEXT;
+import static org.taverna.server.master.rest.ContentTypes.XML;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.DIR;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.GENERATE_PROVENANCE;
+import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.IN;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.LISTEN;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.LOG;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.NAME;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.OUT;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.PROFILE;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.ROOT;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.RUNBUNDLE;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.SEC;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.STATUS;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.STDERR;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.STDOUT;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.T_CREATE;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.T_EXPIRE;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.T_FINISH;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.T_START;
+import static 
org.taverna.server.master.rest.TavernaServerRunREST.PathNames.USAGE;
+import static org.taverna.server.master.rest.TavernaServerRunREST.PathNames.WF;
+import static 
org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+import org.apache.cxf.jaxrs.model.wadl.Description;
+import org.joda.time.format.DateTimeFormatter;
+import org.taverna.server.master.common.Namespaces;
+import org.taverna.server.master.common.ProfileList;
+import org.taverna.server.master.common.Status;
+import org.taverna.server.master.common.Uri;
+import org.taverna.server.master.common.VersionedElement;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.exceptions.NotOwnerException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.port_description.OutputDescription;
+
+/**
+ * This represents how a Taverna Server workflow run looks to a RESTful API.
+ * 
+ * @author Donal Fellows.
+ */
+@Description("This represents how a Taverna Server workflow run looks to a "
+               + "RESTful API.")
+@RolesAllowed(USER)
+public interface TavernaServerRunREST {
+       /**
+        * Describes a workflow run.
+        * 
+        * @param ui
+        *            About the URI used to access this resource.
+        * @return The description.
+        */
+       @GET
+       @Path(ROOT)
+       @Description("Describes a workflow run.")
+       @Produces({ XML, JSON })
+       @Nonnull
+       public RunDescription getDescription(@Nonnull @Context UriInfo ui);
+
+       /**
+        * Deletes a workflow run.
+        * 
+        * @return An HTTP response to the deletion.
+        * @throws NoUpdateException
+        *             If the user may see the handle but may not delete it.
+        */
+       @DELETE
+       @Path(ROOT)
+       @Description("Deletes a workflow run.")
+       @Nonnull
+       public Response destroy() throws NoUpdateException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(ROOT)
+       @Description("Produces the description of the run.")
+       Response runOptions();
+
+       /**
+        * Returns the workflow document used to create the workflow run.
+        * 
+        * @return The workflow document.
+        */
+       @GET
+       @Path(WF)
+       @Produces({ T2FLOW, SCUFL2, XML, JSON })
+       @Description("Gives the workflow document used to create the workflow 
run.")
+       @Nonnull
+       public Workflow getWorkflow();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(WF)
+       @Description("Produces the description of the run workflow.")
+       Response workflowOptions();
+
+       /** Get the workflow name. */
+       @GET
+       @Path(NAME)
+       @Produces(TEXT)
+       @Description("Gives the descriptive name of the workflow run.")
+       @Nonnull
+       public String getName();
+
+       /**
+        * Set the workflow name.
+        * 
+        * @throws NoUpdateException
+        *             If the user is not permitted to change the workflow.
+        */
+       @PUT
+       @Path(NAME)
+       @Consumes(TEXT)
+       @Produces(TEXT)
+       @Description("Set the descriptive name of the workflow run. Note that "
+                       + "this value may be arbitrarily truncated by the 
implementation.")
+       @Nonnull
+       public String setName(String name) throws NoUpdateException;
+
+       /** Produce the workflow name HTTP operations. */
+       @OPTIONS
+       @Path(NAME)
+       @Description("Produces the description of the operations on the run's "
+                       + "descriptive name.")
+       @Nonnull
+       Response nameOptions();
+
+       /**
+        * Produces the name of the workflow's main profile.
+        * 
+        * @return The main profile name, or the empty string if there is no 
such
+        *         profile.
+        */
+       @GET
+       @Path(PROFILE)
+       @Produces(TEXT)
+       @Description("Gives the name of the workflow's main profile, or the 
empty string if none is defined.")
+       @Nonnull
+       String getMainProfileName();
+
+       /**
+        * Get a description of the profiles supported by the workflow document 
used
+        * to create this run.
+        * 
+        * @return A description of the supported profiles.
+        */
+       @GET
+       @Path(PROFILE)
+       @Produces({ XML, JSON })
+       @Description("Describes what profiles exist on the workflow.")
+       @Nonnull
+       ProfileList getProfiles();
+
+       /** Produce the workflow profile HTTP operations. */
+       @OPTIONS
+       @Path(PROFILE)
+       @Description("Produces the description of the operations on the run's "
+                       + "profile.")
+       @Nonnull
+       Response profileOptions();
+
+       /**
+        * Returns a resource that represents the workflow run's security
+        * properties. These may only be accessed by the owner.
+        * 
+        * @return The security resource.
+        * @throws NotOwnerException
+        *             If the accessing principal isn't the owning principal.
+        */
+       @Path(SEC)
+       @Description("Access the workflow run's security.")
+       @Nonnull
+       public TavernaServerSecurityREST getSecurity() throws NotOwnerException;
+
+       /**
+        * Returns the time when the workflow run becomes eligible for automatic
+        * deletion.
+        * 
+        * @return When the run expires.
+        */
+       @GET
+       @Path(T_EXPIRE)
+       @Produces(TEXT)
+       @Description("Gives the time when the workflow run becomes eligible for 
"
+                       + "automatic deletion.")
+       @Nonnull
+       public String getExpiryTime();
+
+       /**
+        * Sets the time when the workflow run becomes eligible for automatic
+        * deletion.
+        * 
+        * @param expiry
+        *            When the run will expire.
+        * @return When the run will actually expire.
+        * @throws NoUpdateException
+        *             If the current user is not permitted to manage the 
lifetime
+        *             of the run.
+        */
+       @PUT
+       @Path(T_EXPIRE)
+       @Consumes(TEXT)
+       @Produces(TEXT)
+       @Description("Sets the time when the workflow run becomes eligible for "
+                       + "automatic deletion.")
+       @Nonnull
+       public String setExpiryTime(@Nonnull String expiry)
+                       throws NoUpdateException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(T_EXPIRE)
+       @Description("Produces the description of the run expiry.")
+       Response expiryOptions();
+
+       /**
+        * Returns the time when the workflow run was created.
+        * 
+        * @return When the run was first submitted to the server.
+        */
+       @GET
+       @Path(T_CREATE)
+       @Produces(TEXT)
+       @Description("Gives the time when the workflow run was first submitted "
+                       + "to the server.")
+       @Nonnull
+       public String getCreateTime();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(T_CREATE)
+       @Description("Produces the description of the run create time.")
+       Response createTimeOptions();
+
+       /**
+        * Returns the time when the workflow run was started (through a 
user-driven
+        * state change).
+        * 
+        * @return When the run was started, or <tt>null</tt>.
+        */
+       @GET
+       @Path(T_START)
+       @Produces(TEXT)
+       @Description("Gives the time when the workflow run was started, or an "
+                       + "empty string if the run has not yet started.")
+       @Nonnull
+       public String getStartTime();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(T_START)
+       @Description("Produces the description of the run start time.")
+       Response startTimeOptions();
+
+       /**
+        * Returns the time when the workflow run was detected to have finished.
+        * 
+        * @return When the run finished, or <tt>null</tt>.
+        */
+       @GET
+       @Path(T_FINISH)
+       @Produces(TEXT)
+       @Description("Gives the time when the workflow run was first detected 
as "
+                       + "finished, or an empty string if it has not yet 
finished "
+                       + "(including if it has never started).")
+       @Nonnull
+       public String getFinishTime();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(T_FINISH)
+       @Description("Produces the description of the run finish time.")
+       Response finishTimeOptions();
+
+       /**
+        * Gets the current status of the workflow run.
+        * 
+        * @return The status code.
+        */
+       @GET
+       @Path(STATUS)
+       @Produces(TEXT)
+       @Description("Gives the current status of the workflow run.")
+       @Nonnull
+       public String getStatus();
+
+       /**
+        * Sets the status of the workflow run. This does nothing if the status 
code
+        * is the same as the run's current state.
+        * 
+        * @param status
+        *            The new status code.
+        * @return Description of what status the run is actually in, or a 202 
to
+        *         indicate that things are still changing.
+        * @throws NoUpdateException
+        *             If the current user is not permitted to update the run.
+        * @throws BadStateChangeException
+        *             If the state cannot be modified in the manner requested.
+        */
+       @PUT
+       @Path(STATUS)
+       @Consumes(TEXT)
+       @Produces(TEXT)
+       @Description("Attempts to update the status of the workflow run.")
+       @Nonnull
+       public Response setStatus(@Nonnull String status) throws 
NoUpdateException,
+                       BadStateChangeException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(STATUS)
+       @Description("Produces the description of the run status.")
+       Response statusOptions();
+
+       /**
+        * Get the working directory of this workflow run.
+        * 
+        * @return A RESTful delegate for the working directory.
+        */
+       @Path(DIR)
+       @Description("Get the working directory of this workflow run.")
+       @Nonnull
+       public TavernaServerDirectoryREST getWorkingDirectory();
+
+       /**
+        * Get the event listeners attached to this workflow run.
+        * 
+        * @return A RESTful delegate for the list of listeners.
+        */
+       @Path(LISTEN)
+       @Description("Get the event listeners attached to this workflow run.")
+       @Nonnull
+       public TavernaServerListenersREST getListeners();
+
+       /**
+        * Get a delegate for working with the inputs to this workflow run.
+        * 
+        * @param ui
+        *            About the URI used to access this resource.
+        * @return A RESTful delegate for the inputs.
+        */
+       @Path(IN)
+       @Description("Get the inputs to this workflow run.")
+       @Nonnull
+       public TavernaServerInputREST getInputs(@Nonnull @Context UriInfo ui);
+
+       /**
+        * Get the output Baclava file for this workflow run.
+        * 
+        * @return The filename, or empty string to indicate that the outputs 
will
+        *         be written to the <tt>out</tt> directory.
+        */
+       @GET
+       @Path(OUT)
+       @Produces(TEXT)
+       @Description("Gives the Baclava file where output will be written; 
empty "
+                       + "means use multiple simple files in the out 
directory.")
+       @Nonnull
+       public String getOutputFile();
+
+       /**
+        * Get a description of the outputs.
+        * 
+        * @param ui
+        *            About the URI used to access this operation.
+        * @return A description of the outputs (higher level than the 
filesystem).
+        * @throws BadStateChangeException
+        *             If the run is in the {@link Status#Initialized 
Initialized}
+        *             state.
+        * @throws FilesystemAccessException
+        *             If problems occur when accessing the filesystem.
+        * @throws NoDirectoryEntryException
+        *             If things are odd in the filesystem.
+        */
+       @GET
+       @Path(OUT)
+       @Produces({ XML, JSON })
+       @Description("Gives a description of the outputs, as currently 
understood")
+       @Nonnull
+       public OutputDescription getOutputDescription(@Nonnull @Context UriInfo 
ui)
+                       throws BadStateChangeException, 
FilesystemAccessException,
+                       NoDirectoryEntryException;
+
+       /**
+        * Set the output Baclava file for this workflow run.
+        * 
+        * @param filename
+        *            The Baclava file to use, or empty to make the outputs be
+        *            written to individual files in the <tt>out</tt> 
subdirectory
+        *            of the working directory.
+        * @return The Baclava file as actually set.
+        * @throws NoUpdateException
+        *             If the current user is not permitted to update the run.
+        * @throws FilesystemAccessException
+        *             If the filename is invalid (starts with <tt>/</tt> or
+        *             contains a <tt>..</tt> segment).
+        * @throws BadStateChangeException
+        *             If the workflow is not in the Initialized state.
+        */
+       @PUT
+       @Path(OUT)
+       @Consumes(TEXT)
+       @Produces(TEXT)
+       @Description("Sets the Baclava file where output will be written; empty 
"
+                       + "means use multiple simple files in the out 
directory.")
+       @Nonnull
+       public String setOutputFile(@Nonnull String filename)
+                       throws NoUpdateException, FilesystemAccessException,
+                       BadStateChangeException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(OUT)
+       @Description("Produces the description of the run output.")
+       Response outputOptions();
+
+       /**
+        * Get a handle to the interaction feed.
+        * 
+        * @return
+        */
+       @Path(FEED_URL_DIR)
+       @Description("Access the interaction feed for the workflow run.")
+       @Nonnull
+       InteractionFeedREST getInteractionFeed();
+
+       /**
+        * @return The stdout for the workflow run, or empty string if the run 
has
+        *         not yet started.
+        * @throws NoListenerException
+        */
+       @GET
+       @Path(STDOUT)
+       @Description("Return the stdout for the workflow run.")
+       @Produces(TEXT)
+       @Nonnull
+       String getStdout() throws NoListenerException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(STDOUT)
+       @Description("Return the stdout for the workflow run.")
+       Response stdoutOptions();
+
+       /**
+        * @return The stderr for the workflow run, or empty string if the run 
has
+        *         not yet started.
+        * @throws NoListenerException
+        */
+       @GET
+       @Path(STDERR)
+       @Description("Return the stderr for the workflow run.")
+       @Produces(TEXT)
+       @Nonnull
+       String getStderr() throws NoListenerException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(STDERR)
+       @Description("Return the stderr for the workflow run.")
+       Response stderrOptions();
+
+       /**
+        * @return The usage record for the workflow run, wrapped in a 
Response, or
+        *         "empty content" if the run has not yet finished.
+        * @throws NoListenerException
+        * @throws JAXBException
+        */
+       @GET
+       @Path(USAGE)
+       @Description("Return the usage record for the workflow run.")
+       @Produces(XML)
+       @Nonnull
+       Response getUsage() throws NoListenerException, JAXBException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(USAGE)
+       @Description("Return the usage record for the workflow run.")
+       Response usageOptions();
+
+       /**
+        * @return The log for the workflow run, or empty string if the run has 
not
+        *         yet started.
+        */
+       @GET
+       @Path(LOG)
+       @Description("Return the log for the workflow run.")
+       @Produces(TEXT)
+       @Nonnull
+       Response getLogContents();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(LOG)
+       @Description("Return the log for the workflow run.")
+       Response logOptions();
+
+       /**
+        * @return The log for the workflow run, or empty string if the run has 
not
+        *         yet started.
+        */
+       @GET
+       @Path(RUNBUNDLE)
+       @Description("Return the run bundle for the workflow run.")
+       @Produces(ROBUNDLE)
+       @Nonnull
+       Response getRunBundle();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(RUNBUNDLE)
+       @Description("Return the run bundle for the workflow run.")
+       Response runBundleOptions();
+
+       /**
+        * @return Whether to create the run bundle for the workflow run. Only
+        *         usefully set-able before the start of the run.
+        */
+       @GET
+       @Path(GENERATE_PROVENANCE)
+       @Description("Whether to create the run bundle for the workflow run.")
+       @Produces(TEXT)
+       @Nonnull
+       boolean getGenerateProvenance();
+
+       /**
+        * @param provenanceFlag
+        *            Whether to create the run bundle for the workflow run. 
Only
+        *            usefully set-able before the start of the run.
+        * @return What it was actually set to.
+        * @throws NoUpdateException 
+        */
+       @PUT
+       @Path(GENERATE_PROVENANCE)
+       @Description("Whether to create the run bundle for the workflow run.")
+       @Consumes(TEXT)
+       @Produces(TEXT)
+       @Nonnull
+       boolean setGenerateProvenance(boolean provenanceFlag) throws 
NoUpdateException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(GENERATE_PROVENANCE)
+       @Description("Whether to create the run bundle for the workflow run.")
+       Response generateProvenanceOptions();
+
+       /**
+        * Factored out path names used in the {@link TavernaServerRunREST}
+        * interface and related places.
+        * 
+        * @author Donal Fellows
+        */
+       interface PathNames {
+               public static final String ROOT = "/";
+               public static final String WF = "workflow";
+               public static final String DIR = "wd";
+               public static final String NAME = "name";
+               public static final String T_EXPIRE = "expiry";
+               public static final String T_CREATE = "createTime";
+               public static final String T_START = "startTime";
+               public static final String T_FINISH = "finishTime";
+               public static final String STATUS = "status";
+               public static final String IN = "input";
+               public static final String OUT = "output";
+               public static final String PROFILE = "profile";
+               public static final String LISTEN = "listeners";
+               public static final String SEC = "security";
+               public static final String STDOUT = "stdout";
+               public static final String STDERR = "stderr";
+               public static final String USAGE = "usage";
+               public static final String LOG = "log";
+               public static final String RUNBUNDLE = "run-bundle";
+               public static final String GENERATE_PROVENANCE = 
"generate-provenance";
+       }
+
+       /**
+        * The description of where everything is in a RESTful view of a 
workflow
+        * run. Done with JAXB.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement
+       @XmlType(name = "")
+       public static class RunDescription extends VersionedElement {
+               /** The identity of the owner of the workflow run. */
+               @XmlAttribute(namespace = Namespaces.SERVER_REST)
+               public String owner;
+               /** The description of the expiry. */
+               public Expiry expiry;
+               /** The location of the creation workflow description. */
+               public Uri creationWorkflow;
+               /** The location of the creation time property. */
+               public Uri createTime;
+               /** The location of the start time property. */
+               public Uri startTime;
+               /** The location of the finish time property. */
+               public Uri finishTime;
+               /** The location of the status description. */
+               public Uri status;
+               /** The location of the working directory. */
+               public Uri workingDirectory;
+               /** The location of the inputs. */
+               public Uri inputs;
+               /** The location of the Baclava output. */
+               public Uri output;
+               /** The location of the security context. */
+               public Uri securityContext;
+               /** The list of listeners. */
+               public ListenerList listeners;
+               /** The location of the interaction feed. */
+               public Uri interaction;
+               /** The name of the run. */
+               public Uri name;
+               /** The stdout of the run. */
+               public Uri stdout;
+               /** The stderr of the run. */
+               public Uri stderr;
+               /** The usage record for the run. */
+               public Uri usage;
+               /** The log from the run. */
+               public Uri log;
+               /** The bundle describing the run. */
+               @XmlElement(name = RUNBUNDLE)
+               public Uri runBundle;
+               /** Whether to generate a bundle describing the run. */
+               @XmlElement(name = GENERATE_PROVENANCE)
+               public Uri generateProvenance;
+
+               /**
+                * How to describe a run's expiry.
+                * 
+                * @author Donal Fellows
+                */
+               @XmlType(name = "")
+               public static class Expiry {
+                       /**
+                        * Where to go to read the exiry
+                        */
+                       @XmlAttribute(name = "href", namespace = 
Namespaces.XLINK)
+                       @XmlSchemaType(name = "anyURI")
+                       public URI ref;
+                       /**
+                        * What the expiry currently is.
+                        */
+                       @XmlValue
+                       public String timeOfDeath;
+
+                       /**
+                        * Make a blank expiry description.
+                        */
+                       public Expiry() {
+                       }
+
+                       private static DateTimeFormatter dtf;
+
+                       Expiry(TavernaRun r, UriInfo ui, String path, String... 
parts) {
+                               ref = fromUri(new Uri(ui, true, path, 
parts).ref).build();
+                               if (dtf == null)
+                                       dtf = basicDateTime();
+                               timeOfDeath = 
dtf.print(r.getExpiry().getTime());
+                       }
+               }
+
+               /**
+                * The description of a list of listeners attached to a run.
+                * 
+                * @author Donal Fellows
+                */
+               @XmlType(name = "")
+               public static class ListenerList extends Uri {
+                       /**
+                        * The references to the individual listeners.
+                        */
+                       public List<Uri> listener;
+
+                       /**
+                        * An empty description of listeners.
+                        */
+                       public ListenerList() {
+                               listener = new ArrayList<>();
+                       }
+
+                       /**
+                        * @param r
+                        *            The run whose listeners we're talking 
about.
+                        * @param ub
+                        *            Uri factory; must've been secured
+                        */
+                       private ListenerList(TavernaRun r, UriBuilder ub) {
+                               super(ub);
+                               listener = new 
ArrayList<>(r.getListeners().size());
+                               UriBuilder pathUB = ub.clone().path("{name}");
+                               for (Listener l : r.getListeners())
+                                       listener.add(new 
Uri(pathUB.build(l.getName())));
+                       }
+
+                       /**
+                        * @param run
+                        *            The run whose listeners we're talking 
about.
+                        * @param ui
+                        *            The source of information about URIs.
+                        * @param path
+                        *            Where we are relative to the URI source.
+                        * @param parts
+                        *            Anything required to fill out the path.
+                        */
+                       ListenerList(TavernaRun run, UriInfo ui, String path,
+                                       String... parts) {
+                               this(run, secure(fromUri(new Uri(ui, path, 
parts).ref)));
+                       }
+               }
+
+               /**
+                * An empty description of a run.
+                */
+               public RunDescription() {
+               }
+
+               /**
+                * A description of a particular run.
+                * 
+                * @param run
+                *            The run to describe.
+                * @param ui
+                *            The factory for URIs.
+                */
+               public RunDescription(TavernaRun run, UriInfo ui) {
+                       super(true);
+                       creationWorkflow = new Uri(ui, WF);
+                       expiry = new Expiry(run, ui, T_EXPIRE);
+                       status = new Uri(ui, STATUS);
+                       workingDirectory = new Uri(ui, DIR);
+                       listeners = new ListenerList(run, ui, LISTEN);
+                       securityContext = new Uri(ui, SEC);
+                       inputs = new Uri(ui, IN);
+                       output = new Uri(ui, OUT);
+                       createTime = new Uri(ui, T_CREATE);
+                       startTime = new Uri(ui, T_START);
+                       finishTime = new Uri(ui, T_FINISH);
+                       interaction = new Uri(ui, FEED_URL_DIR);
+                       name = new Uri(ui, NAME);
+                       owner = run.getSecurityContext().getOwner().getName();
+                       stdout = new Uri(ui, STDOUT);
+                       stderr = new Uri(ui, STDERR);
+                       usage = new Uri(ui, USAGE);
+                       log = new Uri(ui, LOG);
+                       runBundle = new Uri(ui, RUNBUNDLE);
+                       generateProvenance = new Uri(ui, GENERATE_PROVENANCE);
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerSecurityREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerSecurityREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerSecurityREST.java
new file mode 100644
index 0000000..f5101e7
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/TavernaServerSecurityREST.java
@@ -0,0 +1,788 @@
+/*
+ */
+package org.taverna.server.master.rest;
+/*
+ * 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.Collections.emptyList;
+import static org.taverna.server.master.common.Namespaces.SERVER;
+import static org.taverna.server.master.common.Namespaces.XLINK;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.rest.ContentTypes.JSON;
+import static org.taverna.server.master.rest.ContentTypes.TEXT;
+import static org.taverna.server.master.rest.ContentTypes.XML;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.CREDS;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.ONE_CRED;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.ONE_PERM;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.ONE_TRUST;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.OWNER;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.PERMS;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.ROOT;
+import static 
org.taverna.server.master.rest.TavernaServerSecurityREST.PathNames.TRUSTS;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.cxf.jaxrs.model.wadl.Description;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.common.Uri;
+import org.taverna.server.master.common.VersionedElement;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoCredentialException;
+
+/**
+ * Manages the security of the workflow run. In general, only the owner of a 
run
+ * may access this resource. Many of these security-related resources may only
+ * be changed before the run is set to operating.
+ * 
+ * @author Donal Fellows
+ */
+@RolesAllowed(USER)
+@Description("Manages the security of the workflow run. In general, only the "
+               + "owner of a run may access this resource.")
+public interface TavernaServerSecurityREST {
+       interface PathNames {
+               final String ROOT = "/";
+               final String OWNER = "owner";
+               final String CREDS = "credentials";
+               final String ONE_CRED = CREDS + "/{id}";
+               final String TRUSTS = "trusts";
+               final String ONE_TRUST = TRUSTS + "/{id}";
+               final String PERMS = "permissions";
+               final String ONE_PERM = PERMS + "/{id}";
+       }
+
+       /**
+        * Gets a description of the security information supported by the 
workflow
+        * run.
+        * 
+        * @param ui
+        *            About the URI used to access this resource.
+        * @return A description of the security information.
+        */
+       @GET
+       @Path(ROOT)
+       @Produces({ XML, JSON })
+       @Description("Gives a description of the security information supported 
"
+                       + "by the workflow run.")
+       @Nonnull
+       Descriptor describe(@Nonnull @Context UriInfo ui);
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(ROOT)
+       @Description("Produces the description of the run security.")
+       Response descriptionOptions();
+
+       /**
+        * Gets the identity of who owns the workflow run.
+        * 
+        * @return The name of the owner of the run.
+        */
+       @GET
+       @Path(OWNER)
+       @Produces(TEXT)
+       @Description("Gives the identity of who owns the workflow run.")
+       @Nonnull
+       String getOwner();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(OWNER)
+       @Description("Produces the description of the run owner.")
+       Response ownerOptions();
+
+       /*
+        * @PUT @Path("/") @Consumes(ContentTypes.BYTES) @CallCounted @Nonnull
+        * public void set(@Nonnull InputStream contents, @Nonnull @Context 
UriInfo
+        * ui);
+        */
+
+       /**
+        * @return A list of credentials supplied to this workflow run.
+        */
+       @GET
+       @Path(CREDS)
+       @Produces({ XML, JSON })
+       @Description("Gives a list of credentials supplied to this workflow 
run.")
+       @Nonnull
+       CredentialList listCredentials();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(CREDS)
+       @Description("Produces the description of the run credentials' 
operations.")
+       Response credentialsOptions();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(ONE_CRED)
+       @Description("Produces the description of one run credential's 
operations.")
+       Response credentialOptions(@PathParam("id") String id);
+
+       /**
+        * Describe a particular credential.
+        * 
+        * @param id
+        *            The id of the credential to fetch.
+        * @return The description of the credential.
+        * @throws NoCredentialException
+        *             If the credential doesn't exist.
+        */
+       @GET
+       @Path(ONE_CRED)
+       @Produces({ XML, JSON })
+       @Description("Describes a particular credential.")
+       @Nonnull
+       CredentialHolder getParticularCredential(@Nonnull @PathParam("id") 
String id)
+                       throws NoCredentialException;
+
+       /**
+        * Update a particular credential.
+        * 
+        * @param id
+        *            The id of the credential to update.
+        * @param c
+        *            The details of the credential to use in the update.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return Description of the updated credential.
+        * @throws InvalidCredentialException
+        *             If the credential description isn't valid.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @PUT
+       @Path(ONE_CRED)
+       @Consumes({ XML, JSON })
+       @Produces({ XML, JSON })
+       @Description("Updates a particular credential.")
+       @Nonnull
+       CredentialHolder setParticularCredential(
+                       @Nonnull @PathParam("id") String id, @Nonnull 
CredentialHolder c,
+                       @Nonnull @Context UriInfo ui) throws 
InvalidCredentialException,
+                       BadStateChangeException;
+
+       /**
+        * Adds a new credential.
+        * 
+        * @param c
+        *            The details of the credential to create.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return Description of the created credential.
+        * @throws InvalidCredentialException
+        *             If the credential description isn't valid.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @POST
+       @Path(CREDS)
+       @Consumes({ XML, JSON })
+       @Description("Creates a new credential.")
+       @Nonnull
+       Response addCredential(@Nonnull CredentialHolder c,
+                       @Nonnull @Context UriInfo ui) throws 
InvalidCredentialException,
+                       BadStateChangeException;
+
+       /**
+        * Deletes all credentials associated with a run.
+        * 
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return A characterisation of a successful delete.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @DELETE
+       @Path(CREDS)
+       @Description("Deletes all credentials.")
+       @Nonnull
+       Response deleteAllCredentials(@Nonnull @Context UriInfo ui)
+                       throws BadStateChangeException;
+
+       /**
+        * Deletes one credential associated with a run.
+        * 
+        * @param id
+        *            The identity of the credential to delete.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return A characterisation of a successful delete.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @DELETE
+       @Path(ONE_CRED)
+       @Description("Deletes a particular credential.")
+       @Nonnull
+       Response deleteCredential(@Nonnull @PathParam("id") String id,
+                       @Nonnull @Context UriInfo ui) throws 
BadStateChangeException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(TRUSTS)
+       @Description("Produces the description of the run trusted certificates' 
"
+                       + "operations.")
+       Response trustsOptions();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(ONE_TRUST)
+       @Description("Produces the description of one run trusted certificate's 
"
+                       + "operations.")
+       Response trustOptions(@PathParam("id") String id);
+
+       /**
+        * @return A list of trusted identities supplied to this workflow run.
+        */
+       @GET
+       @Path(TRUSTS)
+       @Produces({ XML, JSON })
+       @Description("Gives a list of trusted identities supplied to this "
+                       + "workflow run.")
+       @Nonnull
+       TrustList listTrusted();
+
+       /**
+        * Describe a particular trusted identity.
+        * 
+        * @param id
+        *            The id of the trusted identity to fetch.
+        * @return The description of the trusted identity.
+        * @throws NoCredentialException
+        *             If the trusted identity doesn't exist.
+        */
+       @GET
+       @Path(ONE_TRUST)
+       @Produces({ XML, JSON })
+       @Description("Describes a particular trusted identity.")
+       @Nonnull
+       Trust getParticularTrust(@Nonnull @PathParam("id") String id)
+                       throws NoCredentialException;
+
+       /**
+        * Update a particular trusted identity.
+        * 
+        * @param id
+        *            The id of the trusted identity to update.
+        * @param t
+        *            The details of the trusted identity to use in the update.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return Description of the updated trusted identity.
+        * @throws InvalidCredentialException
+        *             If the trusted identity description isn't valid.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @PUT
+       @Path(ONE_TRUST)
+       @Consumes({ XML, JSON })
+       @Produces({ XML, JSON })
+       @Description("Updates a particular trusted identity.")
+       @Nonnull
+       Trust setParticularTrust(@Nonnull @PathParam("id") String id,
+                       @Nonnull Trust t, @Nonnull @Context UriInfo ui)
+                       throws InvalidCredentialException, 
BadStateChangeException;
+
+       /**
+        * Adds a new trusted identity.
+        * 
+        * @param t
+        *            The details of the trusted identity to create.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return Description of the created trusted identity.
+        * @throws InvalidCredentialException
+        *             If the trusted identity description isn't valid.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @POST
+       @Path(TRUSTS)
+       @Consumes({ XML, JSON })
+       @Description("Adds a new trusted identity.")
+       @Nonnull
+       Response addTrust(@Nonnull Trust t, @Nonnull @Context UriInfo ui)
+                       throws InvalidCredentialException, 
BadStateChangeException;
+
+       /**
+        * Deletes all trusted identities associated with a run.
+        * 
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return A characterisation of a successful delete.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @DELETE
+       @Path(TRUSTS)
+       @Description("Deletes all trusted identities.")
+       @Nonnull
+       Response deleteAllTrusts(@Nonnull @Context UriInfo ui)
+                       throws BadStateChangeException;
+
+       /**
+        * Deletes one trusted identity associated with a run.
+        * 
+        * @param id
+        *            The identity of the trusted identity to delete.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return A characterisation of a successful delete.
+        * @throws BadStateChangeException
+        *             If the workflow run is not in the initialising state.
+        */
+       @DELETE
+       @Path(ONE_TRUST)
+       @Description("Deletes a particular trusted identity.")
+       @Nonnull
+       Response deleteTrust(@Nonnull @PathParam("id") String id,
+                       @Nonnull @Context UriInfo ui) throws 
BadStateChangeException;
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(PERMS)
+       @Description("Produces the description of the run permissions' 
operations.")
+       Response permissionsOptions();
+
+       /** Get an outline of the operations supported. */
+       @OPTIONS
+       @Path(ONE_PERM)
+       @Description("Produces the description of one run permission's 
operations.")
+       Response permissionOptions(@PathParam("id") String id);
+
+       /**
+        * @return A list of (non-default) permissions associated with this 
workflow
+        *         run.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        */
+       @GET
+       @Path(PERMS)
+       @Produces({ XML, JSON })
+       @Description("Gives a list of all non-default permissions associated 
with "
+                       + "the enclosing workflow run. By default, nobody has 
any access "
+                       + "at all except for the owner of the run.")
+       @Nonnull
+       PermissionsDescription describePermissions(@Nonnull @Context UriInfo 
ui);
+
+       /**
+        * Describe the particular permission granted to a user.
+        * 
+        * @param id
+        *            The name of the user whose permissions are to be 
described.
+        * @return The permission they are granted.
+        */
+       @GET
+       @Path(ONE_PERM)
+       @Produces(TEXT)
+       @Description("Describes the permission granted to a particular user.")
+       @Nonnull
+       Permission describePermission(@Nonnull @PathParam("id") String id);
+
+       /**
+        * Update the permission granted to a user.
+        * 
+        * @param id
+        *            The name of the user whose permissions are to be updated. 
Note
+        *            that the owner always has full permissions.
+        * @param perm
+        *            The permission level to set.
+        * @return The permission level that has actually been set.
+        */
+       @PUT
+       @Consumes(TEXT)
+       @Produces(TEXT)
+       @Path(ONE_PERM)
+       @Description("Updates the permissions granted to a particular user.")
+       @Nonnull
+       Permission setPermission(@Nonnull @PathParam("id") String id,
+                       @Nonnull Permission perm);
+
+       /**
+        * Delete the permissions associated with a user, which restores them 
to the
+        * default (no access unless they are the owner or have admin 
privileges).
+        * 
+        * @param id
+        *            The name of the user whose permissions are to be revoked.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return An indication that the delete has been successful (or not).
+        */
+       @DELETE
+       @Path(ONE_PERM)
+       @Description("Deletes (by resetting to default) the permissions "
+                       + "associated with a particular user.")
+       @Nonnull
+       Response deletePermission(@Nonnull @PathParam("id") String id,
+                       @Nonnull @Context UriInfo ui);
+
+       /**
+        * Manufacture a permission setting for a previously-unknown user.
+        * 
+        * @param desc
+        *            A description of the name of the user and the permission 
level
+        *            to grant them.
+        * @param ui
+        *            Information about the URI used to access this resource.
+        * @return An indication that the create has been successful (or not).
+        */
+       @POST
+       @Path(PERMS)
+       @Consumes({ XML, JSON })
+       @Description("Creates a new assignment of permissions to a particular 
user.")
+       @Nonnull
+       Response makePermission(@Nonnull PermissionDescription desc,
+                       @Nonnull @Context UriInfo ui);
+
+       /**
+        * A description of the security resources associated with a workflow 
run.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "securityDescriptor")
+       @XmlType(name = "SecurityDescriptor")
+       public static final class Descriptor extends VersionedElement {
+               /** The identity of the owner of the enclosing workflow run. */
+               @XmlElement
+               public String owner;
+               /** Where to get the permissions on the run. */
+               @XmlElement
+               public Uri permissions;
+
+               /** Characterisation of the credentials attached to the run. */
+               @XmlElement
+               public Credentials credentials;
+               /** Characterisation of the trusted certificates attached to 
the run. */
+               @XmlElement
+               public Trusts trusts;
+
+               public Descriptor() {
+               }
+
+               /**
+                * Initialise a description of the security context.
+                * 
+                * @param ub
+                *            How to build URIs.
+                * @param owner
+                *            Who owns the context.
+                * @param credential
+                *            The credentials associated with the context.
+                * @param trust
+                *            The trusted certificates associated with the 
context.
+                */
+               public Descriptor(@Nonnull UriBuilder ub, @Nonnull String owner,
+                               @Nonnull Credential[] credential, @Nonnull 
Trust[] trust) {
+                       super(true);
+                       this.owner = owner;
+                       this.permissions = new Uri(ub, PERMS);
+                       this.credentials = new Credentials(new Uri(ub, 
CREDS).ref,
+                                       credential);
+                       this.trusts = new Trusts(new Uri(ub, TRUSTS).ref, 
trust);
+               }
+
+               /**
+                * A description of credentials associated with a workflow run.
+                * 
+                * @author Donal Fellows
+                */
+               @XmlType(name = "CredentialCollection")
+               public static final class Credentials {
+                       /** Reference to the collection of credentials */
+                       @XmlAttribute(name = "href", namespace = XLINK)
+                       @XmlSchemaType(name = "anyURI")
+                       public URI href;
+                       /** Descriptions of the credentials themselves. */
+                       @XmlElement
+                       public List<CredentialHolder> credential = new 
ArrayList<>();
+
+                       public Credentials() {
+                       }
+
+                       /**
+                        * Initialise a description of the credentials.
+                        * 
+                        * @param uri
+                        *            the URI of the collection.
+                        * @param credential
+                        *            The credentials in the collection.
+                        */
+                       public Credentials(@Nonnull URI uri,
+                                       @Nonnull Credential[] credential) {
+                               this.href = uri;
+                               for (Credential c : credential)
+                                       this.credential.add(new 
CredentialHolder(c));
+                       }
+               }
+
+               /**
+                * A description of trusted certificates associated with a 
workflow run.
+                * 
+                * @author Donal Fellows
+                */
+               @XmlType(name = "TrustCollection")
+               public static final class Trusts {
+                       /** Reference to the collection of trusted certs */
+                       @XmlAttribute(name = "href", namespace = XLINK)
+                       @XmlSchemaType(name = "anyURI")
+                       public URI href;
+                       /** Descriptions of the trusted certs themselves. */
+                       @XmlElement
+                       public Trust[] trust;
+
+                       public Trusts() {
+                       }
+
+                       /**
+                        * Initialise a description of the trusted certificates.
+                        * 
+                        * @param uri
+                        *            the URI of the collection.
+                        * @param trust
+                        *            The trusted certificates in the 
collection.
+                        */
+                       public Trusts(@Nonnull URI uri, @Nonnull Trust[] trust) 
{
+                               this.href = uri;
+                               this.trust = trust.clone();
+                       }
+               }
+       }
+
+       /**
+        * A container for a credential, used to work around issues with type
+        * inference in CXF's REST service handling and JAXB.
+        * 
+        * @see Credential.KeyPair
+        * @see Credential.Password
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "credential")
+       @XmlType(name = "Credential")
+       public static final class CredentialHolder {
+               /**
+                * The credential inside this holder.
+                */
+               @XmlElements({
+                               @XmlElement(name = "keypair", namespace = 
SERVER, type = Credential.KeyPair.class, required = true),
+                               @XmlElement(name = "userpass", namespace = 
SERVER, type = Credential.Password.class, required = true) })
+               public Credential credential;
+
+               public CredentialHolder() {
+               }
+
+               public CredentialHolder(Credential credential) {
+                       this.credential = credential;
+               }
+
+               /**
+                * Convenience accessor function.
+                * 
+                * @return The keypair credential held in this holder.
+                */
+               @XmlTransient
+               public Credential.KeyPair getKeypair() {
+                       return (Credential.KeyPair) this.credential;
+               }
+
+               /**
+                * Convenience accessor function.
+                * 
+                * @return The userpass credential held in this holder.
+                */
+               @XmlTransient
+               public Credential.Password getUserpass() {
+                       return (Credential.Password) this.credential;
+               }
+       }
+
+       /**
+        * A simple list of credential descriptions.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "credentials")
+       public static final class CredentialList extends VersionedElement {
+               /** The descriptions of the credentials */
+               @XmlElement
+               @Nonnull
+               public List<CredentialHolder> credential = new ArrayList<>();
+
+               public CredentialList() {
+               }
+
+               /**
+                * Initialise the list of credentials.
+                * 
+                * @param credential
+                *            The descriptions of individual credentials.
+                */
+               public CredentialList(@Nonnull Credential[] credential) {
+                       super(true);
+                       for (Credential c : credential)
+                               this.credential.add(new CredentialHolder(c));
+               }
+       }
+
+       /**
+        * A simple list of trusted certificate descriptions.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "trustedIdentities")
+       public static final class TrustList extends VersionedElement {
+               /** The descriptions of the trusted certificates */
+               @XmlElement
+               public Trust[] trust;
+
+               public TrustList() {
+               }
+
+               /**
+                * Initialise the list of trusted certificates.
+                * 
+                * @param trust
+                *            The descriptions of individual certificates.
+                */
+               public TrustList(@Nonnull Trust[] trust) {
+                       super(true);
+                       this.trust = trust.clone();
+               }
+       }
+
+       /**
+        * A description of the permissions granted to others by the owner of a
+        * workflow run.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "permissionsDescriptor")
+       public static class PermissionsDescription extends VersionedElement {
+               /**
+                * A description of the permissions granted to one user by the 
owner of
+                * a workflow run.
+                * 
+                * @author Donal Fellows
+                */
+               @XmlRootElement(name = "userPermission")
+               public static class LinkedPermissionDescription extends Uri {
+                       /** Who is this granted to? */
+                       @XmlElement
+                       public String userName;
+                       /** What are they granted? */
+                       @XmlElement
+                       public Permission permission;
+
+                       public LinkedPermissionDescription() {
+                       }
+
+                       /**
+                        * Initialise a description of one user's permissions.
+                        * 
+                        * @param ub
+                        *            How to build the URI to this permission. 
Already
+                        *            secured.
+                        * @param userName
+                        *            Who this relates to.
+                        * @param permission
+                        *            What permission is granted.
+                        * @param strings
+                        *            Parameters to the URI builder.
+                        */
+                       LinkedPermissionDescription(@Nonnull UriBuilder ub,
+                                       @Nonnull String userName, @Nonnull 
Permission permission,
+                                       String... strings) {
+                               super(ub, strings);
+                               this.userName = userName;
+                               this.permission = permission;
+                       }
+               }
+
+               /** List of descriptions of permissions. */
+               @XmlElement
+               public List<LinkedPermissionDescription> permission;
+
+               public PermissionsDescription() {
+                       permission = emptyList();
+               }
+
+               /**
+                * Initialise the description of a collection of permissions.
+                * 
+                * @param ub
+                *            How to build URIs to this collection. Must have 
already
+                *            been secured.
+                * @param permissionMap
+                *            The permissions to describe.
+                */
+               public PermissionsDescription(@Nonnull UriBuilder ub,
+                               @Nonnull Map<String, Permission> permissionMap) 
{
+                       permission = new ArrayList<>();
+                       List<String> userNames = new 
ArrayList<>(permissionMap.keySet());
+                       Collections.sort(userNames);
+                       for (String user : userNames)
+                               permission.add(new 
LinkedPermissionDescription(ub, user,
+                                               permissionMap.get(user), user));
+               }
+       }
+
+       /**
+        * An instruction to update the permissions for a user.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "permissionUpdate")
+       public static class PermissionDescription {
+               /** Who to set the permission for? */
+               @XmlElement
+               public String userName;
+               /** What permission to grant them? */
+               @XmlElement
+               public Permission permission;
+       }
+}
\ 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/rest/handler/AccessDeniedHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/AccessDeniedHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/AccessDeniedHandler.java
new file mode 100644
index 0000000..3418975
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/AccessDeniedHandler.java
@@ -0,0 +1,34 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Response.Status.FORBIDDEN;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import org.springframework.security.access.AccessDeniedException;
+
+public class AccessDeniedHandler extends HandlerCore implements
+               ExceptionMapper<AccessDeniedException> {
+       @Override
+       public Response toResponse(AccessDeniedException exception) {
+               return respond(FORBIDDEN, exception);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadInputPortNameHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadInputPortNameHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadInputPortNameHandler.java
new file mode 100644
index 0000000..a78693d
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadInputPortNameHandler.java
@@ -0,0 +1,36 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Response.Status.NOT_FOUND;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.taverna.server.master.exceptions.BadInputPortNameException;
+
+@Provider
+public class BadInputPortNameHandler extends HandlerCore implements
+               ExceptionMapper<BadInputPortNameException> {
+       @Override
+       public Response toResponse(BadInputPortNameException exn) {
+               return respond(NOT_FOUND, exn);
+       }
+}
\ 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/rest/handler/BadPropertyValueHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadPropertyValueHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadPropertyValueHandler.java
new file mode 100644
index 0000000..e956749
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadPropertyValueHandler.java
@@ -0,0 +1,36 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Response.Status.BAD_REQUEST;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.taverna.server.master.exceptions.BadPropertyValueException;
+
+@Provider
+public class BadPropertyValueHandler extends HandlerCore implements
+               ExceptionMapper<BadPropertyValueException> {
+       @Override
+       public Response toResponse(BadPropertyValueException exn) {
+               return respond(BAD_REQUEST, exn);
+       }
+}
\ 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/rest/handler/BadStateChangeHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadStateChangeHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadStateChangeHandler.java
new file mode 100644
index 0000000..53f441b
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/BadStateChangeHandler.java
@@ -0,0 +1,36 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Response.Status.BAD_REQUEST;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.taverna.server.master.exceptions.BadStateChangeException;
+
+@Provider
+public class BadStateChangeHandler extends HandlerCore implements
+               ExceptionMapper<BadStateChangeException> {
+       @Override
+       public Response toResponse(BadStateChangeException exn) {
+               return respond(BAD_REQUEST, exn);
+       }
+}
\ 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/rest/handler/EntryHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/EntryHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/EntryHandler.java
new file mode 100644
index 0000000..bc79c22
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/EntryHandler.java
@@ -0,0 +1,147 @@
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Arrays.asList;
+import static java.util.Collections.singletonMap;
+import static javax.ws.rs.core.Response.notAcceptable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Variant;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.model.Document;
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.parser.Parser;
+import org.apache.abdera.writer.Writer;
+import org.springframework.beans.factory.annotation.Required;
+
+@Provider
+@Produces({ "application/atom+xml", "application/atom+xml;type=entry" })
+@Consumes({ "application/atom+xml", "application/atom+xml;type=entry" })
+public class EntryHandler implements MessageBodyWriter<Entry>,
+               MessageBodyReader<Entry> {
+       private static final String ENC = "UTF-8";
+       private static final MediaType ENTRY = new MediaType("application",
+                       "atom+xml", singletonMap("type", "entry"));
+       private static final Variant VARIANT = new Variant(ENTRY, (String) null,
+                       ENC);
+       private static final Charset UTF8 = Charset.forName(ENC);
+
+       @Required
+       public void setAbdera(Abdera abdera) {
+               parser = abdera.getParser();
+               writer = abdera.getWriterFactory().getWriter("prettyxml");
+       }
+
+       private Parser parser;
+       private Writer writer;
+
+       @Override
+       public boolean isReadable(Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               if (!Entry.class.isAssignableFrom(type))
+                       return false;
+               if (!ENTRY.isCompatible(mediaType))
+                       return false;
+               if (mediaType.getParameters().containsKey("type"))
+                       return 
"entry".equalsIgnoreCase(mediaType.getParameters().get(
+                                       "type"));
+               return true;
+       }
+
+       @Override
+       public Entry readFrom(Class<Entry> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType,
+                       MultivaluedMap<String, String> httpHeaders, InputStream 
entityStream)
+                       throws IOException, WebApplicationException {
+               Charset cs = UTF8;
+               try {
+                       String charset = 
mediaType.getParameters().get("charset");
+                       if (charset != null)
+                               cs = Charset.forName(charset);
+               } catch (IllegalCharsetNameException e) {
+                       throw new 
WebApplicationException(notAcceptable(asList(VARIANT))
+                                       .entity("bad charset name").build());
+               } catch (UnsupportedCharsetException e) {
+                       throw new 
WebApplicationException(notAcceptable(asList(VARIANT))
+                                       .entity("unsupportd charset 
name").build());
+               }
+               try {
+                       Document<Entry> doc = parser.parse(new 
InputStreamReader(
+                                       entityStream, cs));
+                       if 
(!Entry.class.isAssignableFrom(doc.getRoot().getClass())) {
+                               throw new WebApplicationException(
+                                               
notAcceptable(asList(VARIANT)).entity(
+                                                               "not really a 
feed entry").build());
+                       }
+                       return doc.getRoot();
+               } catch (ClassCastException e) {
+                       throw new 
WebApplicationException(notAcceptable(asList(VARIANT))
+                                       .entity("not really a feed 
entry").build());
+
+               }
+       }
+
+       @Override
+       public boolean isWriteable(Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               if (!Entry.class.isAssignableFrom(type))
+                       return false;
+               if (!ENTRY.isCompatible(mediaType))
+                       return false;
+               if (mediaType.getParameters().containsKey("type"))
+                       return 
"entry".equalsIgnoreCase(mediaType.getParameters().get(
+                                       "type"));
+               return true;
+       }
+
+       @Override
+       public long getSize(Entry t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return -1;
+       }
+
+       @Override
+       public void writeTo(Entry t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType,
+                       MultivaluedMap<String, Object> httpHeaders,
+                       OutputStream entityStream) throws IOException,
+                       WebApplicationException {
+               httpHeaders.putSingle("Content-Type", ENTRY.toString() + 
";charset="
+                               + ENC);
+               writer.writeTo(t, new OutputStreamWriter(entityStream, UTF8));
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FeedHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FeedHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FeedHandler.java
new file mode 100644
index 0000000..77e7e49
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FeedHandler.java
@@ -0,0 +1,82 @@
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Collections.singletonMap;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.model.Feed;
+import org.apache.abdera.writer.Writer;
+import org.springframework.beans.factory.annotation.Required;
+
+@Provider
+@Produces({ "application/atom+xml", "application/atom+xml;type=feed" })
+public class FeedHandler implements MessageBodyWriter<Feed> {
+       private static final MediaType FEED = new MediaType("application",
+                       "atom+xml", singletonMap("type", "feed"));
+       private static final String ENC = "UTF-8";
+
+       @Required
+       public void setAbdera(Abdera abdera) {
+               writer = abdera.getWriterFactory().getWriter("prettyxml");
+       }
+
+       private Writer writer;
+
+       @Override
+       public boolean isWriteable(Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               if (!Feed.class.isAssignableFrom(type))
+                       return false;
+               if (!FEED.isCompatible(mediaType))
+                       return false;
+               if (mediaType.getParameters().containsKey("type"))
+                       return "feed".equalsIgnoreCase(mediaType.getParameters()
+                                       .get("type"));
+               return true;
+       }
+
+       @Override
+       public long getSize(Feed t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return -1;
+       }
+
+       @Override
+       public void writeTo(Feed t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType,
+                       MultivaluedMap<String, Object> httpHeaders,
+                       OutputStream entityStream) throws IOException,
+                       WebApplicationException {
+               httpHeaders.putSingle("Content-Type", FEED.toString() + 
";charset="
+                               + ENC);
+               writer.writeTo(t, new OutputStreamWriter(entityStream, ENC));
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileConcatenationHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileConcatenationHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileConcatenationHandler.java
new file mode 100644
index 0000000..e0924ad
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileConcatenationHandler.java
@@ -0,0 +1,77 @@
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.FileConcatenation;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.interfaces.File;
+
+public class FileConcatenationHandler implements
+               MessageBodyWriter<FileConcatenation> {
+       /** How much to pull from the worker in one read. */
+       private int maxChunkSize;
+
+       /**
+        * @param maxChunkSize
+        *            How much to pull from the worker in one read.
+        */
+       @Required
+       public void setMaxChunkSize(int maxChunkSize) {
+               this.maxChunkSize = maxChunkSize;
+       }
+
+       @Override
+       public boolean isWriteable(Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return type.isAssignableFrom(FileConcatenation.class);
+       }
+
+       @Override
+       public long getSize(FileConcatenation fc, Class<?> type, Type 
genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return fc.size();
+       }
+
+       @Override
+       public void writeTo(FileConcatenation fc, Class<?> type, Type 
genericType,
+                       Annotation[] annotations, MediaType mediaType,
+                       MultivaluedMap<String, Object> httpHeaders,
+                       OutputStream entityStream) throws IOException {
+               for (File f : fc)
+                       try {
+                               byte[] buffer;
+                               for (int off = 0; true ; off += buffer.length) {
+                                       buffer = f.getContents(off, 
maxChunkSize);
+                                       if (buffer == null || buffer.length == 
0)
+                                               break;
+                                       entityStream.write(buffer);
+                               }
+                       } catch (FilesystemAccessException e) {
+                               // Ignore/skip to next 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/rest/handler/FileMessageHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileMessageHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileMessageHandler.java
new file mode 100644
index 0000000..7d2b381
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileMessageHandler.java
@@ -0,0 +1,93 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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 java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.commons.logging.Log;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.interfaces.File;
+
+/**
+ * How to write out a File object with JAX-RS.
+ * 
+ * @author Donal Fellows
+ */
+@Provider
+public class FileMessageHandler implements MessageBodyWriter<File> {
+       private Log log = getLog("Taverna.Server.Webapp");
+       /** How much to pull from the worker in one read. */
+       private int maxChunkSize;
+
+       /**
+        * @param maxChunkSize
+        *            How much to pull from the worker in one read.
+        */
+       public void setMaxChunkSize(int maxChunkSize) {
+               this.maxChunkSize = maxChunkSize;
+       }
+
+       @Override
+       public boolean isWriteable(Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return File.class.isAssignableFrom(type);
+       }
+
+       @Override
+       public long getSize(File t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               try {
+                       return t.getSize(); // Is it really raw bytes?
+               } catch (FilesystemAccessException e) {
+                       log.info("failed to get file length", e);
+                       return -1;
+               }
+       }
+
+       @Override
+       public void writeTo(File t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType,
+                       MultivaluedMap<String, Object> httpHeaders,
+                       OutputStream entityStream) throws IOException,
+                       WebApplicationException {
+               try {
+                       int off = 0;
+                       while (true) {
+                               byte[] buffer = t.getContents(off, 
maxChunkSize);
+                               if (buffer == null || buffer.length == 0)
+                                       break;
+                               entityStream.write(buffer);
+                               off += buffer.length;
+                       }
+               } catch (FilesystemAccessException e) {
+                       throw new IOException("problem when reading file", e);
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileSegmentHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileSegmentHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileSegmentHandler.java
new file mode 100644
index 0000000..82d5e0a
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FileSegmentHandler.java
@@ -0,0 +1,87 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Math.min;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.rest.FileSegment;
+
+/**
+ * How to write out a segment of a file with JAX-RS.
+ * 
+ * @author Donal Fellows
+ */
+@Provider
+public class FileSegmentHandler implements MessageBodyWriter<FileSegment> {
+       /** How much to pull from the worker in one read. */
+       private int maxChunkSize;
+
+       /**
+        * @param maxChunkSize
+        *            How much to pull from the worker in one read.
+        */
+       public void setMaxChunkSize(int maxChunkSize) {
+               this.maxChunkSize = maxChunkSize;
+       }
+
+       @Override
+       public boolean isWriteable(Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return FileSegment.class.isAssignableFrom(type);
+       }
+
+       @Override
+       public long getSize(FileSegment t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType) {
+               return t.to - t.from;
+       }
+
+       @Override
+       public void writeTo(FileSegment t, Class<?> type, Type genericType,
+                       Annotation[] annotations, MediaType mediaType,
+                       MultivaluedMap<String, Object> httpHeaders,
+                       OutputStream entityStream) throws IOException,
+                       WebApplicationException {
+               try {
+                       int off = t.from;
+                       while (off < t.to) {
+                               byte[] buffer = t.file.getContents(off,
+                                               min(maxChunkSize, t.to - off));
+                               if (buffer == null || buffer.length == 0)
+                                       break;
+                               entityStream.write(buffer);
+                               off += buffer.length;
+                       }
+               } catch (FilesystemAccessException e) {
+                       throw new IOException("problem when reading file", e);
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FilesystemAccessHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FilesystemAccessHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FilesystemAccessHandler.java
new file mode 100644
index 0000000..cfa863c
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/FilesystemAccessHandler.java
@@ -0,0 +1,36 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Response.Status.FORBIDDEN;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+
+@Provider
+public class FilesystemAccessHandler extends HandlerCore implements
+               ExceptionMapper<FilesystemAccessException> {
+       @Override
+       public Response toResponse(FilesystemAccessException exn) {
+               return respond(FORBIDDEN, exn);
+       }
+}
\ 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/rest/handler/GeneralFailureHandler.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/GeneralFailureHandler.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/GeneralFailureHandler.java
new file mode 100644
index 0000000..fe4ba0b
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/GeneralFailureHandler.java
@@ -0,0 +1,34 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.Response.Status.INTERNAL_SERVER_ERROR;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import org.taverna.server.master.exceptions.GeneralFailureException;
+
+public class GeneralFailureHandler extends HandlerCore implements
+               ExceptionMapper<GeneralFailureException> {
+       @Override
+       public Response toResponse(GeneralFailureException exception) {
+               return respond(INTERNAL_SERVER_ERROR, exception);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/HandlerCore.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/HandlerCore.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/HandlerCore.java
new file mode 100644
index 0000000..bc92154
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/rest/handler/HandlerCore.java
@@ -0,0 +1,84 @@
+/*
+ */
+package org.taverna.server.master.rest.handler;
+/*
+ * 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.TEXT_PLAIN_TYPE;
+import static javax.ws.rs.core.Response.status;
+import static org.apache.commons.logging.LogFactory.getLog;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.logging.Log;
+import org.taverna.server.master.api.ManagementModel;
+
+/**
+ * Base class for handlers that grants Spring-enabled access to the management
+ * model.
+ * 
+ * @author Donal Fellows
+ */
+public class HandlerCore {
+       private Log log = getLog("Taverna.Server.Webapp");
+       private ManagementModel managementModel;
+
+       /**
+        * @param managementModel
+        *            the managementModel to set
+        */
+       public void setManagementModel(ManagementModel managementModel) {
+               this.managementModel = managementModel;
+       }
+
+       /**
+        * Simplified interface for building responses.
+        * 
+        * @param status
+        *            What status code to use?
+        * @param exception
+        *            What exception to report on?
+        * @return The build response.
+        */
+       protected Response respond(Response.Status status, Exception exception) 
{
+               if (managementModel.getLogOutgoingExceptions()
+                               || status.getStatusCode() >= 500)
+                       log.info("converting exception to response", exception);
+               return status(status).type(TEXT_PLAIN_TYPE)
+                               .entity(exception.getMessage()).build();
+       }
+
+       /**
+        * Simplified interface for building responses.
+        * 
+        * @param status
+        *            What status code to use?
+        * @param partialMessage
+        *            The prefix to the message.
+        * @param exception
+        *            What exception to report on?
+        * @return The build response.
+        */
+       protected Response respond(Response.Status status, String 
partialMessage,
+                       Exception exception) {
+               if (managementModel.getLogOutgoingExceptions()
+                               || status.getStatusCode() >= 500)
+                       log.info("converting exception to response", exception);
+               return status(status).type(TEXT_PLAIN_TYPE)
+                               .entity(partialMessage + "\n" + 
exception.getMessage()).build();
+       }
+}


Reply via email to