org.taverna.server.master.TavernaServerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.taverna.server.master.TavernaServerImpl.java

Source

/*
 * Copyright (C) 2010-2011 The University of Manchester
 * 
 * See the file "LICENSE.txt" for license terms.
 */
package org.taverna.server.master;

import static java.lang.Math.min;
import static java.util.Collections.sort;
import static java.util.UUID.randomUUID;
import static javax.ws.rs.core.Response.created;
import static javax.ws.rs.core.UriBuilder.fromUri;
import static javax.xml.ws.handler.MessageContext.PATH_INFO;
import static org.apache.commons.logging.LogFactory.getLog;
import static org.taverna.server.master.common.DirEntryReference.newInstance;
import static org.taverna.server.master.common.Namespaces.SERVER_SOAP;
import static org.taverna.server.master.common.Roles.ADMIN;
import static org.taverna.server.master.common.Roles.USER;
import static org.taverna.server.master.common.Status.Initialized;
import static org.taverna.server.master.common.Uri.secure;

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.jws.WebService;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.ws.WebServiceContext;

import org.apache.commons.logging.Log;
import org.apache.cxf.annotations.WSDLDocumentation;
import org.springframework.beans.factory.annotation.Required;
import org.taverna.server.master.TavernaServerImpl.SupportAware;
import org.taverna.server.master.common.Credential;
import org.taverna.server.master.common.DirEntryReference;
import org.taverna.server.master.common.InputDescription;
import org.taverna.server.master.common.Permission;
import org.taverna.server.master.common.RunReference;
import org.taverna.server.master.common.Status;
import org.taverna.server.master.common.Trust;
import org.taverna.server.master.common.Workflow;
import org.taverna.server.master.exceptions.BadStateChangeException;
import org.taverna.server.master.exceptions.FilesystemAccessException;
import org.taverna.server.master.exceptions.InvalidCredentialException;
import org.taverna.server.master.exceptions.NoCredentialException;
import org.taverna.server.master.exceptions.NoDirectoryEntryException;
import org.taverna.server.master.exceptions.NoListenerException;
import org.taverna.server.master.exceptions.NoUpdateException;
import org.taverna.server.master.exceptions.NotOwnerException;
import org.taverna.server.master.exceptions.UnknownRunException;
import org.taverna.server.master.factories.ListenerFactory;
import org.taverna.server.master.interfaces.Directory;
import org.taverna.server.master.interfaces.DirectoryEntry;
import org.taverna.server.master.interfaces.File;
import org.taverna.server.master.interfaces.Input;
import org.taverna.server.master.interfaces.Listener;
import org.taverna.server.master.interfaces.Policy;
import org.taverna.server.master.interfaces.RunStore;
import org.taverna.server.master.interfaces.TavernaRun;
import org.taverna.server.master.interfaces.TavernaSecurityContext;
import org.taverna.server.master.notification.NotificationEngine;
import org.taverna.server.master.notification.atom.EventDAO;
import org.taverna.server.master.rest.TavernaServerREST;
import org.taverna.server.master.rest.TavernaServerREST.EnabledNotificationFabrics;
import org.taverna.server.master.rest.TavernaServerREST.PermittedListeners;
import org.taverna.server.master.rest.TavernaServerREST.PermittedWorkflows;
import org.taverna.server.master.rest.TavernaServerREST.PolicyView;
import org.taverna.server.master.rest.TavernaServerRunREST;
import org.taverna.server.master.soap.FileContents;
import org.taverna.server.master.soap.PermissionList;
import org.taverna.server.master.soap.TavernaServerSOAP;
import org.taverna.server.master.utils.FilenameUtils;
import org.taverna.server.master.utils.InvocationCounter.CallCounted;
import org.taverna.server.port_description.OutputDescription;

import edu.umd.cs.findbugs.annotations.SuppressWarnings;

/**
 * The core implementation of the web application.
 * 
 * @author Donal Fellows
 */
@Path("/")
@DeclareRoles({ USER, ADMIN })
@WebService(endpointInterface = "org.taverna.server.master.soap.TavernaServerSOAP", serviceName = "TavernaServer", targetNamespace = SERVER_SOAP)
@WSDLDocumentation("An instance of Taverna 2.4 Server Release 2.")
public abstract class TavernaServerImpl implements TavernaServerSOAP, TavernaServerREST, TavernaServer {
    /**
     * The root of descriptions of the server in JMX.
     */
    public static final String JMX_ROOT = "Taverna:group=Server-v2,name=";

    /** The logger for the server framework. */
    public static final Log log = getLog("Taverna.Server.Webapp");

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // CONNECTIONS TO JMX, SPRING AND CXF

    @Resource
    WebServiceContext jaxws;
    @Context
    @SuppressWarnings("UWF_UNWRITTEN_FIELD")
    private HttpHeaders jaxrsHeaders;

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // STATE VARIABLES AND SPRING SETTERS

    /**
     * For building descriptions of the expected inputs and actual outputs of a
     * workflow.
     */
    private ContentsDescriptorBuilder cdBuilder;
    /**
     * Utilities for accessing files on the local-worker.
     */
    private FilenameUtils fileUtils;
    /** How notifications are dispatched. */
    private NotificationEngine notificationEngine;
    /** Main support class. */
    private TavernaServerSupport support;
    /** A storage facility for workflow runs. */
    private RunStore runStore;
    /** Encapsulates the policies applied by this server. */
    private Policy policy;
    /** Where Atom events come from. */
    EventDAO eventSource;

    @Override
    @Required
    public void setFileUtils(FilenameUtils converter) {
        this.fileUtils = converter;
    }

    @Override
    @Required
    public void setContentsDescriptorBuilder(ContentsDescriptorBuilder cdBuilder) {
        this.cdBuilder = cdBuilder;
    }

    @Override
    @Required
    public void setNotificationEngine(NotificationEngine notificationEngine) {
        this.notificationEngine = notificationEngine;
    }

    /**
     * @param support
     *            the support to set
     */
    @Override
    @Required
    public void setSupport(TavernaServerSupport support) {
        this.support = support;
    }

    @Override
    @Required
    public void setRunStore(RunStore runStore) {
        this.runStore = runStore;
    }

    @Override
    @Required
    public void setPolicy(Policy policy) {
        this.policy = policy;
    }

    @Override
    @Required
    public void setEventSource(EventDAO eventSource) {
        this.eventSource = eventSource;
    }

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // REST INTERFACE

    @Override
    @CallCounted
    public ServerDescription describeService(UriInfo ui) {
        return new ServerDescription(ui);
    }

    @Override
    @CallCounted
    public RunList listUsersRuns(UriInfo ui) {
        return new RunList(runs(), secure(ui).path("{name}"));
    }

    @Override
    @CallCounted
    public Response submitWorkflow(Workflow workflow, UriInfo ui) throws NoUpdateException {
        String name = support.buildWorkflow(workflow);
        return created(secure(ui).path("{uuid}").build(name)).build();
    }

    @Override
    @CallCounted
    public int getMaxSimultaneousRuns() {
        return support.getMaxSimultaneousRuns();
    }

    @Override
    @CallCounted
    public TavernaServerRunREST getRunResource(final String runName) throws UnknownRunException {
        RunREST rr = makeRunInterface();
        rr.setRun(support.getRun(runName));
        rr.setRunName(runName);
        return rr;
    }

    @Override
    @CallCounted
    public abstract PolicyView getPolicyDescription();

    /**
     * Construct a RESTful interface to a run.
     * 
     * @return The handle to the interface, as decorated by Spring.
     */
    protected abstract RunREST makeRunInterface();

    /**
     * Indicates that this is a class that wants to be told by Spring about the
     * main support bean.
     * 
     * @author Donal Fellows
     */
    interface SupportAware {
        /**
         * How to tell the bean about the support bean.
         * 
         * @param support
         *            Reference to the support bean.
         */
        @Required
        void setSupport(TavernaServerSupport support);
    }

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // SOAP INTERFACE

    @Override
    @CallCounted
    public RunReference[] listRuns() {
        ArrayList<RunReference> ws = new ArrayList<RunReference>();
        UriBuilder ub = getRunUriBuilder();
        for (String runName : runs().keySet())
            ws.add(new RunReference(runName, ub));
        return ws.toArray(new RunReference[ws.size()]);
    }

    @Override
    @CallCounted
    public RunReference submitWorkflow(Workflow workflow) throws NoUpdateException {
        String name = support.buildWorkflow(workflow);
        return new RunReference(name, getRunUriBuilder());
    }

    @Override
    @CallCounted
    public Workflow[] getAllowedWorkflows() {
        List<Workflow> workflows = support.getPermittedWorkflows();
        return workflows.toArray(new Workflow[workflows.size()]);
    }

    @Override
    @CallCounted
    public String[] getAllowedListeners() {
        List<String> types = support.getListenerTypes();
        return types.toArray(new String[types.size()]);
    }

    @Override
    @CallCounted
    public String[] getEnabledNotifiers() {
        List<String> dispatchers = notificationEngine.listAvailableDispatchers();
        return dispatchers.toArray(new String[dispatchers.size()]);
    }

    @Override
    @CallCounted
    public void destroyRun(String runName) throws UnknownRunException, NoUpdateException {
        support.unregisterRun(runName, null);
    }

    @Override
    @CallCounted
    public Workflow getRunWorkflow(String runName) throws UnknownRunException {
        return support.getRun(runName).getWorkflow();
    }

    @Override
    @CallCounted
    public Date getRunExpiry(String runName) throws UnknownRunException {
        return support.getRun(runName).getExpiry();
    }

    @Override
    @CallCounted
    public void setRunExpiry(String runName, Date d) throws UnknownRunException, NoUpdateException {
        support.updateExpiry(support.getRun(runName), d);
    }

    @Override
    @CallCounted
    public Date getRunCreationTime(String runName) throws UnknownRunException {
        return support.getRun(runName).getCreationTimestamp();
    }

    @Override
    @CallCounted
    public Date getRunFinishTime(String runName) throws UnknownRunException {
        return support.getRun(runName).getFinishTimestamp();
    }

    @Override
    @CallCounted
    public Date getRunStartTime(String runName) throws UnknownRunException {
        return support.getRun(runName).getStartTimestamp();
    }

    @Override
    @CallCounted
    public Status getRunStatus(String runName) throws UnknownRunException {
        return support.getRun(runName).getStatus();
    }

    @Override
    @CallCounted
    public void setRunStatus(String runName, Status s) throws UnknownRunException, NoUpdateException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        w.setStatus(s);
    }

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // SOAP INTERFACE - Security

    @Override
    @CallCounted
    public String getRunOwner(String runName) throws UnknownRunException {
        return support.getRun(runName).getSecurityContext().getOwner().getName();
    }

    /**
     * Look up a security context, applying access control rules for access to
     * the parts of the context that are only open to the owner.
     * 
     * @param runName
     *            The name of the workflow run.
     * @param initialOnly
     *            Whether to check if we're in the initial state.
     * @return The security context. Never <tt>null</tt>.
     * @throws UnknownRunException
     * @throws NotOwnerException
     * @throws BadStateChangeException
     */
    private TavernaSecurityContext getRunSecurityContext(String runName, boolean initialOnly)
            throws UnknownRunException, NotOwnerException, BadStateChangeException {
        TavernaRun run = support.getRun(runName);
        TavernaSecurityContext c = run.getSecurityContext();
        if (!c.getOwner().equals(support.getPrincipal()))
            throw new NotOwnerException();
        if (initialOnly && run.getStatus() != Initialized)
            throw new BadStateChangeException();
        return c;
    }

    @Override
    @CallCounted
    public Credential[] getRunCredentials(String runName) throws UnknownRunException, NotOwnerException {
        try {
            return getRunSecurityContext(runName, false).getCredentials();
        } catch (BadStateChangeException e) {
            Error e2 = new Error("impossible");
            e2.initCause(e);
            throw e2;
        }
    }

    @Override
    @CallCounted
    public String setRunCredential(String runName, String credentialID, Credential credential)
            throws UnknownRunException, NotOwnerException, InvalidCredentialException, NoCredentialException,
            BadStateChangeException {
        TavernaSecurityContext c = getRunSecurityContext(runName, true);
        if (credentialID == null || credentialID.isEmpty()) {
            credential.id = randomUUID().toString();
        } else {
            find: do {
                for (Credential t : c.getCredentials())
                    if (t.id.equals(credentialID))
                        break find;
                throw new NoCredentialException();
            } while (false);
            credential.id = credentialID;
        }
        URI uri = getRunUriBuilder().path("security/credentials/{credid}").build(runName, credential.id);
        credential.href = uri.toString();
        c.validateCredential(credential);
        c.addCredential(credential);
        return credential.id;
    }

    @Override
    @CallCounted
    public void deleteRunCredential(String runName, String credentialID)
            throws UnknownRunException, NotOwnerException, NoCredentialException, BadStateChangeException {
        getRunSecurityContext(runName, true).deleteCredential(new Credential.Dummy(credentialID));
    }

    @Override
    @CallCounted
    public Trust[] getRunCertificates(String runName) throws UnknownRunException, NotOwnerException {
        try {
            return getRunSecurityContext(runName, false).getTrusted();
        } catch (BadStateChangeException e) {
            Error e2 = new Error("impossible");
            e2.initCause(e);
            throw e2;
        }
    }

    @Override
    @CallCounted
    public String setRunCertificates(String runName, String certificateID, Trust certificate)
            throws UnknownRunException, NotOwnerException, InvalidCredentialException, NoCredentialException,
            BadStateChangeException {
        TavernaSecurityContext c = getRunSecurityContext(runName, true);
        if (certificateID == null || certificateID.isEmpty()) {
            certificate.id = randomUUID().toString();
        } else {
            find: do {
                for (Trust t : c.getTrusted())
                    if (t.id.equals(certificateID))
                        break find;
                throw new NoCredentialException();
            } while (false);
            certificate.id = certificateID;
        }
        URI uri = getRunUriBuilder().path("security/trusts/{certid}").build(runName, certificate.id);
        certificate.href = uri.toString();
        c.validateTrusted(certificate);
        c.addTrusted(certificate);
        return certificate.id;
    }

    @Override
    @CallCounted
    public void deleteRunCertificates(String runName, String certificateID)
            throws UnknownRunException, NotOwnerException, NoCredentialException, BadStateChangeException {
        TavernaSecurityContext c = getRunSecurityContext(runName, true);
        Trust toDelete = new Trust();
        toDelete.id = certificateID;
        c.deleteTrusted(toDelete);
    }

    @Override
    @CallCounted
    public PermissionList listRunPermissions(String runName) throws UnknownRunException, NotOwnerException {
        PermissionList pl = new PermissionList();
        pl.permission = new ArrayList<PermissionList.SinglePermissionMapping>();
        Map<String, Permission> perm = new HashMap<String, Permission>();
        try {
            TavernaSecurityContext context = getRunSecurityContext(runName, false);
            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);
        } catch (BadStateChangeException e) {
            log.error("unexpected error from internal API", e);
        }
        List<String> users = new ArrayList<String>(perm.keySet());
        sort(users);
        for (String user : users) {
            pl.permission.add(new PermissionList.SinglePermissionMapping(user, perm.get(user)));
        }
        return pl;
    }

    @Override
    @CallCounted
    public void setRunPermission(String runName, String userName, Permission permission)
            throws UnknownRunException, NotOwnerException {
        try {
            support.setPermission(getRunSecurityContext(runName, false), userName, permission);
        } catch (BadStateChangeException e) {
            log.error("unexpected error from internal API", e);
        }
    }

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // SOAP INTERFACE - Filesystem connection

    @Override
    @CallCounted
    public OutputDescription getRunOutputDescription(String runName) throws UnknownRunException,
            BadStateChangeException, FilesystemAccessException, NoDirectoryEntryException {
        TavernaRun run = support.getRun(runName);
        if (run.getStatus() == Initialized) {
            throw new BadStateChangeException("may not get output description in initial state");
        }
        return cdBuilder.makeOutputDescriptor(run, null);
    }

    @Override
    @CallCounted
    public DirEntryReference[] getRunDirectoryContents(String runName, DirEntryReference d)
            throws UnknownRunException, FilesystemAccessException, NoDirectoryEntryException {
        List<DirEntryReference> result = new ArrayList<DirEntryReference>();
        for (DirectoryEntry e : fileUtils.getDirectory(support.getRun(runName), d).getContents())
            result.add(newInstance(null, e));
        return result.toArray(new DirEntryReference[result.size()]);
    }

    @Override
    @CallCounted
    public byte[] getRunDirectoryAsZip(String runName, DirEntryReference d)
            throws UnknownRunException, FilesystemAccessException, NoDirectoryEntryException {
        return fileUtils.getDirectory(support.getRun(runName), d).getContentsAsZip();
    }

    @Override
    @CallCounted
    public DirEntryReference makeRunDirectory(String runName, DirEntryReference parent, String name)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, NoDirectoryEntryException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        Directory dir = fileUtils.getDirectory(w, parent).makeSubdirectory(support.getPrincipal(), name);
        return newInstance(null, dir);
    }

    @Override
    @CallCounted
    public DirEntryReference makeRunFile(String runName, DirEntryReference parent, String name)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, NoDirectoryEntryException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        File f = fileUtils.getDirectory(w, parent).makeEmptyFile(support.getPrincipal(), name);
        return newInstance(null, f);
    }

    @Override
    @CallCounted
    public void destroyRunDirectoryEntry(String runName, DirEntryReference d)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, NoDirectoryEntryException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        fileUtils.getDirEntry(w, d).destroy();
    }

    @Override
    @CallCounted
    public byte[] getRunFileContents(String runName, DirEntryReference d)
            throws UnknownRunException, FilesystemAccessException, NoDirectoryEntryException {
        File f = fileUtils.getFile(support.getRun(runName), d);
        return f.getContents(0, -1);
    }

    @Override
    @CallCounted
    public void setRunFileContents(String runName, DirEntryReference d, byte[] newContents)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, NoDirectoryEntryException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        fileUtils.getFile(w, d).setContents(newContents);
    }

    @Override
    @CallCounted
    public FileContents getRunFileContentsMTOM(String runName, DirEntryReference d)
            throws UnknownRunException, FilesystemAccessException, NoDirectoryEntryException {
        File f = fileUtils.getFile(support.getRun(runName), d);
        FileContents fc = new FileContents();
        fc.setFile(f, support.getEstimatedContentType(f));
        return fc;
    }

    @Override
    @CallCounted
    public void setRunFileContentsMTOM(String runName, FileContents newContents)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, NoDirectoryEntryException {
        TavernaRun run = support.getRun(runName);
        support.permitUpdate(run);
        File f = fileUtils.getFile(run, newContents.name);
        f.setContents(new byte[0]);
        support.copyDataToFile(newContents.fileData, f);
    }

    @Override
    @CallCounted
    public String getRunFileType(String runName, DirEntryReference d)
            throws UnknownRunException, FilesystemAccessException, NoDirectoryEntryException {
        return support.getEstimatedContentType(fileUtils.getFile(support.getRun(runName), d));
    }

    @Override
    @CallCounted
    public long getRunFileLength(String runName, DirEntryReference d)
            throws UnknownRunException, FilesystemAccessException, NoDirectoryEntryException {
        return fileUtils.getFile(support.getRun(runName), d).getSize();
    }

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // SOAP INTERFACE - Run listeners

    @Override
    @CallCounted
    public String[] getRunListeners(String runName) throws UnknownRunException {
        TavernaRun w = support.getRun(runName);
        List<String> result = new ArrayList<String>();
        for (Listener l : w.getListeners())
            result.add(l.getName());
        return result.toArray(new String[result.size()]);
    }

    @Override
    @CallCounted
    public String addRunListener(String runName, String listenerType, String configuration)
            throws UnknownRunException, NoUpdateException, NoListenerException {
        return support.makeListener(support.getRun(runName), listenerType, configuration).getName();
    }

    @Override
    @CallCounted
    public String getRunListenerConfiguration(String runName, String listenerName)
            throws UnknownRunException, NoListenerException {
        return support.getListener(runName, listenerName).getConfiguration();
    }

    @Override
    @CallCounted
    public String[] getRunListenerProperties(String runName, String listenerName)
            throws UnknownRunException, NoListenerException {
        return support.getListener(runName, listenerName).listProperties().clone();
    }

    @Override
    @CallCounted
    public String getRunListenerProperty(String runName, String listenerName, String propName)
            throws UnknownRunException, NoListenerException {
        return support.getListener(runName, listenerName).getProperty(propName);
    }

    @Override
    @CallCounted
    public void setRunListenerProperty(String runName, String listenerName, String propName, String value)
            throws UnknownRunException, NoUpdateException, NoListenerException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        Listener l = support.getListener(w, listenerName);
        try {
            l.getProperty(propName); // sanity check!
            l.setProperty(propName, value);
        } catch (RuntimeException e) {
            throw new NoListenerException("problem setting property: " + e.getMessage(), e);
        }
    }

    @Override
    @CallCounted
    public InputDescription getRunInputs(String runName) throws UnknownRunException {
        return new InputDescription(support.getRun(runName));
    }

    @Override
    @CallCounted
    public String getRunOutputBaclavaFile(String runName) throws UnknownRunException {
        return support.getRun(runName).getOutputBaclavaFile();
    }

    @Override
    @CallCounted
    public void setRunInputBaclavaFile(String runName, String fileName)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, BadStateChangeException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        w.setInputBaclavaFile(fileName);
    }

    @Override
    @CallCounted
    public void setRunInputPortFile(String runName, String portName, String portFilename)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, BadStateChangeException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        Input i = support.getInput(w, portName);
        if (i == null)
            i = w.makeInput(portName);
        i.setFile(portFilename);
    }

    @Override
    @CallCounted
    public void setRunInputPortValue(String runName, String portName, String portValue)
            throws UnknownRunException, NoUpdateException, BadStateChangeException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        Input i = support.getInput(w, portName);
        if (i == null)
            i = w.makeInput(portName);
        i.setValue(portValue);
    }

    @Override
    @CallCounted
    public void setRunOutputBaclavaFile(String runName, String outputFile)
            throws UnknownRunException, NoUpdateException, FilesystemAccessException, BadStateChangeException {
        TavernaRun w = support.getRun(runName);
        support.permitUpdate(w);
        w.setOutputBaclavaFile(outputFile);
    }

    @Override
    @CallCounted
    public org.taverna.server.port_description.InputDescription getRunInputDescriptor(String runName)
            throws UnknownRunException {
        return cdBuilder.makeInputDescriptor(support.getRun(runName), null);
    }

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // SUPPORT METHODS

    @Override
    public void initObsoleteSecurity(TavernaSecurityContext c) {
        /*
         * These next pieces of security initialisation are (hopefully) obsolete
         * now that we use Spring Security, but we keep them Just In Case.
         */
        boolean doRESTinit = true;
        if (jaxws != null) {
            try {
                javax.xml.ws.handler.MessageContext msgCtxt = jaxws.getMessageContext();
                if (msgCtxt != null) {
                    doRESTinit = false;
                    c.initializeSecurityFromSOAPContext(msgCtxt);
                }
            } catch (IllegalStateException e) {
                /* ignore; not much we can do */
            }
        }
        if (doRESTinit && jaxrsHeaders != null)
            c.initializeSecurityFromRESTContext(jaxrsHeaders);
    }

    /**
     * A creator of substitute {@link URI} builders.
     * 
     * @return A URI builder configured so that it takes a path parameter that
     *         corresponds to the run ID (but with no such ID applied).
     */
    UriBuilder getRunUriBuilder() {
        return getBaseUriBuilder().path("runs/{uuid}");
    }

    @Override
    public UriBuilder getRunUriBuilder(TavernaRun run) {
        return fromUri(getRunUriBuilder().build(run.getId()));
    }

    @Override
    public UriBuilder getBaseUriBuilder() {
        if (jaxws == null || jaxws.getMessageContext() == null)
            // Hack to make the test suite work
            return secure(fromUri("/taverna-server/rest/"));
        String pathInfo = (String) jaxws.getMessageContext().get(PATH_INFO);
        pathInfo = pathInfo.replaceFirst("/soap$", "/rest/");
        pathInfo = pathInfo.replaceFirst("/rest/.+$", "/rest/");
        return secure(fromUri(pathInfo));
    }

    private Map<String, TavernaRun> runs() {
        return runStore.listRuns(support.getPrincipal(), policy);
    }
}

/**
 * RESTful interface to the policies of a Taverna Server installation.
 * 
 * @author Donal Fellows
 */
class PolicyREST implements PolicyView, SupportAware {
    private TavernaServerSupport support;
    private Policy policy;
    private ListenerFactory listenerFactory;
    private NotificationEngine notificationEngine;

    @Override
    public void setSupport(TavernaServerSupport support) {
        this.support = support;
    }

    @Required
    public void setPolicy(Policy policy) {
        this.policy = policy;
    }

    @Required
    public void setListenerFactory(ListenerFactory listenerFactory) {
        this.listenerFactory = listenerFactory;
    }

    @Required
    public void setNotificationEngine(NotificationEngine notificationEngine) {
        this.notificationEngine = notificationEngine;
    }

    @Override
    public PolicyDescription getDescription(UriInfo ui) {
        return new PolicyDescription(ui);
    }

    @Override
    @CallCounted
    public int getMaxSimultaneousRuns() {
        Integer limit = policy.getMaxRuns(support.getPrincipal());
        if (limit == null)
            return policy.getMaxRuns();
        return min(limit.intValue(), policy.getMaxRuns());
    }

    @Override
    @CallCounted
    public PermittedListeners getPermittedListeners() {
        return new PermittedListeners(listenerFactory.getSupportedListenerTypes());
    }

    @Override
    @CallCounted
    public PermittedWorkflows getPermittedWorkflows() {
        return new PermittedWorkflows(policy.listPermittedWorkflows(support.getPrincipal()));
    }

    @Override
    @CallCounted
    public EnabledNotificationFabrics getEnabledNotifiers() {
        return new EnabledNotificationFabrics(notificationEngine.listAvailableDispatchers());
    }
}