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:37:54 UTC

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

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/java/org/apache/taverna/workflowmodel/utils/Tools.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/java/org/apache/taverna/workflowmodel/utils/Tools.java b/taverna-workflowmodel-api/src/main/java/org/apache/taverna/workflowmodel/utils/Tools.java
new file mode 100644
index 0000000..cb4b3f2
--- /dev/null
+++ b/taverna-workflowmodel-api/src/main/java/org/apache/taverna/workflowmodel/utils/Tools.java
@@ -0,0 +1,794 @@
+/*
+* 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.workflowmodel.utils;
+
+import static java.lang.Character.isLetterOrDigit;
+import static org.apache.taverna.workflowmodel.utils.AnnotationTools.addAnnotation;
+import static org.apache.taverna.workflowmodel.utils.AnnotationTools.getAnnotation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.taverna.annotation.annotationbeans.IdentificationAssertion;
+import org.apache.taverna.workflowmodel.CompoundEdit;
+import org.apache.taverna.workflowmodel.Dataflow;
+import org.apache.taverna.workflowmodel.DataflowInputPort;
+import org.apache.taverna.workflowmodel.DataflowOutputPort;
+import org.apache.taverna.workflowmodel.Datalink;
+import org.apache.taverna.workflowmodel.Edit;
+import org.apache.taverna.workflowmodel.EditException;
+import org.apache.taverna.workflowmodel.Edits;
+import org.apache.taverna.workflowmodel.EventForwardingOutputPort;
+import org.apache.taverna.workflowmodel.EventHandlingInputPort;
+import org.apache.taverna.workflowmodel.InputPort;
+import org.apache.taverna.workflowmodel.Merge;
+import org.apache.taverna.workflowmodel.MergeInputPort;
+import org.apache.taverna.workflowmodel.MergeOutputPort;
+import org.apache.taverna.workflowmodel.NamedWorkflowEntity;
+import org.apache.taverna.workflowmodel.OutputPort;
+import org.apache.taverna.workflowmodel.Port;
+import org.apache.taverna.workflowmodel.Processor;
+import org.apache.taverna.workflowmodel.ProcessorInputPort;
+import org.apache.taverna.workflowmodel.ProcessorOutputPort;
+import org.apache.taverna.workflowmodel.TokenProcessingEntity;
+import org.apache.taverna.workflowmodel.processor.activity.Activity;
+import org.apache.taverna.workflowmodel.processor.activity.ActivityConfigurationException;
+import org.apache.taverna.workflowmodel.processor.activity.ActivityInputPort;
+import org.apache.taverna.workflowmodel.processor.activity.ActivityOutputPort;
+import org.apache.taverna.workflowmodel.processor.activity.DisabledActivity;
+import org.apache.taverna.workflowmodel.processor.activity.NestedDataflow;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Various workflow model tools that can be helpful when constructing a
+ * dataflow.
+ * <p>
+ * Not to be confused with the @deprecated
+ * {@link org.apache.taverna.workflowmodel.impl.Tools}
+ * 
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public class Tools {
+	private static Logger logger = Logger.getLogger(Tools.class);
+
+	// private static Edits edits = new EditsImpl();
+
+	/**
+	 * Find (and possibly create) an EventHandlingInputPort.
+	 * <p>
+	 * If the given inputPort is an instance of {@link EventHandlingInputPort},
+	 * it is returned directly. If it is an ActivityInputPort - the owning
+	 * processors (found by searching the dataflow) will be searced for a mapped
+	 * input port. If this cannot be found, one will be created and mapped. The
+	 * edits for this will be added to the editList and needs to be executed by
+	 * the caller.
+	 * 
+	 * @see #findEventHandlingOutputPort(List, Dataflow, OutputPort)
+	 * @param editList
+	 *            List of {@link Edit}s to append any required edits (yet to be
+	 *            performed) to
+	 * @param dataflow
+	 *            Dataflow containing the processors
+	 * @param inputPort
+	 *            An EventHandlingInputPort or ActivityInputPort
+	 * @return The found or created EventHandlingInputPort
+	 */
+	protected static EventHandlingInputPort findEventHandlingInputPort(
+			List<Edit<?>> editList, Dataflow dataflow, InputPort inputPort,
+			Edits edits) {
+		if (inputPort instanceof EventHandlingInputPort)
+			return (EventHandlingInputPort) inputPort;
+		else if (!(inputPort instanceof ActivityInputPort))
+			throw new IllegalArgumentException("Unknown input port type for "
+					+ inputPort);
+
+		ActivityInputPort activityInput = (ActivityInputPort) inputPort;
+		Collection<Processor> processors = getProcessorsWithActivityInputPort(
+				dataflow, activityInput);
+		if (processors.isEmpty())
+			throw new IllegalArgumentException("Can't find ActivityInputPort "
+					+ activityInput.getName() + " in workflow " + dataflow);
+
+		// FIXME: Assumes only one matching processor
+		Processor processor = processors.iterator().next();
+		Activity<?> activity = null;
+		for (Activity<?> checkActivity : processor.getActivityList())
+			if (checkActivity.getInputPorts().contains(activityInput)) {
+				activity = checkActivity;
+				break;
+			}
+		if (activity == null)
+			throw new IllegalArgumentException("Can't find activity for port "
+					+ activityInput.getName() + "within processor " + processor);
+
+		ProcessorInputPort input = getProcessorInputPort(processor, activity,
+				activityInput);
+		if (input != null)
+			return input;
+		// port doesn't exist so create a processor port and map it
+		String processorPortName = uniquePortName(activityInput.getName(),
+				processor.getInputPorts());
+		ProcessorInputPort processorInputPort = edits.createProcessorInputPort(
+				processor, processorPortName, activityInput.getDepth());
+		editList.add(edits.getAddProcessorInputPortEdit(processor,
+				processorInputPort));
+		editList.add(edits.getAddActivityInputPortMappingEdit(activity,
+				processorPortName, activityInput.getName()));
+		return processorInputPort;
+	}
+
+	/**
+	 * Find (and possibly create) an EventForwardingOutputPort.
+	 * <p>
+	 * If the given outputPort is an instance of
+	 * {@link EventForwardingOutputPort}, it is returned directly. If it is an
+	 * ActivityOutputPort - the owning processors (found by searching the
+	 * dataflow) will be searced for a mapped output port. If this cannot be
+	 * found, one will be created and mapped. The edits for this will be added
+	 * to the editList and needs to be executed by the caller.
+	 * 
+	 * @see #findEventHandlingInputPort(List, Dataflow, InputPort)
+	 * @param editList
+	 *            List of {@link Edit}s to append any required edits (yet to be
+	 *            performed) to
+	 * @param dataflow
+	 *            Dataflow containing the processors
+	 * @param outputPort
+	 *            An EventForwardingOutputPort or ActivityOutputPort
+	 * @return The found or created EventForwardingOutputPort
+	 */
+	protected static EventForwardingOutputPort findEventHandlingOutputPort(
+			List<Edit<?>> editList, Dataflow dataflow, OutputPort outputPort,
+			Edits edits) {
+		if (outputPort instanceof EventForwardingOutputPort)
+			return (EventForwardingOutputPort) outputPort;
+		else if (!(outputPort instanceof ActivityOutputPort))
+			throw new IllegalArgumentException("Unknown output port type for "
+					+ outputPort);
+
+		ActivityOutputPort activityOutput = (ActivityOutputPort) outputPort;
+		Collection<Processor> processors = getProcessorsWithActivityOutputPort(
+				dataflow, activityOutput);
+		if (processors.isEmpty())
+			throw new IllegalArgumentException("Can't find ActivityOutputPort "
+					+ activityOutput.getName() + " in workflow " + dataflow);
+
+		// FIXME: Assumes only one matching processor
+		Processor processor = processors.iterator().next();
+		Activity<?> activity = null;
+		for (Activity<?> checkActivity : processor.getActivityList())
+			if (checkActivity.getOutputPorts().contains(activityOutput)) {
+				activity = checkActivity;
+				break;
+			}
+		if (activity == null)
+			throw new IllegalArgumentException("Can't find activity for port "
+					+ activityOutput.getName() + "within processor "
+					+ processor);
+
+		ProcessorOutputPort processorOutputPort = Tools.getProcessorOutputPort(
+				processor, activity, activityOutput);
+		if (processorOutputPort != null)
+			return processorOutputPort;
+
+		// port doesn't exist so create a processor port and map it
+		String processorPortName = uniquePortName(activityOutput.getName(),
+				processor.getOutputPorts());
+		processorOutputPort = edits.createProcessorOutputPort(processor,
+				processorPortName, activityOutput.getDepth(),
+				activityOutput.getGranularDepth());
+		editList.add(edits.getAddProcessorOutputPortEdit(processor,
+				processorOutputPort));
+		editList.add(edits.getAddActivityOutputPortMappingEdit(activity,
+				processorPortName, activityOutput.getName()));
+
+		return processorOutputPort;
+	}
+
+	/**
+	 * Creates an Edit that creates a Datalink between a source and sink port
+	 * and connects the Datalink.
+	 * 
+	 * If the sink port already has a Datalink connected this method checks if a
+	 * new Merge is required and creates and connects the required Datalinks.
+	 * 
+	 * @param dataflow
+	 *            the Dataflow to add the Datalink to
+	 * @param source
+	 *            the source of the Datalink
+	 * @param sink
+	 *            the source of the Datalink
+	 * @return an Edit that creates a Datalink between a source and sink port
+	 *         and connects the Datalink
+	 */
+	public static Edit<?> getCreateAndConnectDatalinkEdit(Dataflow dataflow,
+			EventForwardingOutputPort source, EventHandlingInputPort sink,
+			Edits edits) {
+		Edit<?> edit = null;
+
+		Datalink incomingLink = sink.getIncomingLink();
+		if (incomingLink == null) {
+			Datalink datalink = edits.createDatalink(source, sink);
+			edit = edits.getConnectDatalinkEdit(datalink);
+		} else {
+			List<Edit<?>> editList = new ArrayList<>();
+
+			Merge merge = null;
+			int counter = 0; // counter for merge input port names
+			if (incomingLink.getSource() instanceof MergeOutputPort)
+				merge = ((MergeOutputPort) incomingLink.getSource()).getMerge();
+			else {
+				merge = edits.createMerge(dataflow);
+				editList.add(edits.getAddMergeEdit(dataflow, merge));
+				editList.add(edits.getDisconnectDatalinkEdit(incomingLink));
+				MergeInputPort mergeInputPort = edits.createMergeInputPort(
+						merge,
+						getUniqueMergeInputPortName(merge,
+								incomingLink.getSource().getName() + "To"
+										+ merge.getLocalName() + "_input",
+								counter++), incomingLink.getSink().getDepth());
+				editList.add(edits.getAddMergeInputPortEdit(merge,
+						mergeInputPort));
+				Datalink datalink = edits.createDatalink(
+						incomingLink.getSource(), mergeInputPort);
+				editList.add(edits.getConnectDatalinkEdit(datalink));
+				datalink = edits.createDatalink(merge.getOutputPort(),
+						incomingLink.getSink());
+				editList.add(edits.getConnectDatalinkEdit(datalink));
+			}
+			MergeInputPort mergeInputPort = edits.createMergeInputPort(
+					merge,
+					getUniqueMergeInputPortName(merge, source.getName() + "To"
+							+ merge.getLocalName() + "_input", counter),
+					sink.getDepth());
+			editList.add(edits.getAddMergeInputPortEdit(merge, mergeInputPort));
+			Datalink datalink = edits.createDatalink(source, mergeInputPort);
+			editList.add(edits.getConnectDatalinkEdit(datalink));
+
+			edit = new CompoundEdit(editList);
+		}
+
+		return edit;
+	}
+
+	/**
+	 * Get an {@link Edit} that will link the given output port to the given
+	 * input port.
+	 * <p>
+	 * The output port can be an {@link EventForwardingOutputPort} (such as an
+	 * {@link ProcessorOutputPort}, or an {@link ActivityOutputPort}. The input
+	 * port can be an {@link EventHandlingInputPort} (such as an
+	 * {@link ProcessorInputPort}, or an {@link ActivityInputPort}.
+	 * <p>
+	 * If an input and/or output port is an activity port, processors in the
+	 * given dataflow will be searched for matching mappings, create the
+	 * processor port and mapping if needed, before constructing the edits for
+	 * adding the datalink.
+	 * 
+	 * @param dataflow
+	 *            Dataflow (indirectly) containing ports
+	 * @param outputPort
+	 *            An {@link EventForwardingOutputPort} or an
+	 *            {@link ActivityOutputPort}
+	 * @param inputPort
+	 *            An {@link EventHandlingInputPort} or an
+	 *            {@link ActivityInputPort}
+	 * @return A compound edit for creating and connecting the datalink and any
+	 *         neccessary processor ports and mappings
+	 */
+	public static Edit<?> getCreateAndConnectDatalinkEdit(Dataflow dataflow,
+			OutputPort outputPort, InputPort inputPort, Edits edits) {
+		List<Edit<?>> editList = new ArrayList<>();
+		EventHandlingInputPort sink = findEventHandlingInputPort(editList,
+				dataflow, inputPort, edits);
+		EventForwardingOutputPort source = findEventHandlingOutputPort(
+				editList, dataflow, outputPort, edits);
+		editList.add(getCreateAndConnectDatalinkEdit(dataflow, source, sink,
+				edits));
+		return new CompoundEdit(editList);
+	}
+
+	/**
+	 * Find a unique port name given a list of existing ports.
+	 * <p>
+	 * If needed, the returned port name will be prefixed with an underscore and
+	 * a number, starting from 2. (The original being 'number 1')
+	 * <p>
+	 * Although not strictly needed by Taverna, for added user friendliness the
+	 * case of the existing port names are ignored when checking for uniqueness.
+	 * 
+	 * @see #uniqueProcessorName(String, Dataflow)
+	 * 
+	 * @param suggestedPortName
+	 *            Port name suggested for new port
+	 * @param existingPorts
+	 *            Collection of existing {@link Port}s
+	 * @return A port name unique for the given collection of port
+	 */
+	public static String uniquePortName(String suggestedPortName,
+			Collection<? extends Port> existingPorts) {
+		// Make sure we have a unique port name
+		Set<String> existingNames = new HashSet<>();
+		for (Port existingPort : existingPorts)
+			existingNames.add(existingPort.getName().toLowerCase());
+		String candidateName = suggestedPortName;
+		long counter = 2;
+		while (existingNames.contains(candidateName.toLowerCase()))
+			candidateName = suggestedPortName + "_" + counter++;
+		return candidateName;
+	}
+
+	public static Edit<?> getMoveDatalinkSinkEdit(Dataflow dataflow,
+			Datalink datalink, EventHandlingInputPort sink, Edits edits) {
+		List<Edit<?>> editList = new ArrayList<>();
+		editList.add(edits.getDisconnectDatalinkEdit(datalink));
+		if (datalink.getSink() instanceof ProcessorInputPort)
+			editList.add(getRemoveProcessorInputPortEdit(
+					(ProcessorInputPort) datalink.getSink(), edits));
+		editList.add(getCreateAndConnectDatalinkEdit(dataflow,
+				datalink.getSource(), sink, edits));
+		return new CompoundEdit(editList);
+	}
+
+	public static Edit<?> getDisconnectDatalinkAndRemovePortsEdit(
+			Datalink datalink, Edits edits) {
+		List<Edit<?>> editList = new ArrayList<>();
+		editList.add(edits.getDisconnectDatalinkEdit(datalink));
+		if (datalink.getSource() instanceof ProcessorOutputPort) {
+			ProcessorOutputPort processorOutputPort = (ProcessorOutputPort) datalink
+					.getSource();
+			if (processorOutputPort.getOutgoingLinks().size() == 1)
+				editList.add(getRemoveProcessorOutputPortEdit(
+						processorOutputPort, edits));
+		}
+		if (datalink.getSink() instanceof ProcessorInputPort)
+			editList.add(getRemoveProcessorInputPortEdit(
+					(ProcessorInputPort) datalink.getSink(), edits));
+		return new CompoundEdit(editList);
+	}
+
+	public static Edit<?> getRemoveProcessorOutputPortEdit(
+			ProcessorOutputPort port, Edits edits) {
+		List<Edit<?>> editList = new ArrayList<>();
+		Processor processor = port.getProcessor();
+		editList.add(edits.getRemoveProcessorOutputPortEdit(
+				port.getProcessor(), port));
+		for (Activity<?> activity : processor.getActivityList())
+			editList.add(edits.getRemoveActivityOutputPortMappingEdit(activity,
+					port.getName()));
+		return new CompoundEdit(editList);
+	}
+
+	public static Edit<?> getRemoveProcessorInputPortEdit(
+			ProcessorInputPort port, Edits edits) {
+		List<Edit<?>> editList = new ArrayList<>();
+		Processor processor = port.getProcessor();
+		editList.add(edits.getRemoveProcessorInputPortEdit(port.getProcessor(),
+				port));
+		for (Activity<?> activity : processor.getActivityList())
+			editList.add(edits.getRemoveActivityInputPortMappingEdit(activity,
+					port.getName()));
+		return new CompoundEdit(editList);
+	}
+
+	public static Edit<?> getEnableDisabledActivityEdit(Processor processor,
+			DisabledActivity disabledActivity, Edits edits) {
+		List<Edit<?>> editList = new ArrayList<>();
+		Activity<?> brokenActivity = disabledActivity.getActivity();
+		try {
+			@SuppressWarnings("unchecked")
+			Activity<Object> ra = brokenActivity.getClass().newInstance();
+			Object lastConfig = disabledActivity.getLastWorkingConfiguration();
+			if (lastConfig == null)
+				lastConfig = disabledActivity.getActivityConfiguration();
+			ra.configure(lastConfig);
+
+			Map<String, String> portMapping = ra.getInputPortMapping();
+			Set<String> portNames = new HashSet<>();
+			portNames.addAll(portMapping.keySet());
+			for (String portName : portNames)
+				editList.add(edits.getRemoveActivityInputPortMappingEdit(ra,
+						portName));
+			portMapping = ra.getOutputPortMapping();
+			portNames.clear();
+			portNames.addAll(portMapping.keySet());
+			for (String portName : portNames)
+				editList.add(edits.getRemoveActivityOutputPortMappingEdit(ra,
+						portName));
+
+			portMapping = disabledActivity.getInputPortMapping();
+			for (String portName : portMapping.keySet())
+				editList.add(edits.getAddActivityInputPortMappingEdit(ra,
+						portName, portMapping.get(portName)));
+			portMapping = disabledActivity.getOutputPortMapping();
+			for (String portName : portMapping.keySet())
+				editList.add(edits.getAddActivityOutputPortMappingEdit(ra,
+						portName, portMapping.get(portName)));
+
+			editList.add(edits.getRemoveActivityEdit(processor,
+					disabledActivity));
+			editList.add(edits.getAddActivityEdit(processor, ra));
+		} catch (ActivityConfigurationException ex) {
+			logger.error("Configuration exception ", ex);
+			return null;
+		} catch (InstantiationException | IllegalAccessException e) {
+			return null;
+		}
+		return new CompoundEdit(editList);
+	}
+
+	public static ProcessorInputPort getProcessorInputPort(Processor processor,
+			Activity<?> activity, InputPort activityInputPort) {
+		ProcessorInputPort result = null;
+		for (Entry<String, String> mapEntry : activity.getInputPortMapping()
+				.entrySet())
+			if (mapEntry.getValue().equals(activityInputPort.getName())) {
+				for (ProcessorInputPort processorInputPort : processor
+						.getInputPorts())
+					if (processorInputPort.getName().equals(mapEntry.getKey())) {
+						result = processorInputPort;
+						break;
+					}
+				break;
+			}
+		return result;
+	}
+
+	public static ProcessorOutputPort getProcessorOutputPort(
+			Processor processor, Activity<?> activity,
+			OutputPort activityOutputPort) {
+		ProcessorOutputPort result = null;
+		for (Entry<String, String> mapEntry : activity.getOutputPortMapping()
+				.entrySet())
+			if (mapEntry.getValue().equals(activityOutputPort.getName())) {
+				for (ProcessorOutputPort processorOutputPort : processor
+						.getOutputPorts())
+					if (processorOutputPort.getName().equals(mapEntry.getKey())) {
+						result = processorOutputPort;
+						break;
+					}
+				break;
+			}
+		return result;
+	}
+
+	public static ActivityInputPort getActivityInputPort(Activity<?> activity,
+			String portName) {
+		ActivityInputPort activityInputPort = null;
+		for (ActivityInputPort inputPort : activity.getInputPorts())
+			if (inputPort.getName().equals(portName)) {
+				activityInputPort = inputPort;
+				break;
+			}
+		return activityInputPort;
+	}
+
+	public static OutputPort getActivityOutputPort(Activity<?> activity,
+			String portName) {
+		OutputPort activityOutputPort = null;
+		for (OutputPort outputPort : activity.getOutputPorts())
+			if (outputPort.getName().equals(portName)) {
+				activityOutputPort = outputPort;
+				break;
+			}
+		return activityOutputPort;
+	}
+
+	public static String getUniqueMergeInputPortName(Merge merge, String name,
+			int count) {
+		String uniqueName = name + count;
+		for (MergeInputPort mergeInputPort : merge.getInputPorts())
+			if (mergeInputPort.getName().equals(uniqueName))
+				return getUniqueMergeInputPortName(merge, name, ++count);
+		return uniqueName;
+	}
+
+	public static Collection<Processor> getProcessorsWithActivity(
+			Dataflow dataflow, Activity<?> activity) {
+		Set<Processor> processors = new HashSet<>();
+		for (Processor processor : dataflow.getProcessors())
+			if (processor.getActivityList().contains(activity))
+				processors.add(processor);
+		return processors;
+	}
+
+	public static Collection<Processor> getProcessorsWithActivityInputPort(
+			Dataflow dataflow, ActivityInputPort inputPort) {
+		Set<Processor> processors = new HashSet<>();
+		for (Processor processor : dataflow.getProcessors()) {
+			// Does it contain a nested workflow?
+			if (containsNestedWorkflow(processor))
+				// Get the nested workflow and check all its nested processors
+				processors.addAll(getProcessorsWithActivityInputPort(
+						getNestedWorkflow(processor), inputPort));
+
+			/*
+			 * Check all processor's activities (even if the processor contained
+			 * a nested workflow, as its dataflow activity still contains input
+			 * and output ports)
+			 */
+			for (Activity<?> activity : processor.getActivityList())
+				if (activity.getInputPorts().contains(inputPort))
+					processors.add(processor);
+		}
+		return processors;
+	}
+
+	public static Collection<Processor> getProcessorsWithActivityOutputPort(
+			Dataflow dataflow, OutputPort outputPort) {
+		Set<Processor> processors = new HashSet<>();
+		for (Processor processor : dataflow.getProcessors()) {
+			// Does it contain a nested workflow?
+			if (containsNestedWorkflow(processor))
+				// Get the nested workflow and check all its nested processors
+				processors.addAll(getProcessorsWithActivityOutputPort(
+						getNestedWorkflow(processor), outputPort));
+
+			/*
+			 * Check all processor's activities (even if the processor contained
+			 * a nested workflow, as its dataflow activity still contains input
+			 * and output ports)
+			 */
+			for (Activity<?> activity : processor.getActivityList())
+				if (activity.getOutputPorts().contains(outputPort))
+					processors.add(processor);
+		}
+		return processors;
+	}
+
+	/**
+	 * Get the TokenProcessingEntity (Processor, Merge or Dataflow) from the
+	 * workflow that contains the given EventForwardingOutputPort. This can be
+	 * an output port of a Processor or a Merge or an input port of a Dataflow
+	 * that has an internal EventForwardingOutputPort attached to it.
+	 * 
+	 * @param port
+	 * @param workflow
+	 * @return
+	 */
+	public static TokenProcessingEntity getTokenProcessingEntityWithEventForwardingOutputPort(
+			EventForwardingOutputPort port, Dataflow workflow) {
+		// First check the workflow's inputs
+		for (DataflowInputPort input : workflow.getInputPorts())
+			if (input.getInternalOutputPort().equals(port))
+				return workflow;
+
+		// Check workflow's merges
+		for (Merge merge : workflow.getMerges())
+			if (merge.getOutputPort().equals(port))
+				return merge;
+
+		// Check workflow's processors
+		for (Processor processor : workflow.getProcessors()) {
+			for (OutputPort output : processor.getOutputPorts())
+				if (output.equals(port))
+					return processor;
+
+			// If processor contains a nested workflow - descend into it
+			if (containsNestedWorkflow(processor)) {
+				TokenProcessingEntity entity = getTokenProcessingEntityWithEventForwardingOutputPort(
+						port, getNestedWorkflow(processor));
+				if (entity != null)
+					return entity;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Get the TokenProcessingEntity (Processor, Merge or Dataflow) from the
+	 * workflow that contains the given target EventHandlingInputPort. This can
+	 * be an input port of a Processor or a Merge or an output port of a
+	 * Dataflow that has an internal EventHandlingInputPort attached to it.
+	 * 
+	 * @param port
+	 * @param workflow
+	 * @return
+	 */
+	public static TokenProcessingEntity getTokenProcessingEntityWithEventHandlingInputPort(
+			EventHandlingInputPort port, Dataflow workflow) {
+		// First check the workflow's outputs
+		for (DataflowOutputPort output : workflow.getOutputPorts())
+			if (output.getInternalInputPort().equals(port))
+				return workflow;
+
+		// Check workflow's merges
+		for (Merge merge : workflow.getMerges())
+			for (EventHandlingInputPort input : merge.getInputPorts())
+				if (input.equals(port))
+					return merge;
+
+		// Check workflow's processors
+		for (Processor processor : workflow.getProcessors()) {
+			for (EventHandlingInputPort output : processor.getInputPorts())
+				if (output.equals(port))
+					return processor;
+
+			// If processor contains a nested workflow - descend into it
+			if (containsNestedWorkflow(processor)) {
+				TokenProcessingEntity entity = getTokenProcessingEntityWithEventHandlingInputPort(
+						port, getNestedWorkflow(processor));
+				if (entity != null)
+					return entity;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Returns true if processor contains a nested workflow.
+	 */
+	public static boolean containsNestedWorkflow(Processor processor) {
+		List<?> activities = processor.getActivityList();
+		return !activities.isEmpty()
+				&& activities.get(0) instanceof NestedDataflow;
+	}
+
+	/**
+	 * Get the workflow that is nested inside. Only call this if
+	 * {@link #containsNestedWorkflow()} returns true.
+	 */
+	private static Dataflow getNestedWorkflow(Processor processor) {
+		return ((NestedDataflow) processor.getActivityList().get(0))
+				.getNestedDataflow();
+	}
+
+	/**
+	 * Find the first processor that contains an activity that has the given
+	 * activity input port. See #get
+	 * 
+	 * @param dataflow
+	 * @param targetPort
+	 * @return
+	 */
+	public static Processor getFirstProcessorWithActivityInputPort(
+			Dataflow dataflow, ActivityInputPort targetPort) {
+		for (Processor processor : getProcessorsWithActivityInputPort(
+				dataflow, targetPort))
+			return processor;
+		return null;
+	}
+
+	public static Processor getFirstProcessorWithActivityOutputPort(
+			Dataflow dataflow, ActivityOutputPort targetPort) {
+		for (Processor processor : getProcessorsWithActivityOutputPort(
+				dataflow, targetPort))
+			return processor;
+		return null;
+	}
+
+	/**
+	 * Find a unique processor name for the supplied Dataflow, based upon the
+	 * preferred name. If needed, an underscore and a numeric suffix is added to
+	 * the preferred name, and incremented until it is unique, starting from 2.
+	 * (The original being 'number 1')
+	 * <p>
+	 * Note that this method checks the uniqueness against the names of all
+	 * {@link NamedWorkflowEntity}s, including {@link Merge}s.
+	 * <p>
+	 * Although not strictly needed by Taverna, for added user friendliness the
+	 * case of the existing port names are ignored when checking for uniqueness.
+	 * 
+	 * @param preferredName
+	 *            the preferred name for the Processor
+	 * @param dataflow
+	 *            the dataflow for which the Processor name needs to be unique
+	 * @return A unique processor name
+	 */
+	public static String uniqueProcessorName(String preferredName,
+			Dataflow dataflow) {
+		Set<String> existingNames = new HashSet<>();
+		for (NamedWorkflowEntity entity : dataflow
+				.getEntities(NamedWorkflowEntity.class))
+			existingNames.add(entity.getLocalName().toLowerCase());
+		return uniqueObjectName(preferredName, existingNames);
+	}
+
+	/**
+	 * Checks that the name does not have any characters that are invalid for a
+	 * Taverna name.
+	 * 
+	 * The name must contain only the chars[A-Za-z_0-9].
+	 * 
+	 * @param name
+	 *            the original name
+	 * @return the sanitised name
+	 */
+	public static String sanitiseName(String name) {
+		if (Pattern.matches("\\w++", name) == false) {
+			StringBuilder result = new StringBuilder(name.length());
+			for (char c : name.toCharArray())
+				result.append(isLetterOrDigit(c) || c == '_' ? c : "_");
+			return result.toString();
+		}
+		return name;
+	}
+
+	public static String uniqueObjectName(String preferredName,
+			Set<String> existingNames) {
+		String uniqueName = preferredName;
+		long suffix = 2;
+		while (existingNames.contains(uniqueName.toLowerCase()))
+			uniqueName = preferredName + "_" + suffix++;
+		return uniqueName;
+
+	}
+
+	/**
+	 * Add the identification of a Dataflow into its identification annotation
+	 * chain (if necessary)
+	 * 
+	 * @return Whether an identification needed to be added
+	 */
+	public static boolean addDataflowIdentification(Dataflow dataflow,
+			String internalId, Edits edits) {
+		IdentificationAssertion ia = (IdentificationAssertion) getAnnotation(
+				dataflow, IdentificationAssertion.class);
+		if (ia != null && ia.getIdentification().equals(internalId))
+			return false;
+		IdentificationAssertion newIa = new IdentificationAssertion();
+		newIa.setIdentification(internalId);
+		try {
+			addAnnotation(dataflow, newIa, edits).doEdit();
+			return true;
+		} catch (EditException e) {
+			return false;
+		}
+	}
+
+	/**
+	 * Return a path of processors where the last element is this processor and
+	 * previous ones are nested processors that contain this one all the way to
+	 * the top but excluding the top level workflow as this is only a list of
+	 * processors.
+	 */
+	public static List<Processor> getNestedPathForProcessor(
+			Processor processor, Dataflow dataflow) {
+		for (Processor proc : dataflow.getProcessors())
+			if (proc == processor) { // found it
+				List<Processor> list = new ArrayList<>();
+				list.add(processor);
+				return list;
+			} else if (containsNestedWorkflow(proc)) {
+				/*
+				 * check inside this nested processor
+				 */
+				List<Processor> nestedList = getNestedPathForProcessor(
+						processor, getNestedWorkflow(proc));
+				if (nestedList == null)
+					// processor not found in this nested workflow
+					continue;
+				// add this nested processor to the list
+				nestedList.add(0, proc);
+				return nestedList;
+			}
+		return null;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind b/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind
deleted file mode 100644
index 4ef9d66..0000000
--- a/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.visit.VisitKind
+++ /dev/null
@@ -1 +0,0 @@
-net.sf.taverna.t2.workflowmodel.health.HealthCheck

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker b/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker
deleted file mode 100644
index e2708bc..0000000
--- a/taverna-workflowmodel-api/src/main/resources/META-INF/services/net.sf.taverna.t2.workflowmodel.health.HealthChecker
+++ /dev/null
@@ -1,2 +0,0 @@
-net.sf.taverna.t2.workflowmodel.health.DisabledActivityHealthChecker
-net.sf.taverna.t2.workflowmodel.health.UnrecognizedActivityHealthChecker

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.visit.VisitKind
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.visit.VisitKind b/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.visit.VisitKind
new file mode 100644
index 0000000..6568472
--- /dev/null
+++ b/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.visit.VisitKind
@@ -0,0 +1 @@
+org.apache.taverna.workflowmodel.health.HealthCheck

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.workflowmodel.health.HealthChecker
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.workflowmodel.health.HealthChecker b/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.workflowmodel.health.HealthChecker
new file mode 100644
index 0000000..46e84d8
--- /dev/null
+++ b/taverna-workflowmodel-api/src/main/resources/META-INF/services/org.apache.taverna.workflowmodel.health.HealthChecker
@@ -0,0 +1,2 @@
+org.apache.taverna.workflowmodel.health.DisabledActivityHealthChecker
+org.apache.taverna.workflowmodel.health.UnrecognizedActivityHealthChecker

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context-osgi.xml
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context-osgi.xml b/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context-osgi.xml
index 8fdabdf..9dea97c 100644
--- a/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context-osgi.xml
+++ b/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context-osgi.xml
@@ -6,20 +6,20 @@
                                  http://www.springframework.org/schema/osgi 
                                  http://www.springframework.org/schema/osgi/spring-osgi.xsd">
 
-	<service ref="author" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="descriptiveTitle" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="freeTextDescription" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="hostInstitution" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<!--<service ref="mimeType" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />-->
-	<service ref="documentationUrl" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="optional" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="exampleValue" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="semanticAnnotation" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
-	<service ref="identificationAssertion" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
+	<service ref="author" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="descriptiveTitle" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="freeTextDescription" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="hostInstitution" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<!--<service ref="mimeType" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />-->
+	<service ref="documentationUrl" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="optional" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="exampleValue" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="semanticAnnotation" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
+	<service ref="identificationAssertion" interface="org.apache.taverna.annotation.AnnotationBeanSPI" />
 
-	<service ref="healthCheck" interface="net.sf.taverna.t2.visit.VisitKind" />
+	<service ref="healthCheck" interface="org.apache.taverna.visit.VisitKind" />
 	
-	<service ref="disabledActivityHealthChecker" interface="net.sf.taverna.t2.workflowmodel.health.HealthChecker" />
-	<service ref="unrecognizedActivityHealthChecker" interface="net.sf.taverna.t2.workflowmodel.health.HealthChecker" />
+	<service ref="disabledActivityHealthChecker" interface="org.apache.taverna.workflowmodel.health.HealthChecker" />
+	<service ref="unrecognizedActivityHealthChecker" interface="org.apache.taverna.workflowmodel.health.HealthChecker" />
 
 </beans:beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context.xml
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context.xml b/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context.xml
index ea52514..f0c0b77 100644
--- a/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context.xml
+++ b/taverna-workflowmodel-api/src/main/resources/META-INF/spring/workflowmodel-api-context.xml
@@ -4,20 +4,20 @@
 	xsi:schemaLocation="http://www.springframework.org/schema/beans 
                            http://www.springframework.org/schema/beans/spring-beans.xsd">
                            
-	<bean id="author" class="net.sf.taverna.t2.annotation.annotationbeans.Author" />
-	<bean id="descriptiveTitle" class="net.sf.taverna.t2.annotation.annotationbeans.DescriptiveTitle" />
-	<bean id="freeTextDescription" class="net.sf.taverna.t2.annotation.annotationbeans.FreeTextDescription" />
-	<bean id="hostInstitution" class="net.sf.taverna.t2.annotation.annotationbeans.HostInstitution" />
-	<!--<bean id="mimeType" class="net.sf.taverna.t2.annotation.annotationbeans.MimeType" />-->
-	<bean id="documentationUrl" class="net.sf.taverna.t2.annotation.annotationbeans.DocumentationUrl" />
-	<bean id="optional" class="net.sf.taverna.t2.annotation.annotationbeans.Optional" />
-	<bean id="exampleValue" class="net.sf.taverna.t2.annotation.annotationbeans.ExampleValue" />
-	<bean id="semanticAnnotation" class="net.sf.taverna.t2.annotation.annotationbeans.SemanticAnnotation" />
-	<bean id="identificationAssertion" class="net.sf.taverna.t2.annotation.annotationbeans.IdentificationAssertion" />
+	<bean id="author" class="org.apache.taverna.annotation.annotationbeans.Author" />
+	<bean id="descriptiveTitle" class="org.apache.taverna.annotation.annotationbeans.DescriptiveTitle" />
+	<bean id="freeTextDescription" class="org.apache.taverna.annotation.annotationbeans.FreeTextDescription" />
+	<bean id="hostInstitution" class="org.apache.taverna.annotation.annotationbeans.HostInstitution" />
+	<!--<bean id="mimeType" class="org.apache.taverna.annotation.annotationbeans.MimeType" />-->
+	<bean id="documentationUrl" class="org.apache.taverna.annotation.annotationbeans.DocumentationUrl" />
+	<bean id="optional" class="org.apache.taverna.annotation.annotationbeans.Optional" />
+	<bean id="exampleValue" class="org.apache.taverna.annotation.annotationbeans.ExampleValue" />
+	<bean id="semanticAnnotation" class="org.apache.taverna.annotation.annotationbeans.SemanticAnnotation" />
+	<bean id="identificationAssertion" class="org.apache.taverna.annotation.annotationbeans.IdentificationAssertion" />
 
-	<bean id="healthCheck" class="net.sf.taverna.t2.workflowmodel.health.HealthCheck" />
+	<bean id="healthCheck" class="org.apache.taverna.workflowmodel.health.HealthCheck" />
 
-	<bean id="disabledActivityHealthChecker" class="net.sf.taverna.t2.workflowmodel.health.DisabledActivityHealthChecker" />
-	<bean id="unrecognizedActivityHealthChecker" class="net.sf.taverna.t2.workflowmodel.health.UnrecognizedActivityHealthChecker" />
+	<bean id="disabledActivityHealthChecker" class="org.apache.taverna.workflowmodel.health.DisabledActivityHealthChecker" />
+	<bean id="unrecognizedActivityHealthChecker" class="org.apache.taverna.workflowmodel.health.UnrecognizedActivityHealthChecker" />
 		
 </beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/monitor/TestMonitorManager.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/monitor/TestMonitorManager.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/monitor/TestMonitorManager.java
deleted file mode 100644
index 8ce2b6e..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/monitor/TestMonitorManager.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007 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 net.sf.taverna.t2.monitor;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-import net.sf.taverna.t2.lang.observer.Observable;
-import net.sf.taverna.t2.lang.observer.Observer;
-import net.sf.taverna.t2.monitor.MonitorManager.AddPropertiesMessage;
-import net.sf.taverna.t2.monitor.MonitorManager.DeregisterNodeMessage;
-import net.sf.taverna.t2.monitor.MonitorManager.MonitorMessage;
-import net.sf.taverna.t2.monitor.MonitorManager.RegisterNodeMessage;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test {@link MonitorManager}.
- * 
- * @author Stian Soiland-Reyes
- *
- */
-public class TestMonitorManager {
-	private MonitorManager monitorManager;
-
-	@Test
-	public void addMonitor() {
-		TestMonitor testMonitor = new TestMonitor();
-		monitorManager.addObserver(testMonitor);
-		assertEquals(0, testMonitor.getCounts());
-		// Make a fake registration
-		Object workflowObject = "The workflow object as a string";
-		String[] owningProcess = { "dataflow0", "process4", "42424" };
-		Set<MonitorableProperty<?>> properties = new HashSet<>();
-		properties.add(new ExampleProperty());
-		monitorManager.registerNode(workflowObject, owningProcess, properties);
-
-		assertEquals(1, testMonitor.getCounts());
-		assertEquals(monitorManager, testMonitor.lastSender);
-		MonitorMessage lastMessage = testMonitor.lastMessage;
-		assertTrue("Owning process did not match", Arrays.equals(owningProcess,
-				lastMessage.getOwningProcess()));
-
-		assertTrue("Message was not a RegisterNodeMessage",
-				lastMessage instanceof RegisterNodeMessage);
-		RegisterNodeMessage registerNodeMessage = (RegisterNodeMessage) lastMessage;
-		assertSame("Workflow object was not same", workflowObject,
-				registerNodeMessage.getWorkflowObject());
-		assertEquals(properties, registerNodeMessage.getProperties());
-
-		assertEquals("Another event was received", 1, testMonitor.getCounts());
-	}
-
-	@Test
-	public void addProperties() {
-		TestMonitor testMonitor = new TestMonitor();
-		monitorManager.addObserver(testMonitor);
-		assertEquals(0, testMonitor.getCounts());
-		// Make a fake add properties
-		String[] owningProcess = { "dataflow0", "process4", "42424" };
-		Set<MonitorableProperty<?>> newProperties = new HashSet<>();
-		newProperties.add(new ExampleProperty());
-		monitorManager.addPropertiesToNode(owningProcess, newProperties);
-
-		assertEquals(1, testMonitor.getCounts());
-		assertEquals(monitorManager, testMonitor.lastSender);
-		MonitorMessage lastMessage = testMonitor.lastMessage;
-		assertTrue("Owning process did not match", Arrays.equals(owningProcess,
-				lastMessage.getOwningProcess()));
-
-		assertTrue("Message was not a AddPropertiesMessage",
-				lastMessage instanceof AddPropertiesMessage);
-		AddPropertiesMessage registerNodeMessage = (AddPropertiesMessage) lastMessage;
-		assertEquals(newProperties, registerNodeMessage.getNewProperties());
-
-		assertEquals("Another event was received", 1, testMonitor.getCounts());
-	}
-
-	@Before
-	public void findMonitorManager() {
-		monitorManager = MonitorManager.getInstance();
-	}
-
-	@Test
-	public void removeMonitor() {
-		TestMonitor testMonitor = new TestMonitor();
-		monitorManager.addObserver(testMonitor);
-		assertEquals(0, testMonitor.getCounts());
-
-		// Make a fake deregistration
-		String[] owningProcess = { "dataflow0", "process4", "1337" };
-		monitorManager.deregisterNode(owningProcess);
-
-		assertEquals(1, testMonitor.getCounts());
-		assertEquals(monitorManager, testMonitor.lastSender);
-		MonitorMessage lastMessage = testMonitor.lastMessage;
-		assertTrue("Owning process did not match", Arrays.equals(owningProcess,
-				lastMessage.getOwningProcess()));
-		assertTrue("Message was not a DeregisterNodeMessage",
-				lastMessage instanceof DeregisterNodeMessage);
-		assertEquals("Another event was received", 1, testMonitor.getCounts());
-	}
-
-	public class TestMonitor implements Observer<MonitorManager.MonitorMessage> {
-
-		private int counts = 0;
-		private MonitorMessage lastMessage;
-		private Observable<MonitorMessage> lastSender;
-
-		public int getCounts() {
-			return counts;
-		}
-
-		public MonitorMessage getMessage() {
-			return lastMessage;
-		}
-
-		public Observable<MonitorMessage> getSender() {
-			return lastSender;
-		}
-
-		@Override
-		public synchronized void notify(Observable<MonitorMessage> sender,
-				MonitorMessage message) throws Exception {
-			this.lastSender = sender;
-			this.lastMessage = message;
-			this.counts++;
-		}
-	}
-
-	private final class ExampleProperty implements MonitorableProperty<String> {
-		@Override
-		public Date getLastModified() {
-			return new Date();
-		}
-
-		@Override
-		public String[] getName() {
-			return new String[] { "monitor", "test", "example" };
-		}
-
-		@Override
-		public String getValue() throws NoSuchPropertyException {
-			return "Example property value";
-		}
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/DummyVisitKind.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/DummyVisitKind.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/DummyVisitKind.java
deleted file mode 100644
index 7f8767f..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/DummyVisitKind.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * 
- */
-package net.sf.taverna.t2.workflowmodel.health;
-
-import net.sf.taverna.t2.visit.VisitKind;
-import net.sf.taverna.t2.visit.Visitor;
-
-/**
- * @author alanrw
- *
- */
-public class DummyVisitKind extends VisitKind {
-	@Override
-	public Class<? extends Visitor<?>> getVisitorClass() {
-		return null;
-	}
-
-	private static class Singleton {
-		private static DummyVisitKind instance = new DummyVisitKind();
-	}
-	
-	public static DummyVisitKind getInstance() {
-		return Singleton.instance;
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker.java
deleted file mode 100644
index 228ed5d..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007 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 net.sf.taverna.t2.workflowmodel.health;
-
-import java.util.List;
-
-import net.sf.taverna.t2.visit.VisitReport;
-import net.sf.taverna.t2.workflowmodel.health.HealthChecker;
-
-public class FloatHealthChecker implements HealthChecker<Float> {
-
-	@Override
-	public boolean canVisit(Object subject) {
-		return subject!=null && subject instanceof Float;
-	}
-
-	@Override
-	public VisitReport visit(Float o, List<Object> ancestry) {
-		return null;
-	}
-
-	@Override
-	public boolean isTimeConsuming() {
-		return false;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker2.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker2.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker2.java
deleted file mode 100644
index 80acac8..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/FloatHealthChecker2.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007 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 net.sf.taverna.t2.workflowmodel.health;
-
-import java.util.List;
-
-import net.sf.taverna.t2.visit.VisitReport;
-
-public class FloatHealthChecker2 implements HealthChecker<Float> {
-
-	@Override
-	public boolean canVisit(Object subject) {
-		return subject!=null && subject instanceof Float;
-	}
-
-	@Override
-	public VisitReport visit(Float o, List<Object> ancestry) {
-		return null;
-	}
-
-	@Override
-	public boolean isTimeConsuming() {
-		return false;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/HealthReportTest.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/HealthReportTest.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/HealthReportTest.java
deleted file mode 100644
index 9ee1fd9..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/HealthReportTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007 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 net.sf.taverna.t2.workflowmodel.health;
-
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import net.sf.taverna.t2.visit.VisitReport;
-import net.sf.taverna.t2.visit.VisitReport.Status;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class HealthReportTest {
-
-	VisitReport report;
-	
-	@Before
-	public void setUp() throws Exception {
-		List<VisitReport> subreports = new ArrayList<>();
-		subreports.add(new VisitReport(DummyVisitKind.getInstance(), "sub subject","this is a subreport",0,Status.OK));
-		report = new VisitReport(DummyVisitKind.getInstance(), "a subject","a message",0, Status.WARNING,subreports);
-	}
-
-	@Test
-	public void testActivityVisitReportStringStatus() {
-		report = new VisitReport(DummyVisitKind.getInstance(), "the subject","a string",0, Status.SEVERE);
-		assertEquals("a string",report.getMessage());
-		assertEquals(Status.SEVERE,report.getStatus());
-		assertEquals("the subject",report.getSubject());
-		assertEquals("the subreports should be an empty list",0,report.getSubReports().size());
-	}
-
-	@Test
-	public void testGetMessage() {
-		assertEquals("a message",report.getMessage());
-	}
-
-	@Test
-	public void testGetStatus() {
-		assertEquals(Status.WARNING,report.getStatus());
-	}
-	
-	@Test
-	public void testGetSubject() {
-		assertEquals("a subject",report.getSubject());
-	}
-	
-	@Test
-	public void testGetSubreports() {
-		Collection<VisitReport> subreports = report.getSubReports();
-		assertEquals("There should be 1 report",1,subreports.size());
-		assertEquals("Wrong subject","sub subject",subreports.iterator().next().getSubject());
-	}
-	
-	@Test 
-	public void testStatusHighestIncludingSubReports() {
-		report = new VisitReport(DummyVisitKind.getInstance(), "parent","set to ok",0, Status.OK);
-		assertEquals("should be OK",Status.OK,report.getStatus());
-		report.getSubReports().add(new VisitReport(DummyVisitKind.getInstance(), "child1","set to warning",0, Status.WARNING));
-		assertEquals("should be WARNING",Status.WARNING,report.getStatus());
-		report.getSubReports().add(new VisitReport(DummyVisitKind.getInstance(), "child1","set to severe",0, Status.SEVERE));
-		assertEquals("should be SEVERE",Status.SEVERE,report.getStatus());
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/StringHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/StringHealthChecker.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/StringHealthChecker.java
deleted file mode 100644
index d550a40..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/health/StringHealthChecker.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007 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 net.sf.taverna.t2.workflowmodel.health;
-
-import java.util.List;
-
-import net.sf.taverna.t2.visit.VisitReport;
-import net.sf.taverna.t2.workflowmodel.health.HealthChecker;
-
-public class StringHealthChecker implements HealthChecker<String> {
-
-	@Override
-	public boolean canVisit(Object subject) {
-		return subject!=null && subject instanceof String;
-	}
-
-	@Override
-	public VisitReport visit(String o, List<Object> ancestry) {
-		return null;
-	}
-
-	@Override
-	public boolean isTimeConsuming() {
-		return false;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/processor/iteration/TestIterationStrategyNodes.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/processor/iteration/TestIterationStrategyNodes.java b/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/processor/iteration/TestIterationStrategyNodes.java
deleted file mode 100644
index 8cf8141..0000000
--- a/taverna-workflowmodel-api/src/test/java/net/sf/taverna/t2/workflowmodel/processor/iteration/TestIterationStrategyNodes.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007 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 net.sf.taverna.t2.workflowmodel.processor.iteration;
-
-import static org.junit.Assert.*;
-
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.Map;
-
-import javax.swing.tree.TreeNode;
-
-import net.sf.taverna.t2.invocation.Completion;
-import net.sf.taverna.t2.workflowmodel.processor.activity.Job;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test {@link AbstractIterationStrategyNode} implementations for
- * {@link TreeNode} behaviour.
- * 
- * @author Stian Soiland-Reyes
- * 
- */
-public class TestIterationStrategyNodes {
-
-	TerminalNode root;
-	private NamedInputPortNode input1;
-	private NamedInputPortNode input2;
-	private CrossProduct crossProduct1;
-	private CrossProduct crossProduct2;
-	private DotProduct dotProduct1;
-	private DotProduct dotProduct2;
-
-	@Test
-	public void addSingleChildToTerminal() throws Exception {
-		assertNull(input1.getParent());
-		assertEquals(0, root.getChildCount());
-		root.insert(input1);
-		assertEquals(root, input1.getParent());
-		assertEquals(1, root.getChildCount());
-		assertEquals(input1, root.getChildAt(0));
-		assertEquals(Arrays.asList(input1), root.getChildren());
-
-		root.insert(input1);
-		assertEquals(1, root.getChildCount());
-
-		root.insert(input1, 0);
-		assertEquals(1, root.getChildCount());
-	}
-
-	@Test(expected = IllegalStateException.class)
-	public void cantAddSeveralChildrenToTerminal() throws Exception {
-		root.insert(input1);
-		root.insert(input2);
-	}
-
-	@Test
-	public void addCrossProduct() throws Exception {
-		assertNull(crossProduct1.getParent());
-		crossProduct1.setParent(root);
-		assertEquals(root, crossProduct1.getParent());
-		assertEquals(1, root.getChildCount());
-		assertEquals(crossProduct1, root.getChildAt(0));
-		assertEquals(Arrays.asList(crossProduct1), root.getChildren());
-		assertEquals(0, crossProduct1.getChildCount());
-
-		crossProduct1.insert(input1);
-		assertEquals(input1, crossProduct1.getChildAt(0));
-		crossProduct1.insert(input2, 0);
-		assertEquals(input2, crossProduct1.getChildAt(0));
-		assertEquals(input1, crossProduct1.getChildAt(1));
-		assertEquals(2, crossProduct1.getChildCount());
-		assertEquals(Arrays.asList(input2, input1), crossProduct1.getChildren());
-
-		// A re-insert should move it
-		crossProduct1.insert(input2, 2);
-		assertEquals(2, crossProduct1.getChildCount());
-		assertEquals(Arrays.asList(input1, input2), crossProduct1.getChildren());
-
-		crossProduct1.insert(input2, 0);
-		assertEquals(Arrays.asList(input2, input1), crossProduct1.getChildren());
-
-		crossProduct1.insert(input1, 1);
-		assertEquals(Arrays.asList(input2, input1), crossProduct1.getChildren());
-	}
-
-	@Test
-	public void addCrossProductMany() {
-		crossProduct1.insert(dotProduct1);
-		crossProduct1.insert(dotProduct2);
-		crossProduct1.insert(input1);
-		crossProduct1.insert(input2);
-		crossProduct1.insert(crossProduct2);
-		assertEquals(5, crossProduct1.getChildCount());
-		assertEquals(Arrays.asList(dotProduct1, dotProduct2, input1, input2,
-				crossProduct2), crossProduct1.getChildren());
-		Enumeration<IterationStrategyNode> enumeration = crossProduct1
-				.children();
-		assertTrue(enumeration.hasMoreElements());
-		assertEquals(dotProduct1, enumeration.nextElement());
-		assertEquals(dotProduct2, enumeration.nextElement());
-		assertEquals(input1, enumeration.nextElement());
-		assertEquals(input2, enumeration.nextElement());
-		assertEquals(crossProduct2, enumeration.nextElement());
-		assertFalse(enumeration.hasMoreElements());
-	}
-
-	@Test
-	public void moveNodeToDifferentParent() {
-		crossProduct1.setParent(root);
-		crossProduct1.insert(input1);
-		crossProduct1.insert(dotProduct1);
-		dotProduct1.insert(input2);
-		dotProduct1.insert(crossProduct2);
-
-		// Check tree
-		assertEquals(crossProduct2, root.getChildAt(0).getChildAt(1)
-				.getChildAt(1));
-		assertEquals(Arrays.asList(input2, crossProduct2), dotProduct1
-				.getChildren());
-
-		crossProduct1.insert(crossProduct2, 1);
-		assertEquals(Arrays.asList(input1, crossProduct2, dotProduct1),
-				crossProduct1.getChildren());
-		assertEquals(crossProduct1, crossProduct2.getParent());
-		// Should no longer be in dotProduct1
-		assertEquals(Arrays.asList(input2), dotProduct1.getChildren());
-	}
-
-	@Test(expected = IllegalStateException.class)
-	public void cantAddToNamedInput() throws Exception {
-		input1.insert(dotProduct1);
-	}
-
-	@Test
-	public void cantAddSelf() throws Exception {
-		dotProduct1.setParent(crossProduct1);
-		try {
-			dotProduct1.insert(dotProduct1);
-			fail("Didn't throw IllegalArgumentException");
-		} catch (IllegalArgumentException ex) {
-			// Make sure we didn't loose our old parent and
-			// ended up in a funny state
-			assertEquals(crossProduct1, dotProduct1.getParent());
-			assertEquals(dotProduct1, crossProduct1.getChildAt(0));
-		}
-	}
-
-	@Test
-	public void cantSetSelfParent() throws Exception {
-		crossProduct1.insert(dotProduct1);
-		try {
-			dotProduct1.setParent(dotProduct1);
-			fail("Didn't throw IllegalArgumentException");
-		} catch (IllegalArgumentException ex) {
-			// Make sure we didn't loose our old parent and
-			// ended up in a funny state
-			assertEquals(crossProduct1, dotProduct1.getParent());
-			assertEquals(dotProduct1, crossProduct1.getChildAt(0));
-		}
-	}
-
-	@Before
-	public void makeNodes() throws Exception {
-		root = new DummyTerminalNode();
-		input1 = new NamedInputPortNode("input1", 1);
-		input2 = new NamedInputPortNode("input2", 2);
-		crossProduct1 = new CrossProduct();
-		crossProduct2 = new CrossProduct();
-		dotProduct1 = new DotProduct();
-		dotProduct2 = new DotProduct();
-	}
-
-	@SuppressWarnings("serial")
-	protected final class DummyTerminalNode extends TerminalNode {
-
-		@Override
-		public int getIterationDepth(Map<String, Integer> inputDepths)
-				throws IterationTypeMismatchException {
-			return 0;
-		}
-
-		@Override
-		public void receiveCompletion(int inputIndex, Completion completion) {
-		}
-
-		@Override
-		public void receiveJob(int inputIndex, Job newJob) {
-		}
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/org/apache/taverna/monitor/TestMonitorManager.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/org/apache/taverna/monitor/TestMonitorManager.java b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/monitor/TestMonitorManager.java
new file mode 100644
index 0000000..93f7496
--- /dev/null
+++ b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/monitor/TestMonitorManager.java
@@ -0,0 +1,172 @@
+/*
+* 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.monitor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+import org.apache.taverna.monitor.MonitorManager.AddPropertiesMessage;
+import org.apache.taverna.monitor.MonitorManager.DeregisterNodeMessage;
+import org.apache.taverna.monitor.MonitorManager.MonitorMessage;
+import org.apache.taverna.monitor.MonitorManager.RegisterNodeMessage;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test {@link MonitorManager}.
+ * 
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class TestMonitorManager {
+	private MonitorManager monitorManager;
+
+	@Test
+	public void addMonitor() {
+		TestMonitor testMonitor = new TestMonitor();
+		monitorManager.addObserver(testMonitor);
+		assertEquals(0, testMonitor.getCounts());
+		// Make a fake registration
+		Object workflowObject = "The workflow object as a string";
+		String[] owningProcess = { "dataflow0", "process4", "42424" };
+		Set<MonitorableProperty<?>> properties = new HashSet<>();
+		properties.add(new ExampleProperty());
+		monitorManager.registerNode(workflowObject, owningProcess, properties);
+
+		assertEquals(1, testMonitor.getCounts());
+		assertEquals(monitorManager, testMonitor.lastSender);
+		MonitorMessage lastMessage = testMonitor.lastMessage;
+		assertTrue("Owning process did not match", Arrays.equals(owningProcess,
+				lastMessage.getOwningProcess()));
+
+		assertTrue("Message was not a RegisterNodeMessage",
+				lastMessage instanceof RegisterNodeMessage);
+		RegisterNodeMessage registerNodeMessage = (RegisterNodeMessage) lastMessage;
+		assertSame("Workflow object was not same", workflowObject,
+				registerNodeMessage.getWorkflowObject());
+		assertEquals(properties, registerNodeMessage.getProperties());
+
+		assertEquals("Another event was received", 1, testMonitor.getCounts());
+	}
+
+	@Test
+	public void addProperties() {
+		TestMonitor testMonitor = new TestMonitor();
+		monitorManager.addObserver(testMonitor);
+		assertEquals(0, testMonitor.getCounts());
+		// Make a fake add properties
+		String[] owningProcess = { "dataflow0", "process4", "42424" };
+		Set<MonitorableProperty<?>> newProperties = new HashSet<>();
+		newProperties.add(new ExampleProperty());
+		monitorManager.addPropertiesToNode(owningProcess, newProperties);
+
+		assertEquals(1, testMonitor.getCounts());
+		assertEquals(monitorManager, testMonitor.lastSender);
+		MonitorMessage lastMessage = testMonitor.lastMessage;
+		assertTrue("Owning process did not match", Arrays.equals(owningProcess,
+				lastMessage.getOwningProcess()));
+
+		assertTrue("Message was not a AddPropertiesMessage",
+				lastMessage instanceof AddPropertiesMessage);
+		AddPropertiesMessage registerNodeMessage = (AddPropertiesMessage) lastMessage;
+		assertEquals(newProperties, registerNodeMessage.getNewProperties());
+
+		assertEquals("Another event was received", 1, testMonitor.getCounts());
+	}
+
+	@Before
+	public void findMonitorManager() {
+		monitorManager = MonitorManager.getInstance();
+	}
+
+	@Test
+	public void removeMonitor() {
+		TestMonitor testMonitor = new TestMonitor();
+		monitorManager.addObserver(testMonitor);
+		assertEquals(0, testMonitor.getCounts());
+
+		// Make a fake deregistration
+		String[] owningProcess = { "dataflow0", "process4", "1337" };
+		monitorManager.deregisterNode(owningProcess);
+
+		assertEquals(1, testMonitor.getCounts());
+		assertEquals(monitorManager, testMonitor.lastSender);
+		MonitorMessage lastMessage = testMonitor.lastMessage;
+		assertTrue("Owning process did not match", Arrays.equals(owningProcess,
+				lastMessage.getOwningProcess()));
+		assertTrue("Message was not a DeregisterNodeMessage",
+				lastMessage instanceof DeregisterNodeMessage);
+		assertEquals("Another event was received", 1, testMonitor.getCounts());
+	}
+
+	public class TestMonitor implements Observer<MonitorManager.MonitorMessage> {
+
+		private int counts = 0;
+		private MonitorMessage lastMessage;
+		private Observable<MonitorMessage> lastSender;
+
+		public int getCounts() {
+			return counts;
+		}
+
+		public MonitorMessage getMessage() {
+			return lastMessage;
+		}
+
+		public Observable<MonitorMessage> getSender() {
+			return lastSender;
+		}
+
+		@Override
+		public synchronized void notify(Observable<MonitorMessage> sender,
+				MonitorMessage message) throws Exception {
+			this.lastSender = sender;
+			this.lastMessage = message;
+			this.counts++;
+		}
+	}
+
+	private final class ExampleProperty implements MonitorableProperty<String> {
+		@Override
+		public Date getLastModified() {
+			return new Date();
+		}
+
+		@Override
+		public String[] getName() {
+			return new String[] { "monitor", "test", "example" };
+		}
+
+		@Override
+		public String getValue() throws NoSuchPropertyException {
+			return "Example property value";
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/DummyVisitKind.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/DummyVisitKind.java b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/DummyVisitKind.java
new file mode 100644
index 0000000..4c7ef30
--- /dev/null
+++ b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/DummyVisitKind.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.workflowmodel.health;
+
+import org.apache.taverna.visit.VisitKind;
+import org.apache.taverna.visit.Visitor;
+
+/**
+ * @author alanrw
+ *
+ */
+public class DummyVisitKind extends VisitKind {
+	@Override
+	public Class<? extends Visitor<?>> getVisitorClass() {
+		return null;
+	}
+
+	private static class Singleton {
+		private static DummyVisitKind instance = new DummyVisitKind();
+	}
+	
+	public static DummyVisitKind getInstance() {
+		return Singleton.instance;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker.java b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker.java
new file mode 100644
index 0000000..d88aaac
--- /dev/null
+++ b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker.java
@@ -0,0 +1,43 @@
+/*
+* 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.workflowmodel.health;
+
+import java.util.List;
+
+import org.apache.taverna.visit.VisitReport;
+
+public class FloatHealthChecker implements HealthChecker<Float> {
+
+	@Override
+	public boolean canVisit(Object subject) {
+		return subject!=null && subject instanceof Float;
+	}
+
+	@Override
+	public VisitReport visit(Float o, List<Object> ancestry) {
+		return null;
+	}
+
+	@Override
+	public boolean isTimeConsuming() {
+		return false;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker2.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker2.java b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker2.java
new file mode 100644
index 0000000..9a05080
--- /dev/null
+++ b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/FloatHealthChecker2.java
@@ -0,0 +1,43 @@
+/*
+* 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.workflowmodel.health;
+
+import java.util.List;
+
+import org.apache.taverna.visit.VisitReport;
+
+public class FloatHealthChecker2 implements HealthChecker<Float> {
+
+	@Override
+	public boolean canVisit(Object subject) {
+		return subject!=null && subject instanceof Float;
+	}
+
+	@Override
+	public VisitReport visit(Float o, List<Object> ancestry) {
+		return null;
+	}
+
+	@Override
+	public boolean isTimeConsuming() {
+		return false;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/HealthReportTest.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/HealthReportTest.java b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/HealthReportTest.java
new file mode 100644
index 0000000..acd14a3
--- /dev/null
+++ b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/HealthReportTest.java
@@ -0,0 +1,87 @@
+/*
+* 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.workflowmodel.health;
+
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.taverna.visit.VisitReport;
+import org.apache.taverna.visit.VisitReport.Status;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class HealthReportTest {
+
+	VisitReport report;
+	
+	@Before
+	public void setUp() throws Exception {
+		List<VisitReport> subreports = new ArrayList<>();
+		subreports.add(new VisitReport(DummyVisitKind.getInstance(), "sub subject","this is a subreport",0,Status.OK));
+		report = new VisitReport(DummyVisitKind.getInstance(), "a subject","a message",0, Status.WARNING,subreports);
+	}
+
+	@Test
+	public void testActivityVisitReportStringStatus() {
+		report = new VisitReport(DummyVisitKind.getInstance(), "the subject","a string",0, Status.SEVERE);
+		assertEquals("a string",report.getMessage());
+		assertEquals(Status.SEVERE,report.getStatus());
+		assertEquals("the subject",report.getSubject());
+		assertEquals("the subreports should be an empty list",0,report.getSubReports().size());
+	}
+
+	@Test
+	public void testGetMessage() {
+		assertEquals("a message",report.getMessage());
+	}
+
+	@Test
+	public void testGetStatus() {
+		assertEquals(Status.WARNING,report.getStatus());
+	}
+	
+	@Test
+	public void testGetSubject() {
+		assertEquals("a subject",report.getSubject());
+	}
+	
+	@Test
+	public void testGetSubreports() {
+		Collection<VisitReport> subreports = report.getSubReports();
+		assertEquals("There should be 1 report",1,subreports.size());
+		assertEquals("Wrong subject","sub subject",subreports.iterator().next().getSubject());
+	}
+	
+	@Test 
+	public void testStatusHighestIncludingSubReports() {
+		report = new VisitReport(DummyVisitKind.getInstance(), "parent","set to ok",0, Status.OK);
+		assertEquals("should be OK",Status.OK,report.getStatus());
+		report.getSubReports().add(new VisitReport(DummyVisitKind.getInstance(), "child1","set to warning",0, Status.WARNING));
+		assertEquals("should be WARNING",Status.WARNING,report.getStatus());
+		report.getSubReports().add(new VisitReport(DummyVisitKind.getInstance(), "child1","set to severe",0, Status.SEVERE));
+		assertEquals("should be SEVERE",Status.SEVERE,report.getStatus());
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/StringHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/StringHealthChecker.java b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/StringHealthChecker.java
new file mode 100644
index 0000000..7bf6960
--- /dev/null
+++ b/taverna-workflowmodel-api/src/test/java/org/apache/taverna/workflowmodel/health/StringHealthChecker.java
@@ -0,0 +1,43 @@
+/*
+* 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.workflowmodel.health;
+
+import java.util.List;
+
+import org.apache.taverna.visit.VisitReport;
+
+public class StringHealthChecker implements HealthChecker<String> {
+
+	@Override
+	public boolean canVisit(Object subject) {
+		return subject!=null && subject instanceof String;
+	}
+
+	@Override
+	public VisitReport visit(String o, List<Object> ancestry) {
+		return null;
+	}
+
+	@Override
+	public boolean isTimeConsuming() {
+		return false;
+	}
+
+}