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:18 UTC
[11/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/utils/CallTimingFilter.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/CallTimingFilter.java b/server-webapp/src/main/java/org/taverna/server/master/utils/CallTimingFilter.java
new file mode 100644
index 0000000..aead6fa
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/CallTimingFilter.java
@@ -0,0 +1,65 @@
+/**
+ *
+ */
+package org.taverna.server.master.utils;
+
+import static java.lang.String.format;
+import static java.lang.System.nanoTime;
+import static org.apache.commons.logging.LogFactory.getLog;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Logs the time it takes to service HTTP calls into Taverna Server.
+ * <p>
+ * This class is currently not used.
+ *
+ * @author Donal Fellows
+ */
+public class CallTimingFilter implements Filter {
+ private Log log;
+ private String name;
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ log = getLog("Taverna.Server.Performance");
+ name = filterConfig.getInitParameter("name");
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ if (request instanceof HttpServletRequest)
+ doFilter((HttpServletRequest) request,
+ (HttpServletResponse) response, chain);
+ else
+ chain.doFilter(request, response);
+ }
+
+ public void doFilter(HttpServletRequest request,
+ HttpServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ long start = nanoTime();
+ chain.doFilter(request, response);
+ long elapsedTime = nanoTime() - start;
+ log.info(format("%s call to %s %s took %.3fms", name,
+ request.getMethod(), request.getRequestURI(),
+ elapsedTime / 1000000.0));
+ }
+
+ @Override
+ public void destroy() {
+ log = null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/CapabilityLister.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/CapabilityLister.java b/server-webapp/src/main/java/org/taverna/server/master/utils/CapabilityLister.java
new file mode 100644
index 0000000..54b0420
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/CapabilityLister.java
@@ -0,0 +1,44 @@
+package org.taverna.server.master.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+
+import org.taverna.server.master.common.Capability;
+
+/**
+ * Utility for listing the capabilities supported by this Taverna Server
+ * installation.
+ *
+ * @author Donal Fellows
+ */
+public class CapabilityLister {
+ public static final String CAPABILITY_RESOURCE_FILE = "/capabilities.properties";
+ private Properties properties = new Properties();
+
+ @PostConstruct
+ void loadCapabilities() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream(
+ CAPABILITY_RESOURCE_FILE)) {
+ if (is != null)
+ properties.load(is);
+ }
+ }
+
+ public List<Capability> getCapabilities() {
+ List<Capability> caps = new ArrayList<>();
+ for (Entry<Object, Object> entry : properties.entrySet()) {
+ Capability c = new Capability();
+ c.capability = URI.create(entry.getKey().toString());
+ c.version = entry.getValue().toString();
+ caps.add(c);
+ }
+ return caps;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java b/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java
new file mode 100644
index 0000000..c27502f
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/CertificateChainFetcher.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.xml.ws.Holder;
+
+/**
+ * Obtains the certificate chain for an arbitrary SSL service. Maintains a
+ * cache.
+ *
+ * @author Donal Fellows
+ */
+public class CertificateChainFetcher {
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getKeystoreType() {
+ return keystoreType;
+ }
+
+ public void setKeystoreType(String keystoreType) {
+ this.keystoreType = keystoreType;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ public void setSecure(boolean secure) {
+ this.secure = secure;
+ }
+
+ private boolean secure = true;
+ private String protocol = "TLS";
+ private String keystoreType = KeyStore.getDefaultType();
+ private String algorithm = TrustManagerFactory.getDefaultAlgorithm();
+ private int timeout = 10000;
+
+ /**
+ * Get the certificate chain for a service.
+ *
+ * @param host
+ * The host (name or IP address) to contact the service on.
+ * @param port
+ * The port to contact the service on.
+ * @return The certificate chain, or <tt>null</tt> if no credentials are
+ * available.
+ * @throws NoSuchAlgorithmException
+ * If the trust manager cannot be set up because of algorithm
+ * problems.
+ * @throws KeyStoreException
+ * If the trust manager cannot be set up because of problems
+ * with the keystore type.
+ * @throws CertificateException
+ * If a bad certificate is present in the default keystore;
+ * <i>should be impossible</i>.
+ * @throws IOException
+ * If problems happen when trying to contact the service.
+ * @throws KeyManagementException
+ * If the SSL context can't have its special context manager
+ * installed.
+ */
+ private X509Certificate[] getCertificateChainForService(String host,
+ int port) throws NoSuchAlgorithmException, KeyStoreException,
+ CertificateException, IOException, KeyManagementException {
+ KeyStore ks = KeyStore.getInstance(keystoreType);
+ SSLContext context = SSLContext.getInstance(protocol);
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+ ks.load(null, null);
+ tmf.init(ks);
+ final Holder<X509Certificate[]> chain = new Holder<>();
+ final X509TrustManager defaultTrustManager = (X509TrustManager) tmf
+ .getTrustManagers()[0];
+ context.init(null, new TrustManager[] { new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] clientChain,
+ String authType) throws CertificateException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] serverChain,
+ String authType) throws CertificateException {
+ chain.value = serverChain;
+ defaultTrustManager.checkServerTrusted(serverChain, authType);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ throw new UnsupportedOperationException();
+ }
+ } }, null);
+ SSLSocketFactory factory = context.getSocketFactory();
+ try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {
+ socket.setSoTimeout(timeout);
+ socket.startHandshake();
+ } catch (SSLException e) {
+ // Ignore
+ }
+ return chain.value;
+ }
+
+ private Map<URI, List<X509Certificate>> cache = new HashMap<>();
+
+ /**
+ * Gets the certificate chain for a service identified by URI.
+ *
+ * @param uri
+ * The URI of the (secure) service to identify.
+ * @return The certificate chain. Will be <tt>null</tt> if the service is
+ * not secure.
+ * @throws IOException
+ * If the service is unreachable or other connection problems
+ * occur.
+ * @throws GeneralSecurityException
+ * If any of a number of security-related problems occur, such
+ * as an inability to match detailed security protocols.
+ */
+ public List<X509Certificate> getTrustsForURI(URI uri) throws IOException,
+ GeneralSecurityException {
+ if (!secure)
+ return null;
+ synchronized (this) {
+ if (!cache.containsKey(uri)) {
+ int port = uri.getPort();
+ if (port == -1)
+ switch (uri.getScheme()) {
+ case "http":
+ port = 80;
+ break;
+ case "https":
+ port = 443;
+ break;
+ default:
+ return null;
+ }
+ X509Certificate[] chain = getCertificateChainForService(
+ uri.getHost(), port);
+ if (chain != null)
+ cache.put(uri, unmodifiableList(asList(chain)));
+ else
+ cache.put(uri, null);
+ }
+ return cache.get(uri);
+ }
+ }
+
+ /**
+ * Flushes the cache.
+ */
+ public void flushCache() {
+ synchronized (this) {
+ cache.clear();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/Contextualizer.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/Contextualizer.java b/server-webapp/src/main/java/org/taverna/server/master/utils/Contextualizer.java
new file mode 100644
index 0000000..884ff94
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/Contextualizer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.UriInfo;
+
+import org.springframework.web.context.ServletContextAware;
+import org.taverna.server.master.common.version.Version;
+
+/**
+ * Convert a string (URL, etc) to a version that is contextualized to the
+ * web-application.
+ *
+ * @author Donal Fellows
+ */
+public class Contextualizer implements ServletContextAware {
+ static final String ROOT_PLACEHOLDER = "%{WEBAPPROOT}";
+ static final String VERSION_PLACEHOLDER = "%{VERSION}";
+ static final String BASE_PLACEHOLDER = "%{BASEURL}";
+
+ /**
+ * Apply the contextualization operation. This consists of replacing the
+ * string <tt>{@value #ROOT_PLACEHOLDER}</tt> with the real root of the webapp.
+ *
+ * @param input
+ * the string to contextualize
+ * @return the contextualized string
+ */
+ public String contextualize(String input) {
+ // Hack to work around bizarre CXF bug
+ String path = context.getRealPath("/").replace("%2D", "-");
+ return input.replace(ROOT_PLACEHOLDER, path).replace(
+ VERSION_PLACEHOLDER, Version.JAVA);
+ }
+
+ /**
+ * Apply the contextualization operation. This consists of replacing the
+ * string <tt>{@value #ROOT_PLACEHOLDER}</tt> with the real root of the
+ * webapp.
+ *
+ * @param ui
+ * Where to get information about the URL used to access the
+ * webapp.
+ * @param input
+ * the string to contextualize
+ * @return the contextualized string
+ */
+ public String contextualize(UriInfo ui, String input) {
+ // Hack to work around bizarre CXF bug
+ String baseuri = ui.getBaseUri().toString().replace("%2D", "-");
+ if (baseuri.endsWith("/"))
+ baseuri = baseuri.substring(0, baseuri.length() - 1);
+ return contextualize(input).replace(BASE_PLACEHOLDER, baseuri);
+ }
+
+ private ServletContext context;
+
+ @Override
+ public void setServletContext(ServletContext servletContext) {
+ context = servletContext;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java b/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java
new file mode 100644
index 0000000..f8b39e3
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/DerbyUtils.java
@@ -0,0 +1,68 @@
+package org.taverna.server.master.utils;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Utility class, used to make Derby less broken.
+ *
+ * @see <a
+ * href="http://stackoverflow.com/questions/1004327/getting-rid-of-derby-log">
+ * Getting rid of derby.log </a>
+ * @see <a
+ * href="http://stackoverflow.com/questions/3339736/set-system-property-with-spring-configuration-file">
+ * Set system property with Spring configuration file </a>
+ */
+public class DerbyUtils {
+ /**
+ * A writer that channels things on to the log.
+ */
+ public static final Writer TO_LOG = new DBLog();
+ // Hack
+ public static final Writer DEV_NULL = TO_LOG;
+}
+
+class DBLog extends Writer {
+ private Log log = LogFactory.getLog("Taverna.Server.Database");
+ private StringBuilder sb = new StringBuilder();
+ private boolean closed = false;
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ if (closed)
+ throw new EOFException();
+ if (!log.isInfoEnabled())
+ return;
+ sb.append(cbuf, off, len);
+ while (!closed) {
+ int idx = sb.indexOf("\n"), realIdx = idx;
+ if (idx < 0)
+ break;
+ char ch;
+ while (idx > 0 && ((ch = sb.charAt(idx - 1)) == '\r' || ch == ' ' || ch == '\t'))
+ idx--;
+ if (idx > 0)
+ log.info(sb.substring(0, idx));
+ sb.delete(0, realIdx + 1);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (sb.length() > 0) {
+ log.info(sb.toString());
+ sb = new StringBuilder();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ flush();
+ closed = true;
+ sb = null;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/FilenameUtils.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/FilenameUtils.java b/server-webapp/src/main/java/org/taverna/server/master/utils/FilenameUtils.java
new file mode 100644
index 0000000..19299e2
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/FilenameUtils.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import java.util.List;
+
+import javax.ws.rs.core.PathSegment;
+
+import org.taverna.server.master.common.DirEntryReference;
+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;
+
+/**
+ * Utility functions for getting entries from directories.
+ *
+ * @author Donal Fellows
+ */
+public class FilenameUtils {
+ private static final String TYPE_ERROR = "trying to take subdirectory of file";
+ private static final String NO_FILE = "no such directory entry";
+ private static final String NOT_A_FILE = "not a file";
+ private static final String NOT_A_DIR = "not a directory";
+
+ /**
+ * Get a named directory entry from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param name
+ * The name of the directory entry to look up.
+ * @return The directory entry whose name is equal to the last part of the
+ * path; an empty path will retrieve the working directory handle
+ * itself.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ * @throws FilesystemAccessException
+ * If the directory isn't specified or isn't readable.
+ */
+ public DirectoryEntry getDirEntry(TavernaRun run, String name)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ Directory dir = run.getWorkingDirectory();
+ if (name == null || name.isEmpty())
+ return dir;
+ DirectoryEntry found = dir;
+ boolean mustBeLast = false;
+
+ // Must be nested loops; avoids problems with %-encoded "/" chars
+ for (String bit : name.split("/")) {
+ if (mustBeLast)
+ throw new FilesystemAccessException(TYPE_ERROR);
+ found = getEntryFromDir(bit, dir);
+ dir = null;
+ if (found instanceof Directory) {
+ dir = (Directory) found;
+ mustBeLast = false;
+ } else
+ mustBeLast = true;
+ }
+ return found;
+ }
+
+ /**
+ * Get a named directory entry from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param d
+ * The path segments describing what to look up.
+ * @return The directory entry whose name is equal to the last part of the
+ * path; an empty path will retrieve the working directory handle
+ * itself.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ * @throws FilesystemAccessException
+ * If the directory isn't specified or isn't readable.
+ */
+ public DirectoryEntry getDirEntry(TavernaRun run, List<PathSegment> d)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ Directory dir = run.getWorkingDirectory();
+ if (d == null || d.isEmpty())
+ return dir;
+ DirectoryEntry found = dir;
+ boolean mustBeLast = false;
+
+ // Must be nested loops; avoids problems with %-encoded "/" chars
+ for (PathSegment segment : d)
+ for (String bit : segment.getPath().split("/")) {
+ if (mustBeLast)
+ throw new FilesystemAccessException(TYPE_ERROR);
+ found = getEntryFromDir(bit, dir);
+ dir = null;
+ if (found instanceof Directory) {
+ dir = (Directory) found;
+ mustBeLast = false;
+ } else
+ mustBeLast = true;
+ }
+ return found;
+ }
+
+ /**
+ * Get a named directory entry from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param d
+ * The directory reference describing what to look up.
+ * @return The directory entry whose name is equal to the last part of the
+ * path in the directory reference; an empty path will retrieve the
+ * working directory handle itself.
+ * @throws FilesystemAccessException
+ * If the directory isn't specified or isn't readable.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ */
+ public DirectoryEntry getDirEntry(TavernaRun run, DirEntryReference d)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ Directory dir = run.getWorkingDirectory();
+ if (d == null || d.path == null || d.path.isEmpty())
+ return dir;
+ DirectoryEntry found = dir;
+ boolean mustBeLast = false;
+
+ for (String bit : d.path.split("/")) {
+ if (mustBeLast)
+ throw new FilesystemAccessException(TYPE_ERROR);
+ found = getEntryFromDir(bit, dir);
+ dir = null;
+ if (found instanceof Directory) {
+ dir = (Directory) found;
+ mustBeLast = false;
+ } else
+ mustBeLast = true;
+ }
+ return found;
+ }
+
+ /**
+ * Get a named directory entry from a directory.
+ *
+ * @param name
+ * The name of the entry; must be "<tt>/</tt>"-free.
+ * @param dir
+ * The directory to look in.
+ * @return The directory entry whose name is equal to the given name.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ * @throws FilesystemAccessException
+ * If the directory isn't specified or isn't readable.
+ */
+ private DirectoryEntry getEntryFromDir(String name, Directory dir)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ if (dir == null)
+ throw new FilesystemAccessException(NO_FILE);
+ for (DirectoryEntry entry : dir.getContents())
+ if (entry.getName().equals(name))
+ return entry;
+ throw new NoDirectoryEntryException(NO_FILE);
+ }
+
+ /**
+ * Get a named directory from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param d
+ * The directory reference describing what to look up.
+ * @return The directory whose name is equal to the last part of the path in
+ * the directory reference; an empty path will retrieve the working
+ * directory handle itself.
+ * @throws FilesystemAccessException
+ * If the directory isn't specified or isn't readable, or if the
+ * name doesn't refer to a directory.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ */
+ public Directory getDirectory(TavernaRun run, DirEntryReference d)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ DirectoryEntry dirEntry = getDirEntry(run, d);
+ if (dirEntry instanceof Directory)
+ return (Directory) dirEntry;
+ throw new FilesystemAccessException(NOT_A_DIR);
+ }
+
+ /**
+ * Get a named directory from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param name
+ * The name of the directory to look up.
+ * @return The directory.
+ * @throws FilesystemAccessException
+ * If the directory isn't specified or isn't readable, or if the
+ * name doesn't refer to a directory.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ */
+ public Directory getDirectory(TavernaRun run, String name)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ DirectoryEntry dirEntry = getDirEntry(run, name);
+ if (dirEntry instanceof Directory)
+ return (Directory) dirEntry;
+ throw new FilesystemAccessException(NOT_A_DIR);
+ }
+
+ /**
+ * Get a named file from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param d
+ * The directory reference describing what to look up.
+ * @return The file whose name is equal to the last part of the path in the
+ * directory reference; an empty path will retrieve the working
+ * directory handle itself.
+ * @throws FilesystemAccessException
+ * If the file isn't specified or isn't readable, or if the name
+ * doesn't refer to a file.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ */
+ public File getFile(TavernaRun run, DirEntryReference d)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ DirectoryEntry dirEntry = getDirEntry(run, d);
+ if (dirEntry instanceof File)
+ return (File) dirEntry;
+ throw new FilesystemAccessException(NOT_A_FILE);
+ }
+
+ /**
+ * Get a named file from a workflow run.
+ *
+ * @param run
+ * The run whose working directory is to be used as the root of
+ * the search.
+ * @param name
+ * The name of the file to look up.
+ * @return The file whose name is equal to the last part of the path in the
+ * directory reference; an empty path will retrieve the working
+ * directory handle itself.
+ * @throws FilesystemAccessException
+ * If the file isn't specified or isn't readable, or if the name
+ * doesn't refer to a file.
+ * @throws NoDirectoryEntryException
+ * If there is no such entry.
+ */
+ public File getFile(TavernaRun run, String name)
+ throws FilesystemAccessException, NoDirectoryEntryException {
+ DirectoryEntry dirEntry = getDirEntry(run, name);
+ if (dirEntry instanceof File)
+ return (File) dirEntry;
+ throw new FilesystemAccessException(NOT_A_FILE);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java b/server-webapp/src/main/java/org/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java
new file mode 100644
index 0000000..ff52e81
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java
@@ -0,0 +1,18 @@
+package org.taverna.server.master.utils;
+
+import org.apache.cxf.jaxrs.provider.ProviderFactory;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+
+public class FlushThreadLocalCacheInterceptor extends
+ AbstractPhaseInterceptor<Message> {
+ public FlushThreadLocalCacheInterceptor() {
+ super(Phase.USER_LOGICAL_ENDING);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ ProviderFactory.getInstance(message).clearThreadLocalProxies();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/InvocationCounter.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/InvocationCounter.java b/server-webapp/src/main/java/org/taverna/server/master/utils/InvocationCounter.java
new file mode 100644
index 0000000..55a260b
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/InvocationCounter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+
+/**
+ * This class is responsible for counting all invocations of publicly-exposed
+ * methods of the webapp. It's connected to the webapp primarily through an
+ * AspectJ-style pointcut.
+ *
+ * @author Donal Fellows
+ */
+@Aspect
+public class InvocationCounter {
+ private int count;
+
+ @Before("@annotation(org.taverna.server.master.utils.InvocationCounter.CallCounted)")
+ public synchronized void count() {
+ count++;
+ }
+
+ public synchronized int getCount() {
+ return count;
+ }
+
+ /**
+ * Mark methods that should be counted by the invocation counter.
+ *
+ * @author Donal Fellows
+ */
+ @Retention(RUNTIME)
+ @Documented
+ @Target(METHOD)
+ public static @interface CallCounted {
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/JCECheck.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/JCECheck.java b/server-webapp/src/main/java/org/taverna/server/master/utils/JCECheck.java
new file mode 100644
index 0000000..252e316
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/JCECheck.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import static java.lang.Integer.MAX_VALUE;
+import static javax.crypto.Cipher.getMaxAllowedKeyLength;
+import static org.apache.commons.logging.LogFactory.getLog;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Trivial bean that checks for whether the JCE policy files that allow
+ * unlimited strength security are present, and warns in the log if not.
+ *
+ * @author Donal Fellows
+ */
+public class JCECheck {
+ /**
+ * Write a message to the log that says whether an unlimited strength
+ * {@linkplain #Cipher cipher} is present. This is the official proxy for
+ * whether the unlimited strength JCE policy files have been installed; if
+ * absent, the message is logged as a warning, otherwise it is just
+ * informational.
+ */
+ @PostConstruct
+ public void checkForUnlimitedJCE() {
+ Log log = getLog("Taverna.Server.Utils");
+
+ try {
+ if (getMaxAllowedKeyLength("AES") < MAX_VALUE)
+ log.warn("maximum key length very short; unlimited "
+ + "strength JCE policy files maybe missing");
+ else
+ log.info("unlimited strength JCE policy in place");
+ } catch (GeneralSecurityException e) {
+ log.warn("problem computing key length limits!", e);
+ }
+ }
+
+ /**
+ * @return Whether the unlimited strength JCE policy files are present (or
+ * rather whether an unlimited strength {@linkplain #Cipher cipher}
+ * is permitted).
+ */
+ public boolean isUnlimitedStrength() {
+ try {
+ return getMaxAllowedKeyLength("AES") == MAX_VALUE;
+ } catch (NoSuchAlgorithmException e) {
+ return false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java b/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java
new file mode 100644
index 0000000..ba9ec81
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/JDOSupport.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.apache.commons.logging.LogFactory.getLog;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.WeakHashMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.PreDestroy;
+import javax.jdo.JDOException;
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+import javax.jdo.Query;
+import javax.jdo.Transaction;
+
+import org.apache.commons.logging.Log;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.beans.factory.annotation.Required;
+
+/**
+ * Simple support class that wraps up and provides access to the correct parts
+ * of JDO.
+ *
+ * @author Donal Fellows
+ *
+ * @param <T> The context class that the subclass will be working with.
+ */
+public abstract class JDOSupport<T> {
+ private Class<T> contextClass;
+ private PersistenceManagerBuilder pmb;
+
+ /**
+ * Instantiate this class, supplying it a handle to the class that will be
+ * used to provide context for queries and accesses.
+ *
+ * @param contextClass
+ * Must match the type parameter to the class itself.
+ */
+ protected JDOSupport(@Nonnull Class<T> contextClass) {
+ this.contextClass = contextClass;
+ }
+
+ /**
+ * @param persistenceManagerBuilder
+ * The JDO engine to use for managing persistence.
+ */
+ @Required
+ public void setPersistenceManagerBuilder(
+ PersistenceManagerBuilder persistenceManagerBuilder) {
+ pmb = persistenceManagerBuilder;
+ }
+
+ private PersistenceManager pm() {
+ if (isPersistent())
+ return pmb.getPersistenceManager();
+ return null;
+ }
+
+ /**
+ * Has this class actually been configured with a persistence manager by
+ * Spring?
+ *
+ * @return Whether there is a persistence manager installed.
+ */
+ protected boolean isPersistent() {
+ return pmb != null;
+ }
+
+ /**
+ * Get an instance of a query in JDOQL.
+ *
+ * @param filter
+ * The filter part of the query.
+ * @return The query, which should be executed to retrieve the results.
+ */
+ @Nonnull
+ protected Query query(@Nonnull String filter) {
+ return pm().newQuery(contextClass, filter);
+ }
+
+ /**
+ * Get an instance of a named query attached to the context class (as an
+ * annotation).
+ *
+ * @param name
+ * The name of the query.
+ * @return The query, which should be executed to retrieve the results.
+ * @see javax.jdo.annotations.Query
+ */
+ @Nonnull
+ protected Query namedQuery(@Nonnull String name) {
+ return pm().newNamedQuery(contextClass, name);
+ }
+
+ /**
+ * Make an instance of the context class persist in the database. It's
+ * identity must not already exist.
+ *
+ * @param value
+ * The instance to persist.
+ * @return The persistence-coupled instance.
+ */
+ @Nullable
+ protected T persist(@Nullable T value) {
+ if (value == null)
+ return null;
+ return pm().makePersistent(value);
+ }
+
+ /**
+ * Make a non-persistent (i.e., will hold its value past the end of the
+ * transaction) copy of a persistence-coupled instance of the context class.
+ *
+ * @param value
+ * The value to decouple.
+ * @return The non-persistent copy.
+ */
+ @Nullable
+ protected T detach(@Nullable T value) {
+ if (value == null)
+ return null;
+ return pm().detachCopy(value);
+ }
+
+ /**
+ * Look up an instance of the context class by its identity.
+ *
+ * @param id
+ * The identity of the object.
+ * @return The instance, which is persistence-coupled.
+ */
+ @Nullable
+ protected T getById(Object id) {
+ try {
+ return pm().getObjectById(contextClass, id);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Delete a persistence-coupled instance of the context class.
+ *
+ * @param value
+ * The value to delete.
+ */
+ protected void delete(@Nullable T value) {
+ if (value != null)
+ pm().deletePersistent(value);
+ }
+
+ /**
+ * Manages integration of JDO transactions with Spring.
+ *
+ * @author Donal Fellows
+ */
+ @Aspect
+ public static class TransactionAspect {
+ private Object lock = new Object();
+ private Log log = getLog("Taverna.Server.Utils");
+ private volatile int txid;
+
+ @Around(value = "@annotation(org.taverna.server.master.utils.JDOSupport.WithinSingleTransaction) && target(support)", argNames = "support")
+ Object applyTransaction(ProceedingJoinPoint pjp, JDOSupport<?> support)
+ throws Throwable {
+ synchronized (lock) {
+ PersistenceManager pm = support.pm();
+ int id = ++txid;
+ Transaction tx = (pm == null) ? null : pm.currentTransaction();
+ if (tx != null && tx.isActive())
+ tx = null;
+ if (tx != null) {
+ if (log.isDebugEnabled())
+ log.debug("starting transaction #" + id);
+ tx.begin();
+ }
+ try {
+ Object result = pjp.proceed();
+ if (tx != null) {
+ tx.commit();
+ if (log.isDebugEnabled())
+ log.debug("committed transaction #" + id);
+ }
+ tx = null;
+ return result;
+ } catch (Throwable t) {
+ try {
+ if (tx != null) {
+ tx.rollback();
+ if (log.isDebugEnabled())
+ log.debug("rolled back transaction #" + id);
+ }
+ } catch (JDOException e) {
+ log.warn("rollback failed unexpectedly", e);
+ }
+ throw t;
+ }
+ }
+ }
+ }
+
+ /**
+ * Mark a method (of a subclass of {@link JDOSupport}) as having a
+ * transaction wrapped around it. The transactions are managed correctly in
+ * the multi-threaded case.
+ *
+ * @author Donal Fellows
+ */
+ @Target(METHOD)
+ @Retention(RUNTIME)
+ @Documented
+ public @interface WithinSingleTransaction {
+ }
+
+ /**
+ * Manages {@linkplain PersistenceManager persistence managers} in a way
+ * that doesn't cause problems when the web application is unloaded.
+ *
+ * @author Donal Fellows
+ */
+ public static class PersistenceManagerBuilder {
+ private PersistenceManagerFactory pmf;
+ private WeakHashMap<Thread, PersistenceManager> cache = new WeakHashMap<>();
+
+ /**
+ * @param persistenceManagerFactory
+ * The JDO engine to use for managing persistence.
+ */
+ @Required
+ public void setPersistenceManagerFactory(
+ PersistenceManagerFactory persistenceManagerFactory) {
+ pmf = persistenceManagerFactory;
+ }
+
+ @Nonnull
+ public PersistenceManager getPersistenceManager() {
+ if (cache == null)
+ return pmf.getPersistenceManager();
+ Thread t = Thread.currentThread();
+ PersistenceManager pm = cache.get(t);
+ if (pm == null && pmf != null) {
+ pm = pmf.getPersistenceManager();
+ cache.put(t, pm);
+ }
+ return pm;
+ }
+
+ @PreDestroy
+ void clearThreadCache() {
+ WeakHashMap<Thread, PersistenceManager> cache = this.cache;
+ this.cache = null;
+ for (PersistenceManager pm : cache.values())
+ if (pm != null)
+ pm.close();
+ cache.clear();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java b/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java
new file mode 100644
index 0000000..a8aa937
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/LoggingDerbyAdapter.java
@@ -0,0 +1,138 @@
+package org.taverna.server.master.utils;
+
+import static java.lang.System.currentTimeMillis;
+import static java.lang.Thread.sleep;
+import static org.apache.commons.logging.LogFactory.getLog;
+
+import java.sql.DatabaseMetaData;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.datanucleus.store.rdbms.adapter.DerbyAdapter;
+import org.datanucleus.store.rdbms.identifier.IdentifierFactory;
+import org.datanucleus.store.rdbms.key.CandidateKey;
+import org.datanucleus.store.rdbms.key.ForeignKey;
+import org.datanucleus.store.rdbms.key.Index;
+import org.datanucleus.store.rdbms.key.PrimaryKey;
+import org.datanucleus.store.rdbms.sql.SQLTable;
+import org.datanucleus.store.rdbms.table.Column;
+import org.datanucleus.store.rdbms.table.Table;
+import org.datanucleus.store.rdbms.table.TableImpl;
+import org.datanucleus.store.rdbms.table.ViewImpl;
+
+/**
+ * Evil hack to allow logging of the DDL spat out to Derby.
+ *
+ * @author Donal Fellows
+ */
+public class LoggingDerbyAdapter extends DerbyAdapter {
+ Log log = getLog("Taverna.Server.SQL");
+
+ private StringBuilder ddl = new StringBuilder();
+ private volatile long timeout;
+ private Thread timer;
+
+ private synchronized void logDDL() {
+ if (ddl.length() > 0) {
+ log.info("Data definition language:\n" + ddl);
+ ddl.setLength(0);
+ }
+ timer = null;
+ }
+
+ private synchronized void doLog(String item) {
+ ddl.append(item);
+ if (!item.endsWith("\n"))
+ ddl.append('\n');
+ timeout = currentTimeMillis() + 5000;
+ if (timer == null)
+ timer = new OneShotThread("DDL logger timeout", new Runnable() {
+ @Override
+ public void run() {
+ try {
+ while (timeout > currentTimeMillis())
+ sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ logDDL();
+ }
+ });
+ }
+
+ /**
+ * Creates an Apache Derby adapter based on the given metadata which logs
+ * the DDL it creates.
+ */
+ public LoggingDerbyAdapter(DatabaseMetaData metadata) {
+ super(metadata);
+ }
+
+ @Override
+ public String getCreateTableStatement(TableImpl table, Column[] columns,
+ Properties props, IdentifierFactory factory) {
+ String statement = super.getCreateTableStatement(table, columns, props,
+ factory);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getCreateIndexStatement(Index index, IdentifierFactory factory) {
+ String statement = super.getCreateIndexStatement(index, factory);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getAddCandidateKeyStatement(CandidateKey ck,
+ IdentifierFactory factory) {
+ String statement = super.getAddCandidateKeyStatement(ck, factory);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getAddPrimaryKeyStatement(PrimaryKey pk,
+ IdentifierFactory factory) {
+ String statement = super.getAddPrimaryKeyStatement(pk, factory);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getAddColumnStatement(Table table, Column col) {
+ String statement = super.getAddColumnStatement(table, col);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getAddForeignKeyStatement(ForeignKey fk,
+ IdentifierFactory factory) {
+ String statement = super.getAddForeignKeyStatement(fk, factory);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getDeleteTableStatement(SQLTable tbl) {
+ String statement = super.getDeleteTableStatement(tbl);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getDropTableStatement(Table table) {
+ String statement = super.getDropTableStatement(table);
+ doLog(statement);
+ return statement;
+ }
+
+ @Override
+ public String getDropViewStatement(ViewImpl view) {
+ String statement = super.getDropViewStatement(view);
+ doLog(statement);
+ return statement;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java b/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java
new file mode 100644
index 0000000..68b813d
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/OneShotThread.java
@@ -0,0 +1,10 @@
+package org.taverna.server.master.utils;
+
+public class OneShotThread extends Thread {
+ public OneShotThread(String name, Runnable target) {
+ super(target, name);
+ setContextClassLoader(null);
+ setDaemon(true);
+ start();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/RestUtils.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/RestUtils.java b/server-webapp/src/main/java/org/taverna/server/master/utils/RestUtils.java
new file mode 100644
index 0000000..a892b52
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/RestUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.core.Response;
+
+/**
+ * Utilities that make it easier to write REST services.
+ *
+ * @author Donal Fellows
+ */
+public class RestUtils {
+ /**
+ * Generate a response to an HTTP OPTIONS request.
+ *
+ * @param methods
+ * The state-changing methods supported, if any.
+ * @return the required response
+ * @see OPTIONS
+ */
+ public static Response opt(String... methods) {
+ StringBuilder sb = new StringBuilder("GET,");
+ for (String m : methods)
+ sb.append(m).append(",");
+ sb.append("HEAD,OPTIONS");
+ return Response.ok().header("Allow", sb.toString()).entity("").build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/RuntimeExceptionWrapper.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/RuntimeExceptionWrapper.java b/server-webapp/src/main/java/org/taverna/server/master/utils/RuntimeExceptionWrapper.java
new file mode 100644
index 0000000..0b2d4ea
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/RuntimeExceptionWrapper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.taverna.server.master.exceptions.GeneralFailureException;
+
+/**
+ * Aspect used to convert {@linkplain RuntimeException runtime exceptions} into
+ * a form that can be nicely conveyed to the outside world as HTTP errors.
+ *
+ * @author Donal Fellows
+ */
+@Aspect
+public class RuntimeExceptionWrapper {
+ /**
+ * Map an unexpected exception to one that can be correctly reported as a
+ * problem.
+ *
+ * @param exn
+ * The runtime exception being trapped.
+ * @throws GeneralFailureException
+ * The known exception type that it is mapped to.
+ */
+ @AfterThrowing(pointcut = "execution(* org.taverna.server.master.rest..*(..)) && !bean(*Provider.*)", throwing = "exn")
+ public void wrapRuntimeException(RuntimeException exn)
+ throws GeneralFailureException {
+ // Exclude security-related exceptions
+ if (exn.getClass().getName().startsWith("org.springframework.security."))
+ return;
+ throw new GeneralFailureException(exn);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/UsernamePrincipal.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/UsernamePrincipal.java b/server-webapp/src/main/java/org/taverna/server/master/utils/UsernamePrincipal.java
new file mode 100644
index 0000000..9bbcc2f
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/UsernamePrincipal.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * A simple serializable principal that just records the name.
+ *
+ * @author Donal Fellows
+ */
+public class UsernamePrincipal implements Principal, Serializable {
+ private static final long serialVersionUID = 2703493248562435L;
+ public UsernamePrincipal(String username) {
+ this.name = username;
+ }
+
+ public UsernamePrincipal(Principal other) {
+ this.name = other.getName();
+ }
+
+ public UsernamePrincipal(Authentication auth) {
+ this(auth.getPrincipal());
+ }
+
+ public UsernamePrincipal(Object principal) {
+ if (principal instanceof Principal)
+ this.name = ((Principal) principal).getName();
+ else if (principal instanceof String)
+ this.name = (String) principal;
+ else if (principal instanceof UserDetails)
+ this.name = ((UserDetails) principal).getUsername();
+ else
+ this.name = principal.toString();
+ }
+
+ private String name;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "Principal<" + name + ">";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Principal) {
+ Principal p = (Principal) o;
+ return name.equals(p.getName());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java b/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
new file mode 100644
index 0000000..96cdc6d
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
@@ -0,0 +1,65 @@
+package org.taverna.server.master.utils;
+
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.apache.cxf.common.util.UrlUtils.parseQueryString;
+import static org.apache.cxf.message.Message.HTTP_REQUEST_METHOD;
+import static org.apache.cxf.message.Message.QUERY_STRING;
+import static org.apache.cxf.message.Message.REQUEST_URL;
+import static org.apache.cxf.phase.Phase.READ;
+
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.cxf.binding.soap.interceptor.EndpointSelectionInterceptor;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+
+
+/**
+ * Thunk for TAVSERV-293.
+ *
+ * @author Donal Fellows (based on work by Daniel Hagen)
+ */
+public class WSDLHeadOptionsInterceptor extends
+ AbstractPhaseInterceptor<Message> {
+ public static final Log log = getLog("Taverna.Server.Utils");
+
+ public WSDLHeadOptionsInterceptor() {
+ super(READ);
+ getAfter().add(EndpointSelectionInterceptor.class.getName());
+ }
+
+ @Override
+ public void handleMessage(Message message) throws Fault {
+ String method = (String) message.get(HTTP_REQUEST_METHOD);
+ String query = (String) message.get(QUERY_STRING);
+
+ if (("HEAD".equals(method) || "OPTIONS".equals(method))
+ && query != null && !query.trim().isEmpty()
+ && isRecognizedQuery(query)) {
+ log.debug("adjusting message request method " + method + " for "
+ + message.get(REQUEST_URL) + " to GET");
+ message.put(HTTP_REQUEST_METHOD, "GET");
+ }
+ }
+
+ /*
+ * Stolen from http://permalink.gmane.org/gmane.comp.apache.cxf.user/20037
+ * which is itself in turn stolen from
+ * org.apache.cxf.frontend.WSDLGetInterceptor.isRecognizedQuery
+ */
+ /**
+ * Is this a query for WSDL or XSD relating to it?
+ *
+ * @param query
+ * The query string to check
+ * @return If the query is one to handle.
+ * @see org.apache.cxf.frontend.WSDLGetInterceptor#isRecognizedQuery(Map,String,String,org.apache.cxf.service.model.EndpointInfo)
+ * WSDLGetInterceptor
+ */
+ private boolean isRecognizedQuery(String query) {
+ Map<String, String> map = parseQueryString(query);
+ return map.containsKey("wsdl") || map.containsKey("xsd");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java b/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java
new file mode 100644
index 0000000..03fc749
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/WebappAwareDataSource.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import static java.lang.Thread.currentThread;
+import static java.sql.DriverManager.deregisterDriver;
+import static java.sql.DriverManager.getDrivers;
+import static org.taverna.server.master.utils.Contextualizer.ROOT_PLACEHOLDER;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Enumeration;
+
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Required;
+
+/**
+ * Add some awareness of the context so that we can locate databases internally
+ * to the webapp.
+ *
+ * @author Donal Fellows
+ */
+public class WebappAwareDataSource extends BasicDataSource {
+ Log log = LogFactory.getLog("Taverna.Server.Utils");
+ private transient boolean init;
+ private Contextualizer ctxt;
+ private String shutdownUrl;
+
+ @Required
+ public void setContextualizer(Contextualizer ctxt) {
+ this.ctxt = ctxt;
+ }
+
+ /**
+ * A JDBC connection URL to use on shutting down the database. If not set,
+ * do nothing special.
+ *
+ * @param url
+ */
+ public void setShutdownUrl(String url) {
+ shutdownUrl = url;
+ }
+
+ private void doInit() {
+ synchronized (this) {
+ if (!init) {
+ setDriverClassLoader(currentThread().getContextClassLoader());
+ String url = getUrl();
+ if (url.contains(ROOT_PLACEHOLDER)) {
+ String newurl = ctxt.contextualize(url);
+ setUrl(newurl);
+ log.info("mapped " + url + " to " + newurl);
+ } else {
+ log.info("did not find " + ROOT_PLACEHOLDER + " in " + url);
+ }
+ init = true;
+ }
+ }
+ }
+
+ // -=-=-=-=-=-=-=-=-=-=- HOOKS -=-=-=-=-=-=-=-=-=-=-
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ doInit();
+ return super.getConnection();
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter pw) throws SQLException {
+ doInit();
+ super.setLogWriter(pw);
+ }
+
+ @Override
+ public void setLoginTimeout(int num) throws SQLException {
+ doInit();
+ super.setLoginTimeout(num);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ doInit();
+ return super.getLogWriter();
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ doInit();
+ return super.getLoginTimeout();
+ }
+
+ @PreDestroy
+ void realClose() {
+ try {
+ close();
+ } catch (SQLException e) {
+ log.warn("problem shutting down DB connection", e);
+ }
+ try {
+ if (shutdownUrl != null)
+ DriverManager.getConnection(ctxt.contextualize(shutdownUrl));
+ } catch (SQLException e) {
+ // Expected; ignore it
+ }
+ log = null;
+ dropDriver();
+ }
+
+ private void dropDriver() {
+ Enumeration<Driver> drivers = getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver d = drivers.nextElement();
+ if (d.getClass().getClassLoader() == getDriverClassLoader()
+ && d.getClass().getName().equals(getDriverClassName())) {
+ try {
+ deregisterDriver(d);
+ } catch (SQLException e) {
+ }
+ break;
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/X500Utils.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/X500Utils.java b/server-webapp/src/main/java/org/taverna/server/master/utils/X500Utils.java
new file mode 100644
index 0000000..da4cff0
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/X500Utils.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.utils;
+
+import static javax.security.auth.x500.X500Principal.RFC2253;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.PreDestroy;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Support class that factors out some of the messier parts of working with
+ * X.500 identities and X.509 certificates.
+ *
+ * @author Donal Fellows
+ */
+public class X500Utils {
+ private Log log = LogFactory.getLog("Taverna.Server.Utils");
+
+ @PreDestroy
+ void closeLog() {
+ log = null;
+ }
+
+ private static final char DN_SEPARATOR = ',';
+ private static final char DN_ESCAPE = '\\';
+ private static final char DN_QUOTE = '"';
+
+ /**
+ * Parse the DN from the Principal and extract the CN field.
+ *
+ * @param id
+ * The identity to extract the distinguished name from.
+ * @param fields
+ * The names to look at when finding the field to return. Each
+ * should be an upper-cased string.
+ * @return The common-name part of the distinguished name, or the literal
+ * string "<tt>none</tt>" if there is no CN.
+ */
+ public String getName(X500Principal id, String... fields) {
+ String dn = id.getName(RFC2253);
+
+ int i = 0;
+ int startIndex = 0;
+ boolean ignoreThisChar = false;
+ boolean inQuotes = false;
+ Map<String, String> tokenized = new HashMap<>();
+
+ for (i = 0; i < dn.length(); i++)
+ if (ignoreThisChar)
+ ignoreThisChar = false;
+ else if (dn.charAt(i) == DN_QUOTE)
+ inQuotes = !inQuotes;
+ else if (inQuotes)
+ continue;
+ else if (dn.charAt(i) == DN_ESCAPE)
+ ignoreThisChar = true;
+ else if ((dn.charAt(i) == DN_SEPARATOR) && !ignoreThisChar) {
+ storeDNField(tokenized, dn.substring(startIndex, i).trim()
+ .split("=", 2));
+ startIndex = i + 1;
+ }
+ if (inQuotes || ignoreThisChar)
+ log.warn("was parsing invalid DN format");
+ // Add last token - after the last delimiter
+ storeDNField(tokenized, dn.substring(startIndex).trim().split("=", 2));
+
+ for (String field : fields) {
+ String value = tokenized.get(field);
+ if (value != null)
+ return value;
+ }
+ return "none";
+ }
+
+ private void storeDNField(Map<String, String> container, String[] split) {
+ if (split == null || split.length != 2)
+ return;
+ String key = split[0].toUpperCase();
+ if (container.containsKey(key))
+ log.warn("duplicate field in DN: " + key);
+ // LATER: Should the field be de-quoted?
+ container.put(key, split[1]);
+ }
+
+ /**
+ * Get the serial number from a certificate as a hex string.
+ *
+ * @param cert
+ * The certificate to extract from.
+ * @return A hex string, in upper-case.
+ */
+ public String getSerial(X509Certificate cert) {
+ return new BigInteger(1, cert.getSerialNumber().toByteArray())
+ .toString(16).toUpperCase();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/utils/package-info.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/utils/package-info.java b/server-webapp/src/main/java/org/taverna/server/master/utils/package-info.java
new file mode 100644
index 0000000..612e61c
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/utils/package-info.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+/**
+ * Miscellaneous utility classes. Includes aspects that might be attached
+ * for purposes such as transaction management and invocation tracking.
+ */
+package org.taverna.server.master.utils;
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/worker/CompletionNotifier.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/worker/CompletionNotifier.java b/server-webapp/src/main/java/org/taverna/server/master/worker/CompletionNotifier.java
new file mode 100644
index 0000000..10b3830
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/worker/CompletionNotifier.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+
+/**
+ * How to convert a notification about the completion of a job into a message.
+ *
+ * @author Donal Fellows
+ */
+public interface CompletionNotifier {
+ /**
+ * @return The name of this notifier.
+ */
+ String getName();
+
+ /**
+ * Called to get the content of a message that a workflow run has finished.
+ *
+ * @param name
+ * The name of the run.
+ * @param run
+ * What run are we talking about.
+ * @param code
+ * What the exit code was.
+ * @return The plain-text content of the message.
+ */
+ String makeCompletionMessage(String name, RemoteRunDelegate run, int code);
+
+ /**
+ * Called to get the subject of the message to dispatch.
+ *
+ * @param name
+ * The name of the run.
+ * @param run
+ * What run are we talking about.
+ * @param code
+ * What the exit code was.
+ * @return The plain-text subject of the message.
+ */
+ String makeMessageSubject(String name, RemoteRunDelegate run, int code);
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/worker/FactoryBean.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/worker/FactoryBean.java b/server-webapp/src/main/java/org/taverna/server/master/worker/FactoryBean.java
new file mode 100644
index 0000000..5d0f371
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/worker/FactoryBean.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import org.taverna.server.master.notification.atom.EventDAO;
+
+/**
+ * What the remote run really needs of its factory.
+ *
+ * @author Donal Fellows
+ */
+public interface FactoryBean {
+ /**
+ * @return Whether a run can actually be started at this time.
+ */
+ boolean isAllowingRunsToStart();
+
+ /**
+ * @return a handle to the master Atom event feed (<i>not</i> the per-run
+ * feed)
+ */
+ EventDAO getMasterEventFeed();
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/worker/PasswordIssuer.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/worker/PasswordIssuer.java b/server-webapp/src/main/java/org/taverna/server/master/worker/PasswordIssuer.java
new file mode 100644
index 0000000..d3c5b8a
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/worker/PasswordIssuer.java
@@ -0,0 +1,57 @@
+package org.taverna.server.master.worker;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A simple password issuing bean.
+ *
+ * @author Donal Fellows
+ */
+public class PasswordIssuer {
+ private static final char[] ALPHABET = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '0', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ ',', '.', '<', '>', '/', '?', ':', ';', '-', '_', '+', '[', ']',
+ '{', '}', '`', '~' };
+ private Log log = LogFactory.getLog("Taverna.Server.Worker");
+ private SecureRandom r;
+ private int length;
+
+ public PasswordIssuer() {
+ r = new SecureRandom();
+ log.info("constructing passwords with " + r.getAlgorithm());
+ setLength(8);
+ }
+
+ public PasswordIssuer(String algorithm) throws NoSuchAlgorithmException {
+ r = SecureRandom.getInstance(algorithm);
+ log.info("constructing passwords with " + r.getAlgorithm());
+ setLength(8);
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ log.info("issued password will be " + this.length
+ + " symbols chosen from " + ALPHABET.length);
+ }
+
+ /**
+ * Issue a password.
+ *
+ * @return The new password.
+ */
+ public String issue() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++)
+ sb.append(ALPHABET[r.nextInt(ALPHABET.length)]);
+ log.info("issued new password of length " + sb.length());
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyImpl.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyImpl.java b/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyImpl.java
new file mode 100644
index 0000000..f5613c7
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyImpl.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import static org.taverna.server.master.identity.WorkflowInternalAuthProvider.PREFIX;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.taverna.server.master.common.Roles;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.exceptions.NoCreateException;
+import org.taverna.server.master.exceptions.NoDestroyException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Policy;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+/**
+ * Basic policy implementation that allows any workflow to be instantiated by
+ * any user, but which does not permit users to access each others workflow
+ * runs. It also imposes a global limit on the number of workflow runs at once.
+ *
+ * @author Donal Fellows
+ */
+class PolicyImpl implements Policy {
+ Log log = LogFactory.getLog("Taverna.Server.Worker.Policy");
+ private PolicyLimits limits;
+ private RunDBSupport runDB;
+
+ @Required
+ public void setLimits(PolicyLimits limits) {
+ this.limits = limits;
+ }
+
+ @Required
+ public void setRunDB(RunDBSupport runDB) {
+ this.runDB = runDB;
+ }
+
+ @Override
+ public int getMaxRuns() {
+ return limits.getMaxRuns();
+ }
+
+ @Override
+ public Integer getMaxRuns(UsernamePrincipal user) {
+ return null;
+ }
+
+ @Override
+ public int getOperatingLimit() {
+ return limits.getOperatingLimit();
+ }
+
+ @Override
+ public List<URI> listPermittedWorkflowURIs(UsernamePrincipal user) {
+ return limits.getPermittedWorkflowURIs();
+ }
+
+ private boolean isSelfAccess(String runId) {
+ Authentication auth = SecurityContextHolder.getContext()
+ .getAuthentication();
+ boolean self = false;
+ String id = null;
+ for (GrantedAuthority a : auth.getAuthorities()) {
+ String aa = a.getAuthority();
+ if (aa.equals(Roles.SELF)) {
+ self = true;
+ continue;
+ }
+ if (!aa.startsWith(PREFIX))
+ continue;
+ id = aa.substring(PREFIX.length());
+ }
+ return self && runId.equals(id);
+ }
+
+ @Override
+ public boolean permitAccess(UsernamePrincipal user, TavernaRun run) {
+ String username = user.getName();
+ TavernaSecurityContext context = run.getSecurityContext();
+ if (context.getOwner().getName().equals(username)) {
+ if (log.isDebugEnabled())
+ log.debug("granted access by " + user.getName() + " to "
+ + run.getId());
+ return true;
+ }
+ if (isSelfAccess(run.getId())) {
+ if (log.isDebugEnabled())
+ log.debug("access by workflow to itself: " + run.getId());
+ return true;
+ }
+ if (log.isDebugEnabled())
+ log.debug("considering access by " + user.getName() + " to "
+ + run.getId());
+ return context.getPermittedReaders().contains(username);
+ }
+
+ @Override
+ public void permitCreate(UsernamePrincipal user, Workflow workflow)
+ throws NoCreateException {
+ if (user == null)
+ throw new NoCreateException(
+ "anonymous workflow creation not allowed");
+ if (runDB.countRuns() >= getMaxRuns())
+ throw new NoCreateException("server load exceeded; please wait");
+ }
+
+ @Override
+ public synchronized void permitDestroy(UsernamePrincipal user, TavernaRun run)
+ throws NoDestroyException {
+ if (user == null)
+ throw new NoDestroyException();
+ String username = user.getName();
+ TavernaSecurityContext context = run.getSecurityContext();
+ if (context.getOwner() == null
+ || context.getOwner().getName().equals(username))
+ return;
+ if (!context.getPermittedDestroyers().contains(username))
+ throw new NoDestroyException();
+ }
+
+ @Override
+ public void permitUpdate(UsernamePrincipal user, TavernaRun run)
+ throws NoUpdateException {
+ if (user == null)
+ throw new NoUpdateException(
+ "workflow run not owned by you and you're not granted access");
+ TavernaSecurityContext context = run.getSecurityContext();
+ if (context.getOwner().getName().equals(user.getName()))
+ return;
+ if (isSelfAccess(run.getId())) {
+ if (log.isDebugEnabled())
+ log.debug("update access by workflow to itself: " + run.getId());
+ return;
+ }
+ if (!context.getPermittedUpdaters().contains(user.getName()))
+ throw new NoUpdateException(
+ "workflow run not owned by you and you're not granted access");
+ }
+
+ @Override
+ public void setPermittedWorkflowURIs(UsernamePrincipal user,
+ List<URI> permitted) {
+ limits.setPermittedWorkflowURIs(permitted);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyLimits.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyLimits.java b/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyLimits.java
new file mode 100644
index 0000000..8cbc7ea
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/worker/PolicyLimits.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import java.net.URI;
+import java.util.List;
+
+import org.taverna.server.master.common.Status;
+
+/**
+ * The worker policy delegates certain limits to the state model of the
+ * particular worker.
+ *
+ * @author Donal Fellows
+ */
+public interface PolicyLimits {
+ /**
+ * @return the maximum number of extant workflow runs in any state
+ */
+ int getMaxRuns();
+
+ /**
+ * @return the maximum number of workflow runs in the
+ * {@linkplain Status#Operating operating} state.
+ */
+ int getOperatingLimit();
+
+ /**
+ * @return the list of URIs to workflows that may be used to create workflow
+ * runs. If empty or <tt>null</tt>, no restriction is present.
+ */
+ List<URI> getPermittedWorkflowURIs();
+
+ /**
+ * @param permitted
+ * the list of URIs to workflows that may be used to create
+ * workflow runs.
+ */
+ void setPermittedWorkflowURIs(List<URI> permitted);
+}