You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by re...@apache.org on 2015/03/19 14:43:57 UTC

[29/35] incubator-taverna-common-activities git commit: package names changed to org.apache.taverna.*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityRunnable.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityRunnable.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityRunnable.java
new file mode 100644
index 0000000..346d920
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityRunnable.java
@@ -0,0 +1,345 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.taverna.activities.interaction.atom.AtomUtils;
+import org.apache.taverna.activities.interaction.jetty.InteractionJetty;
+import org.apache.taverna.activities.interaction.preference.InteractionPreference;
+import org.apache.taverna.activities.interaction.velocity.InteractionVelocity;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.i18n.text.Normalizer;
+import org.apache.abdera.i18n.text.Sanitizer;
+import org.apache.abdera.model.Element;
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.parser.stax.FOMElement;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.log4j.Logger;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+
+public final class InteractionActivityRunnable implements Runnable {
+
+	private static final Logger logger = Logger
+			.getLogger(InteractionActivityRunnable.class);
+
+	private static final Abdera ABDERA = Abdera.getInstance();
+
+	private final Template presentationTemplate;
+
+	private final InteractionRequestor requestor;
+
+	private CredentialManager credentialManager;
+
+	private InteractionRecorder interactionRecorder;
+	
+	private InteractionUtils interactionUtils;
+
+	private InteractionJetty interactionJetty;
+
+	private InteractionPreference interactionPreference;
+
+	private ResponseFeedListener responseFeedListener;
+
+	private InteractionVelocity interactionVelocity;
+
+	public InteractionActivityRunnable(final InteractionRequestor requestor,
+			final Template presentationTemplate,
+			final CredentialManager credentialManager,
+			final InteractionRecorder interactionRecorder,
+			final InteractionUtils interactionUtils,
+			final InteractionJetty interactionJetty,
+			final InteractionPreference interactionPreference,
+			final ResponseFeedListener responseFeedListener,
+			final InteractionVelocity interactionVelocity) {
+		this.requestor = requestor;
+		this.presentationTemplate = presentationTemplate;
+		this.credentialManager = credentialManager;
+		this.interactionRecorder = interactionRecorder;
+		this.interactionUtils = interactionUtils;
+		this.interactionJetty = interactionJetty;
+		this.interactionPreference = interactionPreference;
+		this.responseFeedListener = responseFeedListener;
+		this.interactionVelocity = interactionVelocity;
+	}
+
+	@Override
+	public void run() {
+		/*
+		 * InvocationContext context = callback.getContext();
+		 */
+		final String runId = InteractionUtils.getUsedRunId(this.requestor
+				.getRunId());
+
+		final String id = Sanitizer.sanitize(UUID.randomUUID().toString(), "",
+				true, Normalizer.Form.D);
+
+		final Map<String, Object> inputData = this.requestor.getInputData();
+
+		if (interactionPreference.getUseJetty()) {
+			interactionJetty.startJettyIfNecessary(credentialManager);
+		}
+		interactionJetty.startListenersIfNecessary();
+		try {
+			interactionUtils.copyFixedFile("pmrpc.js");
+			interactionUtils.copyFixedFile("interaction.css");
+		} catch (final IOException e1) {
+			logger.error(e1);
+			this.requestor.fail("Unable to copy necessary fixed file");
+			return;
+		}
+		synchronized (ABDERA) {
+			final Entry interactionNotificationMessage = this
+					.createBasicInteractionMessage(id, runId);
+
+			for (final String key : inputData.keySet()) {
+				final Object value = inputData.get(key);
+				if (value instanceof byte[]) {
+					final String replacementUrl = interactionPreference
+							.getPublicationUrlString(id, key);
+					final ByteArrayInputStream bais = new ByteArrayInputStream(
+							(byte[]) value);
+					try {
+						interactionUtils.publishFile(replacementUrl, bais,
+								runId, id);
+						bais.close();
+						inputData.put(key, replacementUrl);
+					} catch (final IOException e) {
+						logger.error(e);
+						this.requestor.fail("Unable to publish to " + replacementUrl);
+						return;
+					}
+				}
+			}
+
+			final String inputDataString = this.createInputDataJson(inputData);
+			if (inputDataString == null) {
+				return;
+			}
+			final String inputDataUrl = interactionPreference
+					.getInputDataUrlString(id);
+			try {
+				interactionUtils.publishFile(inputDataUrl, inputDataString,
+						runId, id);
+			} catch (final IOException e) {
+				logger.error(e);
+				this.requestor.fail("Unable to publish to " + inputDataUrl);
+				return;
+			}
+
+			String outputDataUrl = null;
+
+			if (!this.requestor.getInteractionType().equals(
+					InteractionType.Notification)) {
+				outputDataUrl = interactionPreference
+						.getOutputDataUrlString(id);
+			}
+			final String interactionUrlString = this.generateHtml(inputDataUrl,
+					outputDataUrl, inputData, runId, id);
+
+			try {
+				this.postInteractionMessage(id, interactionNotificationMessage,
+						interactionUrlString, runId);
+			} catch (IOException e) {
+				logger.error(e);
+				this.requestor.fail("Unable to post message");
+				return;
+			}
+			if (!this.requestor.getInteractionType().equals(
+					InteractionType.Notification)) {
+				responseFeedListener.registerInteraction(
+						interactionNotificationMessage, this.requestor);
+			} else {
+				this.requestor.carryOn();
+
+			}
+		}
+	}
+
+	private String createInputDataJson(final Map<String, Object> inputData) {
+		try {
+			return InteractionUtils.objectToJson(inputData);
+		} catch (final IOException e) {
+			logger.error(e);
+			this.requestor.fail("Unable to generate JSON");
+		}
+		return null;
+	}
+
+	private Entry createBasicInteractionMessage(final String id,
+			final String runId) {
+		final Entry interactionNotificationMessage = ABDERA.newEntry();
+
+		interactionNotificationMessage.setId(id);
+		final Date timestamp = new Date();
+		interactionNotificationMessage.setPublished(timestamp);
+		interactionNotificationMessage.setUpdated(timestamp);
+
+		interactionNotificationMessage.addAuthor("Taverna");
+		interactionNotificationMessage.setTitle("Interaction from Taverna for "
+				+ this.requestor.generateId());
+
+		final Element runIdElement = interactionNotificationMessage
+				.addExtension(AtomUtils.getRunIdQName());
+		runIdElement.setText(StringEscapeUtils.escapeJavaScript(runId));
+		
+		final Element pathIdElement = interactionNotificationMessage.addExtension(AtomUtils.getPathIdQName());
+		pathIdElement.setText(StringEscapeUtils.escapeJavaScript(this.requestor.getPath()));
+		
+		final Element countElement = interactionNotificationMessage.addExtension(AtomUtils.getCountQName());
+		countElement.setText(StringEscapeUtils.escapeJavaScript(this.requestor.getInvocationCount().toString()));
+		
+		if (this.requestor.getInteractionType().equals(
+				InteractionType.Notification)) {
+			interactionNotificationMessage.addExtension(AtomUtils
+					.getProgressQName());
+		}
+		final Element idElement = interactionNotificationMessage
+				.addExtension(AtomUtils.getIdQName());
+		idElement.setText(id);
+
+		return interactionNotificationMessage;
+	}
+
+	private void postInteractionMessage(final String id, final Entry entry,
+			final String interactionUrlString, final String runId) throws IOException {
+
+		entry.addLink(StringEscapeUtils.escapeXml(interactionUrlString),
+				"presentation");
+		entry.setContentAsXhtml("<p><a href=\""
+				+ StringEscapeUtils.escapeXml(interactionUrlString)
+				+ "\">Open: "
+				+ StringEscapeUtils.escapeXml(interactionUrlString)
+				+ "</a></p>");
+
+		URL feedUrl;
+
+			feedUrl = new URL(interactionPreference
+					.getFeedUrlString());
+			final String entryContent = ((FOMElement) entry)
+					.toFormattedString();
+			final HttpURLConnection httpCon = (HttpURLConnection) feedUrl
+					.openConnection();
+			httpCon.setDoOutput(true);
+			httpCon.setRequestProperty("Content-Type",
+					"application/atom+xml;type=entry;charset=UTF-8");
+			httpCon.setRequestProperty("Content-Length",
+					"" + entryContent.length());
+			httpCon.setRequestProperty("Slug", id);
+			httpCon.setRequestMethod("POST");
+			httpCon.setConnectTimeout(5000);
+			final OutputStream outputStream = httpCon.getOutputStream();
+			IOUtils.write(entryContent, outputStream, "UTF-8");
+			outputStream.close();
+			final int response = httpCon.getResponseCode();
+			if ((response < 0) || (response >= 400)) {
+				logger.error("Received response code" + response);
+				throw (new IOException ("Received response code " + response));
+			}
+			if (response == HttpURLConnection.HTTP_CREATED) {
+				interactionRecorder.addResource(runId, id,
+						httpCon.getHeaderField("Location"));
+			}
+	}
+
+	String generateHtml(final String inputDataUrl, final String outputDataUrl,
+			final Map<String, Object> inputData, final String runId,
+			final String id) {
+
+		final VelocityContext velocityContext = new VelocityContext();
+
+		for (final String inputName : inputData.keySet()) {
+			final Object input = inputData.get(inputName);
+			velocityContext.put(inputName, input);
+		}
+
+		velocityContext.put("feed", interactionPreference
+				.getFeedUrlString());
+		velocityContext.put("runId", runId);
+		velocityContext.put("entryId", id);
+		final String pmrpcUrl = interactionPreference
+				.getLocationUrl() + "/pmrpc.js";
+		velocityContext.put("pmrpcUrl", pmrpcUrl);
+		velocityContext.put("inputDataUrl", inputDataUrl);
+		velocityContext.put("outputDataUrl", outputDataUrl);
+		final String interactionUrl = interactionPreference
+				.getInteractionUrlString(id);
+
+		velocityContext.put("interactionUrl", interactionUrl);
+
+		String presentationUrl = "";
+		final String authorizeUrl = "";
+		try {
+			if (this.requestor.getPresentationType().equals(
+					InteractionActivityType.VelocityTemplate)) {
+
+				presentationUrl = interactionPreference
+						.getPresentationUrlString(id);
+
+				final String presentationString = this.processTemplate(
+						this.presentationTemplate, velocityContext);
+				interactionUtils.publishFile(presentationUrl,
+						presentationString, runId, id);
+
+			} else if (this.requestor.getPresentationType().equals(
+					InteractionActivityType.LocallyPresentedHtml)) {
+				presentationUrl = this.requestor.getPresentationOrigin();
+			}
+
+			velocityContext.put("presentationUrl", presentationUrl);
+
+			final String interactionString = this.processTemplate(
+					interactionVelocity.getInteractionTemplate(),
+					velocityContext);
+			interactionUtils.publishFile(interactionUrl, interactionString,
+					runId, id);
+
+			if (!authorizeUrl.isEmpty()) {
+				return authorizeUrl;
+			}
+			return interactionUrl;
+		} catch (final IOException e) {
+			logger.error(e);
+			this.requestor.fail("Unable to generate HTML");
+			return null;
+		}
+	}
+
+	private String processTemplate(final Template template,
+			final VelocityContext context) throws IOException {
+		final StringWriter resultWriter = new StringWriter();
+		template.merge(context, resultWriter);
+		resultWriter.close();
+		return resultWriter.toString();
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityType.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityType.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityType.java
new file mode 100644
index 0000000..11422b3
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionActivityType.java
@@ -0,0 +1,31 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+/**
+ * @author alanrw
+ * 
+ *         Should be renamed something like presentation type
+ */
+public enum InteractionActivityType {
+
+	VelocityTemplate, LocallyPresentedHtml
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionCallbackRequestor.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionCallbackRequestor.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionCallbackRequestor.java
new file mode 100644
index 0000000..3c8c83b
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionCallbackRequestor.java
@@ -0,0 +1,209 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.reference.ReferenceService;
+import net.sf.taverna.t2.reference.T2Reference;
+import net.sf.taverna.t2.reference.WorkflowRunIdEntity;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityInputPort;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+import net.sf.taverna.t2.workflowmodel.processor.activity.AsynchronousActivityCallback;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class InteractionCallbackRequestor implements InteractionRequestor {
+
+	private final AsynchronousActivityCallback callback;
+
+	private final Map<String, T2Reference> inputs;
+
+	private final InteractionActivity activity;
+
+	private boolean answered = false;
+
+	private String path;
+
+	private Integer count;
+	
+	private static Map<String, Integer> invocationCount = new HashMap<String, Integer> ();
+
+	public InteractionCallbackRequestor(final InteractionActivity activity,
+			final AsynchronousActivityCallback callback,
+			final Map<String, T2Reference> inputs) {
+		this.activity = activity;
+		this.callback = callback;
+		this.inputs = inputs;
+		this.path = calculatePath();
+		this.count = calculateInvocationCount(path);
+	}
+
+	@Override
+	public String getRunId() {
+		return this.callback.getContext()
+				.getEntities(WorkflowRunIdEntity.class).get(0)
+				.getWorkflowRunId();
+	}
+
+	@Override
+	public Map<String, Object> getInputData() {
+		final Map<String, Object> inputData = new HashMap<String, Object>();
+
+		final InvocationContext context = this.callback.getContext();
+		final ReferenceService referenceService = context.getReferenceService();
+		for (final String inputName : this.inputs.keySet()) {
+			final Object input = referenceService.renderIdentifier(this.inputs
+					.get(inputName), this.getInputPort(inputName)
+					.getTranslatedElementClass(), this.callback.getContext());
+			inputData.put(inputName, input);
+		}
+		return inputData;
+	}
+
+	public ActivityInputPort getInputPort(final String name) {
+		for (final ActivityInputPort port : this.activity.getInputPorts()) {
+			if (port.getName().equals(name)) {
+				return port;
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public void fail(final String string) {
+		if (this.answered) {
+			return;
+		}
+		this.callback.fail(string);
+		this.answered = true;
+	}
+
+	@Override
+	public void carryOn() {
+		if (this.answered) {
+			return;
+		}
+		this.callback.receiveResult(new HashMap<String, T2Reference>(),
+				new int[0]);
+		this.answered = true;
+	}
+
+	@Override
+	public String generateId() {
+		final String workflowRunId = getRunId();
+		final String parentProcessIdentifier = this.callback
+				.getParentProcessIdentifier();
+		return (workflowRunId + ":" + parentProcessIdentifier);
+	}
+
+	@Override
+	public InteractionType getInteractionType() {
+		if (this.activity.isProgressNotification()) {
+			return InteractionType.Notification;
+		}
+		return InteractionType.DataRequest;
+	}
+
+	@Override
+	public InteractionActivityType getPresentationType() {
+		return this.activity.getInteractionActivityType();
+	}
+
+	@Override
+	public String getPresentationOrigin() {
+		return this.activity.getPresentationOrigin();
+	}
+
+	@Override
+	public void receiveResult(final Map<String, Object> resultMap) {
+		if (this.answered) {
+			return;
+		}
+		final Map<String, T2Reference> outputs = new HashMap<String, T2Reference>();
+
+		final InvocationContext context = this.callback.getContext();
+		final ReferenceService referenceService = context.getReferenceService();
+
+		for (final Object key : resultMap.keySet()) {
+			final String keyString = (String) key;
+			final Object value = resultMap.get(key);
+			final Integer depth = this.findPortDepth(keyString);
+			if (depth == null) {
+				this.callback.fail("Data sent for unknown port : " + keyString);
+			}
+			outputs.put(keyString,
+					referenceService.register(value, depth, true, context));
+		}
+		this.callback.receiveResult(outputs, new int[0]);
+		this.answered = true;
+	}
+
+	private Integer findPortDepth(final String portName) {
+		final Set<ActivityOutputPort> ports = this.activity.getOutputPorts();
+		for (final ActivityOutputPort op : ports) {
+			if (op.getName().equals(portName)) {
+				return op.getDepth();
+			}
+		}
+		return null;
+	}
+
+	private String calculatePath() {
+		final String parentProcessIdentifier = this.callback
+				.getParentProcessIdentifier();
+		String result = "";
+		String parts[] = parentProcessIdentifier.split(":");
+
+		for (int i = 2; i < parts.length; i += 4) {
+			if (!result.isEmpty()) {
+				result += ":";
+			}
+			result += parts[i];
+		}
+		return result;
+	}
+
+	@Override
+	public String getPath() {
+		return this.path;
+	}
+	
+	private synchronized static Integer calculateInvocationCount(String path) {
+		Integer currentCount = invocationCount.get(path);
+		if (currentCount == null) {
+			currentCount = Integer.valueOf(0);
+		} else {
+			currentCount = currentCount + 1;
+		}
+		invocationCount.put(path, currentCount);
+		return currentCount;
+	}
+
+	@Override
+	public Integer getInvocationCount() {
+		return count;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRecorder.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRecorder.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRecorder.java
new file mode 100644
index 0000000..140d209
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRecorder.java
@@ -0,0 +1,179 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * 
+ * This class is used to remember and forget interactions and their associated
+ * ATOM entries and files
+ * 
+ * @author alanrw
+ * 
+ */
+public class InteractionRecorder {
+
+	private static final Logger logger = Logger
+			.getLogger(InteractionRecorder.class);
+
+	static Map<String, Map<String, Set<String>>> runToInteractionMap = Collections
+			.synchronizedMap(new HashMap<String, Map<String, Set<String>>>());
+	
+	private InteractionUtils interactionUtils;
+
+	private InteractionRecorder() {
+		super();
+	}
+
+	public void deleteRun(final String runToDelete) {
+		final Set<String> interactionIds = new HashSet<String>(
+				getInteractionMap(runToDelete).keySet());
+		for (final String interactionId : interactionIds) {
+			deleteInteraction(runToDelete, interactionId);
+		}
+		runToInteractionMap.remove(runToDelete);
+	}
+
+	public void deleteInteraction(final String runId,
+			final String interactionId) {
+		for (final String urlString : getResourceSet(runId, interactionId)) {
+			try {
+				deleteUrl(urlString);
+			} catch (final IOException e) {
+				logger.info("Unable to delete " + urlString, e);
+			}
+
+		}
+		getInteractionMap(runId).remove(interactionId);
+	}
+
+	private void deleteUrl(final String urlString) throws IOException {
+		logger.info("Deleting resource " + urlString);
+		final URL url = new URL(urlString);
+		final HttpURLConnection httpCon = (HttpURLConnection) url
+				.openConnection();
+		httpCon.setRequestMethod("DELETE");
+		final int response = httpCon.getResponseCode();
+		if (response >= 400) {
+			logger.info("Received response code" + response);
+		}
+	}
+
+	public void addResource(final String runId,
+			final String interactionId, final String resourceId) {
+		if (resourceId == null) {
+			logger.error("Attempt to add null resource",
+					new NullPointerException(""));
+			return;
+		}
+		logger.info("Adding resource " + resourceId);
+		final Set<String> resourceSet = getResourceSet(runId, interactionId);
+
+		resourceSet.add(resourceId);
+	}
+
+	private Set<String> getResourceSet(final String runId,
+			final String interactionId) {
+		final Map<String, Set<String>> interactionMap = getInteractionMap(runId);
+		Set<String> resourceSet = interactionMap.get(interactionId);
+		if (resourceSet == null) {
+			resourceSet = Collections.synchronizedSet(new HashSet<String>());
+			interactionMap.put(interactionId, resourceSet);
+		}
+		return resourceSet;
+	}
+
+	private Map<String, Set<String>> getInteractionMap(final String runId) {
+		Map<String, Set<String>> interactionMap = InteractionRecorder.runToInteractionMap
+				.get(runId);
+		if (interactionMap == null) {
+			interactionMap = Collections.synchronizedMap(Collections
+					.synchronizedMap(new HashMap<String, Set<String>>()));
+			InteractionRecorder.runToInteractionMap.put(runId, interactionMap);
+		}
+		return interactionMap;
+	}
+
+	public void persist() {
+		final File outputFile = getUsageFile();
+		try {
+			FileUtils.writeStringToFile(outputFile, InteractionUtils
+					.objectToJson(InteractionRecorder.runToInteractionMap));
+		} catch (final IOException e) {
+			logger.error(e);
+		}
+	}
+
+	private File getUsageFile() {
+		return new File(getInteractionUtils().getInteractionServiceDirectory(),
+				"usage");
+	}
+
+	public void load() {
+		final File inputFile = getUsageFile();
+		try {
+			final String usageString = FileUtils.readFileToString(inputFile);
+			final ObjectMapper mapper = new ObjectMapper();
+			@SuppressWarnings("unchecked")
+			final Map<String, Object> rootAsMap = mapper.readValue(usageString,
+					Map.class);
+			InteractionRecorder.runToInteractionMap.clear();
+			for (final String runId : rootAsMap.keySet()) {
+				@SuppressWarnings("unchecked")
+				final Map<String, Object> runMap = (Map<String, Object>) rootAsMap
+						.get(runId);
+				for (final String interactionId : runMap.keySet()) {
+					@SuppressWarnings("unchecked")
+					final List<String> urlList = (List<String>) runMap
+							.get(interactionId);
+					for (final String url : urlList) {
+						addResource(runId, interactionId, url);
+					}
+				}
+			}
+		} catch (final IOException e) {
+			logger.info(e);
+		}
+	}
+
+	public InteractionUtils getInteractionUtils() {
+		return interactionUtils;
+	}
+
+	public void setInteractionUtils(InteractionUtils interactionUtils) {
+		this.interactionUtils = interactionUtils;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRequestor.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRequestor.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRequestor.java
new file mode 100644
index 0000000..7826a59
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRequestor.java
@@ -0,0 +1,54 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import java.util.Map;
+
+/**
+ * @author alanrw
+ * 
+ */
+public interface InteractionRequestor {
+
+	String getRunId();
+
+	Map<String, Object> getInputData();
+
+	void fail(String string);
+
+	void carryOn();
+
+	String generateId();
+	
+	// The path to whatever requested the interaction
+	String getPath();
+	
+	// The number of times whatever requested the interaction has requested one
+	Integer getInvocationCount();
+
+	InteractionActivityType getPresentationType();
+
+	InteractionType getInteractionType();
+
+	String getPresentationOrigin();
+
+	void receiveResult(Map<String, Object> resultMap);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRunDeletionListener.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRunDeletionListener.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRunDeletionListener.java
new file mode 100644
index 0000000..07d8db0
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionRunDeletionListener.java
@@ -0,0 +1,47 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import net.sf.taverna.t2.workflowmodel.RunDeletionListener;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class InteractionRunDeletionListener implements RunDeletionListener {
+	
+	private InteractionRecorder interactionRecorder;
+
+	@SuppressWarnings("unused")
+	private static final Logger logger = Logger
+			.getLogger(InteractionRunDeletionListener.class);
+
+	@Override
+	public void deleteRun(final String runToDelete) {
+		interactionRecorder.deleteRun(runToDelete);
+	}
+
+	public void setInteractionRecorder(InteractionRecorder interactionRecorder) {
+		this.interactionRecorder = interactionRecorder;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionType.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionType.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionType.java
new file mode 100644
index 0000000..c5549e2
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionType.java
@@ -0,0 +1,30 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+/**
+ * @author alanrw
+ * 
+ */
+public enum InteractionType {
+
+	DataRequest, Notification, SecurityRequest, AuthenticationRequest
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionUtils.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionUtils.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionUtils.java
new file mode 100644
index 0000000..7165e76
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/InteractionUtils.java
@@ -0,0 +1,143 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+// import net.sf.taverna.raven.appconfig.ApplicationRuntime;
+import org.apache.taverna.activities.interaction.preference.InteractionPreference;
+
+import org.apache.commons.io.IOUtils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class InteractionUtils {
+
+	static final Set<String> publishedUrls = Collections
+			.synchronizedSet(new HashSet<String>());
+	
+	private ApplicationConfiguration appConfig;
+	
+	private InteractionRecorder interactionRecorder;
+
+	private InteractionPreference interactionPreference;
+
+	private InteractionUtils() {
+		super();
+	}
+
+	protected void copyFixedFile(final String fixedFileName)
+			throws IOException {
+		final String targetUrl = interactionPreference
+				.getLocationUrl() + "/" + fixedFileName;
+		this.publishFile(
+				targetUrl,
+				InteractionActivity.class.getResourceAsStream("/"
+						+ fixedFileName), null, null);
+	}
+
+	public void publishFile(final String urlString,
+			final String contents, final String runId,
+			final String interactionId) throws IOException {
+		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
+				contents.getBytes("UTF-8"));
+		this.publishFile(urlString, byteArrayInputStream, runId,
+				interactionId);
+	}
+
+	void publishFile(final String urlString, final InputStream is,
+			final String runId, final String interactionId) throws IOException {
+		if (InteractionUtils.publishedUrls.contains(urlString)) {
+			return;
+		}
+		InteractionUtils.publishedUrls.add(urlString);
+		if (runId != null) {
+			interactionRecorder.addResource(runId, interactionId, urlString);
+		}
+
+		final URL url = new URL(urlString);
+		final HttpURLConnection httpCon = (HttpURLConnection) url
+				.openConnection();
+		httpCon.setDoOutput(true);
+		httpCon.setRequestMethod("PUT");
+		final OutputStream outputStream = httpCon.getOutputStream();
+		IOUtils.copy(is, outputStream);
+		is.close();
+		outputStream.close();
+		int code = httpCon.getResponseCode();
+		if ((code >= 400) || (code < 0)){
+			throw new IOException ("Received code " + code);
+		}
+	}
+
+	public static String getUsedRunId(final String engineRunId) {
+		String runId = engineRunId;
+		final String specifiedId = System.getProperty("taverna.runid");
+		if (specifiedId != null) {
+			runId = specifiedId;
+		}
+		return runId;
+	}
+
+	public File getInteractionServiceDirectory() {
+		final File workingDir = appConfig
+				.getApplicationHomeDir();
+		final File interactionServiceDirectory = new File(workingDir,
+				"interactionService");
+		interactionServiceDirectory.mkdirs();
+		return interactionServiceDirectory;
+	}
+
+	public static String objectToJson(final Object o) throws IOException {
+		final ObjectMapper mapper = new ObjectMapper();
+		final StringWriter sw = new StringWriter();
+		mapper.writeValue(sw, o);
+		final String theString = sw.toString();
+		return theString;
+	}
+
+	public void setAppConfig(ApplicationConfiguration appConfig) {
+		this.appConfig = appConfig;
+	}
+
+	public void setInteractionRecorder(InteractionRecorder interactionRecorder) {
+		this.interactionRecorder = interactionRecorder;
+	}
+
+	public void setInteractionPreference(InteractionPreference interactionPreference) {
+		this.interactionPreference = interactionPreference;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/ResponseFeedListener.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/ResponseFeedListener.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/ResponseFeedListener.java
new file mode 100644
index 0000000..9afd253
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/ResponseFeedListener.java
@@ -0,0 +1,175 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.taverna.activities.interaction.atom.AtomUtils;
+import org.apache.taverna.activities.interaction.preference.InteractionPreference;
+
+import org.apache.abdera.model.Element;
+import org.apache.abdera.model.Entry;
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author alanrw
+ * 
+ */
+public final class ResponseFeedListener extends FeedReader {
+	
+	private InteractionRecorder interactionRecorder;
+
+	private InteractionPreference interactionPreference;
+
+	private static final String STATUS_OK = "OK";
+
+	private static final String DATA_READ_FAILED = "Data read failed";
+
+	private static ResponseFeedListener instance;
+
+	private static final Logger logger = Logger.getLogger(ResponseFeedListener.class);
+
+	private static final Map<String, InteractionRequestor> requestorMap = new HashMap<String, InteractionRequestor>();
+
+	private ResponseFeedListener() {
+		super("ResponseFeedListener");
+	}
+	
+	@Override
+	protected void considerEntry(final Entry entry) {
+		synchronized (requestorMap) {
+			final String refString = getReplyTo(entry);
+			if (refString == null) {
+				return;
+			}
+			final String runId = getRunId(entry);
+
+			final String entryUrl = interactionPreference
+					.getFeedUrlString() + "/" + entry.getId().toASCIIString();
+			interactionRecorder.addResource(runId, refString, entryUrl);
+
+			if (requestorMap.containsKey(refString)) {
+
+				final InteractionRequestor requestor = requestorMap
+						.get(refString);
+
+				final Element statusElement = entry.getExtension(AtomUtils
+						.getResultStatusQName());
+				final String statusContent = statusElement.getText().trim();
+				if (!statusContent.equals(STATUS_OK)) {
+					cleanup(refString);
+					requestor.fail(statusContent);
+					return;
+				}
+				final String outputDataUrl = interactionPreference
+						.getOutputDataUrlString(refString);
+				// Note that this may not really exist
+				interactionRecorder
+						.addResource(runId, refString, outputDataUrl);
+				String content = null;
+				InputStream iStream;
+				try {
+					iStream = new URL(outputDataUrl).openStream();
+					content = IOUtils.toString(iStream);
+					iStream.close();
+				} catch (final MalformedURLException e1) {
+					logger.error(e1);
+					requestor.fail(DATA_READ_FAILED);
+					return;
+				} catch (final IOException e1) {
+					logger.error(e1);
+					requestor.fail(DATA_READ_FAILED);
+					return;
+				}
+
+				try {
+					final ObjectMapper mapper = new ObjectMapper();
+					@SuppressWarnings("unchecked")
+					final Map<String, Object> rootAsMap = mapper.readValue(
+							content, Map.class);
+					requestor.receiveResult(rootAsMap);
+					cleanup(refString);
+					interactionRecorder.deleteInteraction(runId, refString);
+
+				} catch (final JsonParseException e) {
+					logger.error(e);
+				} catch (final IOException e) {
+					logger.error(e);
+				} catch (final Exception e) {
+					logger.error(e);
+				}
+
+			}
+		}
+	}
+
+	private static void cleanup(final String refString) {
+		requestorMap.remove(refString);
+	}
+
+	private static String getReplyTo(final Entry entry) {
+		final Element replyTo = entry.getFirstChild(AtomUtils
+				.getInReplyToQName());
+		if (replyTo == null) {
+			return null;
+		}
+		return replyTo.getText();
+	}
+
+	private static String getRunId(final Entry entry) {
+		final Element runIdElement = entry.getFirstChild(AtomUtils
+				.getRunIdQName());
+		if (runIdElement == null) {
+			return null;
+		}
+		return runIdElement.getText();
+	}
+
+	public void registerInteraction(final Entry entry,
+			final InteractionRequestor requestor) {
+		synchronized (requestorMap) {
+			final String refString = entry.getId().toString();
+			requestorMap.put(refString, requestor);
+		}
+	}
+
+	public void setInteractionRecorder(InteractionRecorder interactionRecorder) {
+		this.interactionRecorder = interactionRecorder;
+	}
+
+	public void setInteractionPreference(InteractionPreference interactionPreference) {
+		this.interactionPreference = interactionPreference;
+	}
+
+	@Override
+	protected InteractionPreference getInteractionPreference() {
+		return this.interactionPreference;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/atom/AtomUtils.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/atom/AtomUtils.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/atom/AtomUtils.java
new file mode 100644
index 0000000..1b8fcf2
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/atom/AtomUtils.java
@@ -0,0 +1,99 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.atom;
+
+import javax.xml.namespace.QName;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class AtomUtils {
+
+	private static QName inputDataQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "input-data",
+			"interaction");
+	private static QName resultDataQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "result-data",
+			"interaction");
+	private static QName resultStatusQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "result-status",
+			"interaction");
+	private static QName idQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "id", "interaction");
+	private static QName pathIdQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "path",
+			"interaction");
+	private static QName countQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "count",
+			"interaction");
+	private static QName runIdQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "run-id",
+			"interaction");
+	private static QName inReplyToQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "in-reply-to",
+			"interaction");
+	private static QName progressQName = new QName(
+			"http://ns.taverna.org.uk/2012/interaction", "progress",
+			"interaction");
+
+	public static QName getInputDataQName() {
+		return inputDataQName;
+	}
+
+	public static QName getIdQName() {
+		return idQName;
+	}
+
+	public static QName getInReplyToQName() {
+		return inReplyToQName;
+	}
+
+	public static QName getResultDataQName() {
+		return resultDataQName;
+	}
+
+	public static QName getResultStatusQName() {
+		return resultStatusQName;
+	}
+
+	/**
+	 * @return the runIdQName
+	 */
+	public static QName getRunIdQName() {
+		return runIdQName;
+	}
+
+	/**
+	 * @return the progressQName
+	 */
+	public static QName getProgressQName() {
+		return progressQName;
+	}
+
+	public static QName getPathIdQName() {
+		return pathIdQName;
+	}
+
+	public static QName getCountQName() {
+		return countQName;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/feed/ShowRequestFeedListener.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/feed/ShowRequestFeedListener.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/feed/ShowRequestFeedListener.java
new file mode 100644
index 0000000..3f2295d
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/feed/ShowRequestFeedListener.java
@@ -0,0 +1,81 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.feed;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.apache.taverna.activities.interaction.FeedReader;
+import org.apache.taverna.activities.interaction.preference.InteractionPreference;
+
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.model.Link;
+import org.apache.log4j.Logger;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class ShowRequestFeedListener extends FeedReader {
+	
+	private static ShowRequestFeedListener instance;
+
+	private static Logger logger = Logger
+			.getLogger(ShowRequestFeedListener.class);
+	
+	private static final String ignore_requests_property = System.getProperty("taverna.interaction.ignore_requests");
+
+	private static boolean operational = (ignore_requests_property == null) || !Boolean.valueOf(ignore_requests_property);
+
+	private InteractionPreference interactionPreference;
+	
+	private ShowRequestFeedListener() {
+		super("ShowRequestFeedListener");
+	}
+	
+			@Override
+			protected void considerEntry(final Entry entry) {
+				if (!operational) {
+					return;
+				}
+				final Link presentationLink = entry.getLink("presentation");
+				if (presentationLink != null) {
+					try {
+						Desktop.getDesktop().browse(
+								presentationLink.getHref().toURI());
+					} catch (final IOException e) {
+						logger.error("Cannot open presentation");
+					} catch (final URISyntaxException e) {
+						logger.error("Cannot open presentation");
+					}
+				}
+			}
+
+			@Override
+			protected InteractionPreference getInteractionPreference() {
+				return this.interactionPreference;
+			}
+
+			public void setInteractionPreference(InteractionPreference interactionPreference) {
+				this.interactionPreference = interactionPreference;
+			}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/HackedFilesystemAdapter.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/HackedFilesystemAdapter.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/HackedFilesystemAdapter.java
new file mode 100644
index 0000000..0729b23
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/HackedFilesystemAdapter.java
@@ -0,0 +1,264 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.jetty;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.i18n.templates.Template;
+import org.apache.abdera.i18n.text.Normalizer;
+import org.apache.abdera.i18n.text.Sanitizer;
+import org.apache.abdera.model.Document;
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.model.Feed;
+import org.apache.abdera.model.Link;
+import org.apache.abdera.protocol.server.ProviderHelper;
+import org.apache.abdera.protocol.server.RequestContext;
+import org.apache.abdera.protocol.server.ResponseContext;
+import org.apache.abdera.protocol.server.Target;
+import org.apache.abdera.protocol.server.provider.managed.FeedConfiguration;
+import org.apache.abdera.protocol.server.provider.managed.ManagedCollectionAdapter;
+
+/**
+ * Simple Filesystem Adapter that uses a local directory to store Atompub
+ * collection entries. As an extension of the ManagedCollectionAdapter class,
+ * the Adapter is intended to be used with implementations of the
+ * ManagedProvider and are configured using /abdera/adapter/*.properties files.
+ * The *.properties file MUST specify the fs.root property to specify the root
+ * directory used by the Adapter.
+ */
+public class HackedFilesystemAdapter extends ManagedCollectionAdapter {
+	
+	private InteractionJetty interactionJetty;
+
+	private final File root;
+	private final static FileSorter sorter = new FileSorter();
+	private final static Template paging_template = new Template(
+			"?{-join|&|count,page}");
+
+	public HackedFilesystemAdapter(final Abdera abdera,
+			final FeedConfiguration config) {
+		super(abdera, config);
+		this.root = this.getRoot();
+	}
+
+	private File getRoot() {
+		return interactionJetty.getFeedDirectory();
+	}
+
+	private Entry getEntry(final File entryFile) {
+		if (!entryFile.exists() || !entryFile.isFile()) {
+			throw new RuntimeException();
+		}
+		try {
+			final FileInputStream fis = new FileInputStream(entryFile);
+			final Document<Entry> doc = this.abdera.getParser().parse(fis);
+			final Entry entry = doc.getRoot();
+			return entry;
+		} catch (final Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void addPagingLinks(final RequestContext request, final Feed feed,
+			final int currentpage, final int count) {
+		final Map<String, Object> params = new HashMap<String, Object>();
+		params.put("count", count);
+		params.put("page", currentpage + 1);
+		String next = paging_template.expand(params);
+		next = request.getResolvedUri().resolve(next).toString();
+		feed.addLink(next, "next");
+		if (currentpage > 0) {
+			params.put("page", currentpage - 1);
+			String prev = paging_template.expand(params);
+			prev = request.getResolvedUri().resolve(prev).toString();
+			feed.addLink(prev, "previous");
+		}
+		params.put("page", 0);
+		String current = paging_template.expand(params);
+		current = request.getResolvedUri().resolve(current).toString();
+		feed.addLink(current, "current");
+	}
+
+	private void getEntries(final RequestContext request, final Feed feed,
+			final File root) {
+		final File[] files = root.listFiles();
+		Arrays.sort(files, sorter);
+		final int length = ProviderHelper.getPageSize(request, "count", 25);
+		final int offset = ProviderHelper.getOffset(request, "page", length);
+		final String _page = request.getParameter("page");
+		final int page = (_page != null) ? Integer.parseInt(_page) : 0;
+		this.addPagingLinks(request, feed, page, length);
+		if (offset > files.length) {
+			return;
+		}
+		for (int n = offset; (n < (offset + length)) && (n < files.length); n++) {
+			final File file = files[n];
+			try {
+				final Entry entry = this.getEntry(file);
+				feed.addEntry((Entry) entry.clone());
+			} catch (final Exception e) {
+				// Do nothing
+			}
+		}
+	}
+
+	@Override
+	public ResponseContext getFeed(final RequestContext request) {
+		final Feed feed = this.abdera.newFeed();
+		feed.setId(this.config.getServerConfiguration().getServerUri() + "/"
+				+ this.config.getFeedId());
+		feed.setTitle(this.config.getFeedTitle());
+		feed.addAuthor(this.config.getFeedAuthor());
+		feed.addLink(this.config.getFeedUri());
+		feed.addLink(this.config.getFeedUri(), "self");
+		feed.setUpdated(new Date());
+		this.getEntries(request, feed, this.root);
+		return ProviderHelper.returnBase(feed.getDocument(), 200, null);
+	}
+
+	@Override
+	public ResponseContext deleteEntry(final RequestContext request) {
+		final Target target = request.getTarget();
+		final String key = target.getParameter("entry");
+		final File file = this.getFile(key, false);
+		if (file.exists()) {
+			file.delete();
+		}
+		return ProviderHelper.nocontent();
+	}
+
+	@Override
+	public ResponseContext getEntry(final RequestContext request) {
+		final Target target = request.getTarget();
+		final String key = target.getParameter("entry");
+		final File file = this.getFile(key, false);
+		final Entry entry = this.getEntry(file);
+		if (entry != null) {
+			return ProviderHelper.returnBase(entry.getDocument(), 200, null);
+		} else {
+			return ProviderHelper.notfound(request);
+		}
+	}
+
+	@Override
+	public ResponseContext postEntry(final RequestContext request) {
+		if (request.isAtom()) {
+			try {
+				final Entry entry = (Entry) request.getDocument().getRoot()
+						.clone();
+				final String key = this.createKey(request);
+				this.setEditDetail(request, entry, key);
+				final File file = this.getFile(key);
+				final FileOutputStream out = new FileOutputStream(file);
+				entry.writeTo(out);
+				final String edit = entry.getEditLinkResolvedHref().toString();
+				return ProviderHelper
+						.returnBase(entry.getDocument(), 201, null)
+						.setLocation(edit);
+			} catch (final Exception e) {
+				return ProviderHelper.badrequest(request);
+			}
+		} else {
+			return ProviderHelper.notsupported(request);
+		}
+	}
+
+	private void setEditDetail(final RequestContext request, final Entry entry,
+			final String key) throws IOException {
+		final Target target = request.getTarget();
+		final String feed = target.getParameter("feed");
+		final String id = key;
+		entry.setEdited(new Date());
+		final Link link = entry.getEditLink();
+		final Map<String, Object> params = new HashMap<String, Object>();
+		params.put("feed", feed);
+		params.put("entry", id);
+		final String href = request.absoluteUrlFor("entry", params);
+		if (link == null) {
+			entry.addLink(href, "edit");
+		} else {
+			link.setHref(href);
+		}
+	}
+
+	private File getFile(final String key) {
+		return this.getFile(key, true);
+	}
+
+	private File getFile(final String key, final boolean post) {
+		final File file = new File(this.root, key);
+		if (post && file.exists()) {
+			throw new RuntimeException("File exists");
+		}
+		return file;
+	}
+
+	private String createKey(final RequestContext request) throws IOException {
+		String slug = request.getSlug();
+		if (slug == null) {
+			slug = ((Entry) request.getDocument().getRoot()).getTitle();
+		}
+		return Sanitizer.sanitize(slug, "", true, Normalizer.Form.D);
+	}
+
+	@Override
+	public ResponseContext putEntry(final RequestContext request) {
+		if (request.isAtom()) {
+			try {
+				final Entry entry = (Entry) request.getDocument().getRoot()
+						.clone();
+				final String key = request.getTarget().getParameter("entry");
+				this.setEditDetail(request, entry, key);
+				final File file = this.getFile(key, false);
+				final FileOutputStream out = new FileOutputStream(file);
+				entry.writeTo(out);
+				final String edit = entry.getEditLinkResolvedHref().toString();
+				return ProviderHelper
+						.returnBase(entry.getDocument(), 200, null)
+						.setLocation(edit);
+			} catch (final Exception e) {
+				return ProviderHelper.badrequest(request);
+			}
+		} else {
+			return ProviderHelper.notsupported(request);
+		}
+	}
+
+	private static class FileSorter implements Comparator<File> {
+		@Override
+		public int compare(final File o1, final File o2) {
+			return o1.lastModified() > o2.lastModified() ? -1 : o1
+					.lastModified() < o2.lastModified() ? 1 : 0;
+		}
+	}
+
+	public void setInteractionJetty(InteractionJetty interactionJetty) {
+		this.interactionJetty = interactionJetty;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/InteractionJetty.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/InteractionJetty.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/InteractionJetty.java
new file mode 100644
index 0000000..c5827ac
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/jetty/InteractionJetty.java
@@ -0,0 +1,219 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.jetty;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.taverna.activities.interaction.FeedReader;
+import org.apache.taverna.activities.interaction.InteractionUtils;
+import org.apache.taverna.activities.interaction.ResponseFeedListener;
+import org.apache.taverna.activities.interaction.feed.ShowRequestFeedListener;
+import org.apache.taverna.activities.interaction.preference.InteractionPreference;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+//import net.sf.taverna.t2.spi.SPIRegistry;
+import net.sf.webdav.WebdavServlet;
+
+import org.apache.abdera.protocol.server.ServiceManager;
+import org.apache.abdera.protocol.server.provider.basic.BasicProvider;
+import org.apache.abdera.protocol.server.servlet.AbderaServlet;
+import org.apache.log4j.Logger;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.handler.HandlerList;
+import org.mortbay.jetty.security.Constraint;
+import org.mortbay.jetty.security.ConstraintMapping;
+import org.mortbay.jetty.security.HashUserRealm;
+import org.mortbay.jetty.security.SecurityHandler;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class InteractionJetty {
+
+	private static Logger logger = Logger.getLogger(InteractionJetty.class);
+	
+	private InteractionUtils interactionUtils;
+	
+	private ShowRequestFeedListener showRequestFeedListener;
+	private ResponseFeedListener responseFeedListener;
+
+	private InteractionPreference interactionPreference;
+
+	private static Server server;
+
+	private static String REALM_NAME = "TavernaInteraction";
+	
+	private static boolean listenersStarted = false;
+
+	public synchronized void startJettyIfNecessary(CredentialManager credentialManager) {
+		if (server != null) {
+			return;
+		}
+		
+//		final ClassLoader previousContextClassLoader = Thread.currentThread()
+//				.getContextClassLoader();
+//		Thread.currentThread().setContextClassLoader(
+//				InteractionJetty.class.getClassLoader());
+
+		final String port = interactionPreference.getPort();
+
+		server = new Server(Integer.parseInt(port));
+		server.setStopAtShutdown(true);
+
+		final WebdavServlet interactionServlet = new WebdavServlet();
+
+		final ServletHolder interactionHolder = new ServletHolder();
+		interactionHolder.setServlet(interactionServlet);
+
+		try {
+
+			interactionHolder.setInitParameter("rootpath",
+					getInteractionDirectory().getCanonicalPath());
+		} catch (final IOException e1) {
+			logger.error("Unable to set root of interaction", e1);
+		}
+
+		final HandlerList handlers = new HandlerList();
+		final Context overallContext = new Context(handlers, "/",
+				Context.SESSIONS);
+		overallContext.setContextPath("/");
+		server.setHandler(overallContext);
+
+		final AbderaServlet abderaServlet = new AbderaServlet();
+		final ServletHolder abderaHolder = new ServletHolder(abderaServlet);
+		abderaHolder.setInitParameter(ServiceManager.PROVIDER,
+				BasicProvider.class.getName());
+
+		overallContext.addServlet(abderaHolder, "/*");
+		overallContext.addServlet(interactionHolder, "/interaction/*");
+
+		if (interactionPreference.getUseUsername()) {
+			final Constraint constraint = new Constraint();
+			constraint.setName(Constraint.__BASIC_AUTH);
+
+			constraint.setRoles(new String[] { "user", "admin", "moderator" });
+			constraint.setAuthenticate(true);
+
+			final ConstraintMapping cm = new ConstraintMapping();
+			cm.setConstraint(constraint);
+			cm.setPathSpec("/*");
+
+			final SecurityHandler sh = new SecurityHandler();
+			try {
+				final HashUserRealm realm = new HashUserRealm(REALM_NAME);
+				final URI serviceURI = createServiceURI(port);
+				final UsernamePassword up = credentialManager
+						.getUsernameAndPasswordForService(serviceURI, true,
+								"Please specify the username and password to secure your interactions");
+				if (up != null) {
+					final String username = up.getUsername();
+					realm.put(username, up.getPasswordAsString());
+					realm.addUserToRole(username, "user");
+				}
+				sh.setUserRealm(realm);
+			} catch (final CMException e) {
+				logger.error(e);
+			} catch (final URISyntaxException e) {
+				logger.error(e);
+			}
+			sh.setConstraintMappings(new ConstraintMapping[] { cm });
+			overallContext.addHandler(sh);
+
+		}
+
+		getFeedDirectory();
+
+		try {
+			server.start();
+			while (!server.isRunning()) {
+				Thread.sleep(5000);
+			}
+		} catch (final Exception e) {
+			logger.error("Unable to start Jetty");
+		}
+//		Thread.currentThread()
+//				.setContextClassLoader(previousContextClassLoader);
+	}
+
+	public static URI createServiceURI(final String port)
+			throws URISyntaxException {
+		return new URI("http://localhost:" + port + "/#" + REALM_NAME);
+	}
+
+	public File getJettySubdirectory(final String subdirectoryName) {
+		final File workingDir = interactionUtils
+				.getInteractionServiceDirectory();
+		final File subDir = new File(workingDir, "jetty/" + subdirectoryName);
+		subDir.mkdirs();
+		return subDir;
+	}
+
+	public File getFeedDirectory() {
+		return getJettySubdirectory("feed");
+	}
+
+	public File getInteractionDirectory() {
+		return getJettySubdirectory("interaction");
+	}
+	
+	public synchronized void startListenersIfNecessary() {
+		if (listenersStarted) {
+			return;
+		}
+		listenersStarted = true;
+		startListener(this.responseFeedListener);
+		startListener(showRequestFeedListener);
+
+	}
+
+	private void startListener(FeedReader fr) {
+		try {
+			fr.start();
+		}
+		catch (Exception e) {
+			logger.error("Failed to start " + fr.getClass().getCanonicalName(), e);
+		}
+	}
+	
+	public void setInteractionUtils(InteractionUtils interactionUtils) {
+		this.interactionUtils = interactionUtils;
+	}
+
+	public void setShowRequestFeedListener(
+			ShowRequestFeedListener showRequestFeedListener) {
+		this.showRequestFeedListener = showRequestFeedListener;
+	}
+
+	public void setResponseFeedListener(ResponseFeedListener responseFeedListener) {
+		this.responseFeedListener = responseFeedListener;
+	}
+
+	public void setInteractionPreference(InteractionPreference interactionPreference) {
+		this.interactionPreference = interactionPreference;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/preference/InteractionPreference.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/preference/InteractionPreference.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/preference/InteractionPreference.java
new file mode 100644
index 0000000..6b47368
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/preference/InteractionPreference.java
@@ -0,0 +1,284 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.preference;
+
+import java.awt.GraphicsEnvironment;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class InteractionPreference {
+	
+	private ApplicationConfiguration appConfig;
+
+	private static final String USE_JETTY = "useJetty";
+
+	private static final String DEFAULT_USE_JETTY = "true";
+
+	private static final String PORT = "port";
+
+	private static final String DEFAULT_PORT = "8080";
+
+	private static final String HOST = "host";
+
+	private static final String DEFAULT_HOST = "http://localhost";
+
+	private static final String WEBDAV_PATH = "webdavPath";
+
+	private static final String DEFAULT_WEBDAV_PATH = "/interaction";
+
+	private static final String FEED_PATH = "feedPath";
+
+	private static final String DEFAULT_FEED_PATH = "/feed";
+
+	private static final String USE_USERNAME = "Secure with username / password";
+
+	private static final String DEFAULT_USE_USERNAME = "false";
+
+	// private static final String USE_HTTPS = "Use HTTPS";
+
+	// private static final String DEFAULT_USE_HTTPS = "false";
+
+	private final Logger logger = Logger.getLogger(InteractionPreference.class);
+
+	private final Properties properties;
+
+	private File getConfigFile() {
+		final File home = appConfig
+				.getApplicationHomeDir();
+		final File config = new File(home, "conf");
+		if (!config.exists()) {
+			config.mkdir();
+		}
+		final File configFile = new File(config, this.getFilePrefix() + "-"
+				+ this.getUUID() + ".config");
+		return configFile;
+	}
+
+	private InteractionPreference(ApplicationConfiguration appConfig) {
+		setAppConfig(appConfig);
+		final File configFile = this.getConfigFile();
+		this.properties = new Properties();
+		if (configFile.exists()) {
+			try {
+				final FileReader reader = new FileReader(configFile);
+				this.properties.load(reader);
+				reader.close();
+			} catch (final FileNotFoundException e) {
+				this.logger.error(e);
+			} catch (final IOException e) {
+				this.logger.error(e);
+			}
+		}
+		if (GraphicsEnvironment.isHeadless()
+				|| ((System.getProperty("java.awt.headless") != null) && System
+						.getProperty("java.awt.headless").equals("true"))) {
+			final String definedHost = System
+					.getProperty("taverna.interaction.host");
+			if (definedHost != null) {
+				this.properties.setProperty(USE_JETTY, "false");
+				this.logger.info("USE_JETTY set to false");
+				this.properties.setProperty(HOST, definedHost);
+			}
+			final String definedPort = System
+					.getProperty("taverna.interaction.port");
+			if (definedPort != null) {
+				this.properties.setProperty(PORT, definedPort);
+			}
+			final String definedWebDavPath = System
+					.getProperty("taverna.interaction.webdav_path");
+			if (definedWebDavPath != null) {
+				this.properties.setProperty(WEBDAV_PATH, definedWebDavPath);
+			}
+			final String definedFeedPath = System
+					.getProperty("taverna.interaction.feed_path");
+			if (definedFeedPath != null) {
+				this.properties.setProperty(FEED_PATH, definedFeedPath);
+			}
+		} else {
+			this.logger.info("Running non-headless");
+		}
+		this.fillDefaultProperties();
+	}
+
+	private void fillDefaultProperties() {
+		if (!this.properties.containsKey(USE_JETTY)) {
+			this.properties.setProperty(USE_JETTY, DEFAULT_USE_JETTY);
+			this.logger.info("USE_JETTY set to " + DEFAULT_USE_JETTY);
+		}
+		if (!this.properties.containsKey(PORT)) {
+			this.properties.setProperty(PORT, DEFAULT_PORT);
+		}
+		if (!this.properties.containsKey(HOST)) {
+			this.properties.setProperty(HOST, DEFAULT_HOST);
+		}
+		if (!this.properties.containsKey(WEBDAV_PATH)) {
+			this.properties.setProperty(WEBDAV_PATH, DEFAULT_WEBDAV_PATH);
+		}
+		if (!this.properties.containsKey(FEED_PATH)) {
+			this.properties.setProperty(FEED_PATH, DEFAULT_FEED_PATH);
+		}
+		if (!this.properties.containsKey(USE_USERNAME)) {
+			this.properties.setProperty(USE_USERNAME, DEFAULT_USE_USERNAME);
+		}
+		/*
+		 * if (!properties.containsKey(USE_HTTPS)) {
+		 * properties.setProperty(USE_HTTPS, DEFAULT_USE_HTTPS); }
+		 */
+	}
+
+	public String getFilePrefix() {
+		return "Interaction";
+	}
+
+	public void store() {
+		try {
+			final FileOutputStream out = new FileOutputStream(
+					this.getConfigFile());
+			this.properties.store(out, "");
+			out.close();
+		} catch (final FileNotFoundException e) {
+			this.logger.error(e);
+		} catch (final IOException e) {
+			this.logger.error(e);
+		}
+	}
+
+	public String getUUID() {
+		return "DA992717-5A46-469D-AE25-883F0E4CD348";
+	}
+
+	public void setPort(final String text) {
+		this.properties.setProperty(PORT, text);
+	}
+
+	public void setHost(final String text) {
+		this.properties.setProperty(HOST, text);
+	}
+
+	public void setUseJetty(final boolean use) {
+		this.properties.setProperty(USE_JETTY, Boolean.toString(use));
+	}
+
+	public void setFeedPath(final String path) {
+		this.properties.setProperty(FEED_PATH, path);
+	}
+
+	public void setWebDavPath(final String path) {
+		this.properties.setProperty(WEBDAV_PATH, path);
+	}
+
+	public String getPort() {
+		return this.properties.getProperty(PORT);
+	}
+
+	public String getHost() {
+		return this.properties.getProperty(HOST);
+	}
+
+	public boolean getUseJetty() {
+		return (Boolean.parseBoolean(this.properties.getProperty(USE_JETTY)));
+	}
+
+	public String getFeedPath() {
+		return this.properties.getProperty(FEED_PATH);
+	}
+
+	public String getWebDavPath() {
+		return this.properties.getProperty(WEBDAV_PATH);
+	}
+
+	public String getDefaultHost() {
+		return DEFAULT_HOST;
+	}
+
+	public String getDefaultFeedPath() {
+		return DEFAULT_FEED_PATH;
+	}
+
+	public String getDefaultWebDavPath() {
+		return DEFAULT_WEBDAV_PATH;
+	}
+
+	public String getFeedUrlString() {
+		return this.getHost() + ":" + this.getPort() + this.getFeedPath();
+	}
+
+	public String getLocationUrl() {
+		return this.getHost() + ":" + this.getPort() + this.getWebDavPath();
+	}
+
+	public boolean getUseUsername() {
+		return (Boolean.parseBoolean(this.properties.getProperty(USE_USERNAME)));
+	}
+
+	public void setUseUsername(final boolean useUsername) {
+		this.properties
+				.setProperty(USE_USERNAME, Boolean.toString(useUsername));
+	}
+
+	public String getOutputDataUrlString(final String interactionId) {
+		return this.getLocationUrl()
+				+ "/interaction" + interactionId + "OutputData.json";
+	}
+
+	public String getInputDataUrlString(final String interactionId) {
+		return this.getLocationUrl()
+				+ "/interaction" + interactionId + "InputData.json";
+	}
+
+	public URL getFeedUrl() throws MalformedURLException {
+		return new URL(this.getFeedUrlString());
+	}
+
+	public String getInteractionUrlString(final String interactionId) {
+		return this.getLocationUrl()
+				+ "/interaction" + interactionId + ".html";
+	}
+
+	public String getPresentationUrlString(final String interactionId) {
+		return this.getLocationUrl()
+				+ "/presentation" + interactionId + ".html";
+	}
+
+	public String getPublicationUrlString(final String interactionId,
+			final String key) {
+		return this.getLocationUrl()
+				+ "/interaction" + interactionId + "_" + key;
+	}
+
+	public void setAppConfig(ApplicationConfiguration appConfig) {
+		this.appConfig = appConfig;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/InteractionVelocity.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/InteractionVelocity.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/InteractionVelocity.java
new file mode 100644
index 0000000..774060e
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/InteractionVelocity.java
@@ -0,0 +1,145 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.velocity;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+
+import org.apache.taverna.activities.interaction.InteractionActivity;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+import org.apache.velocity.Template;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class InteractionVelocity {
+
+	public static Logger logger = Logger.getLogger(InteractionVelocity.class);
+
+	private static boolean velocityInitialized = false;
+
+	private static final String TEMPLATE_SUFFIX = ".vm";
+
+	private Template interactionTemplate = null;
+	private static final String INTERACTION_TEMPLATE_NAME = "interaction";
+
+	private ArrayList<String> templateNames = new ArrayList<String>();
+
+	private VelocityEngine ve = new VelocityEngine();
+	
+	@SuppressWarnings("deprecation")
+	public synchronized void checkVelocity() {
+		if (velocityInitialized) { 
+			return;
+		}
+		velocityInitialized = true;
+		ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "string");
+		ve.setProperty("resource.loader.class",
+				"org.apache.velocity.runtime.resource.loader.StringResourceLoader");
+		ve.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
+				"org.apache.velocity.runtime.log.Log4JLogChute");
+		ve.setProperty("runtime.log.logsystem.log4j.logger",
+				"net.sf.taverna.t2.activities.interaction.velocity.InteractionVelocity");
+		ve.init();
+		ve.loadDirective(RequireDirective.class.getName());
+		ve.loadDirective(ProduceDirective.class.getName());
+		ve.loadDirective(NotifyDirective.class.getName());
+
+		loadTemplates();
+
+		interactionTemplate = ve.getTemplate(INTERACTION_TEMPLATE_NAME);
+		if (interactionTemplate == null) {
+			logger.error("Could not open interaction template "
+					+ INTERACTION_TEMPLATE_NAME);
+		}
+	}
+
+	private void loadTemplates() {
+		final InputStream is = InteractionActivity.class
+				.getResourceAsStream("/index");
+		if (is == null) {
+			logger.error("Unable to read /index");
+			return;
+		}
+		final BufferedReader br = new BufferedReader(new InputStreamReader(is));
+		try {
+			for (String line = br.readLine(); line != null; line = br
+					.readLine()) {
+				if (line.startsWith("#")) {
+					continue;
+				}
+				line = line.trim();
+				if (line.isEmpty()) {
+					continue;
+				}
+				final String templatePath = line + TEMPLATE_SUFFIX;
+				logger.info("Looking for " + templatePath);
+				final StringResourceRepository repo = StringResourceLoader
+						.getRepository();
+				try {
+					repo.putStringResource(line,
+							getTemplateFromResource(templatePath));
+				} catch (final IOException e) {
+					logger.error(
+							"Failed reading template from " + templatePath, e);
+				}
+				final Template t = Velocity.getTemplate(line);
+				if (t == null) {
+					logger.error("Registration failed");
+				}
+				if (!line.equals(INTERACTION_TEMPLATE_NAME)) {
+					templateNames.add(line);
+				}
+			}
+		} catch (final IOException e) {
+			logger.error("Failed reading template index", e);
+		}
+	}
+
+	public Template getInteractionTemplate() {
+		checkVelocity();
+		return interactionTemplate;
+	}
+
+	private String getTemplateFromResource(final String templatePath)
+			throws IOException {
+		checkVelocity();
+		final InputStream stream = InteractionVelocity.class
+				.getResourceAsStream("/" + templatePath);
+		final String result = IOUtils.toString(stream, "UTF-8");
+		return result;
+	}
+
+	public ArrayList<String> getTemplateNames() {
+		checkVelocity();
+		return templateNames;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyChecker.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyChecker.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyChecker.java
new file mode 100644
index 0000000..d9c6ec2
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyChecker.java
@@ -0,0 +1,42 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.velocity;
+
+import org.apache.velocity.runtime.parser.node.ASTDirective;
+import org.apache.velocity.runtime.visitor.BaseVisitor;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class NotifyChecker extends BaseVisitor {
+
+	@Override
+	public Object visit(final ASTDirective node, final Object data) {
+		ObjectNode json = (ObjectNode) data;
+		if (node.getDirectiveName().equals("notify")) {
+			json.put("progressNotification", true);
+		}
+		return null;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyDirective.java
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyDirective.java b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyDirective.java
new file mode 100644
index 0000000..2b314d6
--- /dev/null
+++ b/taverna-interaction-activity/src/main/java/org/apache/taverna/activities/interaction/velocity/NotifyDirective.java
@@ -0,0 +1,74 @@
+/*
+* 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.
+*/
+
+package org.apache.taverna.activities.interaction.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.directive.Directive;
+import org.apache.velocity.runtime.parser.node.Node;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class NotifyDirective extends Directive {
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.velocity.runtime.directive.Directive#getName()
+	 */
+	@Override
+	public String getName() {
+		return "notify";
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.apache.velocity.runtime.directive.Directive#getType()
+	 */
+	@Override
+	public int getType() {
+		return LINE;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.apache.velocity.runtime.directive.Directive#render(org.apache.velocity
+	 * .context.InternalContextAdapter, java.io.Write\ r,
+	 * org.apache.velocity.runtime.parser.node.Node)
+	 */
+	@Override
+	public boolean render(final InternalContextAdapter context,
+			final Writer writer, final Node node) throws IOException,
+			ResourceNotFoundException, ParseErrorException,
+			MethodInvocationException {
+		return true;
+	}
+
+}