http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServerSupport.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServerSupport.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServerSupport.java
new file mode 100644
index 0000000..533acf5
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/TavernaServerSupport.java
@@ -0,0 +1,970 @@
+/*
+ */
+package org.taverna.server.master;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static eu.medsea.util.MimeUtil.UNKNOWN_MIME_TYPE;
+import static eu.medsea.util.MimeUtil.getExtensionMimeTypes;
+import static eu.medsea.util.MimeUtil.getMimeType;
+import static java.lang.Math.min;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.springframework.jmx.support.MetricType.COUNTER;
+import static org.springframework.jmx.support.MetricType.GAUGE;
+import static org.taverna.server.master.TavernaServer.JMX_ROOT;
+import static org.taverna.server.master.common.Roles.ADMIN;
+import static 
org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.activation.DataHandler;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.PreDestroy;
+import javax.ws.rs.WebApplicationException;
+import javax.xml.bind.JAXBException;
+
+import org.apache.commons.logging.Log;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedMetric;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.taverna.server.master.api.ManagementModel;
+import org.taverna.server.master.api.TavernaServerBean;
+import org.taverna.server.master.common.Capability;
+import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.ProfileList;
+import org.taverna.server.master.common.VersionedElement;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.common.version.Version;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoCreateException;
+import org.taverna.server.master.exceptions.NoDestroyException;
+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.UnknownRunException;
+import org.taverna.server.master.factories.ListenerFactory;
+import org.taverna.server.master.factories.RunFactory;
+import 
org.taverna.server.master.identity.WorkflowInternalAuthProvider.WorkflowSelfAuthority;
+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.LocalIdentityMapper;
+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.rest.handler.T2FlowDocumentHandler;
+import org.taverna.server.master.utils.CapabilityLister;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.InvocationCounter;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+import org.apache.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Web application support utilities.
+ * 
+ * @author Donal Fellows
+ */
+@ManagedResource(objectName = JMX_ROOT + "Webapp", description = "The main 
Taverna Server "
+               + Version.JAVA + " web-application interface.")
+public class TavernaServerSupport {
+       /** The main webapp log. */
+       private Log log = getLog("Taverna.Server.Webapp");
+       private Log accessLog = getLog("Taverna.Server.Webapp.Access");;
+       /** Bean used to log counts of external calls. */
+       private InvocationCounter counter;
+       /** A storage facility for workflow runs. */
+       private RunStore runStore;
+       /** Encapsulates the policies applied by this server. */
+       private Policy policy;
+       /** Connection to the persistent state of this service. */
+       private ManagementModel stateModel;
+       /** A factory for event listeners to attach to workflow runs. */
+       private ListenerFactory listenerFactory;
+       /** A factory for workflow runs. */
+       private RunFactory runFactory;
+       /** How to map the user ID to who to run as. */
+       private LocalIdentityMapper idMapper;
+       /** The code that is coupled to CXF. */
+       private TavernaServerBean webapp;
+       /** How to handle files. */
+       private FilenameUtils fileUtils;
+       /** How to get the server capabilities. */
+       private CapabilityLister capabilitySource;
+       /**
+        * Whether to log failures during principal retrieval. Should be 
normally on
+        * as it indicates a serious problem, but can be switched off for 
testing.
+        */
+       private boolean logGetPrincipalFailures = true;
+       private Map<String, String> contentTypeMap;
+       /** Number of bytes to read when guessing the MIME type. */
+       private static final int SAMPLE_SIZE = 1024;
+       /** Number of bytes to ask for when copying a stream to a file. */
+       private static final int TRANSFER_SIZE = 32768;
+
+       @PreDestroy
+       void closeLog() {
+               log = null;
+       }
+
+       /**
+        * @return Count of the number of external calls into this webapp.
+        */
+       @ManagedMetric(description = "Count of the number of external calls 
into this webapp.", metricType = COUNTER, category = "throughput")
+       public int getInvocationCount() {
+               return counter.getCount();
+       }
+
+       /**
+        * @return Current number of runs.
+        */
+       @ManagedMetric(description = "Current number of runs.", metricType = 
GAUGE, category = "utilization")
+       public int getCurrentRunCount() {
+               return runStore.listRuns(null, policy).size();
+       }
+
+       /**
+        * @return Whether to write submitted workflows to the log.
+        */
+       @ManagedAttribute(description = "Whether to write submitted workflows 
to the log.")
+       public boolean getLogIncomingWorkflows() {
+               return stateModel.getLogIncomingWorkflows();
+       }
+
+       /**
+        * @param logIncomingWorkflows
+        *            Whether to write submitted workflows to the log.
+        */
+       @ManagedAttribute(description = "Whether to write submitted workflows 
to the log.")
+       public void setLogIncomingWorkflows(boolean logIncomingWorkflows) {
+               stateModel.setLogIncomingWorkflows(logIncomingWorkflows);
+       }
+
+       /**
+        * @return Whether outgoing exceptions should be logged before being
+        *         converted to responses.
+        */
+       @ManagedAttribute(description = "Whether outgoing exceptions should be 
logged before being converted to responses.")
+       public boolean getLogOutgoingExceptions() {
+               return stateModel.getLogOutgoingExceptions();
+       }
+
+       /**
+        * @param logOutgoing
+        *            Whether outgoing exceptions should be logged before being
+        *            converted to responses.
+        */
+       @ManagedAttribute(description = "Whether outgoing exceptions should be 
logged before being converted to responses.")
+       public void setLogOutgoingExceptions(boolean logOutgoing) {
+               stateModel.setLogOutgoingExceptions(logOutgoing);
+       }
+
+       /**
+        * @return Whether to permit any new workflow runs to be created.
+        */
+       @ManagedAttribute(description = "Whether to permit any new workflow 
runs to be created; has no effect on existing runs.")
+       public boolean getAllowNewWorkflowRuns() {
+               return stateModel.getAllowNewWorkflowRuns();
+       }
+
+       /**
+        * @param allowNewWorkflowRuns
+        *            Whether to permit any new workflow runs to be created.
+        */
+       @ManagedAttribute(description = "Whether to permit any new workflow 
runs to be created; has no effect on existing runs.")
+       public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) {
+               stateModel.setAllowNewWorkflowRuns(allowNewWorkflowRuns);
+       }
+
+       /**
+        * @return The server's version identifier.
+        */
+       @ManagedAttribute(description = "The installed version of the server.")
+       public String getServerVersion() {
+               return VersionedElement.VERSION + " " + 
VersionedElement.REVISION + " "
+                               + VersionedElement.TIMESTAMP;
+       }
+
+       @ManagedAttribute(description = "The URIs of the workfows that this 
server will allow to be instantiated.")
+       public URI[] getPermittedWorkflowURIs() {
+               List<URI> pw = policy.listPermittedWorkflowURIs(null);
+               if (pw == null)
+                       return new URI[0];
+               return pw.toArray(new URI[pw.size()]);
+       }
+
+       @ManagedAttribute(description = "The URIs of the workfows that this 
server will allow to be instantiated.")
+       public void setPermittedWorkflowURIs(URI[] pw) {
+               if (pw == null)
+                       policy.setPermittedWorkflowURIs(null, new 
ArrayList<URI>());
+               else
+                       policy.setPermittedWorkflowURIs(null, 
Arrays.asList(pw));
+       }
+
+       public int getMaxSimultaneousRuns() {
+               Integer limit = policy.getMaxRuns(getPrincipal());
+               if (limit == null)
+                       return policy.getMaxRuns();
+               return min(limit.intValue(), policy.getMaxRuns());
+       }
+
+       @Autowired
+       private T2FlowDocumentHandler t2flowHandler;
+
+       public Workflow getWorkflowDocumentFromURI(URI uri)
+                       throws WebApplicationException, IOException {
+               URLConnection conn = uri.toURL().openConnection();
+               conn.setRequestProperty("Accept", T2FLOW);
+               conn.connect();
+               // Tricky point: we know the reader part of the handler only 
cares
+               // about the stream argument.
+               return t2flowHandler.readFrom(null, null, null, null, null,
+                               conn.getInputStream());
+       }
+
+       public List<String> getListenerTypes() {
+               return listenerFactory.getSupportedListenerTypes();
+       }
+
+       /**
+        * @param policy
+        *            The policy being installed by Spring.
+        */
+       @Required
+       public void setPolicy(Policy policy) {
+               this.policy = policy;
+       }
+
+       /**
+        * @param listenerFactory
+        *            The listener factory being installed by Spring.
+        */
+       @Required
+       public void setListenerFactory(ListenerFactory listenerFactory) {
+               this.listenerFactory = listenerFactory;
+       }
+
+       /**
+        * @param runFactory
+        *            The run factory being installed by Spring.
+        */
+       @Required
+       public void setRunFactory(RunFactory runFactory) {
+               this.runFactory = runFactory;
+       }
+
+       /**
+        * @param runStore
+        *            The run store being installed by Spring.
+        */
+       @Required
+       public void setRunStore(RunStore runStore) {
+               this.runStore = runStore;
+       }
+
+       /**
+        * @param stateModel
+        *            The state model engine being installed by Spring.
+        */
+       @Required
+       public void setStateModel(ManagementModel stateModel) {
+               this.stateModel = stateModel;
+       }
+
+       /**
+        * @param mapper
+        *            The identity mapper being installed by Spring.
+        */
+       @Required
+       public void setIdMapper(LocalIdentityMapper mapper) {
+               this.idMapper = mapper;
+       }
+
+       /**
+        * @param counter
+        *            The object whose job it is to manage the counting of
+        *            invocations. Installed by Spring.
+        */
+       @Required
+       public void setInvocationCounter(InvocationCounter counter) {
+               this.counter = counter;
+       }
+
+       /**
+        * @param webapp
+        *            The web-app being installed by Spring.
+        */
+       @Required
+       public void setWebapp(TavernaServerBean webapp) {
+               this.webapp = webapp;
+       }
+
+       /**
+        * @param fileUtils
+        *            The file handling utilities.
+        */
+       @Required
+       public void setFileUtils(FilenameUtils fileUtils) {
+               this.fileUtils = fileUtils;
+       }
+
+       /**
+        * @param logthem
+        *            Whether to log failures relating to principals.
+        */
+       public void setLogGetPrincipalFailures(boolean logthem) {
+               logGetPrincipalFailures = logthem;
+       }
+
+       public Map<String, String> getContentTypeMap() {
+               return contentTypeMap;
+       }
+
+       /**
+        * Mapping from filename suffixes (e.g., "baclava") to content types.
+        * 
+        * @param contentTypeMap
+        *            The mapping to install.
+        */
+       @Required
+       public void setContentTypeMap(Map<String, String> contentTypeMap) {
+               this.contentTypeMap = contentTypeMap;
+       }
+
+       @Required
+       public void setCapabilitySource(CapabilityLister capabilitySource) {
+               this.capabilitySource = capabilitySource;
+       }
+
+       /**
+        * Test whether the current user can do updates to the given run.
+        * 
+        * @param run
+        *            The workflow run to do the test on.
+        * @throws NoUpdateException
+        *             If the current user is not permitted to update the run.
+        */
+       public void permitUpdate(@Nonnull TavernaRun run) throws 
NoUpdateException {
+               if (isSuperUser()) {
+                       accessLog
+                                       .warn("check for admin powers passed; 
elevated access rights granted for update");
+                       return; // Superusers are fully authorized to access 
others things
+               }
+               if (getSelfAuthority() != null) {
+                       // At this point, must already be accessing self as 
that is checked
+                       // in getRun().
+                       return;
+               }
+               policy.permitUpdate(getPrincipal(), run);
+       }
+
+       /**
+        * Test whether the current user can destroy or control the lifespan of 
the
+        * given run.
+        * 
+        * @param run
+        *            The workflow run to do the test on.
+        * @throws NoDestroyException
+        *             If the current user is not permitted to destroy the run.
+        */
+       public void permitDestroy(TavernaRun run) throws NoDestroyException {
+               if (isSuperUser()) {
+                       accessLog
+                                       .warn("check for admin powers passed; 
elevated access rights granted for destroy");
+                       return; // Superusers are fully authorized to access 
others things
+               }
+               if (getSelfAuthority() != null)
+                       throw new NoDestroyException();
+               policy.permitDestroy(getPrincipal(), run);
+       }
+
+       /**
+        * Gets the identity of the user currently accessing the webapp, which 
is
+        * stored in a thread-safe way in the webapp's container's context.
+        * 
+        * @return The identity of the user accessing the webapp.
+        */
+       @Nonnull
+       public UsernamePrincipal getPrincipal() {
+               try {
+                       Authentication auth = SecurityContextHolder.getContext()
+                                       .getAuthentication();
+                       if (auth == null || !auth.isAuthenticated()) {
+                               if (logGetPrincipalFailures)
+                                       log.warn("failed to get auth; going 
with <NOBODY>");
+                               return new UsernamePrincipal("<NOBODY>");
+                       }
+                       return new UsernamePrincipal(auth);
+               } catch (RuntimeException e) {
+                       if (logGetPrincipalFailures)
+                               log.info("failed to map principal", e);
+                       throw e;
+               }
+       }
+
+       private WorkflowSelfAuthority getSelfAuthority() {
+               try {
+                       Authentication a = SecurityContextHolder.getContext()
+                                       .getAuthentication();
+                       for (GrantedAuthority ga : a.getAuthorities())
+                               if (ga instanceof WorkflowSelfAuthority)
+                                       return (WorkflowSelfAuthority) ga;
+               } catch (RuntimeException e) {
+               }
+               return null;
+       }
+
+       /**
+        * Obtain the workflow run with a particular name.
+        * 
+        * @param name
+        *            The name of the run to look up.
+        * @return A workflow run handle that the current user has at least
+        *         permission to read.
+        * @throws UnknownRunException
+        *             If the workflow run doesn't exist or the current user 
doesn't
+        *             have permission to see it.
+        */
+       @Nonnull
+       public TavernaRun getRun(@Nonnull String name) throws 
UnknownRunException {
+               if (isSuperUser()) {
+                       accessLog
+                                       .info("check for admin powers passed; 
elevated access rights granted for read");
+                       return runStore.getRun(name);
+               }
+               WorkflowSelfAuthority wsa = getSelfAuthority();
+               if (wsa != null) {
+                       if (wsa.getWorkflowID().equals(name))
+                               return runStore.getRun(name);
+                       throw new UnknownRunException();
+               }
+               return runStore.getRun(getPrincipal(), policy, name);
+       }
+
+       /**
+        * Construct a listener attached to the given run.
+        * 
+        * @param run
+        *            The workflow run to attach the listener to.
+        * @param type
+        *            The name of the type of run to create.
+        * @param configuration
+        *            The configuration description to pass into the listener. 
The
+        *            format of this string is up to the listener to define.
+        * @return A handle to the listener which can be used to further 
configure
+        *         any properties.
+        * @throws NoListenerException
+        *             If the listener type is unrecognized or the 
configuration is
+        *             invalid.
+        * @throws NoUpdateException
+        *             If the run does not permit the current user to add 
listeners
+        *             (or perform other types of update).
+        */
+       @Nonnull
+       public Listener makeListener(@Nonnull TavernaRun run, @Nonnull String 
type,
+                       @Nonnull String configuration) throws 
NoListenerException,
+                       NoUpdateException {
+               permitUpdate(run);
+               return listenerFactory.makeListener(run, type, configuration);
+       }
+
+       /**
+        * Obtain a listener that is already attached to a workflow run.
+        * 
+        * @param run
+        *            The workflow run to search.
+        * @param listenerName
+        *            The name of the listener to look up.
+        * @return The listener instance interface.
+        * @throws NoListenerException
+        *             If no listener with that name exists.
+        */
+       @Nonnull
+       public Listener getListener(TavernaRun run, String listenerName)
+                       throws NoListenerException {
+               for (Listener l : run.getListeners())
+                       if (l.getName().equals(listenerName))
+                               return l;
+               throw new NoListenerException();
+       }
+
+       /**
+        * Obtain a property from a listener that is already attached to a 
workflow
+        * run.
+        * 
+        * @param runName
+        *            The ID of the workflow run to search.
+        * @param listenerName
+        *            The name of the listener to look up in.
+        * @param propertyName
+        *            The name of the property to fetch.
+        * @return The property value.
+        * @throws NoListenerException
+        *             If no listener with that name exists, or no property with
+        *             that name exists.
+        * @throws UnknownRunException
+        *             If no run with that name exists.
+        */
+       @Nonnull
+       public String getProperty(String runName, String listenerName,
+                       String propertyName) throws NoListenerException,
+                       UnknownRunException {
+               return getListener(runName, 
listenerName).getProperty(propertyName);
+       }
+
+       /**
+        * Obtain a property from a listener that is already attached to a 
workflow
+        * run.
+        * 
+        * @param run
+        *            The workflow run to search.
+        * @param listenerName
+        *            The name of the listener to look up in.
+        * @param propertyName
+        *            The name of the property to fetch.
+        * @return The property value.
+        * @throws NoListenerException
+        *             If no listener with that name exists, or no property with
+        *             that name exists.
+        */
+       @Nonnull
+       public String getProperty(TavernaRun run, String listenerName,
+                       String propertyName) throws NoListenerException {
+               return getListener(run, listenerName).getProperty(propertyName);
+       }
+
+       /**
+        * Get the permission description for the given user.
+        * 
+        * @param context
+        *            A security context associated with a particular workflow 
run.
+        *            Note that only the owner of a workflow run may get the
+        *            security context in the first place.
+        * @param userName
+        *            The name of the user to look up the permission for.
+        * @return A permission description.
+        */
+       @Nonnull
+       public Permission getPermission(@Nonnull TavernaSecurityContext context,
+                       @Nonnull String userName) {
+               if (context.getPermittedDestroyers().contains(userName))
+                       return Permission.Destroy;
+               if (context.getPermittedUpdaters().contains(userName))
+                       return Permission.Update;
+               if (context.getPermittedReaders().contains(userName))
+                       return Permission.Read;
+               return Permission.None;
+       }
+
+       /**
+        * Set the permissions for the given user.
+        * 
+        * @param context
+        *            A security context associated with a particular workflow 
run.
+        *            Note that only the owner of a workflow run may get the
+        *            security context in the first place.
+        * @param userName
+        *            The name of the user to set the permission for.
+        * @param permission
+        *            The description of the permission to grant. Note that the
+        *            owner of a workflow run always has the equivalent of
+        *            {@link Permission#Destroy}; this is always enforced before
+        *            checking for other permissions.
+        */
+       public void setPermission(TavernaSecurityContext context, String 
userName,
+                       Permission permission) {
+               Set<String> permSet;
+               boolean doRead = false, doWrite = false, doKill = false;
+
+               switch (permission) {
+               case Destroy:
+                       doKill = true;
+               case Update:
+                       doWrite = true;
+               case Read:
+                       doRead = true;
+               default:
+                       break;
+               }
+
+               permSet = context.getPermittedReaders();
+               if (doRead) {
+                       if (!permSet.contains(userName)) {
+                               permSet = new HashSet<>(permSet);
+                               permSet.add(userName);
+                               context.setPermittedReaders(permSet);
+                       }
+               } else if (permSet.contains(userName)) {
+                       permSet = new HashSet<>(permSet);
+                       permSet.remove(userName);
+                       context.setPermittedReaders(permSet);
+               }
+
+               permSet = context.getPermittedUpdaters();
+               if (doWrite) {
+                       if (!permSet.contains(userName)) {
+                               permSet = new HashSet<>(permSet);
+                               permSet.add(userName);
+                               context.setPermittedUpdaters(permSet);
+                       }
+               } else if (permSet.contains(userName)) {
+                       permSet = new HashSet<>(permSet);
+                       permSet.remove(userName);
+                       context.setPermittedUpdaters(permSet);
+               }
+
+               permSet = context.getPermittedDestroyers();
+               if (doKill) {
+                       if (!permSet.contains(userName)) {
+                               permSet = new HashSet<>(permSet);
+                               permSet.add(userName);
+                               context.setPermittedDestroyers(permSet);
+                       }
+               } else if (permSet.contains(userName)) {
+                       permSet = new HashSet<>(permSet);
+                       permSet.remove(userName);
+                       context.setPermittedDestroyers(permSet);
+               }
+       }
+
+       public Map<String, Permission> getPermissionMap(
+                       TavernaSecurityContext context) {
+               Map<String, Permission> perm = new HashMap<>();
+               for (String u : context.getPermittedReaders())
+                       perm.put(u, Permission.Read);
+               for (String u : context.getPermittedUpdaters())
+                       perm.put(u, Permission.Update);
+               for (String u : context.getPermittedDestroyers())
+                       perm.put(u, Permission.Destroy);
+               return perm;
+       }
+
+       /**
+        * Stops a run from being possible to be looked up and destroys it.
+        * 
+        * @param runName
+        *            The name of the run.
+        * @param run
+        *            The workflow run. <i>Must</i> correspond to the name.
+        * @throws NoDestroyException
+        *             If the user is not permitted to destroy the workflow run.
+        * @throws UnknownRunException
+        *             If the run is unknown (e.g., because it is already
+        *             destroyed).
+        */
+       public void unregisterRun(@Nonnull String runName, @Nonnull TavernaRun 
run)
+                       throws NoDestroyException, UnknownRunException {
+               if (run == null)
+                       run = getRun(runName);
+               permitDestroy(run);
+               runStore.unregisterRun(runName);
+               run.destroy();
+       }
+
+       /**
+        * Changes the expiry date of a workflow run. The expiry date is when 
the
+        * workflow run becomes eligible for automated destruction.
+        * 
+        * @param run
+        *            The handle to the workflow run.
+        * @param date
+        *            When the workflow run should be expired.
+        * @return When the workflow run will actually be expired.
+        * @throws NoDestroyException
+        *             If the user is not permitted to destroy the workflow run.
+        *             (Note that lifespan management requires the ability to
+        *             destroy.)
+        */
+       @Nonnull
+       public Date updateExpiry(@Nonnull TavernaRun run, @Nonnull Date date)
+                       throws NoDestroyException {
+               permitDestroy(run);
+               run.setExpiry(date);
+               return run.getExpiry();
+       }
+
+       /**
+        * Manufacture a workflow run instance.
+        * 
+        * @param workflow
+        *            The workflow document (t2flow, scufl2?) to instantiate.
+        * @return The ID of the created workflow run.
+        * @throws NoCreateException
+        *             If the user is not permitted to create workflows.
+        */
+       public String buildWorkflow(Workflow workflow) throws NoCreateException 
{
+               UsernamePrincipal p = getPrincipal();
+               if (getSelfAuthority() != null)
+                       throw new NoCreateException(
+                                       "runs may not create workflows on their 
host server");
+               if (!stateModel.getAllowNewWorkflowRuns())
+                       throw new NoCreateException("run creation not currently 
enabled");
+               try {
+                       if (stateModel.getLogIncomingWorkflows()) {
+                               log.info(workflow.marshal());
+                       }
+               } catch (JAXBException e) {
+                       log.warn("problem when logging workflow", e);
+               }
+
+               // Security checks
+               policy.permitCreate(p, workflow);
+               if (idMapper != null && idMapper.getUsernameForPrincipal(p) == 
null) {
+                       log.error("cannot map principal to local user id");
+                       throw new NoCreateException(
+                                       "failed to map security token to local 
user id");
+               }
+
+               TavernaRun run;
+               try {
+                       run = runFactory.create(p, workflow);
+                       TavernaSecurityContext c = run.getSecurityContext();
+                       
c.initializeSecurityFromContext(SecurityContextHolder.getContext());
+                       /*
+                        * These next pieces of security initialisation are 
(hopefully)
+                        * obsolete now that we use Spring Security, but we 
keep them Just
+                        * In Case.
+                        */
+                       boolean doRESTinit = webapp.initObsoleteSOAPSecurity(c);
+                       if (doRESTinit)
+                               webapp.initObsoleteRESTSecurity(c);
+               } catch (Exception e) {
+                       log.error("failed to build workflow run worker", e);
+                       throw new NoCreateException("failed to build workflow 
run worker");
+               }
+
+               return runStore.registerRun(run);
+       }
+
+       private boolean isSuperUser() {
+               try {
+                       Authentication auth = SecurityContextHolder.getContext()
+                                       .getAuthentication();
+                       if (auth == null || !auth.isAuthenticated())
+                               return false;
+                       UserDetails details = (UserDetails) auth.getPrincipal();
+                       if (log.isDebugEnabled())
+                               log.debug("checking for admin role for user <" 
+ auth.getName()
+                                               + "> in collection " + 
details.getAuthorities());
+                       return details.getAuthorities().contains(ADMIN);
+               } catch (ClassCastException e) {
+                       return false;
+               }
+       }
+
+       /**
+        * Get a particular input to a workflow run.
+        * 
+        * @param run
+        *            The workflow run to search.
+        * @param portName
+        *            The name of the input.
+        * @return The handle of the input, or <tt>null</tt> if no such handle
+        *         exists.
+        */
+       @Nullable
+       public Input getInput(TavernaRun run, String portName) {
+               for (Input i : run.getInputs())
+                       if (i.getName().equals(portName))
+                               return i;
+               return null;
+       }
+
+       /**
+        * Get a listener attached to a run.
+        * 
+        * @param runName
+        *            The name of the run to look up
+        * @param listenerName
+        *            The name of the listener.
+        * @return The handle of the listener.
+        * @throws NoListenerException
+        *             If no such listener exists.
+        * @throws UnknownRunException
+        *             If no such workflow run exists, or if the user does not 
have
+        *             permission to access it.
+        */
+       public Listener getListener(String runName, String listenerName)
+                       throws NoListenerException, UnknownRunException {
+               return getListener(getRun(runName), listenerName);
+       }
+
+       /**
+        * Given a file, produce a guess at its content type. This uses the 
content
+        * type map property, and if that search fails it falls back on the 
Medsea
+        * mime type library.
+        * 
+        * @param f
+        *            The file handle.
+        * @return The content type. If all else fails, produces good old
+        *         "application/octet-stream".
+        */
+       @Nonnull
+       public String getEstimatedContentType(@Nonnull File f) {
+               String name = f.getName();
+               for (int idx = name.indexOf('.'); idx != -1; idx = 
name.indexOf('.',
+                               idx + 1)) {
+                       String mt = contentTypeMap.get(name.substring(idx + 1));
+                       if (mt != null)
+                               return mt;
+               }
+               @Nonnull
+               String type = getExtensionMimeTypes(name);
+               if (!type.equals(UNKNOWN_MIME_TYPE))
+                       return type;
+               try {
+                       return getMimeType(new 
ByteArrayInputStream(f.getContents(0,
+                                       SAMPLE_SIZE)));
+               } catch (FilesystemAccessException e) {
+                       return type;
+               }
+       }
+
+       public void copyDataToFile(DataHandler handler, File file)
+                       throws FilesystemAccessException {
+               try {
+                       copyStreamToFile(handler.getInputStream(), file);
+               } catch (IOException e) {
+                       throw new FilesystemAccessException(
+                                       "problem constructing stream from data 
source", e);
+               }
+       }
+
+       public void copyDataToFile(URI uri, File file)
+                       throws MalformedURLException, FilesystemAccessException,
+                       IOException {
+               copyStreamToFile(uri.toURL().openStream(), file);
+       }
+
+       public void copyStreamToFile(InputStream stream, File file)
+                       throws FilesystemAccessException {
+               String name = file.getFullName();
+               long total = 0;
+               try {
+                       byte[] buffer = new byte[TRANSFER_SIZE];
+                       boolean first = true;
+                       while (true) {
+                               int len = stream.read(buffer);
+                               if (len < 0)
+                                       break;
+                               total += len;
+                               if (log.isDebugEnabled())
+                                       log.debug("read " + len
+                                                       + " bytes from source 
stream (total: " + total
+                                                       + ") bound for " + 
name);
+                               if (len == buffer.length) {
+                                       if (first)
+                                               file.setContents(buffer);
+                                       else
+                                               file.appendContents(buffer);
+                               } else {
+                                       byte[] newBuf = new byte[len];
+                                       System.arraycopy(buffer, 0, newBuf, 0, 
len);
+                                       if (first)
+                                               file.setContents(newBuf);
+                                       else
+                                               file.appendContents(newBuf);
+                               }
+                               first = false;
+                       }
+               } catch (IOException exn) {
+                       throw new FilesystemAccessException("failed to transfer 
bytes", exn);
+               }
+       }
+
+       /**
+        * Build a description of the profiles supported by a workflow. Note 
that we
+        * expect the set of profiles to be fairly small.
+        * 
+        * @param workflow
+        *            The workflow to describe the profiles of.
+        * @return The descriptor (which might be empty).
+        */
+       public ProfileList getProfileDescriptor(Workflow workflow) {
+               ProfileList result = new ProfileList();
+               String main = workflow.getMainProfileName();
+               for (Profile p : workflow.getProfiles()) {
+                       ProfileList.Info i = new ProfileList.Info();
+                       i.name = p.getName();
+                       if (main != null && main.equals(i.name))
+                               i.main = true;
+                       result.profile.add(i);
+               }
+               return result;
+       }
+
+       public boolean getAllowStartWorkflowRuns() {
+               return runFactory.isAllowingRunsToStart();
+       }
+
+       /**
+        * The list of filenames that logs may occupy.
+        */
+       private static final String[] LOGS = { "logs/detail.log.4",
+                       "logs/detail.log.3", "logs/detail.log.2", 
"logs/detail.log.1",
+                       "logs/detail.log" };
+
+       public FileConcatenation getLogs(TavernaRun run) {
+               FileConcatenation fc = new FileConcatenation();
+               for (String name : LOGS) {
+                       try {
+                               fc.add(fileUtils.getFile(run, name));
+                       } catch (FilesystemAccessException | 
NoDirectoryEntryException e) {
+                               // Ignore
+                       }
+               }
+               return fc;
+       }
+
+       @Nonnull
+       public List<Capability> getCapabilities() {
+               return capabilitySource.getCapabilities();
+       }
+
+       static final String PROV_BUNDLE = "out.bundle.zip";
+
+       public FileConcatenation getProv(TavernaRun run) {
+               FileConcatenation fc = new FileConcatenation();
+               try {
+                       fc.add(fileUtils.getFile(run, PROV_BUNDLE));
+               } catch (FilesystemAccessException | NoDirectoryEntryException 
e) {
+                       // Ignore
+               }
+               return fc;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/Admin.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/Admin.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/Admin.java
new file mode 100644
index 0000000..03c0c8b
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/Admin.java
@@ -0,0 +1,1113 @@
+/*
+ */
+package org.taverna.server.master.admin;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.taverna.server.master.admin.Paths.ALLOW_NEW;
+import static org.taverna.server.master.admin.Paths.ARGS;
+import static org.taverna.server.master.admin.Paths.EXEC_WF;
+import static org.taverna.server.master.admin.Paths.EXITCODE;
+import static org.taverna.server.master.admin.Paths.FACTORIES;
+import static org.taverna.server.master.admin.Paths.GEN_PROV;
+import static org.taverna.server.master.admin.Paths.INVOKES;
+import static org.taverna.server.master.admin.Paths.JAR_FORKER;
+import static org.taverna.server.master.admin.Paths.JAR_WORKER;
+import static org.taverna.server.master.admin.Paths.JAVA;
+import static org.taverna.server.master.admin.Paths.LIFE;
+import static org.taverna.server.master.admin.Paths.LOG_EXN;
+import static org.taverna.server.master.admin.Paths.LOG_WFS;
+import static org.taverna.server.master.admin.Paths.OPERATING;
+import static org.taverna.server.master.admin.Paths.OP_LIMIT;
+import static org.taverna.server.master.admin.Paths.PASSFILE;
+import static org.taverna.server.master.admin.Paths.PERM_WF;
+import static org.taverna.server.master.admin.Paths.REG_HOST;
+import static org.taverna.server.master.admin.Paths.REG_JAR;
+import static org.taverna.server.master.admin.Paths.REG_POLL;
+import static org.taverna.server.master.admin.Paths.REG_PORT;
+import static org.taverna.server.master.admin.Paths.REG_WAIT;
+import static org.taverna.server.master.admin.Paths.ROOT;
+import static org.taverna.server.master.admin.Paths.RUNS;
+import static org.taverna.server.master.admin.Paths.RUN_LIMIT;
+import static org.taverna.server.master.admin.Paths.STARTUP;
+import static org.taverna.server.master.admin.Paths.TOTAL_RUNS;
+import static org.taverna.server.master.admin.Paths.URS;
+import static org.taverna.server.master.admin.Paths.UR_FILE;
+import static org.taverna.server.master.admin.Paths.USER;
+import static org.taverna.server.master.admin.Paths.USERS;
+import static org.taverna.server.master.admin.Types.JSON;
+import static org.taverna.server.master.admin.Types.PLAIN;
+import static org.taverna.server.master.admin.Types.XML;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.cxf.jaxrs.model.wadl.Description;
+import org.apache.taverna.server.usagerecord.JobUsageRecord;
+import org.taverna.server.master.common.Uri;
+import org.taverna.server.master.common.VersionedElement;
+
+/**
+ * The administration interface for Taverna Server.
+ * 
+ * @author Donal Fellows
+ */
+@Description("Administration interface for Taverna Server.")
+public interface Admin {
+       /**
+        * Get a simple administration user interface.
+        * 
+        * @return The description document in a response.
+        * @throws IOException
+        */
+       @GET
+       @Path(ROOT)
+       @Produces("text/html")
+       @Nonnull
+       Response getUserInterface() throws IOException;
+
+       /**
+        * Gets support resources for the administration user interface.
+        * 
+        * @param file
+        *            The name of the static resource to provide.
+        * @return The requested document in a response.
+        * @throws IOException
+        */
+       @GET
+       @Path("static/{file}")
+       @Produces("*/*")
+       Response getStaticResource(@PathParam("file") String file)
+                       throws IOException;
+
+       /**
+        * Get a description of the administration interface.
+        * 
+        * @param ui
+        *            What URI was used to access this resource?
+        * @return The description document.
+        */
+       @GET
+       @Path(ROOT)
+       @Produces({ XML, JSON })
+       @Nonnull
+       AdminDescription getDescription(@Context UriInfo ui);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(ROOT)
+       Response optionsRoot();
+
+       /**
+        * Get whether to allow new workflow runs to be created.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(ALLOW_NEW)
+       @Produces(PLAIN)
+       @Description("Whether to allow new workflow runs to be created.")
+       boolean getAllowNew();
+
+       /**
+        * Set whether to allow new workflow runs to be created.
+        * 
+        * @param newValue
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(ALLOW_NEW)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("Whether to allow new workflow runs to be created.")
+       boolean setAllowNew(boolean newValue);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(ALLOW_NEW)
+       @Description("Whether to allow new workflow runs to be created.")
+       Response optionsAllowNew();
+
+       /**
+        * Get whether to log the workflows submitted.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(LOG_WFS)
+       @Produces(PLAIN)
+       @Description("Whether to log the workflows submitted.")
+       boolean getLogWorkflows();
+
+       /**
+        * Set whether to log the workflows submitted.
+        * 
+        * @param logWorkflows
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(LOG_WFS)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("Whether to log the workflows submitted.")
+       boolean setLogWorkflows(boolean logWorkflows);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(LOG_WFS)
+       @Description("Whether to log the workflows submitted.")
+       Response optionsLogWorkflows();
+
+       /**
+        * Get whether to log the user-directed faults.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(LOG_EXN)
+       @Produces(PLAIN)
+       @Description("Whether to log the user-directed faults.")
+       boolean getLogFaults();
+
+       /**
+        * Set whether to log the user-directed faults.
+        * 
+        * @param logFaults
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(LOG_EXN)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("Whether to log the user-directed faults.")
+       boolean setLogFaults(boolean logFaults);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(LOG_EXN)
+       @Description("Whether to log the user-directed faults.")
+       Response optionsLogFaults();
+
+       /**
+        * Get what file to dump usage records to.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(UR_FILE)
+       @Produces(PLAIN)
+       @Description("What file to dump usage records to.")
+       @Nonnull
+       String getURFile();
+
+       /**
+        * Set what file to dump usage records to.
+        * 
+        * @param urFile
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(UR_FILE)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What file to dump usage records to.")
+       @Nonnull
+       String setURFile(@Nonnull String urFile);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(UR_FILE)
+       @Description("What file to dump usage records to.")
+       Response optionsURFile();
+
+       /**
+        * The property for the number of times the service methods have been
+        * invoked.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(INVOKES)
+       @Produces(PLAIN)
+       @Description("How many times have the service methods been invoked.")
+       int invokeCount();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(INVOKES)
+       @Description("How many times have the service methods been invoked.")
+       Response optionsInvokationCount();
+
+       /**
+        * The property for the number of runs that are currently in existence.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(TOTAL_RUNS)
+       @Produces(PLAIN)
+       @Description("How many runs are currently in existence.")
+       int runCount();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(TOTAL_RUNS)
+       @Description("How many runs are currently in existence.")
+       Response optionsRunCount();
+
+       /**
+        * The property for the number of runs that are currently running.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(OPERATING)
+       @Produces(PLAIN)
+       @Description("How many runs are currently actually running.")
+       int operatingCount();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(OPERATING)
+       @Description("How many runs are currently actually running.")
+       Response optionsOperatingCount();
+
+       /**
+        * Get the full pathname of the RMI registry's JAR.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(REG_JAR)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the server's custom RMI 
registry executable JAR file?")
+       @Nonnull
+       String getRegistryJar();
+
+       /**
+        * Set the full pathname of the RMI registry's JAR.
+        * 
+        * @param registryJar
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(REG_JAR)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the server's custom RMI 
registry executable JAR file?")
+       @Nonnull
+       String setRegistryJar(@Nonnull String registryJar);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(REG_JAR)
+       @Description("What is the full pathname of the server's special custom 
RMI registry executable JAR file?")
+       Response optionsRegistryJar();
+
+       /**
+        * Get the location of the RMI registry.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(REG_HOST)
+       @Produces(PLAIN)
+       @Description("Where is the RMI registry?")
+       @Nonnull
+       String getRegistryHost();
+
+       /**
+        * Set the location of the RMI registry.
+        * 
+        * @param registryHost
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(REG_HOST)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("Where is the RMI registry?")
+       @Nonnull
+       String setRegistryHost(@Nonnull String registryHost);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(REG_HOST)
+       @Description("Where is the RMI registry?")
+       Response optionsRegistryHost();
+
+       /**
+        * Get the port of the RMI registry.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(REG_PORT)
+       @Produces(PLAIN)
+       @Description("On what port is the RMI registry?")
+       int getRegistryPort();
+
+       /**
+        * Set the port of the RMI registry.
+        * 
+        * @param registryPort
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(REG_PORT)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("On what port is the RMI registry?")
+       int setRegistryPort(int registryPort);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(REG_PORT)
+       @Description("On what port is the RMI registry?")
+       Response optionsRegistryPort();
+
+       /**
+        * Get the maximum number of simultaneous runs.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(RUN_LIMIT)
+       @Produces(PLAIN)
+       @Description("What is the maximum number of simultaneous runs?")
+       int getRunLimit();
+
+       /**
+        * Set the maximum number of simultaneous runs.
+        * 
+        * @param runLimit
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(RUN_LIMIT)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the maximum number of simultaneous runs?")
+       int setRunLimit(int runLimit);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(RUN_LIMIT)
+       @Description("What is the maximum number of simultaneous runs?")
+       Response optionsRunLimit();
+
+       /**
+        * Get the maximum number of simultaneous executing runs.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(OP_LIMIT)
+       @Produces(PLAIN)
+       @Description("What is the maximum number of simultaneous executing 
runs?")
+       int getOperatingLimit();
+
+       /**
+        * Set the maximum number of simultaneous executing runs.
+        * 
+        * @param operatingLimit
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(OP_LIMIT)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the maximum number of simultaneous executing 
runs?")
+       int setOperatingLimit(int operatingLimit);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(OP_LIMIT)
+       @Description("What is the maximum number of simultaneous executing 
runs?")
+       Response optionsOperatingLimit();
+
+       /**
+        * Get the default lifetime of workflow runs.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(LIFE)
+       @Produces(PLAIN)
+       @Description("What is the default lifetime of workflow runs, in 
seconds?")
+       int getDefaultLifetime();
+
+       /**
+        * Set the default lifetime of workflow runs.
+        * 
+        * @param defaultLifetime
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(LIFE)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the default lifetime of workflow runs, in 
seconds?")
+       int setDefaultLifetime(int defaultLifetime);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(LIFE)
+       @Description("What is the default lifetime of workflow runs, in 
seconds?")
+       Response optionsDefaultLifetime();
+
+       /**
+        * The property for the list of IDs of current runs.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(RUNS)
+       @Produces({ XML, JSON })
+       @Description("List the IDs of all current runs.")
+       StringList currentRuns();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(RUNS)
+       @Description("List the IDs of all current runs.")
+       Response optionsCurrentRuns();
+
+       /**
+        * Get the Java binary to be used for execution of subprocesses.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(JAVA)
+       @Produces(PLAIN)
+       @Description("Which Java binary should be used for execution of 
subprocesses?")
+       @Nonnull
+       String getJavaBinary();
+
+       /**
+        * Set the Java binary to be used for execution of subprocesses.
+        * 
+        * @param javaBinary
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(JAVA)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("Which Java binary should be used for execution of 
subprocesses?")
+       @Nonnull
+       String setJavaBinary(@Nonnull String javaBinary);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(JAVA)
+       @Description("Which Java binary should be used for execution of 
subprocesses?")
+       Response optionsJavaBinary();
+
+       /**
+        * Get the extra arguments to be supplied to Java subprocesses.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(ARGS)
+       @Produces({ XML, JSON })
+       @Description("What extra arguments should be supplied to Java 
subprocesses?")
+       @Nonnull
+       StringList getExtraArguments();
+
+       /**
+        * Set the extra arguments to be supplied to Java subprocesses.
+        * 
+        * @param extraArguments
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(ARGS)
+       @Consumes(XML)
+       @Produces({ XML, JSON })
+       @Description("What extra arguments should be supplied to Java 
subprocesses?")
+       @Nonnull
+       StringList setExtraArguments(@Nonnull StringList extraArguments);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(ARGS)
+       @Description("What extra arguments should be supplied to Java 
subprocesses?")
+       Response optionsExtraArguments();
+
+       /**
+        * Get the full pathname of the worker JAR file.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(JAR_WORKER)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the server's per-user worker 
executable JAR file?")
+       @Nonnull
+       String getServerWorkerJar();
+
+       /**
+        * Set the full pathname of the worker JAR file.
+        * 
+        * @param serverWorkerJar
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(JAR_WORKER)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the server's per-user worker 
executable JAR file?")
+       @Nonnull
+       String setServerWorkerJar(@Nonnull String serverWorkerJar);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(JAR_WORKER)
+       @Description("What is the full pathname of the server's per-user worker 
executable JAR file?")
+       Response optionsServerWorkerJar();
+
+       /**
+        * Get the full pathname of the executeWorkflow.sh file.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(EXEC_WF)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the core Taverna 
executeWorkflow script?")
+       @Nonnull
+       String getExecuteWorkflowScript();
+
+       /**
+        * Set the full pathname of the executeWorkflow.sh file.
+        * 
+        * @param executeWorkflowScript
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(EXEC_WF)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the core Taverna 
executeWorkflow script?")
+       @Nonnull
+       String setExecuteWorkflowScript(@Nonnull String executeWorkflowScript);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(EXEC_WF)
+       @Description("What is the full pathname of the core Taverna 
executeWorkflow script?")
+       Response optionsExecuteWorkflowScript();
+
+       /**
+        * Get the total duration of time to wait for the start of the forker
+        * process.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(REG_WAIT)
+       @Produces(PLAIN)
+       @Description("How long in total should the core wait for registration 
of the \"forker\" process, in seconds.")
+       int getRegistrationWaitSeconds();
+
+       /**
+        * Set the total duration of time to wait for the start of the forker
+        * process.
+        * 
+        * @param registrationWaitSeconds
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(REG_WAIT)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("How long in total should the core wait for registration 
of the \"forker\" process, in seconds.")
+       int setRegistrationWaitSeconds(int registrationWaitSeconds);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(REG_WAIT)
+       @Description("How long in total should the core wait for registration 
of the \"forker\" process, in seconds.")
+       Response optionsRegistrationWaitSeconds();
+
+       /**
+        * Get the interval between checks for registration of the forker 
process.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(REG_POLL)
+       @Produces(PLAIN)
+       @Description("What is the interval between checks for registration of 
the \"forker\" process, in milliseconds.")
+       int getRegistrationPollMillis();
+
+       /**
+        * Set the interval between checks for registration of the forker 
process.
+        * 
+        * @param registrationPollMillis
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(REG_POLL)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the interval between checks for registration of 
the \"forker\" process, in milliseconds.")
+       int setRegistrationPollMillis(int registrationPollMillis);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(REG_POLL)
+       @Description("What is the interval between checks for registration of 
the \"forker\" process, in milliseconds.")
+       Response optionsRegistrationPollMillis();
+
+       /**
+        * Get the full pathname of the file containing the impersonation
+        * credentials for the forker process.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(PASSFILE)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the file containing the 
password used for impersonating other users? (On Unix, this is the password for 
the deployment user to use \"sudo\".)")
+       @Nonnull
+       String getRunasPasswordFile();
+
+       /**
+        * Set the full pathname of the file containing the impersonation
+        * credentials for the forker process.
+        * 
+        * @param runasPasswordFile
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(PASSFILE)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the file containing the 
password used for impersonating other users? (On Unix, this is the password for 
the deployment user to use \"sudo\".)")
+       @Nonnull
+       String setRunasPasswordFile(@Nonnull String runasPasswordFile);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(PASSFILE)
+       @Description("What is the full pathname of the file containing the 
password used for impersonating other users? (On Unix, this is the password for 
the deployment user to use \"sudo\".)")
+       Response optionsRunasPasswordFile();
+
+       /**
+        * Get the full pathname of the forker's JAR.
+        * 
+        * @return The current setting.
+        */
+       @GET
+       @Path(JAR_FORKER)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the server's special 
authorized \"forker\" executable JAR file?")
+       @Nonnull
+       String getServerForkerJar();
+
+       /**
+        * Set the full pathname of the forker's JAR.
+        * 
+        * @param serverForkerJar
+        *            What to set it to.
+        * @return The new setting.
+        */
+       @PUT
+       @Path(JAR_FORKER)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("What is the full pathname of the server's special 
authorized \"forker\" executable JAR file?")
+       @Nonnull
+       String setServerForkerJar(@Nonnull String serverForkerJar);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(JAR_FORKER)
+       @Description("What is the full pathname of the server's special 
authorized \"forker\" executable JAR file?")
+       Response optionsServerForkerJar();
+
+       /**
+        * The property for the length of time it took to start the forker.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(STARTUP)
+       @Produces(PLAIN)
+       @Description("How long did it take for the back-end \"forker\" to set 
itself up, in seconds.")
+       int startupTime();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(STARTUP)
+       @Description("How long did it take for the back-end \"forker\" to set 
itself up, in seconds.")
+       Response optionsStartupTime();
+
+       /**
+        * The property for the last exit code of the forker process.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(EXITCODE)
+       @Produces(PLAIN)
+       @Description("What was the last exit code of the \"forker\"? If null, 
no exit has ever been recorded.")
+       Integer lastExitCode();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(EXITCODE)
+       @Description("What was the last exit code of the \"forker\"? If null, 
no exit has ever been recorded.")
+       Response optionsLastExitCode();
+
+       /**
+        * The property for the mapping of usernames to factory process handles.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(FACTORIES)
+       @Produces({ XML, JSON })
+       @Description("What is the mapping of local usernames to factory process 
RMI IDs?")
+       StringList factoryProcessMapping();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(FACTORIES)
+       @Description("What is the mapping of local usernames to factory process 
RMI IDs?")
+       Response optionsFactoryProcessMapping();
+
+       /**
+        * The property for the list of usage records collected.
+        * 
+        * @return The property value (read-only).
+        */
+       @GET
+       @Path(URS)
+       @Produces(XML)
+       @Description("What is the list of usage records that have been 
collected?")
+       URList usageRecords();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(URS)
+       @Description("What is the list of usage records that have been 
collected?")
+       Response optionsUsageRecords();
+
+       /**
+        * What are the current list of workflow URIs that may be started? Empty
+        * means allow any, including user-supplied workflows.
+        * 
+        * @return List of URIs, encoded as strings.
+        */
+       @GET
+       @Path(PERM_WF)
+       @Produces({ XML, JSON })
+       @Description("What are the current list of workflow URIs that may be 
started? Empty means allow any, including user-supplied workflows.")
+       StringList getPermittedWorkflowURIs();
+
+       /** Do we turn on the generate provenance option by default? */
+       @GET
+       @Path(GEN_PROV)
+       @Produces(PLAIN)
+       @Description("Do we turn on the generate provenance option by default? 
(boolean)")
+       String getGenerateProvenance();
+
+       /** Do we turn on the generate provenance option by default? */
+       @PUT
+       @Path(GEN_PROV)
+       @Consumes(PLAIN)
+       @Produces(PLAIN)
+       @Description("Do we turn on the generate provenance option by default? 
(boolean)")
+       String setGenerateProvenance(String newValue);
+
+       /** Do we turn on the generate provenance option by default? */
+       @OPTIONS
+       @Path(GEN_PROV)
+       @Description("Do we turn on the generate provenance option by default? 
(boolean)")
+       Response optionsGenerateProvenance();
+
+       /**
+        * What are the current list of workflow URIs that may be started? Empty
+        * means allow any, including user-supplied workflows.
+        * 
+        * @param permitted
+        *            List of URIs, encoded as strings.
+        * @return List of URIs, encoded as strings.
+        */
+       @PUT
+       @Path(PERM_WF)
+       @Consumes(XML)
+       @Produces({ XML, JSON })
+       @Description("What are the current list of workflow URIs that may be 
started? Empty means allow any, including user-supplied workflows.")
+       StringList setPermittedWorkflowURIs(@Nonnull StringList permitted);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(PERM_WF)
+       @Description("What are the current list of workflow URIs that may be 
started? Empty means allow any, including user-supplied workflows.")
+       Response optionsPermittedWorkflowURIs();
+
+       @GET
+       @Path(USERS)
+       @Produces({ XML, JSON })
+       @Description("What users are known to the server?")
+       UserList users(@Context UriInfo ui);
+
+       @GET
+       @Path(USER)
+       @Produces({ XML, JSON })
+       @Description("What do we know about a particular user?")
+       UserDesc user(@PathParam("id") String username);
+
+       @POST
+       @Path(USERS)
+       @Consumes(XML)
+       @Description("Create a user.")
+       Response useradd(UserDesc userdesc, @Nonnull @Context UriInfo ui);
+
+       @PUT
+       @Path(USER)
+       @Produces({ XML, JSON })
+       @Consumes(XML)
+       @Description("Update a user.")
+       UserDesc userset(@PathParam("id") String username, UserDesc userdesc);
+
+       @DELETE
+       @Path(USER)
+       @Produces({ XML, JSON })
+       @Description("What do we know about a particular user?")
+       Response userdel(@PathParam("id") String username);
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(USERS)
+       @Description("What users are known to the server?")
+       Response optionsUsers();
+
+       /** What HTTP methods may we use? */
+       @OPTIONS
+       @Path(USER)
+       @Description("What do we know about a particular user?")
+       Response optionsUser(@PathParam("id") String username);
+
+       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+       /**
+        * The description of what properties are supported by the 
administration
+        * interface.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "description")
+       @XmlType(name = "Description")
+       public static class AdminDescription extends VersionedElement {
+               public Uri allowNew;
+               public Uri logWorkflows;
+               public Uri logFaults;
+               public Uri usageRecordDumpFile;
+               public Uri invokationCount;
+               public Uri runCount;
+               public Uri registryHost;
+               public Uri registryPort;
+               public Uri runLimit;
+               public Uri defaultLifetime;
+               public Uri currentRuns;
+               public Uri javaBinary;
+               public Uri extraArguments;
+               public Uri serverWorkerJar;
+               public Uri serverForkerJar;
+               public Uri executeWorkflowScript;
+               public Uri registrationWaitSeconds;
+               public Uri registrationPollMillis;
+               public Uri runasPasswordFile;
+               public Uri startupTime;
+               public Uri lastExitCode;
+               public Uri factoryProcessMapping;
+               public Uri usageRecords;
+               public Uri users;
+               public Uri operatingLimit;
+               public Uri operatingCount;
+               public Uri permittedWorkflowURIs;
+               public Uri generateProvenance;
+
+               public AdminDescription() {
+               }
+
+               public AdminDescription(UriInfo ui) {
+                       allowNew = new Uri(ui, ALLOW_NEW);
+                       logWorkflows = new Uri(ui, LOG_WFS);
+                       logFaults = new Uri(ui, LOG_EXN);
+                       usageRecordDumpFile = new Uri(ui, UR_FILE);
+                       invokationCount = new Uri(ui, INVOKES);
+                       runCount = new Uri(ui, TOTAL_RUNS);
+                       registryHost = new Uri(ui, REG_HOST);
+                       registryPort = new Uri(ui, REG_PORT);
+                       runLimit = new Uri(ui, RUN_LIMIT);
+                       defaultLifetime = new Uri(ui, LIFE);
+                       currentRuns = new Uri(ui, RUNS);
+                       javaBinary = new Uri(ui, JAVA);
+                       extraArguments = new Uri(ui, ARGS);
+                       serverWorkerJar = new Uri(ui, JAR_WORKER);
+                       serverForkerJar = new Uri(ui, JAR_FORKER);
+                       executeWorkflowScript = new Uri(ui, EXEC_WF);
+                       registrationWaitSeconds = new Uri(ui, REG_WAIT);
+                       registrationPollMillis = new Uri(ui, REG_POLL);
+                       runasPasswordFile = new Uri(ui, PASSFILE);
+                       startupTime = new Uri(ui, STARTUP);
+                       lastExitCode = new Uri(ui, EXITCODE);
+                       factoryProcessMapping = new Uri(ui, FACTORIES);
+                       usageRecords = new Uri(ui, URS);
+                       users = new Uri(ui, USERS);
+                       operatingLimit = new Uri(ui, OP_LIMIT);
+                       operatingCount = new Uri(ui, OPERATING);
+                       permittedWorkflowURIs = new Uri(ui, PERM_WF);
+                       generateProvenance = new Uri(ui, GEN_PROV);
+               }
+       }
+
+       /**
+        * A list of strings, as XML.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "stringList")
+       @XmlType(name = "StringList")
+       public static class StringList {
+               @XmlElement
+               public List<String> string = new ArrayList<>();
+       }
+
+       /**
+        * A list of users, as XML.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "userList")
+       @XmlType(name = "UserList")
+       public static class UserList {
+               @XmlElement
+               public List<URI> user = new ArrayList<>();
+       }
+
+       @XmlRootElement(name = "userDesc")
+       @XmlType(name = "UserDesc")
+       public static class UserDesc {
+               @XmlElement
+               public String username;
+               @XmlElement
+               public String password;
+               @XmlElement
+               public String localUserId;
+               @XmlElement
+               public Boolean enabled;
+               @XmlElement
+               public Boolean admin;
+       }
+
+       /**
+        * A list of usage records, as XML.
+        * 
+        * @author Donal Fellows
+        */
+       @XmlRootElement(name = "usageRecordList")
+       @XmlType(name = "UsageRecords")
+       public static class URList {
+               @XmlElement
+               public List<JobUsageRecord> usageRecord;
+       }
+}
+
+interface Paths {
+       static final String ROOT = "/";
+       static final String ALLOW_NEW = "allowNew";
+       static final String LOG_WFS = "logWorkflows";
+       static final String LOG_EXN = "logFaults";
+       static final String UR_FILE = "usageRecordDumpFile";
+       static final String INVOKES = "invokationCount";
+       static final String TOTAL_RUNS = "runCount";
+       static final String OPERATING = "operatingCount";
+       static final String REG_HOST = "registryHost";
+       static final String REG_PORT = "registryPort";
+       static final String REG_JAR = "registryJar";
+       static final String RUN_LIMIT = "runLimit";
+       static final String OP_LIMIT = "operatingLimit";
+       static final String LIFE = "defaultLifetime";
+       static final String RUNS = "currentRuns";
+       static final String JAVA = "javaBinary";
+       static final String ARGS = "extraArguments";
+       static final String JAR_WORKER = "serverWorkerJar";
+       static final String JAR_FORKER = "serverForkerJar";
+       static final String EXEC_WF = "executeWorkflowScript";
+       static final String REG_WAIT = "registrationWaitSeconds";
+       static final String REG_POLL = "registrationPollMillis";
+       static final String PASSFILE = "runasPasswordFile";
+       static final String STARTUP = "startupTime";
+       static final String EXITCODE = "lastExitCode";
+       static final String FACTORIES = "factoryProcessMapping";
+       static final String URS = "usageRecords";
+       static final String PERM_WF = "permittedWorkflowURIs";
+       static final String GEN_PROV = "generateProvenance";
+       static final String USERS = "users";
+       static final String USER = USERS + "/{id}";
+}
+
+interface Types {
+       static final String PLAIN = "text/plain";
+       static final String XML = "application/xml";
+       static final String JSON = "application/json";
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/AdminBean.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/AdminBean.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/AdminBean.java
new file mode 100644
index 0000000..0233cd5
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/AdminBean.java
@@ -0,0 +1,807 @@
+/*
+ */
+package org.taverna.server.master.admin;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.util.Arrays.asList;
+import static java.util.UUID.randomUUID;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+import static org.taverna.server.master.common.Roles.ADMIN;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.ManagementModel;
+import org.taverna.server.master.exceptions.GeneralFailureException;
+import org.taverna.server.master.factories.ConfigurableRunFactory;
+import org.taverna.server.master.identity.User;
+import org.taverna.server.master.identity.UserStoreAPI;
+import org.taverna.server.master.usage.UsageRecordRecorder;
+import org.taverna.server.master.utils.InvocationCounter;
+import org.taverna.server.master.worker.RunDBSupport;
+import org.taverna.server.master.worker.WorkerModel;
+
+/**
+ * The administration interface to Taverna Server.
+ * 
+ * @author Donal Fellows
+ */
+public class AdminBean implements Admin {
+       @Required
+       public void setState(ManagementModel state) {
+               this.state = state;
+       }
+
+       @Required
+       public void setCounter(InvocationCounter counter) {
+               this.counter = counter;
+       }
+
+       @Required
+       public void setRunDB(RunDBSupport runDB) {
+               this.runDB = runDB;
+       }
+
+       @Required
+       public void setFactory(ConfigurableRunFactory factory) {
+               this.factory = factory;
+       }
+
+       @Required
+       public void setUsageRecords(UsageRecordRecorder usageRecords) {
+               this.usageRecords = usageRecords;
+       }
+
+       @Required
+       public void setUserStore(UserStoreAPI userStore) {
+               this.userStore = userStore;
+       }
+
+       @Required
+       public void setLocalWorkerModel(WorkerModel worker) {
+               localWorker = worker;
+       }
+
+       public void setAdminHtmlFile(String filename) {
+               this.adminHtmlFile = filename;
+       }
+
+       public void setResourceRoot(String root) {
+               this.resourceRoot = root;
+       }
+
+       protected byte[] getResource(String name) throws IOException {
+               if (AdminBean.class.getResource(name) == null)
+                       throw new FileNotFoundException(name);
+               return 
IOUtils.toByteArray(AdminBean.class.getResourceAsStream(name));
+       }
+
+       private ManagementModel state;
+       private InvocationCounter counter;
+       private RunDBSupport runDB;
+       private ConfigurableRunFactory factory;
+       private UsageRecordRecorder usageRecords;
+       private UserStoreAPI userStore;
+       private WorkerModel localWorker;
+       private String adminHtmlFile = "/admin.html";
+       private String resourceRoot = "/static/";
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response getUserInterface() throws IOException {
+               return Response.ok(getResource(adminHtmlFile), 
"text/html").build();
+       }
+
+       @Override
+       @RolesAllowed(ADMIN)
+       public Response getStaticResource(String file) throws IOException {
+               if (file.matches("^[-_.a-zA-Z0-9]+$")) {
+                       String type = "application/octet-stream";
+                       if (file.endsWith(".html"))
+                               type = "text/html";
+                       else if (file.endsWith(".js"))
+                               type = "text/javascript";
+                       else if (file.endsWith(".jpg"))
+                               type = "image/jpeg";
+                       else if (file.endsWith(".gif"))
+                               type = "image/gif";
+                       else if (file.endsWith(".png"))
+                               type = "image/png";
+                       else if (file.endsWith(".svg"))
+                               type = "image/svg+xml";
+                       else if (file.endsWith(".css"))
+                               type = "text/css";
+                       try {
+                               return Response.ok(getResource(resourceRoot + 
file), type)
+                                               .build();
+                       } catch (IOException e) {
+                               // ignore; we just treat as 404
+                       }
+               }
+               return Response.status(NOT_FOUND).entity("no such 
resource").build();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public AdminDescription getDescription(UriInfo ui) {
+               return new AdminDescription(ui);
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsRoot() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public boolean getAllowNew() {
+               return state.getAllowNewWorkflowRuns();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public boolean setAllowNew(boolean newValue) {
+               state.setAllowNewWorkflowRuns(newValue);
+               return state.getAllowNewWorkflowRuns();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsAllowNew() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public boolean getLogWorkflows() {
+               return state.getLogIncomingWorkflows();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public boolean setLogWorkflows(boolean newValue) {
+               state.setLogIncomingWorkflows(newValue);
+               return state.getLogIncomingWorkflows();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsLogWorkflows() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public boolean getLogFaults() {
+               return state.getLogOutgoingExceptions();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public boolean setLogFaults(boolean newValue) {
+               state.setLogOutgoingExceptions(newValue);
+               return state.getLogOutgoingExceptions();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsLogFaults() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getURFile() {
+               return state.getUsageRecordLogFile();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setURFile(String newValue) {
+               state.setUsageRecordLogFile(newValue);
+               return state.getUsageRecordLogFile();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsURFile() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int invokeCount() {
+               return counter.getCount();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsInvokationCount() {
+               return opt();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int runCount() {
+               return runDB.countRuns();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsRunCount() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getRegistryHost() {
+               return factory.getRegistryHost();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setRegistryHost(String newValue) {
+               factory.setRegistryHost(newValue);
+               return factory.getRegistryHost();
+       }
+
+       @Override
+       public Response optionsRegistryHost() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int getRegistryPort() {
+               return factory.getRegistryPort();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int setRegistryPort(int newValue) {
+               factory.setRegistryPort(newValue);
+               return factory.getRegistryPort();
+       }
+
+       @Override
+       public Response optionsRegistryPort() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getRegistryJar() {
+               return factory.getRmiRegistryJar();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setRegistryJar(String registryJar) {
+               factory.setRmiRegistryJar(registryJar);
+               return factory.getRmiRegistryJar();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsRegistryJar() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int getRunLimit() {
+               return factory.getMaxRuns();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int setRunLimit(int newValue) {
+               factory.setMaxRuns(newValue);
+               return factory.getMaxRuns();
+       }
+
+       @Override
+       public Response optionsRunLimit() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int getDefaultLifetime() {
+               return factory.getDefaultLifetime();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int setDefaultLifetime(int newValue) {
+               factory.setDefaultLifetime(newValue);
+               return factory.getDefaultLifetime();
+       }
+
+       @Override
+       public Response optionsDefaultLifetime() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public StringList currentRuns() {
+               StringList result = new StringList();
+               result.string = runDB.listRunNames();
+               return result;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsCurrentRuns() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getJavaBinary() {
+               return factory.getJavaBinary();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setJavaBinary(String newValue) {
+               factory.setJavaBinary(newValue);
+               return factory.getJavaBinary();
+       }
+
+       @Override
+       public Response optionsJavaBinary() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public StringList getExtraArguments() {
+               String[] xargs = factory.getExtraArguments();
+               StringList result = new StringList();
+               result.string = asList(xargs == null ? new String[0] : xargs);
+               return result;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public StringList setExtraArguments(StringList newValue) {
+               if (newValue == null || newValue.string == null)
+                       factory.setExtraArguments(new String[0]);
+               else
+                       factory.setExtraArguments(newValue.string
+                                       .toArray(new 
String[newValue.string.size()]));
+               StringList result = new StringList();
+               result.string = asList(factory.getExtraArguments());
+               return result;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsExtraArguments() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getServerWorkerJar() {
+               return factory.getServerWorkerJar();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setServerWorkerJar(String newValue) {
+               factory.setServerWorkerJar(newValue);
+               return factory.getServerWorkerJar();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsServerWorkerJar() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getExecuteWorkflowScript() {
+               return factory.getExecuteWorkflowScript();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setExecuteWorkflowScript(String newValue) {
+               factory.setExecuteWorkflowScript(newValue);
+               return factory.getExecuteWorkflowScript();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsExecuteWorkflowScript() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int getRegistrationWaitSeconds() {
+               return factory.getWaitSeconds();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int setRegistrationWaitSeconds(int newValue) {
+               factory.setWaitSeconds(newValue);
+               return factory.getWaitSeconds();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsRegistrationWaitSeconds() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int getRegistrationPollMillis() {
+               return factory.getSleepTime();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int setRegistrationPollMillis(int newValue) {
+               factory.setSleepTime(newValue);
+               return factory.getSleepTime();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsRegistrationPollMillis() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getRunasPasswordFile() {
+               return factory.getPasswordFile();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setRunasPasswordFile(String newValue) {
+               factory.setPasswordFile(newValue);
+               return factory.getPasswordFile();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsRunasPasswordFile() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getServerForkerJar() {
+               return factory.getServerForkerJar();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setServerForkerJar(String newValue) {
+               factory.setServerForkerJar(newValue);
+               return factory.getServerForkerJar();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsServerForkerJar() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int startupTime() {
+               return factory.getLastStartupCheckCount();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsStartupTime() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Integer lastExitCode() {
+               return factory.getLastExitCode();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsLastExitCode() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public StringList factoryProcessMapping() {
+               StringList result = new StringList();
+               result.string = asList(factory.getFactoryProcessMapping());
+               return result;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsFactoryProcessMapping() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public URList usageRecords() {
+               URList result = new URList();
+               result.usageRecord = usageRecords.getUsageRecords();
+               return result;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsUsageRecords() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public UserList users(UriInfo ui) {
+               UserList ul = new UserList();
+               UriBuilder ub = secure(ui).path("{id}");
+               for (String user : userStore.getUserNames())
+                       ul.user.add(ub.build(user));
+               return ul;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsUsers() {
+               return opt("POST");
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public UserDesc user(String username) {
+               UserDesc desc = new UserDesc();
+               User u = userStore.getUser(username);
+               desc.username = u.getUsername();
+               desc.localUserId = u.getLocalUsername();
+               desc.admin = u.isAdmin();
+               desc.enabled = u.isEnabled();
+               return desc;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsUser(String username) {
+               return opt("PUT", "DELETE");
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response useradd(UserDesc userdesc, UriInfo ui) {
+               if (userdesc.username == null)
+                       throw new IllegalArgumentException("no user name 
supplied");
+               if (userdesc.password == null)
+                       userdesc.password = randomUUID().toString();
+               userStore.addUser(userdesc.username, userdesc.password, false);
+               if (userdesc.localUserId != null)
+                       userStore.setUserLocalUser(userdesc.username, 
userdesc.localUserId);
+               if (userdesc.admin != null && userdesc.admin)
+                       userStore.setUserAdmin(userdesc.username, true);
+               if (userdesc.enabled != null && userdesc.enabled)
+                       userStore.setUserEnabled(userdesc.username, true);
+               return created(secure(ui).path("{id}").build(userdesc.username))
+                               .build();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public UserDesc userset(String username, UserDesc userdesc) {
+               if (userdesc.password != null)
+                       userStore.setUserPassword(username, userdesc.password);
+               if (userdesc.localUserId != null)
+                       userStore.setUserLocalUser(username, 
userdesc.localUserId);
+               if (userdesc.admin != null)
+                       userStore.setUserAdmin(username, userdesc.admin);
+               if (userdesc.enabled != null)
+                       userStore.setUserEnabled(username, userdesc.enabled);
+               userdesc = null; // Stop reuse!
+
+               UserDesc desc = new UserDesc();
+               User u = userStore.getUser(username);
+               desc.username = u.getUsername();
+               desc.localUserId = u.getLocalUsername();
+               desc.admin = u.isAdmin();
+               desc.enabled = u.isEnabled();
+               return desc;
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response userdel(String username) {
+               userStore.deleteUser(username);
+               return noContent().build();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int operatingCount() {
+               try {
+                       return factory.getOperatingCount();
+               } catch (Exception e) {
+                       throw new GeneralFailureException(e);
+               }
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsOperatingCount() {
+               return opt();
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int getOperatingLimit() {
+               return factory.getOperatingLimit();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public int setOperatingLimit(int operatingLimit) {
+               factory.setOperatingLimit(operatingLimit);
+               return factory.getOperatingLimit();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsOperatingLimit() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+       @RolesAllowed(ADMIN)
+       @Override
+       public StringList getPermittedWorkflowURIs() {
+               StringList sl = new StringList();
+               List<URI> uris = localWorker.getPermittedWorkflowURIs();
+               if (uris != null)
+                       for (URI uri : uris)
+                               sl.string.add(uri.toString());
+               return sl;
+       }
+
+       private static final URI myExp = 
URI.create("http://www.myexperment.org/";);
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public StringList setPermittedWorkflowURIs(StringList permitted) {
+               List<URI> uris = new ArrayList<>();
+               for (String uri : permitted.string)
+                       try {
+                               uris.add(myExp.resolve(uri));
+                       } catch (Exception e) {
+                               // Ignore
+                       }
+               localWorker.setPermittedWorkflowURIs(uris);
+               return getPermittedWorkflowURIs();
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsPermittedWorkflowURIs() {
+               return opt("PUT");
+       }
+
+       // /////////////////////////////////////////////////////
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String getGenerateProvenance() {
+               return Boolean.toString(localWorker.getGenerateProvenance());
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public String setGenerateProvenance(String newValue) {
+               boolean b = Boolean.parseBoolean(newValue);
+               localWorker.setGenerateProvenance(b);
+               return Boolean.toString(localWorker.getGenerateProvenance());
+       }
+
+       @RolesAllowed(ADMIN)
+       @Override
+       public Response optionsGenerateProvenance() {
+               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/admin/package-info.java
----------------------------------------------------------------------
diff --git 
a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/package-info.java
 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/package-info.java
new file mode 100644
index 0000000..0a4174e
--- /dev/null
+++ 
b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/admin/package-info.java
@@ -0,0 +1,46 @@
+/*
+ */
+/**
+ * This package contains the RESTful administration interface to Taverna 
Server.
+ * @author Donal Fellows
+ */
+@XmlSchema(namespace = ADMIN, elementFormDefault = QUALIFIED, 
attributeFormDefault = QUALIFIED, xmlns = {
+               @XmlNs(prefix = "xlink", namespaceURI = XLINK),
+               @XmlNs(prefix = "ts", namespaceURI = SERVER),
+               @XmlNs(prefix = "ts-rest", namespaceURI = SERVER_REST),
+               @XmlNs(prefix = "ts-soap", namespaceURI = SERVER_SOAP),
+               @XmlNs(prefix = "feed", namespaceURI = FEED),
+               @XmlNs(prefix = "admin", namespaceURI = ADMIN),
+               @XmlNs(prefix = "ur", namespaceURI = UR),
+               @XmlNs(prefix = "ds", namespaceURI = XSIG) })
+package org.taverna.server.master.admin;
+/*
+ * 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.xml.bind.annotation.XmlNsForm.QUALIFIED;
+import static org.taverna.server.master.common.Namespaces.ADMIN;
+import static org.taverna.server.master.common.Namespaces.FEED;
+import static org.taverna.server.master.common.Namespaces.SERVER;
+import static org.taverna.server.master.common.Namespaces.SERVER_REST;
+import static org.taverna.server.master.common.Namespaces.SERVER_SOAP;
+import static org.taverna.server.master.common.Namespaces.UR;
+import static org.taverna.server.master.common.Namespaces.XLINK;
+import static org.taverna.server.master.common.Namespaces.XSIG;
+
+import javax.xml.bind.annotation.XmlNs;
+import javax.xml.bind.annotation.XmlSchema;
+


Reply via email to