http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenersREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenersREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenersREST.java
new file mode 100644
index 0000000..3cc0d73
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenersREST.java
@@ -0,0 +1,106 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.UriBuilder.fromUri;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.taverna.server.master.api.ListenersBean;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.ListenerDefinition;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single workflow run's event listeners.
+ * 
+ * @author Donal Fellows
+ */
+abstract class ListenersREST implements TavernaServerListenersREST,
+               ListenersBean {
+       private TavernaRun run;
+       private TavernaServerSupport support;
+
+       @Override
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       public ListenersREST connect(TavernaRun run) {
+               this.run = run;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response addListener(ListenerDefinition typeAndConfiguration,
+                       UriInfo ui) throws NoUpdateException, 
NoListenerException {
+               String name = support.makeListener(run, 
typeAndConfiguration.type,
+                               typeAndConfiguration.configuration).getName();
+               return 
created(secure(ui).path("{listenerName}").build(name)).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public TavernaServerListenerREST getListener(String name)
+                       throws NoListenerException {
+               Listener l = support.getListener(run, name);
+               if (l == null)
+                       throw new NoListenerException();
+               return makeListenerInterface().connect(l, run);
+       }
+
+       @Nonnull
+       protected abstract SingleListenerREST makeListenerInterface();
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Listeners getDescription(UriInfo ui) {
+               List<ListenerDescription> result = new ArrayList<>();
+               UriBuilder ub = secure(ui).path("{name}");
+               for (Listener l : run.getListeners())
+                       result.add(new ListenerDescription(l,
+                                       fromUri(ub.build(l.getName()))));
+               return new Listeners(result, ub);
+       }
+
+       @Override
+       @CallCounted
+       public Response listenersOptions() {
+               return opt();
+       }
+}
\ 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/ManagementState.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ManagementState.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ManagementState.java
new file mode 100644
index 0000000..c65a334
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ManagementState.java
@@ -0,0 +1,228 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import javax.annotation.PostConstruct;
+import javax.jdo.Query;
+import javax.jdo.annotations.PersistenceAware;
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+import javax.jdo.annotations.PrimaryKey;
+
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.ManagementModel;
+import org.taverna.server.master.utils.JDOSupport;
+
+/** The persistent, manageable state of the Taverna Server web application. */
+@PersistenceAware
+class ManagementState extends JDOSupport<WebappState> implements
+               ManagementModel {
+       public ManagementState() {
+               super(WebappState.class);
+       }
+
+       /** Whether we should log all workflows sent to us. */
+       private boolean logIncomingWorkflows = false;
+
+       /** Whether we allow the creation of new workflow runs. */
+       private boolean allowNewWorkflowRuns = true;
+
+       /**
+        * Whether outgoing exceptions should be logged before being converted 
to
+        * responses.
+        */
+       private boolean logOutgoingExceptions = false;
+
+       /**
+        * The file that all usage records should be appended to, or 
<tt>null</tt>
+        * if they should be just dropped.
+        */
+       private String usageRecordLogFile = null;
+
+       @Override
+       public void setLogIncomingWorkflows(boolean logIncomingWorkflows) {
+               this.logIncomingWorkflows = logIncomingWorkflows;
+               if (loadedState)
+                       self.store();
+       }
+
+       @Override
+       public boolean getLogIncomingWorkflows() {
+               self.load();
+               return logIncomingWorkflows;
+       }
+
+       @Override
+       public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) {
+               this.allowNewWorkflowRuns = allowNewWorkflowRuns;
+               if (loadedState)
+                       self.store();
+       }
+
+       @Override
+       public boolean getAllowNewWorkflowRuns() {
+               self.load();
+               return allowNewWorkflowRuns;
+       }
+
+       @Override
+       public void setLogOutgoingExceptions(boolean logOutgoingExceptions) {
+               this.logOutgoingExceptions = logOutgoingExceptions;
+               if (loadedState)
+                       self.store();
+       }
+
+       @Override
+       public boolean getLogOutgoingExceptions() {
+               self.load();
+               return logOutgoingExceptions || true;
+       }
+
+       @Override
+       public String getUsageRecordLogFile() {
+               self.load();
+               return usageRecordLogFile;
+       }
+
+       @Override
+       public void setUsageRecordLogFile(String usageRecordLogFile) {
+               this.usageRecordLogFile = usageRecordLogFile;
+               if (loadedState)
+                       self.store();
+       }
+
+       private static final int KEY = 42; // whatever
+
+       private WebappState get() {
+               Query<WebappState> q = query("id == " + KEY);
+               q.setUnique(true);
+               return q.executeUnique();
+       }
+
+       private boolean loadedState;
+       private ManagementState self;
+
+       @Required
+       public void setSelf(ManagementState self) {
+               this.self = self;
+       }
+
+       @PostConstruct
+       @WithinSingleTransaction
+       public void load() {
+               if (loadedState || !isPersistent())
+                       return;
+               WebappState state = get();
+               if (state == null)
+                       return;
+               allowNewWorkflowRuns = state.getAllowNewWorkflowRuns();
+               logIncomingWorkflows = state.getLogIncomingWorkflows();
+               logOutgoingExceptions = state.getLogOutgoingExceptions();
+               usageRecordLogFile = state.getUsageRecordLogFile();
+               loadedState = true;
+       }
+
+       @WithinSingleTransaction
+       public void store() {
+               if (!isPersistent())
+                       return;
+               WebappState state = get();
+               if (state == null) {
+                       state = new WebappState();
+                       // save state
+                       state.id = KEY; // whatever...
+                       state = persist(state);
+               }
+               state.setAllowNewWorkflowRuns(allowNewWorkflowRuns);
+               state.setLogIncomingWorkflows(logIncomingWorkflows);
+               state.setLogOutgoingExceptions(logOutgoingExceptions);
+               state.setUsageRecordLogFile(usageRecordLogFile);
+               loadedState = true;
+       }
+}
+
+// WARNING! If you change the name of this class, update persistence.xml as
+// well!
+@PersistenceCapable(table = "MANAGEMENTSTATE__WEBAPPSTATE")
+class WebappState implements ManagementModel {
+       public WebappState() {
+       }
+
+       @PrimaryKey
+       protected int id;
+
+       /** Whether we should log all workflows sent to us. */
+       @Persistent
+       private boolean logIncomingWorkflows;
+
+       /** Whether we allow the creation of new workflow runs. */
+       @Persistent
+       private boolean allowNewWorkflowRuns;
+
+       /**
+        * Whether outgoing exceptions should be logged before being converted 
to
+        * responses.
+        */
+       @Persistent
+       private boolean logOutgoingExceptions;
+
+       /** Where to write usage records. */
+       @Persistent
+       private String usageRecordLogFile;
+
+       @Override
+       public void setLogIncomingWorkflows(boolean logIncomingWorkflows) {
+               this.logIncomingWorkflows = logIncomingWorkflows;
+       }
+
+       @Override
+       public boolean getLogIncomingWorkflows() {
+               return logIncomingWorkflows;
+       }
+
+       @Override
+       public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) {
+               this.allowNewWorkflowRuns = allowNewWorkflowRuns;
+       }
+
+       @Override
+       public boolean getAllowNewWorkflowRuns() {
+               return allowNewWorkflowRuns;
+       }
+
+       @Override
+       public void setLogOutgoingExceptions(boolean logOutgoingExceptions) {
+               this.logOutgoingExceptions = logOutgoingExceptions;
+       }
+
+       @Override
+       public boolean getLogOutgoingExceptions() {
+               return logOutgoingExceptions;
+       }
+
+       @Override
+       public String getUsageRecordLogFile() {
+               return usageRecordLogFile;
+       }
+
+       @Override
+       public void setUsageRecordLogFile(String usageRecordLogFile) {
+               this.usageRecordLogFile = usageRecordLogFile;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunREST.java
new file mode 100644
index 0000000..a04de46
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunREST.java
@@ -0,0 +1,512 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.ok;
+import static javax.ws.rs.core.Response.status;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.joda.time.format.ISODateTimeFormat.dateTime;
+import static org.joda.time.format.ISODateTimeFormat.dateTimeParser;
+import static org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.common.Status.Initialized;
+import static org.taverna.server.master.common.Status.Operating;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.Date;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.JAXBException;
+
+import org.apache.commons.logging.Log;
+import org.joda.time.DateTime;
+import org.apache.taverna.server.usagerecord.JobUsageRecord;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.RunBean;
+import org.taverna.server.master.common.ProfileList;
+import org.taverna.server.master.common.Status;
+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.exceptions.OverloadedException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.rest.InteractionFeedREST;
+import org.taverna.server.master.rest.TavernaServerInputREST;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.rest.TavernaServerRunREST;
+import org.taverna.server.master.rest.TavernaServerSecurityREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+import org.taverna.server.port_description.OutputDescription;
+
+/**
+ * RESTful interface to a single workflow run.
+ * 
+ * @author Donal Fellows
+ */
+abstract class RunREST implements TavernaServerRunREST, RunBean {
+       private Log log = getLog("Taverna.Server.Webapp");
+       private String runName;
+       private TavernaRun run;
+       private TavernaServerSupport support;
+       private ContentsDescriptorBuilder cdBuilder;
+
+       @Override
+       @Required
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       @Required
+       public void setCdBuilder(ContentsDescriptorBuilder cdBuilder) {
+               this.cdBuilder = cdBuilder;
+       }
+
+       @Override
+       public void setRunName(String runName) {
+               this.runName = runName;
+       }
+
+       @Override
+       public void setRun(TavernaRun run) {
+               this.run = run;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public RunDescription getDescription(UriInfo ui) {
+               return new RunDescription(run, ui);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response destroy() throws NoUpdateException {
+               try {
+                       support.unregisterRun(runName, run);
+               } catch (UnknownRunException e) {
+                       log.fatal("can't happen", e);
+               }
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public TavernaServerListenersREST getListeners() {
+               return makeListenersInterface().connect(run);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public TavernaServerSecurityREST getSecurity() throws NotOwnerException 
{
+               TavernaSecurityContext secContext = run.getSecurityContext();
+               if (!support.getPrincipal().equals(secContext.getOwner()))
+                       throw new NotOwnerException();
+
+               // context.getBean("run.security", run, secContext);
+               return makeSecurityInterface().connect(secContext, run);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getExpiryTime() {
+               return dateTime().print(new DateTime(run.getExpiry()));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getCreateTime() {
+               return dateTime().print(new 
DateTime(run.getCreationTimestamp()));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getFinishTime() {
+               Date f = run.getFinishTimestamp();
+               return f == null ? "" : dateTime().print(new DateTime(f));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getStartTime() {
+               Date f = run.getStartTimestamp();
+               return f == null ? "" : dateTime().print(new DateTime(f));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getStatus() {
+               return run.getStatus().toString();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Workflow getWorkflow() {
+               return run.getWorkflow();
+       }
+
+       @Override
+       @CallCounted
+       public String getMainProfileName() {
+               String name = run.getWorkflow().getMainProfileName();
+               return (name == null ? "" : name);
+       }
+
+       @Override
+       @CallCounted
+       public ProfileList getProfiles() {
+               return support.getProfileDescriptor(run.getWorkflow());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public DirectoryREST getWorkingDirectory() {
+               return makeDirectoryInterface().connect(run);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String setExpiryTime(String expiry) throws NoUpdateException,
+                       IllegalArgumentException {
+               DateTime wanted = dateTimeParser().parseDateTime(expiry.trim());
+               Date achieved = support.updateExpiry(run, wanted.toDate());
+               return dateTime().print(new DateTime(achieved));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response setStatus(String status) throws NoUpdateException {
+               Status newStatus = Status.valueOf(status.trim());
+               support.permitUpdate(run);
+               if (newStatus == Operating && run.getStatus() == Initialized) {
+                       if (!support.getAllowStartWorkflowRuns())
+                               throw new OverloadedException();
+                       String issue = run.setStatus(newStatus);
+                       if (issue == null)
+                               issue = "starting run...";
+                       return 
status(202).entity(issue).type("text/plain").build();
+               }
+               run.setStatus(newStatus); // Ignore the result
+               return 
ok(run.getStatus().toString()).type("text/plain").build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public TavernaServerInputREST getInputs(UriInfo ui) {
+               return makeInputInterface().connect(run, ui);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getOutputFile() {
+               String o = run.getOutputBaclavaFile();
+               return o == null ? "" : o;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String setOutputFile(String filename) throws NoUpdateException,
+                       FilesystemAccessException, BadStateChangeException {
+               support.permitUpdate(run);
+               if (filename != null && filename.length() == 0)
+                       filename = null;
+               run.setOutputBaclavaFile(filename);
+               String o = run.getOutputBaclavaFile();
+               return o == null ? "" : o;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public OutputDescription getOutputDescription(UriInfo ui)
+                       throws BadStateChangeException, 
FilesystemAccessException,
+                       NoDirectoryEntryException {
+               if (run.getStatus() == Initialized)
+                       throw new BadStateChangeException(
+                                       "may not get output description in 
initial state");
+               return cdBuilder.makeOutputDescriptor(run, ui);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public InteractionFeedREST getInteractionFeed() {
+               return makeInteractionFeed().connect(run);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getName() {
+               return run.getName();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String setName(String name) throws NoUpdateException {
+               support.permitUpdate(run);
+               run.setName(name);
+               return run.getName();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getStdout() throws NoListenerException {
+               return support.getProperty(run, "io", "stdout");
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getStderr() throws NoListenerException {
+               return support.getProperty(run, "io", "stderr");
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response getUsage() throws NoListenerException, JAXBException {
+               String ur = support.getProperty(run, "io", "usageRecord");
+               if (ur.isEmpty())
+                       return noContent().build();
+               return ok(JobUsageRecord.unmarshal(ur), 
APPLICATION_XML).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response getLogContents() {
+               FileConcatenation fc = support.getLogs(run);
+               if (fc.isEmpty())
+                       return Response.noContent().build();
+               return Response.ok(fc, TEXT_PLAIN).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response getRunBundle() {
+               FileConcatenation fc = support.getProv(run);
+               if (fc.isEmpty())
+                       return Response.status(404).entity("no provenance 
currently available").build();
+               return Response.ok(fc, 
"application/vnd.wf4ever.robundle+zip").build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public boolean getGenerateProvenance() {
+               return run.getGenerateProvenance();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public boolean setGenerateProvenance(boolean newValue) throws 
NoUpdateException {
+               support.permitUpdate(run);
+               run.setGenerateProvenance(newValue);
+               return run.getGenerateProvenance();
+       }
+
+       /**
+        * Construct a RESTful interface to a run's filestore.
+        * 
+        * @return The handle to the interface, as decorated by Spring.
+        */
+       protected abstract DirectoryREST makeDirectoryInterface();
+
+       /**
+        * Construct a RESTful interface to a run's input descriptors.
+        * 
+        * @return The handle to the interface, as decorated by Spring.
+        */
+       protected abstract InputREST makeInputInterface();
+
+       /**
+        * Construct a RESTful interface to a run's listeners.
+        * 
+        * @return The handle to the interface, as decorated by Spring.
+        */
+       protected abstract ListenersREST makeListenersInterface();
+
+       /**
+        * Construct a RESTful interface to a run's security.
+        * 
+        * @return The handle to the interface, as decorated by Spring.
+        */
+       protected abstract RunSecurityREST makeSecurityInterface();
+
+       /**
+        * Construct a RESTful interface to a run's interaction feed.
+        * 
+        * @return The handle to the interaface, as decorated by Spring.
+        */
+       protected abstract InteractionFeed makeInteractionFeed();
+
+       @Override
+       @CallCounted
+       public Response runOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response workflowOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response profileOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response expiryOptions() {
+               return opt("PUT");
+       }
+
+       @Override
+       @CallCounted
+       public Response createTimeOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response startTimeOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response finishTimeOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response statusOptions() {
+               return opt("PUT");
+       }
+
+       @Override
+       @CallCounted
+       public Response outputOptions() {
+               return opt("PUT");
+       }
+
+       @Override
+       @CallCounted
+       public Response nameOptions() {
+               return opt("PUT");
+       }
+
+       @Override
+       @CallCounted
+       public Response stdoutOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response stderrOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response usageOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response logOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response runBundleOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response generateProvenanceOptions() {
+               return opt("PUT");
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunSecurityREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunSecurityREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunSecurityREST.java
new file mode 100644
index 0000000..9dc69d7
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/RunSecurityREST.java
@@ -0,0 +1,316 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.util.UUID.randomUUID;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.taverna.server.master.common.Status.Initialized;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.net.URI;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.taverna.server.master.api.SecurityBean;
+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.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoCredentialException;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.rest.TavernaServerSecurityREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single workflow run's security settings.
+ * 
+ * @author Donal Fellows
+ */
+class RunSecurityREST implements TavernaServerSecurityREST, SecurityBean {
+       private TavernaServerSupport support;
+       private TavernaSecurityContext context;
+       private TavernaRun run;
+
+       @Override
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       public RunSecurityREST connect(TavernaSecurityContext context,
+                       TavernaRun run) {
+               this.context = context;
+               this.run = run;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Descriptor describe(UriInfo ui) {
+               return new Descriptor(secure(ui).path("{element}"), 
context.getOwner()
+                               .getName(), context.getCredentials(), 
context.getTrusted());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String getOwner() {
+               return context.getOwner().getName();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public CredentialList listCredentials() {
+               return new CredentialList(context.getCredentials());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public CredentialHolder getParticularCredential(String id)
+                       throws NoCredentialException {
+               for (Credential c : context.getCredentials())
+                       if (c.id.equals(id))
+                               return new CredentialHolder(c);
+               throw new NoCredentialException();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public CredentialHolder setParticularCredential(String id,
+                       CredentialHolder cred, UriInfo ui)
+                       throws InvalidCredentialException, 
BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               Credential c = cred.credential;
+               c.id = id;
+               c.href = ui.getAbsolutePath().toString();
+               context.validateCredential(c);
+               context.deleteCredential(c);
+               context.addCredential(c);
+               return new CredentialHolder(c);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response addCredential(CredentialHolder cred, UriInfo ui)
+                       throws InvalidCredentialException, 
BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               Credential c = cred.credential;
+               c.id = randomUUID().toString();
+               URI uri = secure(ui).path("{id}").build(c.id);
+               c.href = uri.toString();
+               context.validateCredential(c);
+               context.addCredential(c);
+               return created(uri).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response deleteAllCredentials(UriInfo ui)
+                       throws BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               for (Credential c : context.getCredentials())
+                       context.deleteCredential(c);
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response deleteCredential(String id, UriInfo ui)
+                       throws BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               context.deleteCredential(new Credential.Dummy(id));
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public TrustList listTrusted() {
+               return new TrustList(context.getTrusted());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Trust getParticularTrust(String id) throws NoCredentialException 
{
+               for (Trust t : context.getTrusted())
+                       if (t.id.equals(id))
+                               return t;
+               throw new NoCredentialException();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Trust setParticularTrust(String id, Trust t, UriInfo ui)
+                       throws InvalidCredentialException, 
BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               t.id = id;
+               t.href = ui.getAbsolutePath().toString();
+               context.validateTrusted(t);
+               context.deleteTrusted(t);
+               context.addTrusted(t);
+               return t;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response addTrust(Trust t, UriInfo ui)
+                       throws InvalidCredentialException, 
BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               t.id = randomUUID().toString();
+               URI uri = secure(ui).path("{id}").build(t.id);
+               t.href = uri.toString();
+               context.validateTrusted(t);
+               context.addTrusted(t);
+               return created(uri).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response deleteAllTrusts(UriInfo ui) throws 
BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               for (Trust t : context.getTrusted())
+                       context.deleteTrusted(t);
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response deleteTrust(String id, UriInfo ui)
+                       throws BadStateChangeException {
+               if (run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               Trust toDelete = new Trust();
+               toDelete.id = id;
+               context.deleteTrusted(toDelete);
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public PermissionsDescription describePermissions(UriInfo ui) {
+               Map<String, Permission> perm = 
support.getPermissionMap(context);
+               return new PermissionsDescription(secure(ui).path("{id}"), 
perm);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Permission describePermission(String id) {
+               return support.getPermission(context, id);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Permission setPermission(String id, Permission perm) {
+               support.setPermission(context, id, perm);
+               return support.getPermission(context, id);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response deletePermission(String id, UriInfo ui) {
+               support.setPermission(context, id, Permission.None);
+               return noContent().build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public Response makePermission(PermissionDescription desc, UriInfo ui) {
+               support.setPermission(context, desc.userName, desc.permission);
+               return 
created(secure(ui).path("{user}").build(desc.userName)).build();
+       }
+
+       @Override
+       @CallCounted
+       public Response descriptionOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response ownerOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response credentialsOptions() {
+               return opt("POST", "DELETE");
+       }
+
+       @Override
+       @CallCounted
+       public Response credentialOptions(String id) {
+               return opt("PUT", "DELETE");
+       }
+
+       @Override
+       @CallCounted
+       public Response trustsOptions() {
+               return opt("POST", "DELETE");
+       }
+
+       @Override
+       @CallCounted
+       public Response trustOptions(String id) {
+               return opt("PUT", "DELETE");
+       }
+
+       @Override
+       @CallCounted
+       public Response permissionsOptions() {
+               return opt("POST");
+       }
+
+       @Override
+       @CallCounted
+       public Response permissionOptions(String id) {
+               return opt("PUT", "DELETE");
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/SingleListenerREST.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/SingleListenerREST.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/SingleListenerREST.java
new file mode 100644
index 0000000..2c841cc
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/SingleListenerREST.java
@@ -0,0 +1,110 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.util.Arrays.asList;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.taverna.server.master.api.OneListenerBean;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import 
org.taverna.server.master.rest.TavernaServerListenersREST.ListenerDescription;
+import 
org.taverna.server.master.rest.TavernaServerListenersREST.TavernaServerListenerREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single listener attached to a workflow run.
+ * 
+ * @author Donal Fellows
+ */
+abstract class SingleListenerREST implements TavernaServerListenerREST,
+               OneListenerBean {
+       private Listener listen;
+       private TavernaRun run;
+
+       @Override
+       public SingleListenerREST connect(Listener listen, TavernaRun run) {
+               this.listen = listen;
+               this.run = run;
+               return this;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String getConfiguration() {
+               return listen.getConfiguration();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public ListenerDescription getDescription(UriInfo ui) {
+               return new ListenerDescription(listen, secure(ui));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public TavernaServerListenersREST.Properties getProperties(UriInfo ui) {
+               return new 
TavernaServerListenersREST.Properties(secure(ui).path(
+                               "{prop}"), listen.listProperties());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public TavernaServerListenersREST.Property getProperty(
+                       final String propertyName) throws NoListenerException {
+               List<String> p = asList(listen.listProperties());
+               if (p.contains(propertyName)) {
+                       return makePropertyInterface().connect(listen, run, 
propertyName);
+               }
+               throw new NoListenerException("no such property");
+       }
+
+       protected abstract ListenerPropertyREST makePropertyInterface();
+
+       @Override
+       @CallCounted
+       public Response listenerOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response configurationOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response propertiesOptions() {
+               return opt();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServer.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServer.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServer.java
new file mode 100644
index 0000000..4844147
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServer.java
@@ -0,0 +1,1438 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.lang.Math.min;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.sort;
+import static java.util.UUID.randomUUID;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.UriBuilder.fromUri;
+import static javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS;
+import static javax.xml.ws.handler.MessageContext.PATH_INFO;
+import static org.apache.commons.io.IOUtils.toByteArray;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.taverna.server.master.TavernaServerSupport.PROV_BUNDLE;
+import static org.taverna.server.master.common.DirEntryReference.newInstance;
+import static org.taverna.server.master.common.Namespaces.SERVER_SOAP;
+import static org.taverna.server.master.common.Roles.ADMIN;
+import static org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.common.Status.Initialized;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.soap.DirEntry.convert;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import javax.annotation.security.DeclareRoles;
+import javax.annotation.security.RolesAllowed;
+import javax.jws.WebService;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+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.ws.WebServiceContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.cxf.annotations.WSDLDocumentation;
+import org.apache.taverna.server.usagerecord.JobUsageRecord;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.SupportAware;
+import org.taverna.server.master.api.TavernaServerBean;
+import org.taverna.server.master.common.Capability;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.DirEntryReference;
+import org.taverna.server.master.common.InputDescription;
+import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
+import org.taverna.server.master.common.RunReference;
+import org.taverna.server.master.common.Status;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.common.version.Version;
+import org.taverna.server.master.exceptions.BadPropertyValueException;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoCreateException;
+import org.taverna.server.master.exceptions.NoCredentialException;
+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.exceptions.OverloadedException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.factories.ListenerFactory;
+import org.taverna.server.master.interfaces.Directory;
+import org.taverna.server.master.interfaces.DirectoryEntry;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.Input;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.Policy;
+import org.taverna.server.master.interfaces.RunStore;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.notification.NotificationEngine;
+import org.taverna.server.master.notification.atom.EventDAO;
+import org.taverna.server.master.rest.TavernaServerREST;
+import 
org.taverna.server.master.rest.TavernaServerREST.EnabledNotificationFabrics;
+import org.taverna.server.master.rest.TavernaServerREST.PermittedListeners;
+import org.taverna.server.master.rest.TavernaServerREST.PermittedWorkflows;
+import org.taverna.server.master.rest.TavernaServerREST.PolicyView;
+import org.taverna.server.master.rest.TavernaServerRunREST;
+import org.taverna.server.master.soap.DirEntry;
+import org.taverna.server.master.soap.FileContents;
+import org.taverna.server.master.soap.PermissionList;
+import org.taverna.server.master.soap.TavernaServerSOAP;
+import org.taverna.server.master.soap.WrappedWorkflow;
+import org.taverna.server.master.soap.ZippedDirectory;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+import org.taverna.server.port_description.OutputDescription;
+
+/**
+ * The core implementation of the web application.
+ * 
+ * @author Donal Fellows
+ */
+@Path("/")
+@DeclareRoles({ USER, ADMIN })
+@WebService(endpointInterface = 
"org.taverna.server.master.soap.TavernaServerSOAP", serviceName = 
"TavernaServer", targetNamespace = SERVER_SOAP)
+@WSDLDocumentation("An instance of Taverna " + Version.JAVA + " Server.")
+public abstract class TavernaServer implements TavernaServerSOAP,
+               TavernaServerREST, TavernaServerBean {
+       /**
+        * The root of descriptions of the server in JMX.
+        */
+       public static final String JMX_ROOT = "Taverna:group=Server-"
+                       + Version.JAVA + ",name=";
+
+       /** The logger for the server framework. */
+       public Log log = getLog("Taverna.Server.Webapp");
+
+       @PreDestroy
+       void closeLog() {
+               log = null;
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // CONNECTIONS TO JMX, SPRING AND CXF
+
+       @Resource
+       WebServiceContext jaxws;
+       @Context
+       private HttpHeaders jaxrsHeaders;
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // STATE VARIABLES AND SPRING SETTERS
+
+       /**
+        * For building descriptions of the expected inputs and actual outputs 
of a
+        * workflow.
+        */
+       private ContentsDescriptorBuilder cdBuilder;
+       /**
+        * Utilities for accessing files on the local-worker.
+        */
+       private FilenameUtils fileUtils;
+       /** How notifications are dispatched. */
+       private NotificationEngine notificationEngine;
+       /** Main support class. */
+       private TavernaServerSupport support;
+       /** A storage facility for workflow runs. */
+       private RunStore runStore;
+       /** Encapsulates the policies applied by this server. */
+       private Policy policy;
+       /** Where Atom events come from. */
+       EventDAO eventSource;
+       /** Reference to the main interaction feed. */
+       private String interactionFeed;
+
+       @Override
+       @Required
+       public void setFileUtils(FilenameUtils converter) {
+               this.fileUtils = converter;
+       }
+
+       @Override
+       @Required
+       public void setContentsDescriptorBuilder(ContentsDescriptorBuilder 
cdBuilder) {
+               this.cdBuilder = cdBuilder;
+       }
+
+       @Override
+       @Required
+       public void setNotificationEngine(NotificationEngine 
notificationEngine) {
+               this.notificationEngine = notificationEngine;
+       }
+
+       /**
+        * @param support
+        *            the support to set
+        */
+       @Override
+       @Required
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Override
+       @Required
+       public void setRunStore(RunStore runStore) {
+               this.runStore = runStore;
+       }
+
+       @Override
+       @Required
+       public void setPolicy(Policy policy) {
+               this.policy = policy;
+       }
+
+       @Override
+       @Required
+       public void setEventSource(EventDAO eventSource) {
+               this.eventSource = eventSource;
+       }
+
+       /**
+        * The location of a service-wide interaction feed, derived from a
+        * properties file. Expected to be <i>actually</i> not set (to a real
+        * value).
+        * 
+        * @param interactionFeed
+        *            The URL, which will be resolved relative to the location 
of
+        *            the webapp, or the string "<tt>none</tt>" (which 
corresponds
+        *            to a <tt>null</tt>).
+        */
+       public void setInteractionFeed(String interactionFeed) {
+               if ("none".equals(interactionFeed))
+                       interactionFeed = null;
+               else if (interactionFeed != null && 
interactionFeed.startsWith("${"))
+                       interactionFeed = null;
+               this.interactionFeed = interactionFeed;
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // REST INTERFACE
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public ServerDescription describeService(UriInfo ui) {
+               jaxrsUriInfo.set(new WeakReference<>(ui));
+               return new ServerDescription(ui, resolve(interactionFeed));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public RunList listUsersRuns(UriInfo ui) {
+               jaxrsUriInfo.set(new WeakReference<>(ui));
+               return new RunList(runs(), secure(ui).path("{name}"));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response submitWorkflow(Workflow workflow, UriInfo ui)
+                       throws NoUpdateException {
+               jaxrsUriInfo.set(new WeakReference<>(ui));
+               checkCreatePolicy(workflow);
+               String name = support.buildWorkflow(workflow);
+               return created(secure(ui).path("{uuid}").build(name)).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Response submitWorkflowByURL(List<URI> referenceList, UriInfo ui)
+                       throws NoCreateException {
+               jaxrsUriInfo.set(new WeakReference<>(ui));
+               if (referenceList == null || referenceList.size() == 0)
+                       throw new NoCreateException("no workflow URI supplied");
+               URI workflowURI = referenceList.get(0);
+               checkCreatePolicy(workflowURI);
+               Workflow workflow;
+               try {
+                       workflow = 
support.getWorkflowDocumentFromURI(workflowURI);
+               } catch (IOException e) {
+                       throw new NoCreateException("could not read workflow", 
e);
+               }
+               String name = support.buildWorkflow(workflow);
+               return created(secure(ui).path("{uuid}").build(name)).build();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public int getServerMaxRuns() {
+               return support.getMaxSimultaneousRuns();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed({ USER, SELF })
+       public TavernaServerRunREST getRunResource(String runName, UriInfo ui)
+                       throws UnknownRunException {
+               jaxrsUriInfo.set(new WeakReference<>(ui));
+               RunREST rr = makeRunInterface();
+               rr.setRun(support.getRun(runName));
+               rr.setRunName(runName);
+               return rr;
+       }
+
+       private ThreadLocal<Reference<UriInfo>> jaxrsUriInfo = new 
InheritableThreadLocal<>();
+
+       private UriInfo getUriInfo() {
+               if (jaxrsUriInfo.get() == null)
+                       return null;
+               return jaxrsUriInfo.get().get();
+       }
+
+       @Override
+       @CallCounted
+       public abstract PolicyView getPolicyDescription();
+
+       @Override
+       @CallCounted
+       public Response serviceOptions() {
+               return opt();
+       }
+
+       @Override
+       @CallCounted
+       public Response runsOptions() {
+               return opt("POST");
+       }
+
+       /**
+        * Construct a RESTful interface to a run.
+        * 
+        * @return The handle to the interface, as decorated by Spring.
+        */
+       protected abstract RunREST makeRunInterface();
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // SOAP INTERFACE
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public RunReference[] listRuns() {
+               ArrayList<RunReference> ws = new ArrayList<>();
+               UriBuilder ub = getRunUriBuilder();
+               for (String runName : runs().keySet())
+                       ws.add(new RunReference(runName, ub));
+               return ws.toArray(new RunReference[ws.size()]);
+       }
+
+       private void checkCreatePolicy(Workflow workflow) throws 
NoCreateException {
+               List<URI> pwu = policy
+                               
.listPermittedWorkflowURIs(support.getPrincipal());
+               if (pwu == null || pwu.size() == 0)
+                       return;
+               throw new NoCreateException("server policy: will only start "
+                               + "workflows sourced from permitted URI list");
+       }
+
+       private void checkCreatePolicy(URI workflowURI) throws 
NoCreateException {
+               List<URI> pwu = policy
+                               
.listPermittedWorkflowURIs(support.getPrincipal());
+               if (pwu == null || pwu.size() == 0 || pwu.contains(workflowURI))
+                       return;
+               throw new NoCreateException("workflow URI not on permitted 
list");
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public RunReference submitWorkflow(Workflow workflow)
+                       throws NoUpdateException {
+               checkCreatePolicy(workflow);
+               String name = support.buildWorkflow(workflow);
+               return new RunReference(name, getRunUriBuilder());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public RunReference submitWorkflowMTOM(WrappedWorkflow workflow)
+                       throws NoUpdateException {
+               Workflow wf;
+               try {
+                       wf = workflow.getWorkflow();
+               } catch (IOException e) {
+                       throw new NoCreateException(e.getMessage(), e);
+               }
+               checkCreatePolicy(wf);
+               String name = support.buildWorkflow(wf);
+               return new RunReference(name, getRunUriBuilder());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public RunReference submitWorkflowByURI(URI workflowURI)
+                       throws NoCreateException {
+               checkCreatePolicy(workflowURI);
+               Workflow workflow;
+               try {
+                       workflow = 
support.getWorkflowDocumentFromURI(workflowURI);
+               } catch (IOException e) {
+                       throw new NoCreateException("could not read workflow", 
e);
+               }
+               String name = support.buildWorkflow(workflow);
+               return new RunReference(name, getRunUriBuilder());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public URI[] getServerWorkflows() {
+               return support.getPermittedWorkflowURIs();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String[] getServerListeners() {
+               List<String> types = support.getListenerTypes();
+               return types.toArray(new String[types.size()]);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public String[] getServerNotifiers() {
+               List<String> dispatchers = notificationEngine
+                               .listAvailableDispatchers();
+               return dispatchers.toArray(new String[dispatchers.size()]);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public List<Capability> getServerCapabilities() {
+               return support.getCapabilities();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void destroyRun(String runName) throws UnknownRunException,
+                       NoUpdateException {
+               support.unregisterRun(runName, null);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunDescriptiveName(String runName)
+                       throws UnknownRunException {
+               return support.getRun(runName).getName();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunDescriptiveName(String runName, String 
descriptiveName)
+                       throws UnknownRunException, NoUpdateException {
+               TavernaRun run = support.getRun(runName);
+               support.permitUpdate(run);
+               run.setName(descriptiveName);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Workflow getRunWorkflow(String runName) throws 
UnknownRunException {
+               return support.getRun(runName).getWorkflow();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public WrappedWorkflow getRunWorkflowMTOM(String runName)
+                       throws UnknownRunException {
+               WrappedWorkflow ww = new WrappedWorkflow();
+               ww.setWorkflow(support.getRun(runName).getWorkflow());
+               return ww;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public ProfileList getRunWorkflowProfiles(String runName)
+                       throws UnknownRunException {
+               return support.getProfileDescriptor(support.getRun(runName)
+                               .getWorkflow());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Date getRunExpiry(String runName) throws UnknownRunException {
+               return support.getRun(runName).getExpiry();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunExpiry(String runName, Date d)
+                       throws UnknownRunException, NoUpdateException {
+               support.updateExpiry(support.getRun(runName), d);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Date getRunCreationTime(String runName) throws 
UnknownRunException {
+               return support.getRun(runName).getCreationTimestamp();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Date getRunFinishTime(String runName) throws UnknownRunException 
{
+               return support.getRun(runName).getFinishTimestamp();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Date getRunStartTime(String runName) throws UnknownRunException {
+               return support.getRun(runName).getStartTimestamp();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Status getRunStatus(String runName) throws UnknownRunException {
+               return support.getRun(runName).getStatus();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String setRunStatus(String runName, Status s)
+                       throws UnknownRunException, NoUpdateException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               if (s == Status.Operating && w.getStatus() == 
Status.Initialized) {
+                       if (!support.getAllowStartWorkflowRuns())
+                               throw new OverloadedException();
+                       try {
+                               String issue = w.setStatus(s);
+                               if (issue == null)
+                                       return "";
+                               if (issue.isEmpty())
+                                       return "unknown reason for partial 
change";
+                               return issue;
+                       } catch (RuntimeException | NoUpdateException e) {
+                               log.info("failed to start run " + runName, e);
+                               throw e;
+                       }
+               } else {
+                       w.setStatus(s);
+                       return "";
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunStdout(String runName) throws UnknownRunException {
+               try {
+                       return support.getProperty(runName, "io", "stdout");
+               } catch (NoListenerException e) {
+                       return "";
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunStderr(String runName) throws UnknownRunException {
+               try {
+                       return support.getProperty(runName, "io", "stderr");
+               } catch (NoListenerException e) {
+                       return "";
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public JobUsageRecord getRunUsageRecord(String runName)
+                       throws UnknownRunException {
+               try {
+                       String ur = support.getProperty(runName, "io", 
"usageRecord");
+                       if (ur.isEmpty())
+                               return null;
+                       return JobUsageRecord.unmarshal(ur);
+               } catch (NoListenerException e) {
+                       return null;
+               } catch (JAXBException e) {
+                       log.info("failed to deserialize non-empty usage 
record", e);
+                       return null;
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunLog(String runName) throws UnknownRunException {
+               try {
+                       return 
support.getLogs(support.getRun(runName)).get("UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       log.warn("unexpected encoding problem", e);
+                       return "";
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public FileContents getRunBundle(String runName)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               File f = fileUtils.getFile(support.getRun(runName), 
PROV_BUNDLE);
+               FileContents fc = new FileContents();
+               // We *know* the content type, by definition
+               fc.setFile(f, "application/vnd.wf4ever.robundle+zip");
+               return fc;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public boolean getRunGenerateProvenance(String runName)
+                       throws UnknownRunException {
+               return support.getRun(runName).getGenerateProvenance();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunGenerateProvenance(String runName, boolean generate)
+                       throws UnknownRunException, NoUpdateException {
+               TavernaRun run = support.getRun(runName);
+               support.permitUpdate(run);
+               run.setGenerateProvenance(generate);
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // SOAP INTERFACE - Security
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunOwner(String runName) throws UnknownRunException {
+               return support.getRun(runName).getSecurityContext().getOwner()
+                               .getName();
+       }
+
+       /**
+        * Look up a security context, applying access control rules for access 
to
+        * the parts of the context that are only open to the owner.
+        * 
+        * @param runName
+        *            The name of the workflow run.
+        * @param initialOnly
+        *            Whether to check if we're in the initial state.
+        * @return The security context. Never <tt>null</tt>.
+        * @throws UnknownRunException
+        * @throws NotOwnerException
+        * @throws BadStateChangeException
+        */
+       private TavernaSecurityContext getRunSecurityContext(String runName,
+                       boolean initialOnly) throws UnknownRunException, 
NotOwnerException,
+                       BadStateChangeException {
+               TavernaRun run = support.getRun(runName);
+               TavernaSecurityContext c = run.getSecurityContext();
+               if (!c.getOwner().equals(support.getPrincipal()))
+                       throw new NotOwnerException();
+               if (initialOnly && run.getStatus() != Initialized)
+                       throw new BadStateChangeException();
+               return c;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Credential[] getRunCredentials(String runName)
+                       throws UnknownRunException, NotOwnerException {
+               try {
+                       return getRunSecurityContext(runName, 
false).getCredentials();
+               } catch (BadStateChangeException e) {
+                       Error e2 = new Error("impossible");
+                       e2.initCause(e);
+                       throw e2;
+               }
+       }
+
+       private Credential findCredential(TavernaSecurityContext c, String id)
+                       throws NoCredentialException {
+               for (Credential t : c.getCredentials())
+                       if (t.id.equals(id))
+                               return t;
+               throw new NoCredentialException();
+       }
+
+       private Trust findTrust(TavernaSecurityContext c, String id)
+                       throws NoCredentialException {
+               for (Trust t : c.getTrusted())
+                       if (t.id.equals(id))
+                               return t;
+               throw new NoCredentialException();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String setRunCredential(String runName, String credentialID,
+                       Credential credential) throws UnknownRunException,
+                       NotOwnerException, InvalidCredentialException,
+                       NoCredentialException, BadStateChangeException {
+               TavernaSecurityContext c = getRunSecurityContext(runName, true);
+               if (credentialID == null || credentialID.isEmpty()) {
+                       credential.id = randomUUID().toString();
+               } else {
+                       credential.id = findCredential(c, credentialID).id;
+               }
+               URI uri = 
getRunUriBuilder().path("security/credentials/{credid}")
+                               .build(runName, credential.id);
+               credential.href = uri.toString();
+               c.validateCredential(credential);
+               c.addCredential(credential);
+               return credential.id;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void deleteRunCredential(String runName, String credentialID)
+                       throws UnknownRunException, NotOwnerException,
+                       NoCredentialException, BadStateChangeException {
+               getRunSecurityContext(runName, true).deleteCredential(
+                               new Credential.Dummy(credentialID));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Trust[] getRunCertificates(String runName)
+                       throws UnknownRunException, NotOwnerException {
+               try {
+                       return getRunSecurityContext(runName, 
false).getTrusted();
+               } catch (BadStateChangeException e) {
+                       Error e2 = new Error("impossible");
+                       e2.initCause(e);
+                       throw e2;
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String setRunCertificates(String runName, String certificateID,
+                       Trust certificate) throws UnknownRunException, 
NotOwnerException,
+                       InvalidCredentialException, NoCredentialException,
+                       BadStateChangeException {
+               TavernaSecurityContext c = getRunSecurityContext(runName, true);
+               if (certificateID == null || certificateID.isEmpty()) {
+                       certificate.id = randomUUID().toString();
+               } else {
+                       certificate.id = findTrust(c, certificateID).id;
+               }
+               URI uri = 
getRunUriBuilder().path("security/trusts/{certid}").build(
+                               runName, certificate.id);
+               certificate.href = uri.toString();
+               c.validateTrusted(certificate);
+               c.addTrusted(certificate);
+               return certificate.id;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void deleteRunCertificates(String runName, String certificateID)
+                       throws UnknownRunException, NotOwnerException,
+                       NoCredentialException, BadStateChangeException {
+               TavernaSecurityContext c = getRunSecurityContext(runName, true);
+               Trust toDelete = new Trust();
+               toDelete.id = certificateID;
+               c.deleteTrusted(toDelete);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public PermissionList listRunPermissions(String runName)
+                       throws UnknownRunException, NotOwnerException {
+               PermissionList pl = new PermissionList();
+               pl.permission = new ArrayList<>();
+               Map<String, Permission> perm;
+               try {
+                       perm = 
support.getPermissionMap(getRunSecurityContext(runName,
+                                       false));
+               } catch (BadStateChangeException e) {
+                       log.error("unexpected error from internal API", e);
+                       perm = emptyMap();
+               }
+               List<String> users = new ArrayList<>(perm.keySet());
+               sort(users);
+               for (String user : users)
+                       pl.permission.add(new 
PermissionList.SinglePermissionMapping(user,
+                                       perm.get(user)));
+               return pl;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunPermission(String runName, String userName,
+                       Permission permission) throws UnknownRunException,
+                       NotOwnerException {
+               try {
+                       support.setPermission(getRunSecurityContext(runName, 
false),
+                                       userName, permission);
+               } catch (BadStateChangeException e) {
+                       log.error("unexpected error from internal API", e);
+               }
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // SOAP INTERFACE - Filesystem connection
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public OutputDescription getRunOutputDescription(String runName)
+                       throws UnknownRunException, BadStateChangeException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun run = support.getRun(runName);
+               if (run.getStatus() == Initialized)
+                       throw new BadStateChangeException(
+                                       "may not get output description in 
initial state");
+               return cdBuilder.makeOutputDescriptor(run, null);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public DirEntry[] getRunDirectoryContents(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               List<DirEntry> result = new ArrayList<>();
+               for (DirectoryEntry e : 
fileUtils.getDirectory(support.getRun(runName),
+                               convert(d)).getContents())
+                       result.add(convert(newInstance(null, e)));
+               return result.toArray(new DirEntry[result.size()]);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public byte[] getRunDirectoryAsZip(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               try {
+                       return 
toByteArray(fileUtils.getDirectory(support.getRun(runName),
+                                       convert(d)).getContentsAsZip());
+               } catch (IOException e) {
+                       throw new FilesystemAccessException("problem 
serializing ZIP data",
+                                       e);
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public ZippedDirectory getRunDirectoryAsZipMTOM(String runName, 
DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               return new ZippedDirectory(fileUtils.getDirectory(
+                               support.getRun(runName), convert(d)));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public DirEntry makeRunDirectory(String runName, DirEntry parent,
+                       String name) throws UnknownRunException, 
NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               Directory dir = fileUtils.getDirectory(w, convert(parent))
+                               .makeSubdirectory(support.getPrincipal(), name);
+               return convert(newInstance(null, dir));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public DirEntry makeRunFile(String runName, DirEntry parent, String 
name)
+                       throws UnknownRunException, NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               File f = fileUtils.getDirectory(w, 
convert(parent)).makeEmptyFile(
+                               support.getPrincipal(), name);
+               return convert(newInstance(null, f));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void destroyRunDirectoryEntry(String runName, DirEntry d)
+                       throws UnknownRunException, NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               fileUtils.getDirEntry(w, convert(d)).destroy();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public byte[] getRunFileContents(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               File f = fileUtils.getFile(support.getRun(runName), convert(d));
+               return f.getContents(0, -1);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunFileContents(String runName, DirEntry d,
+                       byte[] newContents) throws UnknownRunException, 
NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               fileUtils.getFile(w, convert(d)).setContents(newContents);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public FileContents getRunFileContentsMTOM(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               File f = fileUtils.getFile(support.getRun(runName), convert(d));
+               FileContents fc = new FileContents();
+               fc.setFile(f, support.getEstimatedContentType(f));
+               return fc;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunFileContentsFromURI(String runName,
+                       DirEntryReference file, URI reference)
+                       throws UnknownRunException, NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun run = support.getRun(runName);
+               support.permitUpdate(run);
+               File f = fileUtils.getFile(run, file);
+               try {
+                       support.copyDataToFile(reference, f);
+               } catch (IOException e) {
+                       throw new FilesystemAccessException(
+                                       "problem transferring data from URI", 
e);
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunFileContentsMTOM(String runName, FileContents 
newContents)
+                       throws UnknownRunException, NoUpdateException,
+                       FilesystemAccessException, NoDirectoryEntryException {
+               TavernaRun run = support.getRun(runName);
+               support.permitUpdate(run);
+               File f = fileUtils.getFile(run, newContents.name);
+               f.setContents(new byte[0]);
+               support.copyDataToFile(newContents.fileData, f);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunFileType(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               return support.getEstimatedContentType(fileUtils.getFile(
+                               support.getRun(runName), convert(d)));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public long getRunFileLength(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               return fileUtils.getFile(support.getRun(runName), 
convert(d)).getSize();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public Date getRunFileModified(String runName, DirEntry d)
+                       throws UnknownRunException, FilesystemAccessException,
+                       NoDirectoryEntryException {
+               return fileUtils.getFile(support.getRun(runName), convert(d))
+                               .getModificationDate();
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // SOAP INTERFACE - Run listeners
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String[] getRunListeners(String runName) throws 
UnknownRunException {
+               TavernaRun w = support.getRun(runName);
+               List<String> result = new ArrayList<>();
+               for (Listener l : w.getListeners())
+                       result.add(l.getName());
+               return result.toArray(new String[result.size()]);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String addRunListener(String runName, String listenerType,
+                       String configuration) throws UnknownRunException,
+                       NoUpdateException, NoListenerException {
+               return support.makeListener(support.getRun(runName), 
listenerType,
+                               configuration).getName();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunListenerConfiguration(String runName,
+                       String listenerName) throws UnknownRunException,
+                       NoListenerException {
+               return support.getListener(runName, 
listenerName).getConfiguration();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String[] getRunListenerProperties(String runName, String 
listenerName)
+                       throws UnknownRunException, NoListenerException {
+               return support.getListener(runName, 
listenerName).listProperties()
+                               .clone();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunListenerProperty(String runName, String 
listenerName,
+                       String propName) throws UnknownRunException, 
NoListenerException {
+               return support.getListener(runName, 
listenerName).getProperty(propName);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunListenerProperty(String runName, String listenerName,
+                       String propName, String value) throws 
UnknownRunException,
+                       NoUpdateException, NoListenerException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               Listener l = support.getListener(w, listenerName);
+               try {
+                       l.getProperty(propName); // sanity check!
+                       l.setProperty(propName, value);
+               } catch (RuntimeException e) {
+                       throw new NoListenerException("problem setting 
property: "
+                                       + e.getMessage(), e);
+               }
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public InputDescription getRunInputs(String runName)
+                       throws UnknownRunException {
+               return new InputDescription(support.getRun(runName));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getRunOutputBaclavaFile(String runName)
+                       throws UnknownRunException {
+               return support.getRun(runName).getOutputBaclavaFile();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunInputBaclavaFile(String runName, String fileName)
+                       throws UnknownRunException, NoUpdateException,
+                       FilesystemAccessException, BadStateChangeException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               w.setInputBaclavaFile(fileName);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunInputPortFile(String runName, String portName,
+                       String portFilename) throws UnknownRunException, 
NoUpdateException,
+                       FilesystemAccessException, BadStateChangeException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               Input i = support.getInput(w, portName);
+               if (i == null)
+                       i = w.makeInput(portName);
+               i.setFile(portFilename);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunInputPortValue(String runName, String portName,
+                       String portValue) throws UnknownRunException, 
NoUpdateException,
+                       BadStateChangeException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               Input i = support.getInput(w, portName);
+               if (i == null)
+                       i = w.makeInput(portName);
+               i.setValue(portValue);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunInputPortListDelimiter(String runName, String 
portName,
+                       String delimiter) throws UnknownRunException, 
NoUpdateException,
+                       BadStateChangeException, BadPropertyValueException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               Input i = support.getInput(w, portName);
+               if (i == null)
+                       i = w.makeInput(portName);
+               if (delimiter != null && delimiter.isEmpty())
+                       delimiter = null;
+               if (delimiter != null) {
+                       if (delimiter.length() > 1)
+                               throw new BadPropertyValueException("delimiter 
too long");
+                       if (delimiter.charAt(0) < 1 || delimiter.charAt(0) > 
127)
+                               throw new BadPropertyValueException(
+                                               "delimiter character must be 
non-NUL ASCII");
+               }
+               i.setDelimiter(delimiter);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public void setRunOutputBaclavaFile(String runName, String outputFile)
+                       throws UnknownRunException, NoUpdateException,
+                       FilesystemAccessException, BadStateChangeException {
+               TavernaRun w = support.getRun(runName);
+               support.permitUpdate(w);
+               w.setOutputBaclavaFile(outputFile);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public org.taverna.server.port_description.InputDescription 
getRunInputDescriptor(
+                       String runName) throws UnknownRunException {
+               return cdBuilder.makeInputDescriptor(support.getRun(runName), 
null);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       @RolesAllowed(USER)
+       public String getServerStatus() {
+               return support.getAllowNewWorkflowRuns() ? "operational" : 
"suspended";
+       }
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+       // SUPPORT METHODS
+
+       @Override
+       public boolean initObsoleteSOAPSecurity(TavernaSecurityContext c) {
+               try {
+                       javax.xml.ws.handler.MessageContext msgCtxt = (jaxws == 
null ? null
+                                       : jaxws.getMessageContext());
+                       if (msgCtxt == null)
+                               return true;
+                       c.initializeSecurityFromSOAPContext(msgCtxt);
+                       return false;
+               } catch (IllegalStateException e) {
+                       /* ignore; not much we can do */
+                       return true;
+               }
+       }
+
+       @Override
+       public boolean initObsoleteRESTSecurity(TavernaSecurityContext c) {
+               if (jaxrsHeaders == null)
+                       return true;
+               c.initializeSecurityFromRESTContext(jaxrsHeaders);
+               return false;
+       }
+
+       /**
+        * A creator of substitute {@link URI} builders.
+        * 
+        * @return A URI builder configured so that it takes a path parameter 
that
+        *         corresponds to the run ID (but with no such ID applied).
+        */
+       UriBuilder getRunUriBuilder() {
+               return getBaseUriBuilder().path("runs/{uuid}");
+       }
+
+       @Override
+       public UriBuilder getRunUriBuilder(TavernaRun run) {
+               return fromUri(getRunUriBuilder().build(run.getId()));
+       }
+
+       private final String DEFAULT_HOST = "localhost:8080"; // Crappy default
+
+       private String getHostLocation() {
+               @java.lang.SuppressWarnings("unchecked")
+               Map<String, List<String>> headers = (Map<String, List<String>>) 
jaxws
+                               .getMessageContext().get(HTTP_REQUEST_HEADERS);
+               if (headers != null) {
+                       List<String> host = headers.get("HOST");
+                       if (host != null && !host.isEmpty())
+                               return host.get(0);
+               }
+               return DEFAULT_HOST;
+       }
+
+       @Nonnull
+       private URI getPossiblyInsecureBaseUri() {
+               // See if JAX-RS can supply the info
+               UriInfo ui = getUriInfo();
+               if (ui != null && ui.getBaseUri() != null)
+                       return ui.getBaseUri();
+               // See if JAX-WS *cannot* supply the info
+               if (jaxws == null || jaxws.getMessageContext() == null)
+                       // Hack to make the test suite work
+                       return URI.create("http://"; + DEFAULT_HOST
+                                       + "/taverna-server/rest/");
+               String pathInfo = (String) 
jaxws.getMessageContext().get(PATH_INFO);
+               pathInfo = pathInfo.replaceFirst("/soap$", "/rest/");
+               pathInfo = pathInfo.replaceFirst("/rest/.+$", "/rest/");
+               return URI.create("http://"; + getHostLocation() + pathInfo);
+       }
+
+       @Override
+       public UriBuilder getBaseUriBuilder() {
+               return secure(fromUri(getPossiblyInsecureBaseUri()));
+       }
+
+       @Override
+       @Nullable
+       public String resolve(@Nullable String uri) {
+               if (uri == null)
+                       return null;
+               return secure(getPossiblyInsecureBaseUri(), uri).toString();
+       }
+
+       private Map<String, TavernaRun> runs() {
+               return runStore.listRuns(support.getPrincipal(), policy);
+       }
+}
+
+/**
+ * RESTful interface to the policies of a Taverna Server installation.
+ * 
+ * @author Donal Fellows
+ */
+class PolicyREST implements PolicyView, SupportAware {
+       private TavernaServerSupport support;
+       private Policy policy;
+       private ListenerFactory listenerFactory;
+       private NotificationEngine notificationEngine;
+
+       @Override
+       public void setSupport(TavernaServerSupport support) {
+               this.support = support;
+       }
+
+       @Required
+       public void setPolicy(Policy policy) {
+               this.policy = policy;
+       }
+
+       @Required
+       public void setListenerFactory(ListenerFactory listenerFactory) {
+               this.listenerFactory = listenerFactory;
+       }
+
+       @Required
+       public void setNotificationEngine(NotificationEngine 
notificationEngine) {
+               this.notificationEngine = notificationEngine;
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public PolicyDescription getDescription(UriInfo ui) {
+               return new PolicyDescription(ui);
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public int getMaxSimultaneousRuns() {
+               Integer limit = policy.getMaxRuns(support.getPrincipal());
+               if (limit == null)
+                       return policy.getMaxRuns();
+               return min(limit.intValue(), policy.getMaxRuns());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public PermittedListeners getPermittedListeners() {
+               return new PermittedListeners(
+                               listenerFactory.getSupportedListenerTypes());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public PermittedWorkflows getPermittedWorkflows() {
+               return new 
PermittedWorkflows(policy.listPermittedWorkflowURIs(support
+                               .getPrincipal()));
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public EnabledNotificationFabrics getEnabledNotifiers() {
+               return new EnabledNotificationFabrics(
+                               notificationEngine.listAvailableDispatchers());
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public int getMaxOperatingRuns() {
+               return policy.getOperatingLimit();
+       }
+
+       @Override
+       @CallCounted
+       @PerfLogged
+       public CapabilityList getCapabilities() {
+               CapabilityList cl = new CapabilityList();
+               cl.capability.addAll(support.getCapabilities());
+               return cl;
+       }
+}

Reply via email to