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:38 UTC

[22/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/usage/UsageRecordRecorder.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/usage/UsageRecordRecorder.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/usage/UsageRecordRecorder.java
new file mode 100644
index 0000000..18aeb3b
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/usage/UsageRecordRecorder.java
@@ -0,0 +1,178 @@
+/*
+ */
+package org.taverna.server.master.usage;
+/*
+ * 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.apache.commons.logging.LogFactory.getLog;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PreDestroy;
+import javax.xml.bind.JAXBException;
+
+import org.apache.commons.logging.Log;
+import org.apache.taverna.server.usagerecord.JobUsageRecord;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.ManagementModel;
+import org.taverna.server.master.utils.Contextualizer;
+import org.taverna.server.master.utils.JDOSupport;
+
+/**
+ * A simple state-aware writer of usage records. It just appends them, one by
+ * one, to the file whose name is stored in the state.
+ * 
+ * @author Donal Fellows
+ */
+public class UsageRecordRecorder extends JDOSupport<UsageRecord> {
+	private Log log = getLog("Taverna.Server.Webapp");
+	public UsageRecordRecorder() {
+		super(UsageRecord.class);
+	}
+
+	private String logFile = null;
+	private boolean disableDB = false;
+	private ManagementModel state;
+	private Contextualizer contextualizer;
+	private String logDestination;
+	private PrintWriter writer;
+	private Object lock = new Object();
+	private UsageRecordRecorder self;
+
+	/**
+	 * @param state
+	 *            the state to set
+	 */
+	@Required
+	public void setState(ManagementModel state) {
+		this.state = state;
+	}
+
+	@Required
+	public void setSelf(UsageRecordRecorder self) {
+		this.self = self;
+	}
+
+	public void setLogFile(String logFile) {
+		this.logFile = (logFile == null || logFile.equals("none")) ? null : logFile;
+	}
+
+	public void setDisableDB(String disable) {
+		disableDB = "yes".equalsIgnoreCase(disable);
+	}
+
+	/**
+	 * @param contextualizer
+	 *            the system's contextualizer, used to allow making the UR dump
+	 *            file be placed relative to the webapp.
+	 */
+	@Required
+	public void setContextualizer(Contextualizer contextualizer) {
+		this.contextualizer = contextualizer;
+	}
+
+	/**
+	 * Accept a usage record for recording.
+	 * 
+	 * @param usageRecord
+	 *            The serialized usage record to record.
+	 */
+	public void storeUsageRecord(String usageRecord) {
+		String logfile = state.getUsageRecordLogFile();
+		if (logfile == null)
+			logfile = this.logFile;
+		if (logfile != null) {
+			logfile = contextualizer.contextualize(logfile);
+			synchronized (lock) {
+				if (!logfile.equals(logDestination)) {
+					if (writer != null) {
+						writer.close();
+						writer = null;
+					}
+					try {
+						writer = new PrintWriter(new FileWriter(logfile));
+						logDestination = logfile;
+					} catch (IOException e) {
+						log.warn("failed to open usage record log file", e);
+					}
+				}
+				if (writer != null) {
+					writer.println(usageRecord);
+					writer.flush();
+				}
+			}
+		}
+
+		if (!disableDB)
+			saveURtoDB(usageRecord);
+	}
+
+	/**
+	 * How to save a usage record to the database.
+	 * 
+	 * @param usageRecord
+	 *            The serialized usage record to save.
+	 */
+	protected void saveURtoDB(String usageRecord) {
+		UsageRecord ur;
+		try {
+			ur = new UsageRecord(usageRecord);
+		} catch (JAXBException e) {
+			log.warn("failed to deserialize usage record", e);
+			return;
+		}
+
+		try {
+			self.saveURtoDB(ur);
+		} catch (RuntimeException e) {
+			log.warn("failed to save UR to database", e);
+		}
+	}
+
+	@WithinSingleTransaction
+	public void saveURtoDB(UsageRecord ur) {
+		persist(ur);
+	}
+
+	@WithinSingleTransaction
+	public List<JobUsageRecord> getUsageRecords() {
+		List<String> urs = allByDate();
+		List<JobUsageRecord> result = new ArrayList<>();
+		for (String ur : urs)
+			try {
+				result.add(JobUsageRecord.unmarshal(ur));
+			} catch (JAXBException e) {
+				log.warn("failed to unmarshal UR", e);
+			}
+		return result;
+	}
+
+	@SuppressWarnings("unchecked")
+	private List<String> allByDate() {
+		return (List<String>) namedQuery("allByDate").execute();
+	}
+
+	@PreDestroy
+	public void close() {
+		if (writer != null)
+			writer.close();
+	}
+}
\ 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/usage/package-info.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/usage/package-info.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/usage/package-info.java
new file mode 100644
index 0000000..a7fe733
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/usage/package-info.java
@@ -0,0 +1,22 @@
+/*
+ */
+/**
+ * Resource usage recording mechanism.
+ */
+package org.taverna.server.master.usage;
+/*
+ * 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/apache/taverna/server/master/utils/CallTimeLogger.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimeLogger.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimeLogger.java
new file mode 100644
index 0000000..4452935
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimeLogger.java
@@ -0,0 +1,100 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.lang.System.nanoTime;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.taverna.server.master.TavernaServer.JMX_ROOT;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+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.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.taverna.server.master.common.version.Version;
+
+/**
+ * This class is responsible for timing all invocations of publicly-exposed
+ * methods of the webapp. It's connected to the webapp through an AspectJ-style
+ * pointcut that targets a custom annotation.
+ * 
+ * @author Donal Fellows
+ */
+@Aspect
+@ManagedResource(objectName = JMX_ROOT + "PerformanceMonitor", description = "The performance monitor for Taverna Server "
+		+ Version.JAVA
+		+ ". Writes to application log using the category 'Taverna.Server.Performance'.")
+public class CallTimeLogger {
+	private long threshold = 4000000;
+	private Log log = getLog("Taverna.Server.Performance");
+
+	@ManagedAttribute(description = "Threshold beneath which monitored call times are not logged. In nanoseconds.")
+	public long getThreshold() {
+		return threshold;
+	}
+
+	@ManagedAttribute(description = "Threshold beneath which monitored call times are not logged. In nanoseconds.")
+	public void setThreshold(long threshold) {
+		this.threshold = threshold;
+	}
+
+	/**
+	 * The timer for this aspect. The wrapped invocation will be timed, and a
+	 * log message written if the configured threshold is exceeded.
+	 * 
+	 * @param call
+	 *            The call being wrapped.
+	 * @return The result of the call.
+	 * @throws Throwable
+	 *             If anything goes wrong with the wrapped call.
+	 * @see System#nanoTime()
+	 */
+	@Around("@annotation(org.taverna.server.master.utils.CallTimeLogger.PerfLogged)")
+	public Object time(ProceedingJoinPoint call) throws Throwable {
+		long fore = nanoTime();
+		try {
+			return call.proceed();
+		} finally {
+			long aft = nanoTime();
+			long elapsed = aft - fore;
+			if (elapsed > threshold)
+				log.info(format("call to %s took %.3fms", call.toShortString(),
+						elapsed / 1000000.0));
+		}
+	}
+
+	/**
+	 * Mark methods that should be counted by the invocation counter.
+	 * 
+	 * @author Donal Fellows
+	 */
+	@Retention(RUNTIME)
+	@Documented
+	@Target(METHOD)
+	public static @interface PerfLogged {
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimingFilter.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimingFilter.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimingFilter.java
new file mode 100644
index 0000000..d8ad78d
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CallTimingFilter.java
@@ -0,0 +1,81 @@
+/**
+ * 
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CapabilityLister.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CapabilityLister.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CapabilityLister.java
new file mode 100644
index 0000000..1387e05
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CapabilityLister.java
@@ -0,0 +1,60 @@
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CertificateChainFetcher.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CertificateChainFetcher.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CertificateChainFetcher.java
new file mode 100644
index 0000000..76ef017
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/CertificateChainFetcher.java
@@ -0,0 +1,212 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static java.util.Arrays.asList;
+import static java.util.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/Contextualizer.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/Contextualizer.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/Contextualizer.java
new file mode 100644
index 0000000..e0ee4d1
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/Contextualizer.java
@@ -0,0 +1,79 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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 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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/DerbyUtils.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/DerbyUtils.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/DerbyUtils.java
new file mode 100644
index 0000000..e4be90b
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/DerbyUtils.java
@@ -0,0 +1,84 @@
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FilenameUtils.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FilenameUtils.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FilenameUtils.java
new file mode 100644
index 0000000..3c39326
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FilenameUtils.java
@@ -0,0 +1,281 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java
new file mode 100644
index 0000000..7f5f92a
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/FlushThreadLocalCacheInterceptor.java
@@ -0,0 +1,34 @@
+package org.taverna.server.master.utils;
+/*
+ * 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 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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/InvocationCounter.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/InvocationCounter.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/InvocationCounter.java
new file mode 100644
index 0000000..0600857
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/InvocationCounter.java
@@ -0,0 +1,61 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JCECheck.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JCECheck.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JCECheck.java
new file mode 100644
index 0000000..7f248ff
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JCECheck.java
@@ -0,0 +1,73 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JDOSupport.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JDOSupport.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JDOSupport.java
new file mode 100644
index 0000000..96f6a11
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/JDOSupport.java
@@ -0,0 +1,285 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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 &lt;T&gt; 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<T> query(@Nonnull String filter) {
+		return pm().newQuery(contextClass, filter);
+	}
+
+	/**
+	 * Get an instance of a named query attached to the context class (as an
+	 * annotation). Note that the result is a <i>raw</i> {@link Query} because
+	 * not all queries return instances of the context class.
+	 * 
+	 * @param name
+	 *            The name of the query.
+	 * @return The query, which should be executed to retrieve the results.
+	 * @see javax.jdo.annotations.Query
+	 */
+	@Nonnull
+	@SuppressWarnings("rawtypes")
+	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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/LoggingDerbyAdapter.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/LoggingDerbyAdapter.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/LoggingDerbyAdapter.java
new file mode 100644
index 0000000..8bd0506
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/LoggingDerbyAdapter.java
@@ -0,0 +1,154 @@
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/OneShotThread.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/OneShotThread.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/OneShotThread.java
new file mode 100644
index 0000000..32cca5a
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/OneShotThread.java
@@ -0,0 +1,26 @@
+package org.taverna.server.master.utils;
+/*
+ * 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.
+ */
+
+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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RestUtils.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RestUtils.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RestUtils.java
new file mode 100644
index 0000000..d69612e
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RestUtils.java
@@ -0,0 +1,45 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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 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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RuntimeExceptionWrapper.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RuntimeExceptionWrapper.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RuntimeExceptionWrapper.java
new file mode 100644
index 0000000..333febe
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/RuntimeExceptionWrapper.java
@@ -0,0 +1,50 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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 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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/UsernamePrincipal.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/UsernamePrincipal.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/UsernamePrincipal.java
new file mode 100644
index 0000000..25cf64f
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/UsernamePrincipal.java
@@ -0,0 +1,82 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
new file mode 100644
index 0000000..1bd2a95
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WSDLHeadOptionsInterceptor.java
@@ -0,0 +1,81 @@
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WebappAwareDataSource.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WebappAwareDataSource.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WebappAwareDataSource.java
new file mode 100644
index 0000000..24246b5
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/WebappAwareDataSource.java
@@ -0,0 +1,147 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/X500Utils.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/X500Utils.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/X500Utils.java
new file mode 100644
index 0000000..d258079
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/X500Utils.java
@@ -0,0 +1,120 @@
+/*
+ */
+package org.taverna.server.master.utils;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static javax.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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/package-info.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/package-info.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/package-info.java
new file mode 100644
index 0000000..64a9f68
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/utils/package-info.java
@@ -0,0 +1,23 @@
+/*
+ */
+/**
+ * Miscellaneous utility classes. Includes aspects that might be attached
+ * for purposes such as transaction management and invocation tracking.
+ */
+package org.taverna.server.master.utils;
+/*
+ * 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.
+ */