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