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 2018/01/09 23:30:36 UTC
[20/42] incubator-taverna-server git commit: package org.taverna ->
org.apache.taverna
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
new file mode 100644
index 0000000..642a6d6
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/RunFactoryConfiguration.java
@@ -0,0 +1,411 @@
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.springframework.jmx.support.MetricType.COUNTER;
+import static org.springframework.jmx.support.MetricType.GAUGE;
+import static org.taverna.server.master.TavernaServer.JMX_ROOT;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.annotation.Order;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedMetric;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.taverna.server.master.factories.ConfigurableRunFactory;
+import org.taverna.server.master.localworker.LocalWorkerState;
+
+@ManagedResource(objectName = JMX_ROOT + "Factory", description = "The factory for runs.")
+public abstract class RunFactoryConfiguration implements ConfigurableRunFactory {
+ protected Log log = LogFactory.getLog("Taverna.Server.Worker");
+ protected LocalWorkerState state;
+ protected RunDBSupport runDB;
+ private int totalRuns = 0;
+
+ @PreDestroy
+ void closeLog() {
+ log = null;
+ }
+
+ @Autowired(required = true)
+ @Order(0)
+ void setState(LocalWorkerState state) {
+ this.state = state;
+ }
+
+ @Autowired(required = true)
+ @Order(0)
+ void setRunDB(RunDBSupport runDB) {
+ this.runDB = runDB;
+ }
+
+ /**
+ * Drop any current references to the registry of runs, and kill off that
+ * process.
+ */
+ protected abstract void reinitRegistry();
+
+ /**
+ * Drop any current references to the run factory subprocess and kill it
+ * off.
+ */
+ protected abstract void reinitFactory();
+
+ /** Count the number of operating runs. */
+ protected abstract int operatingCount() throws Exception;
+
+ protected final synchronized void incrementRunCount() {
+ totalRuns++;
+ }
+
+ @Override
+ @ManagedAttribute(description = "Whether it is allowed to start a run executing.", currencyTimeLimit = 30)
+ public final boolean isAllowingRunsToStart() {
+ try {
+ return state.getOperatingLimit() > getOperatingCount();
+ } catch (Exception e) {
+ log.info("failed to get operating run count", e);
+ return false;
+ }
+ }
+
+ @Override
+ @ManagedAttribute(description = "The host holding the RMI registry to communicate via.")
+ public final String getRegistryHost() {
+ return state.getRegistryHost();
+ }
+
+ @Override
+ @ManagedAttribute(description = "The host holding the RMI registry to communicate via.")
+ public final void setRegistryHost(String host) {
+ state.setRegistryHost(host);
+ reinitRegistry();
+ reinitFactory();
+ }
+
+ @Override
+ @ManagedAttribute(description = "The port number of the RMI registry. Should not normally be set.")
+ public final int getRegistryPort() {
+ return state.getRegistryPort();
+ }
+
+ @Override
+ @ManagedAttribute(description = "The port number of the RMI registry. Should not normally be set.")
+ public final void setRegistryPort(int port) {
+ state.setRegistryPort(port);
+ reinitRegistry();
+ reinitFactory();
+ }
+
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "What JAR do we use to start the RMI registry process?")
+ public final String getRmiRegistryJar() {
+ return state.getRegistryJar();
+ }
+
+ @Override
+ @ManagedAttribute(description = "What JAR do we use to start the RMI registry process?")
+ public final void setRmiRegistryJar(String rmiRegistryJar) {
+ state.setRegistryJar(rmiRegistryJar);
+ reinitRegistry();
+ reinitFactory();
+ }
+
+ @Override
+ @ManagedAttribute(description = "The maximum number of simultaneous runs supported by the server.", currencyTimeLimit = 300)
+ public final int getMaxRuns() {
+ return state.getMaxRuns();
+ }
+
+ @Override
+ @ManagedAttribute(description = "The maximum number of simultaneous runs supported by the server.", currencyTimeLimit = 300)
+ public final void setMaxRuns(int maxRuns) {
+ state.setMaxRuns(maxRuns);
+ }
+
+ /** @return How many minutes should a workflow live by default? */
+ @Override
+ @ManagedAttribute(description = "How many minutes should a workflow live by default?", currencyTimeLimit = 300)
+ public final int getDefaultLifetime() {
+ return state.getDefaultLifetime();
+ }
+
+ /**
+ * Set how long a workflow should live by default.
+ *
+ * @param defaultLifetime
+ * Default lifetime, in minutes.
+ */
+ @Override
+ @ManagedAttribute(description = "How many minutes should a workflow live by default?", currencyTimeLimit = 300)
+ public final void setDefaultLifetime(int defaultLifetime) {
+ state.setDefaultLifetime(defaultLifetime);
+ }
+
+ /**
+ * @return How many milliseconds to wait between checks to see if a worker
+ * process has registered.
+ */
+ @Override
+ @ManagedAttribute(description = "How many milliseconds to wait between checks to see if a worker process has registered.", currencyTimeLimit = 300)
+ public final int getSleepTime() {
+ return state.getSleepMS();
+ }
+
+ /**
+ * @param sleepTime
+ * How many milliseconds to wait between checks to see if a
+ * worker process has registered.
+ */
+ @Override
+ @ManagedAttribute(description = "How many milliseconds to wait between checks to see if a worker process has registered.", currencyTimeLimit = 300)
+ public final void setSleepTime(int sleepTime) {
+ state.setSleepMS(sleepTime);
+ }
+
+ /**
+ * @return How many seconds to wait for a worker process to register itself.
+ */
+ @Override
+ @ManagedAttribute(description = "How many seconds to wait for a worker process to register itself.", currencyTimeLimit = 300)
+ public final int getWaitSeconds() {
+ return state.getWaitSeconds();
+ }
+
+ /**
+ * @param seconds
+ * How many seconds to wait for a worker process to register
+ * itself.
+ */
+ @Override
+ @ManagedAttribute(description = "How many seconds to wait for a worker process to register itself.", currencyTimeLimit = 300)
+ public final void setWaitSeconds(int seconds) {
+ state.setWaitSeconds(seconds);
+ }
+
+ /** @return The script to run to start running a workflow. */
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "The script to run to start running a workflow.", currencyTimeLimit = 300)
+ public final String getExecuteWorkflowScript() {
+ return state.getExecuteWorkflowScript();
+ }
+
+ /**
+ * @param executeWorkflowScript
+ * The script to run to start running a workflow.
+ */
+ @Override
+ @ManagedAttribute(description = "The script to run to start running a workflow.", currencyTimeLimit = 300)
+ public final void setExecuteWorkflowScript(String executeWorkflowScript) {
+ state.setExecuteWorkflowScript(executeWorkflowScript);
+ reinitFactory();
+ }
+
+ /** @return The location of the JAR implementing the server worker processes. */
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "The location of the JAR implementing the server worker processes.")
+ public final String getServerWorkerJar() {
+ return state.getServerWorkerJar();
+ }
+
+ /**
+ * @param serverWorkerJar
+ * The location of the JAR implementing the server worker
+ * processes.
+ */
+ @Override
+ @ManagedAttribute(description = "The location of the JAR implementing the server worker processes.")
+ public final void setServerWorkerJar(String serverWorkerJar) {
+ state.setServerWorkerJar(serverWorkerJar);
+ reinitFactory();
+ }
+
+ /** @return The list of additional arguments used to make a worker process. */
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "The list of additional arguments used to make a worker process.", currencyTimeLimit = 300)
+ public final String[] getExtraArguments() {
+ return state.getExtraArgs();
+ }
+
+ /**
+ * @param extraArguments
+ * The list of additional arguments used to make a worker
+ * process.
+ */
+ @Override
+ @ManagedAttribute(description = "The list of additional arguments used to make a worker process.", currencyTimeLimit = 300)
+ public final void setExtraArguments(@Nonnull String[] extraArguments) {
+ state.setExtraArgs(extraArguments);
+ reinitFactory();
+ }
+
+ /** @return Which java executable to run. */
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "Which java executable to run.", currencyTimeLimit = 300)
+ public final String getJavaBinary() {
+ return state.getJavaBinary();
+ }
+
+ /**
+ * @param javaBinary
+ * Which java executable to run.
+ */
+ @Override
+ @ManagedAttribute(description = "Which java executable to run.", currencyTimeLimit = 300)
+ public final void setJavaBinary(@Nonnull String javaBinary) {
+ state.setJavaBinary(javaBinary);
+ reinitFactory();
+ }
+
+ /**
+ * @return A file containing a password to use when running a program as
+ * another user (e.g., with sudo).
+ */
+ @Nullable
+ @Override
+ @ManagedAttribute(description = "A file containing a password to use when running a program as another user (e.g., with sudo).", currencyTimeLimit = 300)
+ public final String getPasswordFile() {
+ return state.getPasswordFile();
+ }
+
+ /**
+ * @param passwordFile
+ * A file containing a password to use when running a program as
+ * another user (e.g., with sudo).
+ */
+ @Override
+ @ManagedAttribute(description = "A file containing a password to use when running a program as another user (e.g., with sudo).", currencyTimeLimit = 300)
+ public final void setPasswordFile(@Nullable String passwordFile) {
+ state.setPasswordFile(passwordFile);
+ reinitFactory();
+ }
+
+ /**
+ * @return The location of the JAR implementing the secure-fork process.
+ */
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "The location of the JAR implementing the secure-fork process.", currencyTimeLimit = 300)
+ public final String getServerForkerJar() {
+ return state.getServerForkerJar();
+ }
+
+ /**
+ * @param serverForkerJar
+ * The location of the JAR implementing the secure-fork process.
+ */
+ @Override
+ @ManagedAttribute(description = "The location of the JAR implementing the secure-fork process.", currencyTimeLimit = 300)
+ public final void setServerForkerJar(String forkerJarFilename) {
+ state.setServerForkerJar(forkerJarFilename);
+ reinitFactory();
+ }
+
+ /**
+ * @return How many times has a workflow run been spawned by this engine.
+ * Restarts reset this counter.
+ */
+ @Override
+ @ManagedMetric(description = "How many times has a workflow run been spawned by this engine.", currencyTimeLimit = 10, metricType = COUNTER, category = "throughput")
+ public final synchronized int getTotalRuns() {
+ return totalRuns;
+ }
+
+ /**
+ * @return How many checks were done for the worker process the last time a
+ * spawn was tried.
+ */
+ @Override
+ @ManagedAttribute(description = "How many checks were done for the worker process the last time a spawn was tried.", currencyTimeLimit = 60)
+ public abstract int getLastStartupCheckCount();
+
+ @Nonnull
+ @Override
+ @ManagedAttribute(description = "The names of the current runs.", currencyTimeLimit = 5)
+ public final String[] getCurrentRunNames() {
+ List<String> names = runDB.listRunNames();
+ return names.toArray(new String[names.size()]);
+ }
+
+ @Override
+ @ManagedAttribute(description = "What the factory subprocess's main RMI interface is registered as.", currencyTimeLimit = 60)
+ public abstract String getFactoryProcessName();
+
+ /**
+ * @return What was the exit code from the last time the factory subprocess
+ * was killed?
+ */
+ @Override
+ @ManagedAttribute(description = "What was the exit code from the last time the factory subprocess was killed?")
+ public abstract Integer getLastExitCode();
+
+ /**
+ * @return The mapping of user names to RMI factory IDs.
+ */
+ @Override
+ @ManagedAttribute(description = "The mapping of user names to RMI factory IDs.", currencyTimeLimit = 60)
+ public abstract String[] getFactoryProcessMapping();
+
+ @Override
+ @ManagedAttribute(description = "The maximum number of simultaneous operating runs supported by the server.", currencyTimeLimit = 300)
+ public final void setOperatingLimit(int operatingLimit) {
+ state.setOperatingLimit(operatingLimit);
+ }
+
+ @Override
+ @ManagedAttribute(description = "The maximum number of simultaneous operating runs supported by the server.", currencyTimeLimit = 300)
+ public final int getOperatingLimit() {
+ return state.getOperatingLimit();
+ }
+
+ /**
+ * @return A count of the number of runs believed to actually be in the
+ * {@linkplain uk.org.taverna.server.master.common.Status#Operating
+ * operating} state.
+ * @throws Exception
+ * If anything goes wrong.
+ */
+ @Override
+ @ManagedMetric(description = "How many workflow runs are currently actually executing.", currencyTimeLimit = 10, metricType = GAUGE, category = "throughput")
+ public final int getOperatingCount() throws Exception {
+ return operatingCount();
+ }
+
+ @Override
+ @ManagedAttribute(description="Whether to tell a workflow to generate provenance bundles by default.")
+ public final void setGenerateProvenance(boolean genProv) {
+ state.setGenerateProvenance(genProv);
+ }
+
+ @Override
+ @ManagedAttribute(description="Whether to tell a workflow to generate provenance bundles by default.")
+ public final boolean getGenerateProvenance() {
+ return state.getGenerateProvenance();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
new file mode 100644
index 0000000..bb76f85
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegate.java
@@ -0,0 +1,662 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.lang.String.format;
+import static java.util.Arrays.fill;
+import static java.util.UUID.randomUUID;
+import static org.taverna.server.master.defaults.Default.CERTIFICATE_FIELD_NAMES;
+import static org.taverna.server.master.defaults.Default.CERTIFICATE_TYPE;
+import static org.taverna.server.master.defaults.Default.CREDENTIAL_FILE_SIZE_LIMIT;
+import static org.taverna.server.master.identity.WorkflowInternalAuthProvider.PREFIX;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.rmi.RemoteException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.security.auth.x500.X500Principal;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriBuilder;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.taverna.server.localworker.remote.ImplementationException;
+import org.taverna.server.localworker.remote.RemoteSecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+/**
+ * Implementation of a security context.
+ *
+ * @author Donal Fellows
+ */
+public abstract class SecurityContextDelegate implements TavernaSecurityContext {
+ Log log = LogFactory.getLog("Taverna.Server.Worker");
+ private final UsernamePrincipal owner;
+ private final List<Credential> credentials = new ArrayList<>();
+ private final List<Trust> trusted = new ArrayList<>();
+ private final RemoteRunDelegate run;
+ private final Object lock = new Object();
+ final SecurityContextFactory factory;
+
+ private transient Keystore keystore;
+ private transient Map<URI, String> uriToAliasMap;
+
+ /**
+ * Initialise the context delegate.
+ *
+ * @param run
+ * What workflow run is this for?
+ * @param owner
+ * Who owns the workflow run?
+ * @param factory
+ * What class built this object?
+ */
+ protected SecurityContextDelegate(RemoteRunDelegate run,
+ UsernamePrincipal owner, SecurityContextFactory factory) {
+ this.run = run;
+ this.owner = owner;
+ this.factory = factory;
+ }
+
+ @Override
+ public SecurityContextFactory getFactory() {
+ return factory;
+ }
+
+ @Override
+ public UsernamePrincipal getOwner() {
+ return owner;
+ }
+
+ @Override
+ public Credential[] getCredentials() {
+ synchronized (lock) {
+ return credentials.toArray(new Credential[credentials.size()]);
+ }
+ }
+
+ /**
+ * Get the human-readable name of a principal.
+ *
+ * @param principal
+ * The principal being decoded.
+ * @return A name.
+ */
+ protected final String getPrincipalName(X500Principal principal) {
+ return factory.x500Utils.getName(principal, CERTIFICATE_FIELD_NAMES);
+ }
+
+ /**
+ * Cause the current state to be flushed to the database.
+ */
+ protected final void flushToDB() {
+ factory.db.flushToDisk(run);
+ }
+
+ @Override
+ public void addCredential(Credential toAdd) {
+ synchronized (lock) {
+ int idx = credentials.indexOf(toAdd);
+ if (idx != -1)
+ credentials.set(idx, toAdd);
+ else
+ credentials.add(toAdd);
+ flushToDB();
+ }
+ }
+
+ @Override
+ public void deleteCredential(Credential toDelete) {
+ synchronized (lock) {
+ credentials.remove(toDelete);
+ flushToDB();
+ }
+ }
+
+ @Override
+ public Trust[] getTrusted() {
+ synchronized (lock) {
+ return trusted.toArray(new Trust[trusted.size()]);
+ }
+ }
+
+ @Override
+ public void addTrusted(Trust toAdd) {
+ synchronized (lock) {
+ int idx = trusted.indexOf(toAdd);
+ if (idx != -1)
+ trusted.set(idx, toAdd);
+ else
+ trusted.add(toAdd);
+ flushToDB();
+ }
+ }
+
+ @Override
+ public void deleteTrusted(Trust toDelete) {
+ synchronized (lock) {
+ trusted.remove(toDelete);
+ flushToDB();
+ }
+ }
+
+ @Override
+ public abstract void validateCredential(Credential c)
+ throws InvalidCredentialException;
+
+ @Override
+ public void validateTrusted(Trust t) throws InvalidCredentialException {
+ InputStream contentsAsStream;
+ if (t.certificateBytes != null && t.certificateBytes.length > 0) {
+ contentsAsStream = new ByteArrayInputStream(t.certificateBytes);
+ t.certificateFile = null;
+ } else if (t.certificateFile == null
+ || t.certificateFile.trim().isEmpty())
+ throw new InvalidCredentialException(
+ "absent or empty certificateFile");
+ else {
+ contentsAsStream = contents(t.certificateFile);
+ t.certificateBytes = null;
+ }
+ t.serverName = null;
+ if (t.fileType == null || t.fileType.trim().isEmpty())
+ t.fileType = CERTIFICATE_TYPE;
+ t.fileType = t.fileType.trim();
+ try {
+ t.loadedCertificates = CertificateFactory.getInstance(t.fileType)
+ .generateCertificates(contentsAsStream);
+ t.serverName = new ArrayList<>(t.loadedCertificates.size());
+ for (Certificate c : t.loadedCertificates)
+ t.serverName.add(getPrincipalName(((X509Certificate) c)
+ .getSubjectX500Principal()));
+ } catch (CertificateException e) {
+ throw new InvalidCredentialException(e);
+ } catch (ClassCastException e) {
+ // Do nothing; truncates the list of server names
+ }
+ }
+
+ @Override
+ public void initializeSecurityFromContext(SecurityContext securityContext)
+ throws Exception {
+ // This is how to get the info from Spring Security
+ Authentication auth = securityContext.getAuthentication();
+ if (auth == null)
+ return;
+ auth.getPrincipal();
+ // do nothing else in this implementation
+ }
+
+ @Override
+ public void initializeSecurityFromSOAPContext(MessageContext context) {
+ // do nothing in this implementation
+ }
+
+ @Override
+ public void initializeSecurityFromRESTContext(HttpHeaders context) {
+ // do nothing in this implementation
+ }
+
+ private UriBuilder getUB() {
+ return factory.uriSource.getRunUriBuilder(run);
+ }
+
+ private RunDatabaseDAO getDAO() {
+ return ((RunDatabase) factory.db).dao;
+ }
+
+ @Nullable
+ private List<X509Certificate> getCerts(URI uri) throws IOException,
+ GeneralSecurityException {
+ return factory.certFetcher.getTrustsForURI(uri);
+ }
+
+ private void installLocalPasswordCredential(List<Credential> credentials,
+ List<Trust> trusts) throws InvalidCredentialException, IOException,
+ GeneralSecurityException {
+ Credential.Password pw = new Credential.Password();
+ pw.id = "run:self";
+ pw.username = PREFIX + run.id;
+ pw.password = getDAO().getSecurityToken(run.id);
+ UriBuilder ub = getUB().segment("").fragment(factory.httpRealm);
+ pw.serviceURI = ub.build();
+ validateCredential(pw);
+ log.info("issuing self-referential credential for " + pw.serviceURI);
+ credentials.add(pw);
+ List<X509Certificate> myCerts = getCerts(pw.serviceURI);
+ if (myCerts != null && myCerts.size() > 0) {
+ Trust t = new Trust();
+ t.loadedCertificates = getCerts(pw.serviceURI);
+ trusts.add(t);
+ }
+ }
+
+ /**
+ * Builds and transfers a keystore with suitable credentials to the back-end
+ * workflow execution engine.
+ *
+ * @throws GeneralSecurityException
+ * If the manipulation of the keystore, keys or certificates
+ * fails.
+ * @throws IOException
+ * If there are problems building the data (should not happen).
+ * @throws RemoteException
+ * If the conveyancing fails.
+ */
+ @Override
+ public final void conveySecurity() throws GeneralSecurityException,
+ IOException, ImplementationException {
+ RemoteSecurityContext rc = run.run.getSecurityContext();
+
+ List<Trust> trusted = new ArrayList<>(this.trusted);
+ this.trusted.clear();
+ List<Credential> credentials = new ArrayList<>(this.credentials);
+ this.credentials.clear();
+
+ try {
+ installLocalPasswordCredential(credentials, trusted);
+ } catch (Exception e) {
+ log.warn("failed to construct local credential: "
+ + "interaction service will fail", e);
+ }
+
+ char[] password = null;
+ try {
+ password = generateNewPassword();
+
+ log.info("constructing merged keystore");
+ Truststore truststore = new Truststore(password);
+ Keystore keystore = new Keystore(password);
+ Map<URI, String> uriToAliasMap = new HashMap<>();
+ int trustedCount = 0, keyCount = 0;
+
+ synchronized (lock) {
+ try {
+ for (Trust t : trusted) {
+ if (t == null || t.loadedCertificates == null)
+ continue;
+ for (Certificate cert : t.loadedCertificates)
+ if (cert != null) {
+ truststore.addCertificate(cert);
+ trustedCount++;
+ }
+ }
+
+ this.uriToAliasMap = uriToAliasMap;
+ this.keystore = keystore;
+ for (Credential c : credentials) {
+ addCredentialToKeystore(c);
+ keyCount++;
+ }
+ } finally {
+ this.uriToAliasMap = null;
+ this.keystore = null;
+ credentials.clear();
+ trusted.clear();
+ flushToDB();
+ }
+ }
+
+ byte[] trustbytes = null, keybytes = null;
+ try {
+ trustbytes = truststore.serialize();
+ keybytes = keystore.serialize();
+
+ // Now we've built the security information, ship it off...
+
+ log.info("transfering merged truststore with " + trustedCount
+ + " entries");
+ rc.setTruststore(trustbytes);
+
+ log.info("transfering merged keystore with " + keyCount
+ + " entries");
+ rc.setKeystore(keybytes);
+ } finally {
+ if (trustbytes != null)
+ fill(trustbytes, (byte) 0);
+ if (keybytes != null)
+ fill(keybytes, (byte) 0);
+ }
+ rc.setPassword(password);
+
+ log.info("transferring serviceURL->alias map with "
+ + uriToAliasMap.size() + " entries");
+ rc.setUriToAliasMap(uriToAliasMap);
+ } finally {
+ if (password != null)
+ fill(password, ' ');
+ }
+
+ synchronized (lock) {
+ conveyExtraSecuritySettings(rc);
+ }
+ }
+
+ /**
+ * Hook that allows additional information to be conveyed to the remote run.
+ *
+ * @param remoteSecurityContext
+ * The remote resource that information would be passed to.
+ * @throws IOException
+ * If anything goes wrong with the communication.
+ */
+ protected void conveyExtraSecuritySettings(
+ RemoteSecurityContext remoteSecurityContext) throws IOException {
+ // Does nothing by default; overrideable
+ }
+
+ /**
+ * @return A new password with a reasonable level of randomness.
+ */
+ protected final char[] generateNewPassword() {
+ return randomUUID().toString().toCharArray();
+ }
+
+ /**
+ * Adds a credential to the current keystore.
+ *
+ * @param alias
+ * The alias to create within the keystore.
+ * @param c
+ * The key-pair.
+ * @throws KeyStoreException
+ */
+ protected final void addKeypairToKeystore(String alias, Credential c)
+ throws KeyStoreException {
+ if (c.loadedKey == null)
+ throw new KeyStoreException("critical: credential was not verified");
+ if (uriToAliasMap.containsKey(c.serviceURI))
+ log.warn("duplicate URI in alias mapping: " + c.serviceURI);
+ keystore.addKey(alias, c.loadedKey, c.loadedTrustChain);
+ uriToAliasMap.put(c.serviceURI, alias);
+ }
+
+ /**
+ * Adds a credential to the current keystore.
+ *
+ * @param c
+ * The credential to add.
+ * @throws KeyStoreException
+ */
+ public abstract void addCredentialToKeystore(Credential c)
+ throws KeyStoreException;
+
+ /**
+ * Read a file up to {@value #FILE_SIZE_LIMIT}kB in size.
+ *
+ * @param name
+ * The path name of the file, relative to the context run's
+ * working directory.
+ * @return A stream of the file's contents.
+ * @throws InvalidCredentialException
+ * If anything goes wrong.
+ */
+ final InputStream contents(String name) throws InvalidCredentialException {
+ try {
+ File f = (File) factory.fileUtils.getDirEntry(run, name);
+ long size = f.getSize();
+ if (size > CREDENTIAL_FILE_SIZE_LIMIT * 1024)
+ throw new InvalidCredentialException(CREDENTIAL_FILE_SIZE_LIMIT
+ + "kB limit hit");
+ return new ByteArrayInputStream(f.getContents(0, (int) size));
+ } catch (NoDirectoryEntryException | FilesystemAccessException e) {
+ throw new InvalidCredentialException(e);
+ } catch (ClassCastException e) {
+ throw new InvalidCredentialException("not a file", e);
+ }
+ }
+
+ @Override
+ public Set<String> getPermittedDestroyers() {
+ return run.getDestroyers();
+ }
+
+ @Override
+ public void setPermittedDestroyers(Set<String> destroyers) {
+ run.setDestroyers(destroyers);
+ }
+
+ @Override
+ public Set<String> getPermittedUpdaters() {
+ return run.getWriters();
+ }
+
+ @Override
+ public void setPermittedUpdaters(Set<String> updaters) {
+ run.setWriters(updaters);
+ }
+
+ @Override
+ public Set<String> getPermittedReaders() {
+ return run.getReaders();
+ }
+
+ @Override
+ public void setPermittedReaders(Set<String> readers) {
+ run.setReaders(readers);
+ }
+
+ /**
+ * Reinstall the credentials and the trust extracted from serialization to
+ * the database.
+ *
+ * @param credentials
+ * The credentials to reinstall.
+ * @param trust
+ * The trusted certificates to reinstall.
+ */
+ void setCredentialsAndTrust(Credential[] credentials, Trust[] trust) {
+ synchronized (lock) {
+ this.credentials.clear();
+ if (credentials != null)
+ for (Credential c : credentials)
+ try {
+ validateCredential(c);
+ this.credentials.add(c);
+ } catch (InvalidCredentialException e) {
+ log.warn("failed to revalidate credential: " + c, e);
+ }
+ this.trusted.clear();
+ if (trust != null)
+ for (Trust t : trust)
+ try {
+ validateTrusted(t);
+ this.trusted.add(t);
+ } catch (InvalidCredentialException e) {
+ log.warn("failed to revalidate trust assertion: " + t,
+ e);
+ }
+ }
+ }
+
+ static class SecurityStore {
+ private KeyStore ks;
+ private char[] password;
+
+ SecurityStore(char[] password) throws GeneralSecurityException {
+ this.password = password.clone();
+ ks = KeyStore.getInstance("UBER", "BC");
+ try {
+ ks.load(null, this.password);
+ } catch (IOException e) {
+ throw new GeneralSecurityException(
+ "problem initializing blank truststore", e);
+ }
+ }
+
+ final synchronized void setCertificate(String alias, Certificate c)
+ throws KeyStoreException {
+ if (ks == null)
+ throw new IllegalStateException("store already written");
+ ks.setCertificateEntry(alias, c);
+ }
+
+ final synchronized void setKey(String alias, Key key, Certificate[] trustChain)
+ throws KeyStoreException {
+ if (ks == null)
+ throw new IllegalStateException("store already written");
+ ks.setKeyEntry(alias, key, password, trustChain);
+ }
+
+ final synchronized byte[] serialize(boolean logIt)
+ throws GeneralSecurityException {
+ if (ks == null)
+ throw new IllegalStateException("store already written");
+ try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+ ks.store(stream, password);
+ if (logIt)
+ LogFactory.getLog("Taverna.Server.Worker").debug(
+ "serialized UBER/BC truststore (size: " + ks.size()
+ + ") with password \""
+ + new String(password) + "\"");
+ return stream.toByteArray();
+ } catch (IOException e) {
+ throw new GeneralSecurityException(
+ "problem serializing keystore", e);
+ } finally {
+ ks = null;
+ fill(password, ' ');
+ }
+ }
+
+ @Override
+ protected final void finalize() {
+ fill(password, ' ');
+ ks = null;
+ }
+ }
+
+ /**
+ * A trust store that can only be added to or serialized. Only trusted
+ * certificates can be placed in it.
+ *
+ * @author Donal Fellows
+ */
+ class Truststore extends SecurityStore {
+ Truststore(char[] password) throws GeneralSecurityException {
+ super(password);
+ }
+
+ /**
+ * Add a trusted certificate to the truststore. No certificates can be
+ * added after the truststore is serialized.
+ *
+ * @param cert
+ * The certificate (typically belonging to a root CA) to add.
+ * @throws KeyStoreException
+ * If anything goes wrong.
+ */
+ public void addCertificate(Certificate cert) throws KeyStoreException {
+ X509Certificate c = (X509Certificate) cert;
+ String alias = format("trustedcert#%s#%s#%s",
+ getPrincipalName(c.getSubjectX500Principal()),
+ getPrincipalName(c.getIssuerX500Principal()),
+ factory.x500Utils.getSerial(c));
+ setCertificate(alias, c);
+ if (log.isDebugEnabled() && factory.logSecurityDetails)
+ log.debug("added cert with alias \"" + alias + "\" of type "
+ + c.getClass().getCanonicalName());
+ }
+
+ /**
+ * Get the byte serialization of this truststore. This can only be
+ * fetched exactly once.
+ *
+ * @return The serialization.
+ * @throws GeneralSecurityException
+ * If anything goes wrong.
+ */
+ public byte[] serialize() throws GeneralSecurityException {
+ return serialize(log.isDebugEnabled() && factory.logSecurityDetails);
+ }
+ }
+
+ /**
+ * A key store that can only be added to or serialized. Only keys can be
+ * placed in it.
+ *
+ * @author Donal Fellows
+ */
+ class Keystore extends SecurityStore {
+ Keystore(char[] password) throws GeneralSecurityException {
+ super(password);
+ }
+
+ /**
+ * Add a key to the keystore. No keys can be added after the keystore is
+ * serialized.
+ *
+ * @param alias
+ * The alias of the key.
+ * @param key
+ * The secret/private key to add.
+ * @param trustChain
+ * The trusted certificate chain of the key. Should be
+ * <tt>null</tt> for secret keys.
+ * @throws KeyStoreException
+ * If anything goes wrong.
+ */
+ public void addKey(String alias, Key key, Certificate[] trustChain)
+ throws KeyStoreException {
+ setKey(alias, key, trustChain);
+ if (log.isDebugEnabled() && factory.logSecurityDetails)
+ log.debug("added key with alias \"" + alias + "\" of type "
+ + key.getClass().getCanonicalName());
+ }
+
+ /**
+ * Get the byte serialization of this keystore. This can only be fetched
+ * exactly once.
+ *
+ * @return The serialization.
+ * @throws GeneralSecurityException
+ * If anything goes wrong.
+ */
+ public byte[] serialize() throws GeneralSecurityException {
+ return serialize(log.isDebugEnabled() && factory.logSecurityDetails);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
new file mode 100644
index 0000000..ef29b55
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextDelegateImpl.java
@@ -0,0 +1,311 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.lang.String.format;
+import static javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.rmi.RemoteException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.spec.SecretKeySpec;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.taverna.server.localworker.remote.RemoteSecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.utils.UsernamePrincipal;
+import org.taverna.server.master.utils.X500Utils;
+
+/**
+ * Factoring out of the part of the security context handling that actually
+ * deals with the different types of credentials.
+ *
+ * @author Donal Fellows
+ */
+class SecurityContextDelegateImpl extends SecurityContextDelegate {
+ private static final char USERNAME_PASSWORD_SEPARATOR = '\u0000';
+ private static final String USERNAME_PASSWORD_KEY_ALGORITHM = "DUMMY";
+ /** What passwords are encoded as. */
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+ private X500Utils x500Utils;
+
+ /**
+ * Initialise the context delegate.
+ *
+ * @param run
+ * What workflow run is this for?
+ * @param owner
+ * Who owns the workflow run?
+ * @param factory
+ * What class built this object?
+ */
+ protected SecurityContextDelegateImpl(RemoteRunDelegate run,
+ UsernamePrincipal owner, SecurityContextFactory factory) {
+ super(run, owner, factory);
+ this.x500Utils = factory.x500Utils;
+ }
+
+ @Override
+ public void validateCredential(Credential c)
+ throws InvalidCredentialException {
+ try {
+ if (c instanceof Credential.Password)
+ validatePasswordCredential((Credential.Password) c);
+ else if (c instanceof Credential.KeyPair)
+ validateKeyCredential((Credential.KeyPair) c);
+ else
+ throw new InvalidCredentialException("unknown credential type");
+ } catch (InvalidCredentialException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new InvalidCredentialException(e);
+ }
+ }
+
+ @Override
+ public void addCredentialToKeystore(Credential c) throws KeyStoreException {
+ try {
+ if (c instanceof Credential.Password)
+ addUserPassToKeystore((Credential.Password) c);
+ else if (c instanceof Credential.KeyPair)
+ addKeypairToKeystore((Credential.KeyPair) c);
+ else
+ throw new KeyStoreException("unknown credential type");
+ } catch (KeyStoreException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new KeyStoreException(e);
+ }
+ }
+
+ // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+ /**
+ * Tests whether the given username+password credential descriptor is valid.
+ * If it is invalid, an exception will be thrown describing what the problem
+ * is. Validation mainly consists of listing what the username is.
+ *
+ * @param passwordDescriptor
+ * The credential descriptor to validate.
+ * @throws InvalidCredentialException
+ * If the username is empty. NB: the password may be empty!
+ * That's legal (if unwise).
+ */
+ protected void validatePasswordCredential(
+ Credential.Password passwordDescriptor)
+ throws InvalidCredentialException {
+ if (passwordDescriptor.username == null
+ || passwordDescriptor.username.trim().isEmpty())
+ throw new InvalidCredentialException("absent or empty username");
+ if (passwordDescriptor.serviceURI == null)
+ throw new InvalidCredentialException("absent service URI");
+ String keyToSave = passwordDescriptor.username
+ + USERNAME_PASSWORD_SEPARATOR + passwordDescriptor.password;
+ passwordDescriptor.loadedKey = encodeKey(keyToSave);
+ passwordDescriptor.loadedTrustChain = null;
+ }
+
+ private static Key encodeKey(String key) {
+ return new SecretKeySpec(key.getBytes(UTF8),
+ USERNAME_PASSWORD_KEY_ALGORITHM);
+ }
+
+ /**
+ * Adds a username/password credential pair to the current keystore.
+ *
+ * @param userpassCredential
+ * The username and password.
+ * @throws KeyStoreException
+ */
+ protected void addUserPassToKeystore(Credential.Password userpassCredential)
+ throws KeyStoreException {
+ String alias = format("password#%s",
+ userpassCredential.serviceURI.toASCIIString());
+ addKeypairToKeystore(alias, userpassCredential);
+ }
+
+ // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+ /**
+ * Tests whether the given key-pair credential descriptor is valid. If it is
+ * invalid, an exception will be thrown describing what the problem is.
+ *
+ * @param keypairDescriptor
+ * The descriptor to validate.
+ * @throws InvalidCredentialException
+ * If the descriptor is invalid
+ * @throws KeyStoreException
+ * If we don't understand the keystore type or the contents of
+ * the keystore
+ * @throws NoSuchAlgorithmException
+ * If the keystore is of a known type but we can't comprehend
+ * its security
+ * @throws CertificateException
+ * If the keystore does not include enough information about the
+ * trust chain of the keypair
+ * @throws UnrecoverableKeyException
+ * If we can't get the key out of the keystore
+ * @throws IOException
+ * If we can't read the keystore for prosaic reasons (e.g., file
+ * absent)
+ */
+ protected void validateKeyCredential(Credential.KeyPair keypairDescriptor)
+ throws InvalidCredentialException, KeyStoreException,
+ NoSuchAlgorithmException, CertificateException, IOException,
+ UnrecoverableKeyException {
+ if (keypairDescriptor.credentialName == null
+ || keypairDescriptor.credentialName.trim().isEmpty())
+ throw new InvalidCredentialException(
+ "absent or empty credentialName");
+
+ InputStream contentsAsStream;
+ if (keypairDescriptor.credentialBytes != null
+ && keypairDescriptor.credentialBytes.length > 0) {
+ contentsAsStream = new ByteArrayInputStream(
+ keypairDescriptor.credentialBytes);
+ keypairDescriptor.credentialFile = null;
+ } else if (keypairDescriptor.credentialFile == null
+ || keypairDescriptor.credentialFile.trim().isEmpty())
+ throw new InvalidCredentialException(
+ "absent or empty credentialFile");
+ else {
+ contentsAsStream = contents(keypairDescriptor.credentialFile);
+ keypairDescriptor.credentialBytes = new byte[0];
+ }
+ if (keypairDescriptor.fileType == null
+ || keypairDescriptor.fileType.trim().isEmpty())
+ keypairDescriptor.fileType = KeyStore.getDefaultType();
+ keypairDescriptor.fileType = keypairDescriptor.fileType.trim();
+
+ KeyStore ks = KeyStore.getInstance(keypairDescriptor.fileType);
+ char[] password = keypairDescriptor.unlockPassword.toCharArray();
+ ks.load(contentsAsStream, password);
+
+ try {
+ keypairDescriptor.loadedKey = ks.getKey(
+ keypairDescriptor.credentialName, password);
+ } catch (UnrecoverableKeyException ignored) {
+ keypairDescriptor.loadedKey = ks.getKey(
+ keypairDescriptor.credentialName, new char[0]);
+ }
+ if (keypairDescriptor.loadedKey == null)
+ throw new InvalidCredentialException(
+ "no such credential in key store");
+ keypairDescriptor.loadedTrustChain = ks
+ .getCertificateChain(keypairDescriptor.credentialName);
+ if (keypairDescriptor.loadedTrustChain == null
+ || keypairDescriptor.loadedTrustChain.length == 0)
+ throw new InvalidCredentialException(
+ "could not establish trust chain for credential");
+ }
+
+ /**
+ * Adds a key-pair to the current keystore.
+ *
+ * @param c
+ * The key-pair.
+ * @throws KeyStoreException
+ */
+ protected void addKeypairToKeystore(Credential.KeyPair c)
+ throws KeyStoreException {
+ X509Certificate subjectCert = (X509Certificate) c.loadedTrustChain[0];
+ String alias = format("keypair#%s#%s#%s",
+ getPrincipalName(subjectCert.getSubjectX500Principal()),
+ getPrincipalName(subjectCert.getIssuerX500Principal()),
+ x500Utils.getSerial(subjectCert));
+ addKeypairToKeystore(alias, c);
+ }
+}
+
+/**
+ * Special subclass that adds support for HELIO project security tokens.
+ *
+ * @author Donal Fellows
+ */
+class HelioSecurityContextDelegateImpl extends SecurityContextDelegateImpl {
+ /**
+ * Initialise the context delegate.
+ *
+ * @param run
+ * What workflow run is this for?
+ * @param owner
+ * Who owns the workflow run?
+ * @param factory
+ * What class built this object?
+ */
+ protected HelioSecurityContextDelegateImpl(RemoteRunDelegate run,
+ UsernamePrincipal owner, SecurityContextFactory factory) {
+ super(run, owner, factory);
+ }
+
+ private Log log = LogFactory.getLog("Taverna.Server.Worker");
+ /** The name of the HTTP header holding the CIS token. */
+ private static final String HELIO_CIS_TOKEN = "X-Helio-CIS";
+ private transient String helioToken;
+
+ @Override
+ public void initializeSecurityFromSOAPContext(MessageContext context) {
+ // does nothing
+ @SuppressWarnings("unchecked")
+ Map<String, List<String>> headers = (Map<String, List<String>>) context
+ .get(HTTP_REQUEST_HEADERS);
+ if (factory.supportHelioToken && headers.containsKey(HELIO_CIS_TOKEN))
+ helioToken = headers.get(HELIO_CIS_TOKEN).get(0);
+ }
+
+ @Override
+ public void initializeSecurityFromRESTContext(HttpHeaders context) {
+ // does nothing
+ MultivaluedMap<String, String> headers = context.getRequestHeaders();
+ if (factory.supportHelioToken && headers.containsKey(HELIO_CIS_TOKEN))
+ helioToken = headers.get(HELIO_CIS_TOKEN).get(0);
+ }
+
+ @Override
+ protected void conveyExtraSecuritySettings(RemoteSecurityContext rc)
+ throws RemoteException {
+ try {
+ if (factory.supportHelioToken && helioToken != null) {
+ if (factory.logSecurityDetails)
+ log.info("transfering HELIO CIS token: " + helioToken);
+ rc.setHelioToken(helioToken);
+ }
+ } finally {
+ helioToken = null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
new file mode 100644
index 0000000..1d485d8
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SecurityContextFactory.java
@@ -0,0 +1,167 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.security.Security.addProvider;
+import static java.security.Security.getProvider;
+import static java.security.Security.removeProvider;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+import java.io.Serializable;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.beans.factory.annotation.Value;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.UriBuilderFactory;
+import org.taverna.server.master.utils.CertificateChainFetcher;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.UsernamePrincipal;
+import org.taverna.server.master.utils.X500Utils;
+
+/**
+ * Singleton factory. Really is a singleton (and is also very trivial); the
+ * singleton-ness is just about limiting the number of instances of this around
+ * even when lots of serialization is going on.
+ *
+ * @see Serializable
+ * @author Donal Fellows
+ */
+public class SecurityContextFactory implements
+ org.taverna.server.master.interfaces.SecurityContextFactory {
+ private static final long serialVersionUID = 12345678987654321L;
+ private static SecurityContextFactory instance;
+ transient RunDBSupport db;
+ transient FilenameUtils fileUtils;
+ transient X500Utils x500Utils;
+ transient UriBuilderFactory uriSource;
+ transient CertificateChainFetcher certFetcher;
+ transient String httpRealm;
+ private transient PasswordIssuer passwordIssuer;
+ private transient BouncyCastleProvider provider;
+
+ /**
+ * Whether to support HELIO CIS tokens.
+ */
+ @Value("${helio.cis.enableTokenPassing}")
+ boolean supportHelioToken;
+
+ /**
+ * Whether to log the details of security (passwords, etc).
+ */
+ @Value("${log.security.details}")
+ boolean logSecurityDetails;
+
+ private Log log() {
+ return getLog("Taverna.Server.Worker.Security");
+ }
+
+ private void installAsInstance(SecurityContextFactory handle) {
+ instance = handle;
+ }
+
+ @PreDestroy
+ void removeAsSingleton() {
+ installAsInstance(null);
+ try {
+ if (provider != null)
+ removeProvider(provider.getName());
+ } catch (SecurityException e) {
+ log().warn(
+ "failed to remove BouncyCastle security provider; "
+ + "might be OK if configured in environment", e);
+ }
+ }
+
+ @PostConstruct
+ void setAsSingleton() {
+ installAsInstance(this);
+ if (getProvider(PROVIDER_NAME) == null)
+ try {
+ provider = new BouncyCastleProvider();
+ if (addProvider(provider) == -1)
+ provider = null;
+ } catch (SecurityException e) {
+ log().warn(
+ "failed to install BouncyCastle security provider; "
+ + "might be OK if already configured", e);
+ provider = null;
+ }
+ }
+
+ @Required
+ public void setRunDatabase(RunDBSupport db) {
+ this.db = db;
+ }
+
+ @Required
+ public void setCertificateFetcher(CertificateChainFetcher fetcher) {
+ this.certFetcher = fetcher;
+ }
+
+ @Required
+ public void setFilenameConverter(FilenameUtils fileUtils) {
+ this.fileUtils = fileUtils;
+ }
+
+ @Required
+ public void setX500Utils(X500Utils x500Utils) {
+ this.x500Utils = x500Utils;
+ }
+
+ @Required
+ public void setUriSource(UriBuilderFactory uriSource) {
+ this.uriSource = uriSource;
+ }
+
+ @Required
+ public void setHttpRealm(String realm) {
+ this.httpRealm = realm; //${http.realmName}
+ }
+
+ @Required
+ public void setPasswordIssuer(PasswordIssuer issuer) {
+ this.passwordIssuer = issuer;
+ }
+
+ @Override
+ public SecurityContextDelegate create(TavernaRun run,
+ UsernamePrincipal owner) throws Exception {
+ Log log = log();
+ if (log.isDebugEnabled())
+ log.debug("constructing security context delegate for " + owner);
+ RemoteRunDelegate rrd = (RemoteRunDelegate) run;
+ return new HelioSecurityContextDelegateImpl(rrd, owner, this);
+ }
+
+ private Object readResolve() {
+ if (instance == null)
+ installAsInstance(this);
+ return instance;
+ }
+
+ public String issueNewPassword() {
+ return passwordIssuer.issue();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
new file mode 100644
index 0000000..f9f4d16
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
@@ -0,0 +1,77 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.taverna.server.master.defaults.Default.NOTIFY_MESSAGE_FORMAT;
+
+import java.text.MessageFormat;
+
+import org.springframework.beans.factory.annotation.Required;
+
+/**
+ * Completion notifier that sends messages by email.
+ *
+ * @author Donal Fellows
+ */
+public class SimpleFormattedCompletionNotifier implements CompletionNotifier {
+ @Required
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @param subject
+ * The subject of the notification email.
+ */
+ @Required
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ /**
+ * @param messageFormat
+ * The template for the body of the message to send. Parameter #0
+ * will be substituted with the ID of the job, and parameter #1
+ * will be substituted with the exit code.
+ */
+ public void setMessageFormat(String messageFormat) {
+ this.format = new MessageFormat(messageFormat);
+ }
+
+ private String name;
+ private String subject;
+ private MessageFormat format = new MessageFormat(NOTIFY_MESSAGE_FORMAT);
+
+ @Override
+ public String makeCompletionMessage(String name, RemoteRunDelegate run,
+ int code) {
+ return format.format(new Object[] { name, code });
+ }
+
+ @Override
+ public String makeMessageSubject(String name, RemoteRunDelegate run,
+ int code) {
+ return subject;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
new file mode 100644
index 0000000..a81e610
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/VelocityCompletionNotifier.java
@@ -0,0 +1,121 @@
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.StringWriter;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.common.version.Version;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.UriBuilderFactory;
+
+public class VelocityCompletionNotifier implements CompletionNotifier {
+ private String subject;
+ private VelocityEngine engine;
+ private Template template;
+ private String name;
+ private String templateName;
+ private UriBuilderFactory ubf;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param subject
+ * The subject of the notification email.
+ */
+ @Required
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ /**
+ * @param engine
+ * The configured Apache Velocity engine.
+ */
+ @Required
+ public void setVelocityEngine(VelocityEngine engine) {
+ this.engine = engine;
+ }
+
+ /**
+ * @param uriBuilderFactory
+ * The configured URI builder factory.
+ */
+ @Required
+ public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
+ this.ubf = uriBuilderFactory;
+ }
+
+ /**
+ * @param name
+ * The name of the template.
+ */
+ @Required
+ public void setName(String name) {
+ this.name = name;
+ this.templateName = getClass().getName() + "_" + name + ".vtmpl";
+ }
+
+ private Template getTemplate() {
+ if (template == null)
+ synchronized(this) {
+ if (template == null)
+ template = engine.getTemplate(templateName);
+ }
+ return template;
+ }
+
+ @Override
+ public String makeCompletionMessage(String name, RemoteRunDelegate run,
+ int code) {
+ VelocityContext ctxt = new VelocityContext();
+ ctxt.put("id", name);
+ ctxt.put("uriBuilder", ubf.getRunUriBuilder(run));
+ ctxt.put("name", run.getName());
+ ctxt.put("creationTime", run.getCreationTimestamp());
+ ctxt.put("startTime", run.getStartTimestamp());
+ ctxt.put("finishTime", run.getFinishTimestamp());
+ ctxt.put("expiryTime", run.getExpiry());
+ ctxt.put("serverVersion", Version.JAVA);
+ for (Listener l : run.getListeners())
+ if (l.getName().equals("io")) {
+ for (String p : l.listProperties())
+ try {
+ ctxt.put("prop_" + p, l.getProperty(p));
+ } catch (NoListenerException e) {
+ // Ignore...
+ }
+ break;
+ }
+ StringWriter sw = new StringWriter();
+ getTemplate().merge(ctxt, sw);
+ return sw.toString();
+ }
+
+ @Override
+ public String makeMessageSubject(String name, RemoteRunDelegate run,
+ int code) {
+ return subject;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
new file mode 100644
index 0000000..510c8d0
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/WorkerModel.java
@@ -0,0 +1,216 @@
+/*
+ */
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.URI;
+import java.util.List;
+
+import org.taverna.server.master.common.Status;
+
+/**
+ * Profile of the getters and setters in a worker system. Ensures that the
+ * persisted state matches the public view on the state model at least fairly
+ * closely.
+ *
+ * @author Donal Fellows
+ */
+public interface WorkerModel extends PolicyLimits {
+
+ /**
+ * @param defaultLifetime
+ * how long a workflow run should live by default, in minutes.
+ */
+ public abstract void setDefaultLifetime(int defaultLifetime);
+
+ /**
+ * @return how long a workflow run should live by default, in minutes.
+ */
+ public abstract int getDefaultLifetime();
+
+ /**
+ * @param maxRuns
+ * the maximum number of extant workflow runs
+ */
+ public abstract void setMaxRuns(int maxRuns);
+
+ /**
+ * @param factoryProcessNamePrefix
+ * the prefix used for factory processes in RMI
+ */
+ public abstract void setFactoryProcessNamePrefix(
+ String factoryProcessNamePrefix);
+
+ /**
+ * @return the prefix used for factory processes in RMI
+ */
+ public abstract String getFactoryProcessNamePrefix();
+
+ /**
+ * @param executeWorkflowScript
+ * the script to run to actually run a workflow
+ */
+ public abstract void setExecuteWorkflowScript(String executeWorkflowScript);
+
+ /**
+ * @return the script to run to actually run a workflow
+ */
+ public abstract String getExecuteWorkflowScript();
+
+ /**
+ * @param extraArgs
+ * the extra arguments to pass into the workflow runner
+ */
+ public abstract void setExtraArgs(String[] extraArgs);
+
+ /**
+ * @return the extra arguments to pass into the workflow runner
+ */
+ public abstract String[] getExtraArgs();
+
+ /**
+ * @param waitSeconds
+ * the number of seconds to wait for subprocesses to start
+ */
+ public abstract void setWaitSeconds(int waitSeconds);
+
+ /**
+ * @return the number of seconds to wait for subprocesses to start
+ */
+ public abstract int getWaitSeconds();
+
+ /**
+ * @param sleepMS
+ * milliseconds to wait between polling for a started
+ * subprocess's status
+ */
+ public abstract void setSleepMS(int sleepMS);
+
+ /**
+ * @return milliseconds to wait between polling for a started subprocess's
+ * status
+ */
+ public abstract int getSleepMS();
+
+ /**
+ * @param serverWorkerJar
+ * the full path name of the file system access worker
+ * subprocess's implementation JAR
+ */
+ public abstract void setServerWorkerJar(String serverWorkerJar);
+
+ /**
+ * @return the full path name of the file system access worker subprocess's
+ * implementation JAR
+ */
+ public abstract String getServerWorkerJar();
+
+ /**
+ * @param javaBinary
+ * the full path name to the Java binary to use
+ */
+ public abstract void setJavaBinary(String javaBinary);
+
+ /**
+ * @return the full path name to the Java binary to use
+ */
+ public abstract String getJavaBinary();
+
+ /**
+ * @param registryPort
+ * what port is the RMI registry on
+ */
+ public abstract void setRegistryPort(int registryPort);
+
+ /**
+ * @return what port is the RMI registry on
+ */
+ public abstract int getRegistryPort();
+
+ /**
+ * @param registryHost
+ * what host (network interface) is the RMI registry on
+ */
+ public abstract void setRegistryHost(String registryHost);
+
+ /**
+ * @return what host (network interface) is the RMI registry on
+ */
+ public abstract String getRegistryHost();
+
+ /**
+ * @param serverForkerJar
+ * the full path name of the impersonation engine's
+ * implementation JAR
+ */
+ public abstract void setServerForkerJar(String serverForkerJar);
+
+ /**
+ * @return the full path name of the impersonation engine's implementation
+ * JAR
+ */
+ public abstract String getServerForkerJar();
+
+ /**
+ * @param passwordFile
+ * the full path name of a file containing a password to use with
+ * sudo (or empty for none)
+ */
+ public abstract void setPasswordFile(String passwordFile);
+
+ /**
+ * @return the full path name of a file containing a password to use with
+ * sudo (or empty for none)
+ */
+ public abstract String getPasswordFile();
+
+ /**
+ * @param operatingLimit
+ * the maximum number of runs in the
+ * {@linkplain Status#Operating operating} state at once
+ */
+ public abstract void setOperatingLimit(int operatingLimit);
+
+ @Override
+ void setPermittedWorkflowURIs(List<URI> permittedWorkflows);
+
+ /**
+ * @return the full path name of the RMI registry subprocess's
+ * implementation JAR
+ */
+ String getRegistryJar();
+
+ /**
+ * @param rmiRegistryJar
+ * the full path name of the RMI registry subprocess's
+ * implementation JAR
+ */
+ void setRegistryJar(String rmiRegistryJar);
+
+ /**
+ * @return whether a run should generate provenance information by default
+ */
+ boolean getGenerateProvenance();
+
+ /**
+ * @param generateProvenance
+ * whether a run should generate provenance information by
+ * default
+ */
+ void setGenerateProvenance(boolean generateProvenance);
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
new file mode 100644
index 0000000..e96b794
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/worker/package-info.java
@@ -0,0 +1,23 @@
+/*
+ */
+/**
+ * A Taverna Server back-end that works by forking off workflow executors.
+ */
+package org.taverna.server.master.worker;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
deleted file mode 100644
index 051a037..0000000
--- a/taverna-server-webapp/src/main/java/org/taverna/server/master/ContentsDescriptorBuilder.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- */
-package org.taverna.server.master;
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import static eu.medsea.util.MimeUtil.getMimeType;
-import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
-import static javax.ws.rs.core.UriBuilder.fromUri;
-import static org.apache.commons.logging.LogFactory.getLog;
-import static org.taverna.server.master.common.Uri.secure;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
-
-import org.apache.commons.logging.Log;
-import org.springframework.beans.factory.annotation.Required;
-import org.taverna.server.master.exceptions.FilesystemAccessException;
-import org.taverna.server.master.exceptions.NoDirectoryEntryException;
-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.TavernaRun;
-import org.taverna.server.master.interfaces.UriBuilderFactory;
-import org.taverna.server.master.utils.FilenameUtils;
-import org.taverna.server.port_description.AbsentValue;
-import org.taverna.server.port_description.AbstractPortDescription;
-import org.taverna.server.port_description.AbstractValue;
-import org.taverna.server.port_description.ErrorValue;
-import org.taverna.server.port_description.InputDescription;
-import org.taverna.server.port_description.InputDescription.InputPort;
-import org.taverna.server.port_description.LeafValue;
-import org.taverna.server.port_description.ListValue;
-import org.taverna.server.port_description.OutputDescription;
-import org.taverna.server.port_description.OutputDescription.OutputPort;
-
-import org.apache.taverna.scufl2.api.container.WorkflowBundle;
-import org.apache.taverna.scufl2.api.core.Workflow;
-import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
-import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
-
-/**
- * A class that is used to build descriptions of the contents of a workflow
- * run's filesystem.
- *
- * @author Donal Fellows
- */
-public class ContentsDescriptorBuilder {
- private Log log = getLog("Taverna.Server.Webapp");
- private FilenameUtils fileUtils;
- private UriBuilderFactory uriBuilderFactory;
-
- @Required
- public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
- this.uriBuilderFactory = uriBuilderFactory;
- }
-
- @Required
- public void setFileUtils(FilenameUtils fileUtils) {
- this.fileUtils = fileUtils;
- }
-
- // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
- private Workflow fillInFromWorkflow(TavernaRun run, UriBuilder ub,
- AbstractPortDescription portDesc) throws IOException {
- WorkflowBundle bundle = run.getWorkflow().getScufl2Workflow();
- bundle.getMainWorkflow().getInputPorts();
- portDesc.fillInBaseData(bundle.getMainWorkflow()
- .getIdentifier().toString(), run.getId(), ub.build());
- return bundle.getMainWorkflow();
- }
-
- /**
- * Computes the depth of value in a descriptor.
- *
- * @param value
- * The value description to characterise.
- * @return Its depth (i.e., the depth of the port outputting the value) or
- * <tt>null</tt> if that is impossible to determine.
- */
- private Integer computeDepth(AbstractValue value) {
- if (value instanceof ListValue) {
- int mv = 1;
- for (AbstractValue v : ((ListValue) value).contents) {
- Integer d = computeDepth(v);
- if (d != null && mv <= d)
- mv = d + 1;
- }
- return mv;
- } else if (value instanceof LeafValue || value instanceof ErrorValue)
- return 0;
- else
- return null;
- }
-
- /**
- * Build a description of a leaf value.
- *
- * @param file
- * The file representing the value.
- * @return A value descriptor.
- * @throws FilesystemAccessException
- * If anything goes wrong.
- */
- private LeafValue constructLeafValue(File file)
- throws FilesystemAccessException {
- LeafValue v = new LeafValue();
- v.fileName = file.getFullName();
- v.byteLength = file.getSize();
- try {
- byte[] head = file.getContents(0, 1024);
- v.contentType = getMimeType(new ByteArrayInputStream(head));
- } catch (Exception e) {
- v.contentType = APPLICATION_OCTET_STREAM_TYPE.toString();
- }
- return v;
- }
-
- /**
- * Build a description of an error value.
- *
- * @param file
- * The file representing the error.
- * @return A value descriptor.
- * @throws FilesystemAccessException
- * If anything goes wrong.
- */
- private ErrorValue constructErrorValue(File file)
- throws FilesystemAccessException {
- ErrorValue v = new ErrorValue();
- v.fileName = file.getFullName();
- v.byteLength = file.getSize();
- return v;
- }
-
- /**
- * Build a description of a list value.
- *
- * @param dir
- * The directory representing the list.
- * @param ub
- * The factory for URIs.
- * @return A value descriptor.
- * @throws FilesystemAccessException
- * If anything goes wrong.
- */
- private ListValue constructListValue(Directory dir, UriBuilder ub)
- throws FilesystemAccessException {
- ListValue v = new ListValue();
- v.length = 0;
- Set<DirectoryEntry> contents = new HashSet<>(dir.getContents());
- Iterator<DirectoryEntry> it = contents.iterator();
- while (it.hasNext())
- if (!it.next().getName().matches("^[0-9]+([.].*)?$"))
- it.remove();
- for (int i = 1; !contents.isEmpty(); i++) {
- String exact = Integer.toString(i);
- AbstractValue subval = constructValue(contents, ub, exact);
- v.contents.add(subval);
- if (!(subval instanceof AbsentValue)) {
- v.length = i;
- String pfx = i + ".";
- for (DirectoryEntry de : contents)
- if (de.getName().equals(exact)
- || de.getName().startsWith(pfx)) {
- contents.remove(de);
- break;
- }
- }
- }
- return v;
- }
-
- /**
- * Build a value description.
- *
- * @param parentContents
- * The contents of the parent directory.
- * @param ub
- * The factory for URIs.
- * @param name
- * The name of the value's file/directory representative.
- * @return A value descriptor.
- * @throws FilesystemAccessException
- * If anything goes wrong.
- */
- private AbstractValue constructValue(
- Collection<DirectoryEntry> parentContents, UriBuilder ub,
- String name) throws FilesystemAccessException {
- String error = name + ".error";
- String prefix = name + ".";
- for (DirectoryEntry entry : parentContents) {
- AbstractValue av;
- if (entry.getName().equals(error) && entry instanceof File) {
- av = constructErrorValue((File) entry);
- } else if (!entry.getName().equals(name)
- && !entry.getName().startsWith(prefix))
- continue;
- else if (entry instanceof File)
- av = constructLeafValue((File) entry);
- else
- av = constructListValue((Directory) entry, ub);
- String fullPath = entry.getFullName().replaceFirst("^/", "");
- av.href = ub.clone().path(fullPath).build();
- return av;
- }
- return new AbsentValue();
- }
-
- /**
- * Construct a description of the outputs of a workflow run.
- *
- * @param run
- * The workflow run whose outputs are to be described.
- * @param ui
- * The origin for URIs.
- * @return The description, which can be serialized to XML.
- * @throws FilesystemAccessException
- * If something goes wrong reading the directories.
- * @throws NoDirectoryEntryException
- * If something goes wrong reading the directories.
- */
- public OutputDescription makeOutputDescriptor(TavernaRun run, UriInfo ui)
- throws FilesystemAccessException, NoDirectoryEntryException {
- OutputDescription descriptor = new OutputDescription();
- try {
- UriBuilder ub = getRunUriBuilder(run, ui);
- Workflow dataflow = fillInFromWorkflow(run, ub, descriptor);
- Collection<DirectoryEntry> outs = null;
- ub = ub.path("wd/{path}");
- for (OutputWorkflowPort output : dataflow.getOutputPorts()) {
- OutputPort p = descriptor.addPort(output.getName());
- if (run.getOutputBaclavaFile() == null) {
- if (outs == null)
- outs = fileUtils.getDirectory(run, "out").getContents();
- p.output = constructValue(outs, ub, p.name);
- p.depth = computeDepth(p.output);
- }
- }
- } catch (IOException e) {
- log.info("failure in conversion to .scufl2", e);
- }
- return descriptor;
- }
-
- private UriBuilder getRunUriBuilder(TavernaRun run, UriInfo ui) {
- if (ui == null)
- return secure(uriBuilderFactory.getRunUriBuilder(run));
- else
- return secure(fromUri(ui.getAbsolutePath().toString()
- .replaceAll("/(out|in)put/?$", "")));
- }
-
- /**
- * Constructs input descriptions.
- *
- * @param run
- * The run to build for.
- * @param ui
- * The mechanism for building URIs.
- * @return The description of the <i>expected</i> inputs of the run.
- */
- public InputDescription makeInputDescriptor(TavernaRun run, UriInfo ui) {
- InputDescription desc = new InputDescription();
- try {
- UriBuilder ub = getRunUriBuilder(run, ui);
- Workflow workflow = fillInFromWorkflow(run, ub, desc);
- ub = ub.path("input/{name}");
- for (InputWorkflowPort port : workflow.getInputPorts()) {
- InputPort in = desc.addPort(port.getName());
- in.href = ub.build(in.name);
- try {
- in.depth = port.getDepth();
- } catch (NumberFormatException ex) {
- in.depth = null;
- }
- }
- } catch (IOException e) {
- log.info("failure in conversion to .scufl2", e);
- }
- return desc;
- }
-}