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/23 17:38:11 UTC

[27/51] [partial] incubator-taverna-engine git commit:

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/RunServiceImpl.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/RunServiceImpl.java b/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/RunServiceImpl.java
deleted file mode 100755
index 1a4ca29..0000000
--- a/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/RunServiceImpl.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2010 The University of Manchester
- *
- *  Modifications to the initial code base are copyright of their
- *  respective authors, or their employers as appropriate.
- *
- *  This program is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public License
- *  as published by the Free Software Foundation; either version 2.1 of
- *  the License, or (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful, but
- *  WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- ******************************************************************************/
-package uk.org.taverna.platform.run.impl;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.ClosedFileSystemException;
-import java.nio.file.InvalidPathException;
-import java.nio.file.Path;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.apache.taverna.robundle.Bundle;
-
-import org.apache.taverna.databundle.DataBundles;
-import uk.org.taverna.platform.execution.api.ExecutionEnvironment;
-import uk.org.taverna.platform.execution.api.ExecutionEnvironmentService;
-import uk.org.taverna.platform.execution.api.InvalidExecutionIdException;
-import uk.org.taverna.platform.execution.api.InvalidWorkflowException;
-import uk.org.taverna.platform.report.ReportListener;
-import uk.org.taverna.platform.report.State;
-import uk.org.taverna.platform.report.WorkflowReport;
-import uk.org.taverna.platform.run.api.InvalidRunIdException;
-import uk.org.taverna.platform.run.api.RunProfile;
-import uk.org.taverna.platform.run.api.RunProfileException;
-import uk.org.taverna.platform.run.api.RunService;
-import uk.org.taverna.platform.run.api.RunStateException;
-import org.apache.taverna.scufl2.api.container.WorkflowBundle;
-import org.apache.taverna.scufl2.api.core.Workflow;
-import org.apache.taverna.scufl2.api.io.ReaderException;
-import org.apache.taverna.scufl2.api.io.WorkflowBundleIO;
-import org.apache.taverna.scufl2.api.profiles.Profile;
-
-/**
- * Implementation of the <code>RunService</code>.
- *
- * @author David Withers
- */
-public class RunServiceImpl implements RunService {
-	private static final Logger logger = Logger.getLogger(RunServiceImpl.class.getName());
-	private static SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
-
-	private final Map<String, Run> runMap;
-	private ExecutionEnvironmentService executionEnvironmentService;
-	private EventAdmin eventAdmin;
-
-	public RunServiceImpl() {
-		runMap = new TreeMap<>();
-	}
-
-	@Override
-	public Set<ExecutionEnvironment> getExecutionEnvironments() {
-		return executionEnvironmentService.getExecutionEnvironments();
-	}
-
-	@Override
-	public Set<ExecutionEnvironment> getExecutionEnvironments(WorkflowBundle workflowBundle) {
-		return getExecutionEnvironments(workflowBundle.getMainProfile());
-	}
-
-	@Override
-	public Set<ExecutionEnvironment> getExecutionEnvironments(Profile profile) {
-		return executionEnvironmentService.getExecutionEnvironments(profile);
-	}
-
-	@Override
-	public List<String> getRuns() {
-		return new ArrayList<>(runMap.keySet());
-	}
-
-	@Override
-	public String createRun(RunProfile runProfile) throws InvalidWorkflowException, RunProfileException {
-		Run run = new Run(runProfile);
-		run.getWorkflowReport().addReportListener(new RunReportListener(run.getID()));
-		runMap.put(run.getID(), run);
-		postEvent(RUN_CREATED, run.getID());
-		return run.getID();
-	}
-
-	@Override
-	public String open(File runFile) throws IOException {
-		try {
-			String runID = runFile.getName();
-			int dot = runID.indexOf('.');
-			if (dot > 0)
-				runID = runID.substring(0, dot);
-			if (!runMap.containsKey(runID)) {
-				Bundle bundle = DataBundles.openBundle(runFile.toPath());
-				Run run = new Run(runID, bundle);
-				runMap.put(run.getID(), run);
-			}
-			postEvent(RUN_OPENED, runID);
-			return runID;
-		} catch (ReaderException | ParseException e) {
-			throw new IOException("Error opening file " + runFile, e);
-		}
-	}
-
-	@Override
-	public void close(String runID) throws InvalidRunIdException, InvalidExecutionIdException {
-		Run run = getRun(runID);
-		try {
-			Bundle dataBundle = run.getDataBundle();
-			DataBundles.closeBundle(dataBundle);
-		} catch (IOException | ClosedFileSystemException e) {
-			logger.log(Level.WARNING, "Error closing data bundle for run " + runID, e);
-		}
-		runMap.remove(runID);
-		postEvent(RUN_CLOSED, runID);
-	}
-
-	@Override
-	public void save(String runID, File runFile) throws InvalidRunIdException, IOException {
-		Run run = getRun(runID);
-		Bundle dataBundle = run.getDataBundle();
-		try {
-			DataBundles.closeAndSaveBundle(dataBundle, runFile.toPath());
-		} catch (InvalidPathException e) {
-			throw new IOException(e);
-		}
-	}
-
-	@Override
-	public void delete(String runID) throws InvalidRunIdException, InvalidExecutionIdException {
-		Run run = getRun(runID);
-		run.delete();
-		Bundle dataBundle = run.getDataBundle();
-		try {
-			DataBundles.closeBundle(dataBundle);
-		} catch (IOException e) {
-			logger.log(Level.WARNING, "Error closing data bundle for run " + runID, e);
-		}
-		runMap.remove(runID);
-		postEvent(RUN_DELETED, runID);
-	}
-
-	@Override
-	public void start(String runID) throws InvalidRunIdException, RunStateException, InvalidExecutionIdException {
-		getRun(runID).start();
-		postEvent(RUN_STARTED, runID);
-	}
-
-	@Override
-	public void pause(String runID) throws InvalidRunIdException, RunStateException, InvalidExecutionIdException {
-		getRun(runID).pause();
-		postEvent(RUN_PAUSED, runID);
-	}
-
-	@Override
-	public void resume(String runID) throws InvalidRunIdException, RunStateException, InvalidExecutionIdException {
-		getRun(runID).resume();
-		postEvent(RUN_RESUMED, runID);
-	}
-
-	@Override
-	public void cancel(String runID) throws InvalidRunIdException, RunStateException, InvalidExecutionIdException {
-		getRun(runID).cancel();
-		postEvent(RUN_STOPPED, runID);
-	}
-
-	@Override
-	public State getState(String runID) throws InvalidRunIdException {
-		return getRun(runID).getState();
-	}
-
-	@Override
-	public Bundle getDataBundle(String runID) throws InvalidRunIdException {
-		return getRun(runID).getDataBundle();
-	}
-
-	@Override
-	public WorkflowReport getWorkflowReport(String runID) throws InvalidRunIdException {
-		return getRun(runID).getWorkflowReport();
-	}
-
-	@Override
-	public Workflow getWorkflow(String runID) throws InvalidRunIdException {
-		return getRun(runID).getWorkflow();
-	}
-
-	@Override
-	public Profile getProfile(String runID) throws InvalidRunIdException {
-		return getRun(runID).getProfile();
-	}
-
-	@Override
-	public String getRunName(String runID) throws InvalidRunIdException {
-		WorkflowReport workflowReport = getWorkflowReport(runID);
-		return workflowReport.getSubject().getName() + "_" + ISO_8601.format(workflowReport.getCreatedDate());
-	}
-
-	private Run getRun(String runID) throws InvalidRunIdException {
-		Run run = runMap.get(runID);
-		if (run == null)
-			throw new InvalidRunIdException("Run ID " + runID + " is not valid");
-		return run;
-	}
-
-	private void postEvent(String topic, String runId) {
-		HashMap<String, String> properties = new HashMap<>();
-		properties.put("RUN_ID", runId);
-		Event event = new Event(topic, properties);
-		eventAdmin.postEvent(event);
-	}
-
-	public void setExecutionEnvironmentService(ExecutionEnvironmentService executionEnvironmentService) {
-		this.executionEnvironmentService = executionEnvironmentService;
-	}
-
-	public void setEventAdmin(EventAdmin eventAdmin) {
-		this.eventAdmin = eventAdmin;
-	}
-
-	public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
-		DataBundles.setWfBundleIO(workflowBundleIO);
-	}
-
-	private class RunReportListener implements ReportListener {
-		private final String runId;
-
-		public RunReportListener(String runId) {
-			this.runId = runId;
-		}
-
-		@Override
-		public void outputAdded(Path path, String portName, int[] index) {
-		}
-
-		@Override
-		public void stateChanged(State oldState, State newState) {
-			switch (newState) {
-			case COMPLETED:
-			case FAILED:
-				postEvent(RUN_STOPPED, runId);
-			default:
-				break;
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/WorkflowReportJSON.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/WorkflowReportJSON.java b/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/WorkflowReportJSON.java
deleted file mode 100644
index 0c6f8e3..0000000
--- a/taverna-run-impl/src/main/java/uk/org/taverna/platform/run/impl/WorkflowReportJSON.java
+++ /dev/null
@@ -1,328 +0,0 @@
-package uk.org.taverna.platform.run.impl;
-
-import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
-import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
-import static com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
-import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
-import static com.fasterxml.jackson.databind.SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS;
-import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
-import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_EMPTY_JSON_ARRAYS;
-import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_NULL_MAP_VALUES;
-import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED;
-import static java.nio.file.Files.newBufferedWriter;
-import static java.nio.file.StandardOpenOption.CREATE;
-import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
-import static java.nio.file.StandardOpenOption.WRITE;
-import static org.apache.taverna.databundle.DataBundles.getWorkflow;
-import static org.apache.taverna.databundle.DataBundles.getWorkflowBundle;
-import static org.apache.taverna.databundle.DataBundles.getWorkflowRunReport;
-import static org.apache.taverna.databundle.DataBundles.setWorkflowBundle;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.net.URI;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.apache.taverna.robundle.Bundle;
-import org.apache.taverna.robundle.manifest.Manifest.PathMixin;
-
-import uk.org.taverna.platform.report.ActivityReport;
-import uk.org.taverna.platform.report.Invocation;
-import uk.org.taverna.platform.report.ProcessorReport;
-import uk.org.taverna.platform.report.State;
-import uk.org.taverna.platform.report.StatusReport;
-import uk.org.taverna.platform.report.WorkflowReport;
-import org.apache.taverna.scufl2.api.activity.Activity;
-import org.apache.taverna.scufl2.api.common.URITools;
-import org.apache.taverna.scufl2.api.common.WorkflowBean;
-import org.apache.taverna.scufl2.api.container.WorkflowBundle;
-import org.apache.taverna.scufl2.api.core.Processor;
-import org.apache.taverna.scufl2.api.core.Workflow;
-import org.apache.taverna.scufl2.api.io.ReaderException;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.util.StdDateFormat;
-
-public class WorkflowReportJSON {
-	private static URITools uriTools = new URITools();
-	private static final StdDateFormat STD_DATE_FORMAT = new StdDateFormat();
-
-	public void save(WorkflowReport wfReport, Path path) throws IOException {
-		ObjectMapper om = makeObjectMapperForSave();
-		try (Writer w = newBufferedWriter(path, Charset.forName("UTF-8"),
-				WRITE, CREATE, TRUNCATE_EXISTING)) {
-			om.writeValue(w, wfReport);
-		}
-	}
-
-	protected static ObjectMapper makeObjectMapperForLoad() {
-		ObjectMapper om = new ObjectMapper();
-		om.disable(FAIL_ON_UNKNOWN_PROPERTIES);
-		return om;
-	}
-
-	protected static ObjectMapper makeObjectMapperForSave() {
-		ObjectMapper om = new ObjectMapper();
-		om.enable(INDENT_OUTPUT);
-		om.disable(FAIL_ON_EMPTY_BEANS);
-		om.enable(ORDER_MAP_ENTRIES_BY_KEYS);
-		om.disable(WRITE_EMPTY_JSON_ARRAYS);
-		om.disable(WRITE_NULL_MAP_VALUES);
-		om.disable(WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
-		om.disable(WRITE_DATES_AS_TIMESTAMPS);
-		om.disable(WRITE_NULL_MAP_VALUES);
-		om.addMixInAnnotations(Path.class, PathMixin.class);
-		om.setSerializationInclusion(NON_NULL);
-		return om;
-	}
-
-	@SuppressWarnings("unused")
-	private void injectContext(ObjectNode objNode) {
-		ObjectNode context = objNode.with("@context");
-		context.put("wfprov", "http://purl.org/wf4ever/wfprov#");
-		context.put("wfdesc", "http://purl.org/wf4ever/wfdesc#");
-		context.put("prov", "http://www.w3.org/ns/prov#");
-	}
-
-	public void save(WorkflowReport wfReport, Bundle dataBundle)
-			throws IOException {
-		Path path = getWorkflowRunReport(dataBundle);
-		save(wfReport, path);
-		if (!Files.exists(getWorkflow(dataBundle)))
-			// Usually already done by Run constructor
-			setWorkflowBundle(wfReport.getDataBundle(), wfReport.getSubject()
-					.getParent());
-	}
-
-	public WorkflowReport load(Bundle bundle) throws IOException,
-			ReaderException, ParseException {
-		Path path = getWorkflowRunReport(bundle);
-		WorkflowBundle workflow = getWorkflowBundle(bundle);
-		return load(path, workflow);
-	}
-
-	public WorkflowReport load(Path workflowReportJson,
-			WorkflowBundle workflowBundle) throws IOException, ParseException {
-		JsonNode json = loadWorkflowReportJson(workflowReportJson);
-		if (!json.isObject())
-			throw new IOException(
-					"Invalid workflow report, expected JSON Object:\n" + json);
-		return parseWorkflowReport(json, workflowReportJson, null,
-				workflowBundle);
-	}
-
-	protected WorkflowReport parseWorkflowReport(JsonNode reportJson,
-			Path workflowReportJson, ActivityReport actReport,
-			WorkflowBundle workflowBundle) throws ParseException {
-		Workflow wf = (Workflow) getSubject(reportJson, workflowBundle);
-		WorkflowReport workflowReport = new WorkflowReport(wf);
-		workflowReport.setParentReport(actReport);
-
-		parseDates(reportJson, workflowReport);
-
-		for (JsonNode invocJson : reportJson.path("invocations"))
-			// NOTE: Invocation constructor will add to parents
-			parseInvocation(invocJson, workflowReportJson, workflowReport);
-
-		for (JsonNode procJson : reportJson.path("processorReports")) {
-			ProcessorReport procReport = parseProcessorReport(procJson,
-					workflowReportJson, workflowReport, workflowBundle);
-			workflowReport.addProcessorReport(procReport);
-		}
-		return workflowReport;
-	}
-
-	protected ProcessorReport parseProcessorReport(JsonNode reportJson,
-			Path workflowReportJson, WorkflowReport workflowReport,
-			WorkflowBundle workflowBundle) throws ParseException {
-		Processor p = (Processor) getSubject(reportJson, workflowBundle);
-		ProcessorReport procReport = new ProcessorReport(p);
-		procReport.setParentReport(workflowReport);
-
-		procReport.setJobsQueued(reportJson.path("jobsQueued").asInt());
-		procReport.setJobsStarted(reportJson.path("jobsStarted").asInt());
-		procReport.setJobsCompleted(reportJson.path("jobsCompleted").asInt());
-		procReport.setJobsCompletedWithErrors(reportJson.path(
-				"jobsCompletedWithErrors").asInt());
-		// TODO: procReport properties
-
-		parseDates(reportJson, procReport);
-
-		for (JsonNode invocJson : reportJson.path("invocations"))
-			parseInvocation(invocJson, workflowReportJson, procReport);
-
-		for (JsonNode actJson : reportJson.path("activityReports")) {
-			ActivityReport activityReport = parseActivityReport(actJson,
-					workflowReportJson, procReport, workflowBundle);
-			procReport.addActivityReport(activityReport);
-		}
-		return procReport;
-	}
-
-	protected ActivityReport parseActivityReport(JsonNode actJson,
-			Path workflowReportJson, ProcessorReport procReport,
-			WorkflowBundle workflowBundle) throws ParseException {
-		Activity a = (Activity) getSubject(actJson, workflowBundle);
-		ActivityReport actReport = new ActivityReport(a);
-		actReport.setParentReport(procReport);
-
-		parseDates(actJson, actReport);
-
-		for (JsonNode invocJson : actJson.path("invocations"))
-			parseInvocation(invocJson, workflowReportJson, actReport);
-
-		JsonNode nestedWf = actJson.get("nestedWorkflowReport");
-		if (nestedWf != null)
-			actReport.setNestedWorkflowReport(parseWorkflowReport(nestedWf,
-					workflowReportJson, actReport, workflowBundle));
-		return actReport;
-	}
-
-	protected void parseInvocation(JsonNode json, Path workflowReportJson,
-			@SuppressWarnings("rawtypes") StatusReport report)
-			throws ParseException {
-		String name = json.path("name").asText();
-
-		String parentId = json.path("parent").asText();
-		Invocation parent = null;
-		if (!parentId.isEmpty()) {
-			@SuppressWarnings("rawtypes")
-			StatusReport parentReport = report.getParentReport();
-			if (parentReport != null)
-				parent = parentReport.getInvocation(parentId);
-		}
-
-		int[] index;
-		if (json.has("index")) {
-			ArrayNode array = (ArrayNode) json.get("index");
-			index = new int[array.size()];
-			for (int i = 0; i < index.length; i++)
-				index[i] = array.get(i).asInt();
-		} else
-			index = new int[0];
-
-		Invocation invocation = new Invocation(name, index, parent, report);
-		Date startedDate = getDate(json, "startedDate");
-		if (startedDate != null)
-			invocation.setStartedDate(startedDate);
-		Date completedDate = getDate(json, "completedDate");
-		if (completedDate != null)
-			invocation.setCompletedDate(completedDate);
-
-		invocation.setInputs(parseValues(json.path("inputs"),
-				workflowReportJson));
-		invocation.setOutputs(parseValues(json.path("outputs"),
-				workflowReportJson));
-	}
-
-	protected Map<String, Path> parseValues(JsonNode json, Path basePath) {
-		SortedMap<String, Path> values = new TreeMap<>();
-		for (String port : iterate(json.fieldNames())) {
-			String pathStr = json.get(port).asText();
-			Path value = basePath.resolve(pathStr);
-			values.put(port, value);
-		}
-		return values;
-	}
-
-	private static <T> Iterable<T> iterate(final Iterator<T> iterator) {
-		return new Iterable<T>() {
-			@Override
-			public Iterator<T> iterator() {
-				return iterator;
-			}
-		};
-	}
-
-	protected void parseDates(JsonNode json,
-			@SuppressWarnings("rawtypes") StatusReport report)
-			throws ParseException {
-		Date createdDate = getDate(json, "createdDate");
-		if (createdDate != null)
-			report.setCreatedDate(createdDate);
-
-		Date startedDate = getDate(json, "startedDate");
-		if (startedDate != null)
-			report.setStartedDate(startedDate);
-
-		// Special case for paused and resumed dates>
-		for (JsonNode s : json.path("pausedDates")) {
-			Date pausedDate = STD_DATE_FORMAT.parse(s.asText());
-			report.setPausedDate(pausedDate);
-		}
-		Date pausedDate = getDate(json, "pausedDate");
-		if (report.getPausedDates().isEmpty() && pausedDate != null) {
-			/*
-			 * "pausedDate" is normally redundant (last value of "pausedDates")
-			 * but here for some reason the list is missing, so we'll parse it
-			 * separately.
-			 * 
-			 * Note that if there was a list, we will ignore "pauseDate" no
-			 * matter its value
-			 */
-			report.setPausedDate(pausedDate);
-		}
-
-		for (JsonNode s : json.path("resumedDates")) {
-			Date resumedDate = STD_DATE_FORMAT.parse(s.asText());
-			report.setResumedDate(resumedDate);
-		}
-		Date resumedDate = getDate(json, "resumedDate");
-		if (report.getResumedDates().isEmpty() && resumedDate != null)
-			// Same fall-back as for "pausedDate" above
-			report.setResumedDate(resumedDate);
-
-		Date cancelledDate = getDate(json, "cancelledDate");
-		if (cancelledDate != null)
-			report.setCancelledDate(cancelledDate);
-
-		Date failedDate = getDate(json, "failedDate");
-		if (failedDate != null)
-			report.setFailedDate(failedDate);
-
-		Date completedDate = getDate(json, "completedDate");
-		if (completedDate != null)
-			report.setCompletedDate(completedDate);
-
-		try {
-			State state = State.valueOf(json.get("state").asText());
-			report.setState(state);
-		} catch (IllegalArgumentException ex) {
-			throw new ParseException("Invalid state: " + json.get("state"), -1);
-		}
-	}
-
-	protected Date getDate(JsonNode json, String name) throws ParseException {
-		String date = json.path(name).asText();
-		if (date.isEmpty())
-			return null;
-		return STD_DATE_FORMAT.parse(date);
-	}
-
-	private WorkflowBean getSubject(JsonNode reportJson,
-			WorkflowBundle workflowBundle) {
-		URI subjectUri = URI.create(reportJson.path("subject").asText());
-		return uriTools.resolveUri(subjectUri, workflowBundle);
-	}
-
-	protected JsonNode loadWorkflowReportJson(Path path) throws IOException,
-			JsonProcessingException {
-		ObjectMapper om = makeObjectMapperForLoad();
-		try (InputStream stream = Files.newInputStream(path)) {
-			return om.readTree(stream);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/main/resources/META-INF/spring/run-context-osgi.xml
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/main/resources/META-INF/spring/run-context-osgi.xml b/taverna-run-impl/src/main/resources/META-INF/spring/run-context-osgi.xml
index 739d06f..6e4ff35 100644
--- a/taverna-run-impl/src/main/resources/META-INF/spring/run-context-osgi.xml
+++ b/taverna-run-impl/src/main/resources/META-INF/spring/run-context-osgi.xml
@@ -7,9 +7,9 @@
                                  http://www.springframework.org/schema/osgi
                                  http://www.springframework.org/schema/osgi/spring-osgi.xsd">
 
-    <service ref="runService" interface="uk.org.taverna.platform.run.api.RunService"/>
+    <service ref="runService" interface="org.apache.taverna.platform.run.api.RunService"/>
 
-    <reference id="executionEnvironmentService" interface="uk.org.taverna.platform.execution.api.ExecutionEnvironmentService"/>
+    <reference id="executionEnvironmentService" interface="org.apache.taverna.platform.execution.api.ExecutionEnvironmentService"/>
 
 	<reference id="eventAdmin" interface="org.osgi.service.event.EventAdmin" />
 

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/main/resources/META-INF/spring/run-context.xml
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/main/resources/META-INF/spring/run-context.xml b/taverna-run-impl/src/main/resources/META-INF/spring/run-context.xml
index d7b2d3a..094718b 100644
--- a/taverna-run-impl/src/main/resources/META-INF/spring/run-context.xml
+++ b/taverna-run-impl/src/main/resources/META-INF/spring/run-context.xml
@@ -4,7 +4,7 @@
        xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd">
 
-    <bean id="runService" class="uk.org.taverna.platform.run.impl.RunServiceImpl" >
+    <bean id="runService" class="org.apache.taverna.platform.run.impl.RunServiceImpl" >
     		<property name="executionEnvironmentService" ref="executionEnvironmentService"/>
     		<property name="eventAdmin" ref="eventAdmin"/>
     		<property name="workflowBundleIO" ref="workflowBundleIO"/>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/DummyWorkflowReport.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/DummyWorkflowReport.java b/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/DummyWorkflowReport.java
new file mode 100644
index 0000000..661e725
--- /dev/null
+++ b/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/DummyWorkflowReport.java
@@ -0,0 +1,131 @@
+package org.apache.taverna.platform.run.impl;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.apache.taverna.robundle.Bundle;
+
+import org.apache.taverna.databundle.DataBundles;
+import org.apache.taverna.platform.report.ActivityReport;
+import org.apache.taverna.platform.report.Invocation;
+import org.apache.taverna.platform.report.ProcessorReport;
+import org.apache.taverna.platform.report.WorkflowReport;
+import org.apache.taverna.scufl2.api.common.Scufl2Tools;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.io.ReaderException;
+import org.apache.taverna.scufl2.api.io.WorkflowBundleIO;
+import org.apache.taverna.scufl2.api.profiles.ProcessorBinding;
+
+
+public class DummyWorkflowReport {
+    
+    private static TimeZone UTC = TimeZone.getTimeZone("UTC");
+
+    protected Bundle dataBundle;
+    
+    protected WorkflowReport wfReport;
+
+    protected static final Scufl2Tools scufl2Tools = new Scufl2Tools();
+    protected static final WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
+    protected WorkflowBundle wfBundle;
+
+    @Before
+    public void loadWf() throws ReaderException, IOException {
+        wfBundle = workflowBundleIO.readBundle(getClass().getResource("/hello_anyone.wfbundle"), 
+                "application/vnd.taverna.scufl2.workflow-bundle");
+    }
+
+    protected Date date(int year, int month, int date, int hourOfDay, int minute) {
+        GregorianCalendar cal = new GregorianCalendar(UTC);
+        cal.setTimeInMillis(0);
+        cal.set(year, month-1, date, hourOfDay, minute);
+        return cal.getTime();
+    }
+    
+    @Before
+    public void dummyReport() throws Exception {
+        wfReport = new WorkflowReport(wfBundle.getMainWorkflow());
+        dataBundle = DataBundles.createBundle();
+        wfReport.setDataBundle(dataBundle);
+        wfReport.setCreatedDate(date(2013,1,2,13,37));
+        wfReport.setStartedDate(date(2013,1,2,14,50));        
+        Invocation wfInvocation = new Invocation("wf0", null, wfReport);
+        wfInvocation.setStartedDate(date(2013,1,2,14,51));
+        wfInvocation.setCompletedDate(date(2013,12,30,23,50));
+
+        wfReport.addInvocation(wfInvocation);
+        
+        Path name = DataBundles.getPort(DataBundles.getInputs(dataBundle), "name");
+        DataBundles.setStringValue(name, "John Doe");
+        wfInvocation.getInputs().put("name", name);
+        
+        Path greeting = DataBundles.getPort(DataBundles.getOutputs(dataBundle), "greeting");
+        DataBundles.setStringValue(greeting, "Hello, John Doe");
+        wfInvocation.getOutputs().put("greeting", greeting);
+        
+        Path helloValue = DataBundles.getIntermediate(dataBundle, UUID.randomUUID());
+        Path concatenateOutput = DataBundles.getIntermediate(dataBundle, UUID.randomUUID());
+        
+        for (Processor p : wfBundle.getMainWorkflow().getProcessors()) {
+            ProcessorReport processorReport = new ProcessorReport(p);
+            processorReport.setJobsQueued(1);
+            processorReport.setJobsStarted(5);
+            processorReport.setJobsCompleted(3);
+            processorReport.setJobsCompletedWithErrors(2);
+                        
+            wfReport.addProcessorReport(processorReport);
+
+            processorReport.setCreatedDate(date(2013,2,1,0,0));
+            processorReport.setStartedDate(date(2013,2,2,0,0));
+            processorReport.setPausedDate(date(2013,2,3,0,0));
+            processorReport.setResumedDate(date(2013,2,4,0,0));
+            processorReport.setPausedDate(date(2013,2,5,0,0));
+            processorReport.setResumedDate(date(2013,2,6,0,0));
+            
+            Invocation pInvocation = new Invocation("proc-" + p.getName() + "0", wfInvocation, processorReport);
+
+            pInvocation.setStartedDate(date(2013,2,2,11,0));
+            pInvocation.setCompletedDate(date(2013,2,2,13,0));
+
+            if (p.getName().equals("hello")) {
+                pInvocation.getOutputs().put("value", helloValue);
+                DataBundles.setStringValue(helloValue, "Hello, ");
+            } else if (p.getName().equals("Concatenate_two_strings")) {
+                pInvocation.getInputs().put("string1", helloValue);
+                pInvocation.getInputs().put("string2", name);
+                pInvocation.getOutputs().put("output", concatenateOutput);
+                DataBundles.setStringValue(concatenateOutput, "Hello, John Doe");
+            } else {
+                throw new Exception("Unexpected processor " + p);
+            }
+            processorReport.addInvocation(pInvocation);
+            
+            for (ProcessorBinding b : scufl2Tools.processorBindingsForProcessor(p, wfBundle.getMainProfile())) {
+                ActivityReport activityReport = new ActivityReport(b.getBoundActivity());
+                processorReport.addActivityReport(activityReport);
+                activityReport.setCreatedDate(date(2013,2,20,0,0));
+                activityReport.setStartedDate(date(2013,2,20,11,00));
+                activityReport.setCancelledDate(date(2013,2,21,11,30));
+                Invocation aInvocation = new Invocation("act-" + p.getName() + "0", pInvocation, activityReport);
+
+                aInvocation.setStartedDate(date(2013,2,20,11,30));
+//                aInvocation.setCompletedDate(date(2013,2,20,12,0));
+
+                activityReport.addInvocation(aInvocation);
+                aInvocation.getInputs().putAll(pInvocation.getInputs());
+                aInvocation.getOutputs().putAll(pInvocation.getOutputs());
+            }
+//            processorReport.setFailedDate(date(2013,7,28,23,59));
+            // In the summer to check that daylight saving does not sneak in
+            processorReport.setCompletedDate(date(2013,7,28,12,00));
+        }
+        wfReport.setCompletedDate(date(2013,12,31,0,0));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/RunTest.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/RunTest.java b/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/RunTest.java
new file mode 100644
index 0000000..9108d62
--- /dev/null
+++ b/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/RunTest.java
@@ -0,0 +1,69 @@
+package org.apache.taverna.platform.run.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.apache.taverna.robundle.Bundle;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import org.apache.taverna.databundle.DataBundles;
+import org.apache.taverna.platform.execution.api.ExecutionEnvironment;
+import org.apache.taverna.platform.execution.api.ExecutionService;
+import org.apache.taverna.platform.report.State;
+import org.apache.taverna.platform.run.api.RunProfile;
+import org.apache.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class RunTest extends DummyWorkflowReport {
+
+    private static final WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
+    private Run run;
+
+    public ExecutionEnvironment mockExecution() throws Exception {
+        ExecutionService exService = mock(ExecutionService.class);
+        when(exService.createExecution(null,null,null,null,null)).thenReturn("id0");
+        ExecutionEnvironment execution = mock(ExecutionEnvironment.class);
+        when(execution.getExecutionService()).thenReturn(exService);
+        when(exService.getWorkflowReport(null)).thenReturn(wfReport);
+        return execution;
+    }
+    
+    @Before
+    public void makeRun() throws Exception {
+        RunProfile runProfile = new RunProfile(mockExecution(), wfBundle, dataBundle);
+        run = new Run(runProfile);
+    }
+
+    @Test
+    public void getID() throws Exception {
+        assertEquals(4, UUID.fromString(run.getID()).version());
+    }
+    
+    @Test
+    public void getBundle() throws Exception {
+        Bundle bundle = run.getDataBundle();
+        // Contains a copy of workflow
+        assertEquals(wfBundle.getGlobalBaseURI(),
+                DataBundles.getWorkflowBundle(bundle).getGlobalBaseURI());
+        // Contains a run report
+        Path runReport = DataBundles.getWorkflowRunReport(bundle);
+        assertTrue(Files.exists(runReport));
+        JsonNode runReportJson = DataBundles.getWorkflowRunReportAsJson(bundle);
+        assertEquals("COMPLETED", runReportJson.get("state").asText());
+    }
+    
+
+    @Test
+    public void getState() throws Exception {
+        assertEquals(State.COMPLETED, run.getState());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/WorkflowReportJSONTest.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/WorkflowReportJSONTest.java b/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/WorkflowReportJSONTest.java
new file mode 100644
index 0000000..6ba4802
--- /dev/null
+++ b/taverna-run-impl/src/test/java/org/apache/taverna/platform/run/impl/WorkflowReportJSONTest.java
@@ -0,0 +1,285 @@
+package org.apache.taverna.platform.run.impl;
+
+import static org.junit.Assert.*;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+import org.junit.After;
+import org.junit.Test;
+import org.apache.taverna.robundle.Bundle;
+
+import org.apache.taverna.databundle.DataBundles;
+import org.apache.taverna.platform.report.ActivityReport;
+import org.apache.taverna.platform.report.Invocation;
+import org.apache.taverna.platform.report.ProcessorReport;
+import org.apache.taverna.platform.report.State;
+import org.apache.taverna.platform.report.WorkflowReport;
+import org.apache.taverna.scufl2.api.common.URITools;
+import org.apache.taverna.scufl2.api.core.Processor;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class WorkflowReportJSONTest extends DummyWorkflowReport {
+    
+    private final WorkflowReportJSON workflowReportJson = new WorkflowReportJSON();
+
+
+    @Test
+    public void save() throws Exception {
+        workflowReportJson.save(wfReport, dataBundle);
+        Path path = wfReport.getDataBundle().getRoot().resolve("/workflowrun.json");
+        assertTrue("Did not save to expected path "  + path, Files.exists(path));
+
+//        System.out.println(DataBundles.getStringValue(path));
+        
+        JsonNode json;
+        try (InputStream jsonIn = Files.newInputStream(path)) {
+            json = new ObjectMapper().readTree(jsonIn);
+        }
+        assertEquals("COMPLETED", json.get("state").asText());
+        assertEquals("2013-01-02T13:37:00.000+0000", json.get("createdDate").asText());
+        assertEquals("2013-01-02T14:50:00.000+0000", json.get("startedDate").asText());
+        assertEquals("2013-12-31T00:00:00.000+0000", json.get("completedDate").asText());
+        String wfId = wfBundle.getGlobalBaseURI().toString();
+        assertEquals(wfId + "workflow/Hello_Anyone/", 
+                json.get("subject").asText());
+        
+        // workflow invocation
+        JsonNode wfInvoc = json.get("invocations").get(0);
+        assertEquals("wf0", wfInvoc.get("id").asText());
+        assertEquals("wf0", wfInvoc.get("name").asText());
+        
+        assertEquals("2013-01-02T14:51:00.000+0000", wfInvoc.get("startedDate").asText());
+        assertEquals("2013-12-30T23:50:00.000+0000", wfInvoc.get("completedDate").asText());
+
+        String inputsName = wfInvoc.get("inputs").get("name").asText();
+        assertEquals("/inputs/name", inputsName);
+        String outputsGreeting = wfInvoc.get("outputs").get("greeting").asText();
+        assertEquals("/outputs/greeting", outputsGreeting);
+        assertEquals(
+                "John Doe",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(inputsName)));        
+        assertEquals(
+                "Hello, John Doe",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(outputsGreeting)));        
+
+        // NOTE: This assumes alphabetical ordering when constructing
+        // processor reports - which generally is given as
+        // Workflow.getProcessors() is sorted.
+        JsonNode proc0 = json.get("processorReports").get(0);
+        assertEquals(wfId + "workflow/Hello_Anyone/processor/Concatenate_two_strings/",
+                proc0.get("subject").asText());
+        assertEquals("COMPLETED", proc0.get("state").asText());
+        assertEquals("2013-02-01T00:00:00.000+0000", proc0.get("createdDate").asText());
+        assertEquals("2013-02-02T00:00:00.000+0000", proc0.get("startedDate").asText());
+        assertEquals("2013-02-03T00:00:00.000+0000", proc0.get("pausedDates").get(0).asText());
+        assertEquals("2013-02-05T00:00:00.000+0000", proc0.get("pausedDates").get(1).asText());
+        assertEquals("2013-02-05T00:00:00.000+0000", proc0.get("pausedDate").asText());
+
+        assertEquals("2013-02-04T00:00:00.000+0000", proc0.get("resumedDates").get(0).asText());
+        assertEquals("2013-02-06T00:00:00.000+0000", proc0.get("resumedDates").get(1).asText());
+        assertEquals("2013-02-06T00:00:00.000+0000", proc0.get("resumedDate").asText());
+
+        assertEquals("2013-07-28T12:00:00.000+0000", proc0.get("completedDate").asText());
+
+        // processor invocations
+        JsonNode pInvoc0 = proc0.get("invocations").get(0);
+        assertEquals("proc-Concatenate_two_strings0", pInvoc0.get("name").asText());
+        assertEquals("wf0/proc-Concatenate_two_strings0", pInvoc0.get("id").asText());
+        assertEquals("wf0", pInvoc0.get("parent").asText());
+
+        String inputString1 = pInvoc0.get("inputs").get("string1").asText();
+        assertTrue(inputString1.startsWith("/intermediates/"));
+        assertEquals(
+                "Hello, ",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(inputString1)));        
+        
+        String inputString2 = pInvoc0.get("inputs").get("string2").asText();
+        assertEquals("/inputs/name", inputString2);
+        String output = pInvoc0.get("outputs").get("output").asText();
+        assertTrue(output.startsWith("/intermediates/"));
+        assertEquals(
+                "Hello, John Doe",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(output)));        
+
+        // Activity reports
+        JsonNode act0 = proc0.get("activityReports").get(0);
+        assertEquals("CANCELLED", act0.get("state").asText());
+        assertEquals(wfId + "profile/taverna-2.4.0/activity/Concatenate_two_strings/", 
+                act0.get("subject").asText());
+        
+        
+        // activity invocation
+        JsonNode aInvoc0 = act0.get("invocations").get(0);
+
+        assertEquals("act-Concatenate_two_strings0", aInvoc0.get("name").asText());
+        assertEquals("wf0/proc-Concatenate_two_strings0/act-Concatenate_two_strings0", aInvoc0.get("id").asText());
+        assertEquals("wf0/proc-Concatenate_two_strings0", aInvoc0.get("parent").asText());
+
+        String actInputString1 = aInvoc0.get("inputs").get("string1").asText();
+        assertTrue(actInputString1.startsWith("/intermediates/"));
+        assertEquals(
+                "Hello, ",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(actInputString1)));        
+        
+        String actInputString2 = aInvoc0.get("inputs").get("string2").asText();
+        assertEquals("/inputs/name", actInputString2);
+        String actOutput = pInvoc0.get("outputs").get("output").asText();
+        assertTrue(actOutput.startsWith("/intermediates/"));
+        assertEquals(
+                "Hello, John Doe",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(actOutput)));        
+
+        
+        
+        
+        JsonNode proc1 = json.get("processorReports").get(1);
+        assertEquals(wfId + "workflow/Hello_Anyone/processor/hello/",
+                proc1.get("subject").asText());
+        assertEquals("COMPLETED", proc1.get("state").asText());
+        assertEquals("2013-02-01T00:00:00.000+0000", proc1.get("createdDate").asText());
+        assertEquals("2013-02-02T00:00:00.000+0000", proc1.get("startedDate").asText());
+        // etc.
+
+        JsonNode pInvoc1 = proc1.get("invocations").get(0);
+
+        String value = pInvoc1.get("outputs").get("value").asText();
+        assertTrue(value.startsWith("/intermediates/"));
+        assertEquals(
+                "Hello, ",
+                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
+                        .resolve(value)));        
+        assertEquals(inputString1, value);
+    }
+    
+    @Test
+    public void load() throws Exception {
+        URI bundleUri = getClass().getResource("/workflowrun.bundle.zip").toURI();
+        Path bundlePath = Paths.get(bundleUri);
+        try (Bundle bundle = DataBundles.openBundle(bundlePath)) {
+            WorkflowReport wfReport = workflowReportJson.load(bundle);
+            assertEquals(State.COMPLETED, wfReport.getState());
+            assertNull(wfReport.getParentReport());
+            
+            assertEquals(wfBundle.getMainWorkflow().getName(), wfReport.getSubject().getName());
+            URI mainWf = new URITools().uriForBean(wfBundle.getMainWorkflow());
+            assertEquals(mainWf, wfReport.getSubjectURI());
+            
+            assertEquals(date(2013,1,2,13,37), wfReport.getCreatedDate());
+            assertEquals(date(2013,1,2,14,50), wfReport.getStartedDate());
+            assertEquals(date(2013,12,31,0,0), wfReport.getCompletedDate());
+            assertNull(wfReport.getCancelledDate());
+            assertNull(wfReport.getResumedDate());
+            assertNull(wfReport.getPausedDate());
+            assertTrue(wfReport.getResumedDates().isEmpty());
+            assertTrue(wfReport.getPausedDates().isEmpty());
+            
+            // wf invocation
+            assertEquals(1, wfReport.getInvocations().size());
+            Invocation wfInvov = wfReport.getInvocations().first();
+            assertEquals("wf0", wfInvov.getName());
+            assertEquals("wf0", wfInvov.getId());
+            assertNull(wfInvov.getParentId());
+            assertNull(wfInvov.getParent());
+            assertEquals(0, wfInvov.getIndex().length);
+            assertSame(wfReport, wfInvov.getReport());
+            assertEquals(State.COMPLETED, wfInvov.getState());
+
+            assertEquals(date(2013,1,2,14,51), wfInvov.getStartedDate());
+            assertEquals(date(2013,12,30,23,50), wfInvov.getCompletedDate());
+
+            // wf invocation in/out
+            assertEquals(1, wfInvov.getInputs().size());
+            assertEquals(1, wfInvov.getOutputs().size());
+            
+            Path name = wfInvov.getInputs().get("name");
+            assertEquals("/inputs/name", name.toString());
+            assertEquals("John Doe", DataBundles.getStringValue(name));
+            
+            Path greeting = wfInvov.getOutputs().get("greeting");
+            assertEquals("/outputs/greeting", greeting.toString());
+            assertEquals("Hello, John Doe", DataBundles.getStringValue(greeting));
+            
+            
+            // processor reports
+            assertEquals(2, wfReport.getProcessorReports().size());
+            for (ProcessorReport procRepo : wfReport.getProcessorReports()) {
+                Processor processor = procRepo.getSubject();
+                assertTrue(wfBundle.getMainWorkflow().getProcessors().containsName(processor.getName()));
+                assertEquals(1, procRepo.getJobsQueued());
+                assertEquals(2, procRepo.getJobsCompletedWithErrors());
+                assertEquals(3, procRepo.getJobsCompleted());
+                assertEquals(5, procRepo.getJobsStarted());
+                
+
+                assertEquals(date(2013,2,1,00,00), procRepo.getCreatedDate());
+                assertEquals(date(2013,2,2,00,00), procRepo.getStartedDate());
+                assertEquals(date(2013,7,28,12,0), procRepo.getCompletedDate());
+                assertEquals(date(2013,2,5,0,0), procRepo.getPausedDate());
+                assertEquals(Arrays.asList(date(2013,2,3,0,0), date(2013,2,5,0,0)),
+                        procRepo.getPausedDates());
+                assertEquals(date(2013,2,6,0,0), procRepo.getResumedDate());
+                assertEquals(Arrays.asList(date(2013,2,4,0,0), date(2013,2,6,0,0)),
+                        procRepo.getResumedDates());
+
+                assertEquals(date(2013,7,28,12,0), procRepo.getCompletedDate());
+                
+                assertEquals(1, procRepo.getInvocations().size());
+                Invocation pInvoc = procRepo.getInvocations().first();
+                assertEquals(date(2013,2,2,11,00), pInvoc.getStartedDate());
+                assertEquals(date(2013,2,2,13,00), pInvoc.getCompletedDate());
+                assertEquals(State.COMPLETED, pInvoc.getState());
+                assertEquals(wfInvov, pInvoc.getParent());
+                assertEquals("wf0", pInvoc.getParentId());                
+                if (processor.getName().equals("hello")) {
+                    assertEquals("proc-hello0", pInvoc.getName());
+                    assertEquals("wf0/proc-hello0", pInvoc.getId());
+                    assertEquals(0, pInvoc.getInputs().size());
+                    assertEquals(1, pInvoc.getOutputs().size());
+                    assertEquals("Hello, ", DataBundles.getStringValue(pInvoc.getOutputs().get("value")));
+                } else if (processor.getName().equals("Concatenate_two_strings")) {
+                    assertEquals("proc-Concatenate_two_strings0", pInvoc.getName());
+                    assertEquals("wf0/proc-Concatenate_two_strings0", pInvoc.getId());
+                    assertEquals(2, pInvoc.getInputs().size());
+                    assertEquals("Hello, ", DataBundles.getStringValue(pInvoc.getInputs().get("string1")));
+                    assertEquals("John Doe", DataBundles.getStringValue(pInvoc.getInputs().get("string2")));
+
+                    assertEquals(1, pInvoc.getOutputs().size());
+                    assertEquals("Hello, John Doe", DataBundles.getStringValue(pInvoc.getOutputs().get("output")));                    
+                } else {
+                    fail("Unknown processor: " + processor.getName());
+                }
+                
+                assertEquals(1, procRepo.getActivityReports().size());
+                for (ActivityReport actRepo : procRepo.getActivityReports()) {
+                    assertEquals(procRepo, actRepo.getParentReport());
+                    assertEquals(State.CANCELLED, actRepo.getState());
+                    assertEquals(date(2013,2,20,00,00), actRepo.getCreatedDate());
+                    assertEquals(date(2013,2,20,11,00), actRepo.getStartedDate());
+                    assertEquals(date(2013,2,21,11,30), actRepo.getCancelledDate());                   
+                    // TODO: Test nested workflow
+                }
+            }
+        }
+        
+    }
+    
+    @After
+    public void closeBundle() throws Exception {
+        Path saved = dataBundle.getSource().resolveSibling("workflowrun.bundle.zip");
+        DataBundles.closeAndSaveBundle(dataBundle, saved);
+        System.out.println("Saved to " + saved);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/DummyWorkflowReport.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/DummyWorkflowReport.java b/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/DummyWorkflowReport.java
deleted file mode 100644
index b30b90b..0000000
--- a/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/DummyWorkflowReport.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package uk.org.taverna.platform.run.impl;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.TimeZone;
-import java.util.UUID;
-
-import org.junit.Before;
-import org.apache.taverna.robundle.Bundle;
-
-import org.apache.taverna.databundle.DataBundles;
-import uk.org.taverna.platform.report.ActivityReport;
-import uk.org.taverna.platform.report.Invocation;
-import uk.org.taverna.platform.report.ProcessorReport;
-import uk.org.taverna.platform.report.WorkflowReport;
-import org.apache.taverna.scufl2.api.common.Scufl2Tools;
-import org.apache.taverna.scufl2.api.container.WorkflowBundle;
-import org.apache.taverna.scufl2.api.core.Processor;
-import org.apache.taverna.scufl2.api.io.ReaderException;
-import org.apache.taverna.scufl2.api.io.WorkflowBundleIO;
-import org.apache.taverna.scufl2.api.profiles.ProcessorBinding;
-
-
-public class DummyWorkflowReport {
-    
-    private static TimeZone UTC = TimeZone.getTimeZone("UTC");
-
-    protected Bundle dataBundle;
-    
-    protected WorkflowReport wfReport;
-
-    protected static final Scufl2Tools scufl2Tools = new Scufl2Tools();
-    protected static final WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
-    protected WorkflowBundle wfBundle;
-
-    @Before
-    public void loadWf() throws ReaderException, IOException {
-        wfBundle = workflowBundleIO.readBundle(getClass().getResource("/hello_anyone.wfbundle"), 
-                "application/vnd.taverna.scufl2.workflow-bundle");
-    }
-
-    protected Date date(int year, int month, int date, int hourOfDay, int minute) {
-        GregorianCalendar cal = new GregorianCalendar(UTC);
-        cal.setTimeInMillis(0);
-        cal.set(year, month-1, date, hourOfDay, minute);
-        return cal.getTime();
-    }
-    
-    @Before
-    public void dummyReport() throws Exception {
-        wfReport = new WorkflowReport(wfBundle.getMainWorkflow());
-        dataBundle = DataBundles.createBundle();
-        wfReport.setDataBundle(dataBundle);
-        wfReport.setCreatedDate(date(2013,1,2,13,37));
-        wfReport.setStartedDate(date(2013,1,2,14,50));        
-        Invocation wfInvocation = new Invocation("wf0", null, wfReport);
-        wfInvocation.setStartedDate(date(2013,1,2,14,51));
-        wfInvocation.setCompletedDate(date(2013,12,30,23,50));
-
-        wfReport.addInvocation(wfInvocation);
-        
-        Path name = DataBundles.getPort(DataBundles.getInputs(dataBundle), "name");
-        DataBundles.setStringValue(name, "John Doe");
-        wfInvocation.getInputs().put("name", name);
-        
-        Path greeting = DataBundles.getPort(DataBundles.getOutputs(dataBundle), "greeting");
-        DataBundles.setStringValue(greeting, "Hello, John Doe");
-        wfInvocation.getOutputs().put("greeting", greeting);
-        
-        Path helloValue = DataBundles.getIntermediate(dataBundle, UUID.randomUUID());
-        Path concatenateOutput = DataBundles.getIntermediate(dataBundle, UUID.randomUUID());
-        
-        for (Processor p : wfBundle.getMainWorkflow().getProcessors()) {
-            ProcessorReport processorReport = new ProcessorReport(p);
-            processorReport.setJobsQueued(1);
-            processorReport.setJobsStarted(5);
-            processorReport.setJobsCompleted(3);
-            processorReport.setJobsCompletedWithErrors(2);
-                        
-            wfReport.addProcessorReport(processorReport);
-
-            processorReport.setCreatedDate(date(2013,2,1,0,0));
-            processorReport.setStartedDate(date(2013,2,2,0,0));
-            processorReport.setPausedDate(date(2013,2,3,0,0));
-            processorReport.setResumedDate(date(2013,2,4,0,0));
-            processorReport.setPausedDate(date(2013,2,5,0,0));
-            processorReport.setResumedDate(date(2013,2,6,0,0));
-            
-            Invocation pInvocation = new Invocation("proc-" + p.getName() + "0", wfInvocation, processorReport);
-
-            pInvocation.setStartedDate(date(2013,2,2,11,0));
-            pInvocation.setCompletedDate(date(2013,2,2,13,0));
-
-            if (p.getName().equals("hello")) {
-                pInvocation.getOutputs().put("value", helloValue);
-                DataBundles.setStringValue(helloValue, "Hello, ");
-            } else if (p.getName().equals("Concatenate_two_strings")) {
-                pInvocation.getInputs().put("string1", helloValue);
-                pInvocation.getInputs().put("string2", name);
-                pInvocation.getOutputs().put("output", concatenateOutput);
-                DataBundles.setStringValue(concatenateOutput, "Hello, John Doe");
-            } else {
-                throw new Exception("Unexpected processor " + p);
-            }
-            processorReport.addInvocation(pInvocation);
-            
-            for (ProcessorBinding b : scufl2Tools.processorBindingsForProcessor(p, wfBundle.getMainProfile())) {
-                ActivityReport activityReport = new ActivityReport(b.getBoundActivity());
-                processorReport.addActivityReport(activityReport);
-                activityReport.setCreatedDate(date(2013,2,20,0,0));
-                activityReport.setStartedDate(date(2013,2,20,11,00));
-                activityReport.setCancelledDate(date(2013,2,21,11,30));
-                Invocation aInvocation = new Invocation("act-" + p.getName() + "0", pInvocation, activityReport);
-
-                aInvocation.setStartedDate(date(2013,2,20,11,30));
-//                aInvocation.setCompletedDate(date(2013,2,20,12,0));
-
-                activityReport.addInvocation(aInvocation);
-                aInvocation.getInputs().putAll(pInvocation.getInputs());
-                aInvocation.getOutputs().putAll(pInvocation.getOutputs());
-            }
-//            processorReport.setFailedDate(date(2013,7,28,23,59));
-            // In the summer to check that daylight saving does not sneak in
-            processorReport.setCompletedDate(date(2013,7,28,12,00));
-        }
-        wfReport.setCompletedDate(date(2013,12,31,0,0));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/RunTest.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/RunTest.java b/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/RunTest.java
deleted file mode 100644
index 38c2f8f..0000000
--- a/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/RunTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package uk.org.taverna.platform.run.impl;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.UUID;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.apache.taverna.robundle.Bundle;
-
-import com.fasterxml.jackson.databind.JsonNode;
-
-import org.apache.taverna.databundle.DataBundles;
-import uk.org.taverna.platform.execution.api.ExecutionEnvironment;
-import uk.org.taverna.platform.execution.api.ExecutionService;
-import uk.org.taverna.platform.report.State;
-import uk.org.taverna.platform.run.api.RunProfile;
-import org.apache.taverna.scufl2.api.io.WorkflowBundleIO;
-
-public class RunTest extends DummyWorkflowReport {
-
-    private static final WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
-    private Run run;
-
-    public ExecutionEnvironment mockExecution() throws Exception {
-        ExecutionService exService = mock(ExecutionService.class);
-        when(exService.createExecution(null,null,null,null,null)).thenReturn("id0");
-        ExecutionEnvironment execution = mock(ExecutionEnvironment.class);
-        when(execution.getExecutionService()).thenReturn(exService);
-        when(exService.getWorkflowReport(null)).thenReturn(wfReport);
-        return execution;
-    }
-    
-    @Before
-    public void makeRun() throws Exception {
-        RunProfile runProfile = new RunProfile(mockExecution(), wfBundle, dataBundle);
-        run = new Run(runProfile);
-    }
-
-    @Test
-    public void getID() throws Exception {
-        assertEquals(4, UUID.fromString(run.getID()).version());
-    }
-    
-    @Test
-    public void getBundle() throws Exception {
-        Bundle bundle = run.getDataBundle();
-        // Contains a copy of workflow
-        assertEquals(wfBundle.getGlobalBaseURI(),
-                DataBundles.getWorkflowBundle(bundle).getGlobalBaseURI());
-        // Contains a run report
-        Path runReport = DataBundles.getWorkflowRunReport(bundle);
-        assertTrue(Files.exists(runReport));
-        JsonNode runReportJson = DataBundles.getWorkflowRunReportAsJson(bundle);
-        assertEquals("COMPLETED", runReportJson.get("state").asText());
-    }
-    
-
-    @Test
-    public void getState() throws Exception {
-        assertEquals(State.COMPLETED, run.getState());
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/WorkflowReportJSONTest.java
----------------------------------------------------------------------
diff --git a/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/WorkflowReportJSONTest.java b/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/WorkflowReportJSONTest.java
deleted file mode 100644
index 4e566e2..0000000
--- a/taverna-run-impl/src/test/java/uk/org/taverna/platform/run/impl/WorkflowReportJSONTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-package uk.org.taverna.platform.run.impl;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.*;
-
-import java.io.InputStream;
-import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-
-import org.junit.After;
-import org.junit.Test;
-import org.apache.taverna.robundle.Bundle;
-
-import org.apache.taverna.databundle.DataBundles;
-import uk.org.taverna.platform.report.ActivityReport;
-import uk.org.taverna.platform.report.Invocation;
-import uk.org.taverna.platform.report.ProcessorReport;
-import uk.org.taverna.platform.report.State;
-import uk.org.taverna.platform.report.WorkflowReport;
-import org.apache.taverna.scufl2.api.common.URITools;
-import org.apache.taverna.scufl2.api.core.Processor;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-public class WorkflowReportJSONTest extends DummyWorkflowReport {
-    
-    private final WorkflowReportJSON workflowReportJson = new WorkflowReportJSON();
-
-
-    @Test
-    public void save() throws Exception {
-        workflowReportJson.save(wfReport, dataBundle);
-        Path path = wfReport.getDataBundle().getRoot().resolve("/workflowrun.json");
-        assertTrue("Did not save to expected path "  + path, Files.exists(path));
-
-//        System.out.println(DataBundles.getStringValue(path));
-        
-        JsonNode json;
-        try (InputStream jsonIn = Files.newInputStream(path)) {
-            json = new ObjectMapper().readTree(jsonIn);
-        }
-        assertEquals("COMPLETED", json.get("state").asText());
-        assertEquals("2013-01-02T13:37:00.000+0000", json.get("createdDate").asText());
-        assertEquals("2013-01-02T14:50:00.000+0000", json.get("startedDate").asText());
-        assertEquals("2013-12-31T00:00:00.000+0000", json.get("completedDate").asText());
-        String wfId = wfBundle.getGlobalBaseURI().toString();
-        assertEquals(wfId + "workflow/Hello_Anyone/", 
-                json.get("subject").asText());
-        
-        // workflow invocation
-        JsonNode wfInvoc = json.get("invocations").get(0);
-        assertEquals("wf0", wfInvoc.get("id").asText());
-        assertEquals("wf0", wfInvoc.get("name").asText());
-        
-        assertEquals("2013-01-02T14:51:00.000+0000", wfInvoc.get("startedDate").asText());
-        assertEquals("2013-12-30T23:50:00.000+0000", wfInvoc.get("completedDate").asText());
-
-        String inputsName = wfInvoc.get("inputs").get("name").asText();
-        assertEquals("/inputs/name", inputsName);
-        String outputsGreeting = wfInvoc.get("outputs").get("greeting").asText();
-        assertEquals("/outputs/greeting", outputsGreeting);
-        assertEquals(
-                "John Doe",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(inputsName)));        
-        assertEquals(
-                "Hello, John Doe",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(outputsGreeting)));        
-
-        // NOTE: This assumes alphabetical ordering when constructing
-        // processor reports - which generally is given as
-        // Workflow.getProcessors() is sorted.
-        JsonNode proc0 = json.get("processorReports").get(0);
-        assertEquals(wfId + "workflow/Hello_Anyone/processor/Concatenate_two_strings/",
-                proc0.get("subject").asText());
-        assertEquals("COMPLETED", proc0.get("state").asText());
-        assertEquals("2013-02-01T00:00:00.000+0000", proc0.get("createdDate").asText());
-        assertEquals("2013-02-02T00:00:00.000+0000", proc0.get("startedDate").asText());
-        assertEquals("2013-02-03T00:00:00.000+0000", proc0.get("pausedDates").get(0).asText());
-        assertEquals("2013-02-05T00:00:00.000+0000", proc0.get("pausedDates").get(1).asText());
-        assertEquals("2013-02-05T00:00:00.000+0000", proc0.get("pausedDate").asText());
-
-        assertEquals("2013-02-04T00:00:00.000+0000", proc0.get("resumedDates").get(0).asText());
-        assertEquals("2013-02-06T00:00:00.000+0000", proc0.get("resumedDates").get(1).asText());
-        assertEquals("2013-02-06T00:00:00.000+0000", proc0.get("resumedDate").asText());
-
-        assertEquals("2013-07-28T12:00:00.000+0000", proc0.get("completedDate").asText());
-
-        // processor invocations
-        JsonNode pInvoc0 = proc0.get("invocations").get(0);
-        assertEquals("proc-Concatenate_two_strings0", pInvoc0.get("name").asText());
-        assertEquals("wf0/proc-Concatenate_two_strings0", pInvoc0.get("id").asText());
-        assertEquals("wf0", pInvoc0.get("parent").asText());
-
-        String inputString1 = pInvoc0.get("inputs").get("string1").asText();
-        assertTrue(inputString1.startsWith("/intermediates/"));
-        assertEquals(
-                "Hello, ",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(inputString1)));        
-        
-        String inputString2 = pInvoc0.get("inputs").get("string2").asText();
-        assertEquals("/inputs/name", inputString2);
-        String output = pInvoc0.get("outputs").get("output").asText();
-        assertTrue(output.startsWith("/intermediates/"));
-        assertEquals(
-                "Hello, John Doe",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(output)));        
-
-        // Activity reports
-        JsonNode act0 = proc0.get("activityReports").get(0);
-        assertEquals("CANCELLED", act0.get("state").asText());
-        assertEquals(wfId + "profile/taverna-2.4.0/activity/Concatenate_two_strings/", 
-                act0.get("subject").asText());
-        
-        
-        // activity invocation
-        JsonNode aInvoc0 = act0.get("invocations").get(0);
-
-        assertEquals("act-Concatenate_two_strings0", aInvoc0.get("name").asText());
-        assertEquals("wf0/proc-Concatenate_two_strings0/act-Concatenate_two_strings0", aInvoc0.get("id").asText());
-        assertEquals("wf0/proc-Concatenate_two_strings0", aInvoc0.get("parent").asText());
-
-        String actInputString1 = aInvoc0.get("inputs").get("string1").asText();
-        assertTrue(actInputString1.startsWith("/intermediates/"));
-        assertEquals(
-                "Hello, ",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(actInputString1)));        
-        
-        String actInputString2 = aInvoc0.get("inputs").get("string2").asText();
-        assertEquals("/inputs/name", actInputString2);
-        String actOutput = pInvoc0.get("outputs").get("output").asText();
-        assertTrue(actOutput.startsWith("/intermediates/"));
-        assertEquals(
-                "Hello, John Doe",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(actOutput)));        
-
-        
-        
-        
-        JsonNode proc1 = json.get("processorReports").get(1);
-        assertEquals(wfId + "workflow/Hello_Anyone/processor/hello/",
-                proc1.get("subject").asText());
-        assertEquals("COMPLETED", proc1.get("state").asText());
-        assertEquals("2013-02-01T00:00:00.000+0000", proc1.get("createdDate").asText());
-        assertEquals("2013-02-02T00:00:00.000+0000", proc1.get("startedDate").asText());
-        // etc.
-
-        JsonNode pInvoc1 = proc1.get("invocations").get(0);
-
-        String value = pInvoc1.get("outputs").get("value").asText();
-        assertTrue(value.startsWith("/intermediates/"));
-        assertEquals(
-                "Hello, ",
-                DataBundles.getStringValue(wfReport.getDataBundle().getRoot()
-                        .resolve(value)));        
-        assertEquals(inputString1, value);
-    }
-    
-    @Test
-    public void load() throws Exception {
-        URI bundleUri = getClass().getResource("/workflowrun.bundle.zip").toURI();
-        Path bundlePath = Paths.get(bundleUri);
-        try (Bundle bundle = DataBundles.openBundle(bundlePath)) {
-            WorkflowReport wfReport = workflowReportJson.load(bundle);
-            assertEquals(State.COMPLETED, wfReport.getState());
-            assertNull(wfReport.getParentReport());
-            
-            assertEquals(wfBundle.getMainWorkflow().getName(), wfReport.getSubject().getName());
-            URI mainWf = new URITools().uriForBean(wfBundle.getMainWorkflow());
-            assertEquals(mainWf, wfReport.getSubjectURI());
-            
-            assertEquals(date(2013,1,2,13,37), wfReport.getCreatedDate());
-            assertEquals(date(2013,1,2,14,50), wfReport.getStartedDate());
-            assertEquals(date(2013,12,31,0,0), wfReport.getCompletedDate());
-            assertNull(wfReport.getCancelledDate());
-            assertNull(wfReport.getResumedDate());
-            assertNull(wfReport.getPausedDate());
-            assertTrue(wfReport.getResumedDates().isEmpty());
-            assertTrue(wfReport.getPausedDates().isEmpty());
-            
-            // wf invocation
-            assertEquals(1, wfReport.getInvocations().size());
-            Invocation wfInvov = wfReport.getInvocations().first();
-            assertEquals("wf0", wfInvov.getName());
-            assertEquals("wf0", wfInvov.getId());
-            assertNull(wfInvov.getParentId());
-            assertNull(wfInvov.getParent());
-            assertEquals(0, wfInvov.getIndex().length);
-            assertSame(wfReport, wfInvov.getReport());
-            assertEquals(State.COMPLETED, wfInvov.getState());
-
-            assertEquals(date(2013,1,2,14,51), wfInvov.getStartedDate());
-            assertEquals(date(2013,12,30,23,50), wfInvov.getCompletedDate());
-
-            // wf invocation in/out
-            assertEquals(1, wfInvov.getInputs().size());
-            assertEquals(1, wfInvov.getOutputs().size());
-            
-            Path name = wfInvov.getInputs().get("name");
-            assertEquals("/inputs/name", name.toString());
-            assertEquals("John Doe", DataBundles.getStringValue(name));
-            
-            Path greeting = wfInvov.getOutputs().get("greeting");
-            assertEquals("/outputs/greeting", greeting.toString());
-            assertEquals("Hello, John Doe", DataBundles.getStringValue(greeting));
-            
-            
-            // processor reports
-            assertEquals(2, wfReport.getProcessorReports().size());
-            for (ProcessorReport procRepo : wfReport.getProcessorReports()) {
-                Processor processor = procRepo.getSubject();
-                assertTrue(wfBundle.getMainWorkflow().getProcessors().containsName(processor.getName()));
-                assertEquals(1, procRepo.getJobsQueued());
-                assertEquals(2, procRepo.getJobsCompletedWithErrors());
-                assertEquals(3, procRepo.getJobsCompleted());
-                assertEquals(5, procRepo.getJobsStarted());
-                
-
-                assertEquals(date(2013,2,1,00,00), procRepo.getCreatedDate());
-                assertEquals(date(2013,2,2,00,00), procRepo.getStartedDate());
-                assertEquals(date(2013,7,28,12,0), procRepo.getCompletedDate());
-                assertEquals(date(2013,2,5,0,0), procRepo.getPausedDate());
-                assertEquals(Arrays.asList(date(2013,2,3,0,0), date(2013,2,5,0,0)),
-                        procRepo.getPausedDates());
-                assertEquals(date(2013,2,6,0,0), procRepo.getResumedDate());
-                assertEquals(Arrays.asList(date(2013,2,4,0,0), date(2013,2,6,0,0)),
-                        procRepo.getResumedDates());
-
-                assertEquals(date(2013,7,28,12,0), procRepo.getCompletedDate());
-                
-                assertEquals(1, procRepo.getInvocations().size());
-                Invocation pInvoc = procRepo.getInvocations().first();
-                assertEquals(date(2013,2,2,11,00), pInvoc.getStartedDate());
-                assertEquals(date(2013,2,2,13,00), pInvoc.getCompletedDate());
-                assertEquals(State.COMPLETED, pInvoc.getState());
-                assertEquals(wfInvov, pInvoc.getParent());
-                assertEquals("wf0", pInvoc.getParentId());                
-                if (processor.getName().equals("hello")) {
-                    assertEquals("proc-hello0", pInvoc.getName());
-                    assertEquals("wf0/proc-hello0", pInvoc.getId());
-                    assertEquals(0, pInvoc.getInputs().size());
-                    assertEquals(1, pInvoc.getOutputs().size());
-                    assertEquals("Hello, ", DataBundles.getStringValue(pInvoc.getOutputs().get("value")));
-                } else if (processor.getName().equals("Concatenate_two_strings")) {
-                    assertEquals("proc-Concatenate_two_strings0", pInvoc.getName());
-                    assertEquals("wf0/proc-Concatenate_two_strings0", pInvoc.getId());
-                    assertEquals(2, pInvoc.getInputs().size());
-                    assertEquals("Hello, ", DataBundles.getStringValue(pInvoc.getInputs().get("string1")));
-                    assertEquals("John Doe", DataBundles.getStringValue(pInvoc.getInputs().get("string2")));
-
-                    assertEquals(1, pInvoc.getOutputs().size());
-                    assertEquals("Hello, John Doe", DataBundles.getStringValue(pInvoc.getOutputs().get("output")));                    
-                } else {
-                    fail("Unknown processor: " + processor.getName());
-                }
-                
-                assertEquals(1, procRepo.getActivityReports().size());
-                for (ActivityReport actRepo : procRepo.getActivityReports()) {
-                    assertEquals(procRepo, actRepo.getParentReport());
-                    assertEquals(State.CANCELLED, actRepo.getState());
-                    assertEquals(date(2013,2,20,00,00), actRepo.getCreatedDate());
-                    assertEquals(date(2013,2,20,11,00), actRepo.getStartedDate());
-                    assertEquals(date(2013,2,21,11,30), actRepo.getCancelledDate());                   
-                    // TODO: Test nested workflow
-                }
-            }
-        }
-        
-    }
-    
-    @After
-    public void closeBundle() throws Exception {
-        Path saved = dataBundle.getSource().resolveSibling("workflowrun.bundle.zip");
-        DataBundles.closeAndSaveBundle(dataBundle, saved);
-        System.out.println("Saved to " + saved);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ActivityTypeNotFoundException.java
----------------------------------------------------------------------
diff --git a/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ActivityTypeNotFoundException.java b/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ActivityTypeNotFoundException.java
new file mode 100644
index 0000000..b260f91
--- /dev/null
+++ b/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ActivityTypeNotFoundException.java
@@ -0,0 +1,46 @@
+/*
+* 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.commons.services;
+
+/**
+ * Thrown when an activity type is not found.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class ActivityTypeNotFoundException extends Exception {
+
+	public ActivityTypeNotFoundException() {
+		super();
+	}
+
+	public ActivityTypeNotFoundException(String message) {
+		super(message);
+	}
+
+	public ActivityTypeNotFoundException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public ActivityTypeNotFoundException(Throwable cause) {
+		super(cause);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-services-api/src/main/java/org/apache/taverna/commons/services/InvalidConfigurationException.java
----------------------------------------------------------------------
diff --git a/taverna-services-api/src/main/java/org/apache/taverna/commons/services/InvalidConfigurationException.java b/taverna-services-api/src/main/java/org/apache/taverna/commons/services/InvalidConfigurationException.java
new file mode 100644
index 0000000..35a1db8
--- /dev/null
+++ b/taverna-services-api/src/main/java/org/apache/taverna/commons/services/InvalidConfigurationException.java
@@ -0,0 +1,46 @@
+/*
+* 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.commons.services;
+
+/**
+ * Thrown when a Configuration is not valid for a Configurable.
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class InvalidConfigurationException extends Exception {
+
+	public InvalidConfigurationException() {
+		super();
+	}
+
+	public InvalidConfigurationException(String message) {
+		super(message);
+	}
+
+	public InvalidConfigurationException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public InvalidConfigurationException(Throwable cause) {
+		super(cause);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ServiceRegistry.java
----------------------------------------------------------------------
diff --git a/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ServiceRegistry.java b/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ServiceRegistry.java
new file mode 100644
index 0000000..7411dd2
--- /dev/null
+++ b/taverna-services-api/src/main/java/org/apache/taverna/commons/services/ServiceRegistry.java
@@ -0,0 +1,88 @@
+/*
+* 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.commons.services;
+
+import java.net.URI;
+import java.util.Set;
+
+import org.apache.taverna.scufl2.api.port.InputActivityPort;
+import org.apache.taverna.scufl2.api.port.OutputActivityPort;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Register of Taverna services.
+ *
+ * @author David Withers
+ */
+public interface ServiceRegistry {
+
+	/**
+	 * Returns the activity types in the registry.
+	 *
+	 * @return the activity types in the registry
+	 */
+	public Set<URI> getActivityTypes();
+
+	/**
+	 * Returns the JSON Schema for the configuration required by an activity.
+	 *
+	 * @param activityType
+	 *            the activity type
+	 * @return the JSON Schema for the configuration required by an activity
+	 * @throws ActivityTypeNotFoundException
+	 *             if the activity type is not in the registry
+	 */
+	public JsonNode getActivityConfigurationSchema(URI activityType)
+			throws InvalidConfigurationException, ActivityTypeNotFoundException;
+
+	/**
+	 * Returns the input ports that the activity type requires to be present in order to execute
+	 * with the specified configuration.
+	 * <p>
+	 * If the activity does not require any input port for the configuration then an empty set is
+	 * returned.
+	 *
+	 * @param configuration
+	 *            the activity configuration
+	 * @throws ActivityTypeNotFoundException
+	 *             if the activity type is not in the registry
+	 * @return the input ports that the activity requires to be present in order to execute
+	 */
+	public Set<InputActivityPort> getActivityInputPorts(URI activityType,
+			JsonNode configuration) throws InvalidConfigurationException, ActivityTypeNotFoundException;
+
+	/**
+	 * Returns the output ports that the activity type requires to be present in order to execute
+	 * with the specified configuration.
+	 * <p>
+	 * If the activity type does not require any output ports for the configuration then an empty
+	 * set is returned.
+	 *
+	 * @param configuration
+	 *            the activity configuration
+	 * @throws ActivityTypeNotFoundException
+	 *             if the activity type is not in the registry
+	 * @return the output ports that the activity requires to be present in order to execute
+	 */
+	public Set<OutputActivityPort> getActivityOutputPorts(URI activityType,
+			JsonNode configuration) throws InvalidConfigurationException, ActivityTypeNotFoundException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-services-api/src/main/java/uk/org/taverna/commons/services/ActivityTypeNotFoundException.java
----------------------------------------------------------------------
diff --git a/taverna-services-api/src/main/java/uk/org/taverna/commons/services/ActivityTypeNotFoundException.java b/taverna-services-api/src/main/java/uk/org/taverna/commons/services/ActivityTypeNotFoundException.java
deleted file mode 100644
index dafccd7..0000000
--- a/taverna-services-api/src/main/java/uk/org/taverna/commons/services/ActivityTypeNotFoundException.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2013 The University of Manchester
- *
- *  Modifications to the initial code base are copyright of their
- *  respective authors, or their employers as appropriate.
- *
- *  This program is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public License
- *  as published by the Free Software Foundation; either version 2.1 of
- *  the License, or (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful, but
- *  WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- ******************************************************************************/
-package uk.org.taverna.commons.services;
-
-/**
- * Thrown when an activity type is not found.
- *
- * @author David Withers
- */
-@SuppressWarnings("serial")
-public class ActivityTypeNotFoundException extends Exception {
-
-	public ActivityTypeNotFoundException() {
-		super();
-	}
-
-	public ActivityTypeNotFoundException(String message) {
-		super(message);
-	}
-
-	public ActivityTypeNotFoundException(String message, Throwable cause) {
-		super(message, cause);
-	}
-
-	public ActivityTypeNotFoundException(Throwable cause) {
-		super(cause);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-services-api/src/main/java/uk/org/taverna/commons/services/InvalidConfigurationException.java
----------------------------------------------------------------------
diff --git a/taverna-services-api/src/main/java/uk/org/taverna/commons/services/InvalidConfigurationException.java b/taverna-services-api/src/main/java/uk/org/taverna/commons/services/InvalidConfigurationException.java
deleted file mode 100644
index 0f118d6..0000000
--- a/taverna-services-api/src/main/java/uk/org/taverna/commons/services/InvalidConfigurationException.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2013 The University of Manchester
- *
- *  Modifications to the initial code base are copyright of their
- *  respective authors, or their employers as appropriate.
- *
- *  This program is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public License
- *  as published by the Free Software Foundation; either version 2.1 of
- *  the License, or (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful, but
- *  WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- ******************************************************************************/
-package uk.org.taverna.commons.services;
-
-/**
- * Thrown when a Configuration is not valid for a Configurable.
- *
- * @author David Withers
- */
-@SuppressWarnings("serial")
-public class InvalidConfigurationException extends Exception {
-
-	public InvalidConfigurationException() {
-		super();
-	}
-
-	public InvalidConfigurationException(String message) {
-		super(message);
-	}
-
-	public InvalidConfigurationException(String message, Throwable cause) {
-		super(message, cause);
-	}
-
-	public InvalidConfigurationException(Throwable cause) {
-		super(cause);
-	}
-
-}