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

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

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceServiceImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceServiceImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceServiceImpl.java
new file mode 100644
index 0000000..30620bd
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceServiceImpl.java
@@ -0,0 +1,730 @@
+/*
+* 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.reference.impl;
+
+import static java.lang.Float.MAX_VALUE;
+import static org.apache.taverna.reference.T2ReferenceType.ErrorDocument;
+import static org.apache.taverna.reference.T2ReferenceType.IdentifiedList;
+import static org.apache.taverna.reference.T2ReferenceType.ReferenceSet;
+import static org.apache.taverna.reference.impl.T2ReferenceImpl.getAsImpl;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.taverna.reference.ContextualizedT2Reference;
+import org.apache.taverna.reference.ErrorDocument;
+import org.apache.taverna.reference.ErrorDocumentServiceException;
+import org.apache.taverna.reference.ExternalReferenceSPI;
+import org.apache.taverna.reference.Identified;
+import org.apache.taverna.reference.IdentifiedList;
+import org.apache.taverna.reference.ListServiceException;
+import org.apache.taverna.reference.ReferenceContext;
+import org.apache.taverna.reference.ReferenceService;
+import org.apache.taverna.reference.ReferenceServiceException;
+import org.apache.taverna.reference.ReferenceSet;
+import org.apache.taverna.reference.ReferenceSetServiceException;
+import org.apache.taverna.reference.StreamToValueConverterSPI;
+import org.apache.taverna.reference.T2Reference;
+import org.apache.taverna.reference.ValueCarryingExternalReference;
+import org.apache.taverna.reference.ValueToReferenceConversionException;
+import org.apache.taverna.reference.ValueToReferenceConverterSPI;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementation of ReferenceService, inject with ReferenceSetService,
+ * ErrorDocumentService and ListService to enable.
+ * 
+ * @author Tom Oinn
+ * @author Alan R Williams
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ */
+public class ReferenceServiceImpl extends AbstractReferenceServiceImpl
+		implements ReferenceService {
+	private final Logger log = Logger.getLogger(ReferenceServiceImpl.class);
+
+	/**
+	 * The top level registration method is used to register either as yet
+	 * unregistered ErrorDocuments and ReferenceSets (if these are passed in and
+	 * already have an identifier this call does nothing) and arbitrarily nested
+	 * Lists of the same. In addition any ExternalReferenceSPI instances found
+	 * will be wrapped in a single item ReferenceSet and registered, and any
+	 * Throwables will be wrapped in an ErrorDocument and registered. Lists will
+	 * be converted to IdentifiedList<T2Reference> and registered if all
+	 * children can be (or were already) appropriately named.
+	 * <p>
+	 * This method is only valid on parameters of the following type :
+	 * <ol>
+	 * <li>{@link ReferenceSet} - registered if not already registered,
+	 * otherwise returns existing T2Reference</li>
+	 * <li>{@link ErrorDocument} - same behaviour as ReferenceSet</li>
+	 * <li>{@link ExternalReferenceSPI} - wrapped in ReferenceSet, registered
+	 * and ID returned</li>
+	 * <li>Throwable - wrapped in ErrorDocument with no message, registered and
+	 * ID returned</li>
+	 * <li>List - all children are first registered, if this succeeds the list
+	 * is itself registered as an IdentifiedList of T2Reference and its
+	 * reference returned.</li>
+	 * </ol>
+	 * The exception to this is if the useConvertorSPI parameter is set to true
+	 * - in this case any objects which do not match the above allowed list will
+	 * be run through any available ValueToReferenceConvertorSPI instances in
+	 * turn until one succeeds or all fail, which may result in the creation of
+	 * ExternalReferenceSPI instances. As these can be registered such objects
+	 * will not cause an exception to be thrown.
+	 * 
+	 * @param o
+	 *            the object to register with the reference system, must comply
+	 *            with and will be interpreted as shown in the type list above.
+	 * @param targetDepth
+	 *            the depth of the top level object supplied. This is needed
+	 *            when registering empty collections and error documents,
+	 *            whether as top level types or as members of a collection
+	 *            within the top level type. If registering a collection this is
+	 *            the collection depth, so a List of ReferenceSchemeSPI would be
+	 *            depth 1. Failing to specify this correctly will result in
+	 *            serious problems downstream so be careful! We can't catch all
+	 *            potential problems in this method (although some errors will
+	 *            be trapped).
+	 * @param useConverterSPI
+	 *            whether to attempt to use the ValueToReferenceConvertorSPI
+	 *            registry (if defined and available) to map arbitrary objects
+	 *            to ExternalReferenceSPI instances on the fly. The registry of
+	 *            converters is generally injected into the implementation of
+	 *            this service.
+	 * @param context
+	 *            ReferenceContext to use if required by component services,
+	 *            this is most likely to be used by the object to reference
+	 *            converters if engaged.
+	 *            <p>
+	 *            If the context is null a new empty reference context is
+	 *            inserted.
+	 * @return a T2Reference to the registered object
+	 * @throws ReferenceServiceException
+	 *             if the object type (or, for collections, the recursive type
+	 *             of its contents) is not in the allowed list or if a problem
+	 *             occurs during registration. Also thrown if attempting to use
+	 *             the converter SPI without an attached registry.
+	 */
+	@Override
+	public T2Reference register(Object o, int targetDepth,
+			boolean useConverterSPI, ReferenceContext context)
+			throws ReferenceServiceException {
+		checkServices();
+		if (context == null)
+			context = new EmptyReferenceContext();
+		if (useConverterSPI)
+			checkConverterRegistry();
+		return getNameForObject(o, targetDepth, useConverterSPI, context);
+	}
+
+	@SuppressWarnings("unchecked")
+	private T2Reference getNameForObject(Object o, int currentDepth,
+			boolean useConverterSPI, ReferenceContext context)
+			throws ReferenceServiceException {
+		if (currentDepth < 0)
+			throw new ReferenceServiceException("Cannot register at depth "
+					+ currentDepth + ": " + o);
+		/*
+		 * First check whether this is an Identified, and if so whether it
+		 * already has an ID. If this is the case then return it, we assume that
+		 * anything which has an identifier already allocated must have been
+		 * registered (this is implicit in the contract for the various
+		 * sub-services
+		 */
+		if (o instanceof Identified) {
+			Identified i = (Identified) o;
+			if (i.getId() != null)
+				return i.getId();
+		}
+		/*
+		 * Then check whether the item *is* a T2Reference, in which case we can
+		 * just return it (useful for when registering lists of existing
+		 * references)
+		 */
+		if (o instanceof T2Reference)
+			return (T2Reference) o;
+
+		if (o.getClass().isArray()) {
+			Class<?> elementType = o.getClass().getComponentType();
+			if (elementType.getCanonicalName().equals("char")) {
+				char[] cArray = (char[]) o;
+				List<Character> cList = new ArrayList<>();
+				for (char c : cArray)
+					cList.add(new Character(c));
+				o = cList;
+			} else if (elementType.getCanonicalName().equals("short")) {
+				short[] cArray = (short[]) o;
+				List<Short> cList = new ArrayList<>();
+				for (short c : cArray)
+					cList.add(new Short(c));
+				o = cList;
+			} else if (elementType.getCanonicalName().equals("int")) {
+				int[] cArray = (int[]) o;
+				List<Integer> cList = new ArrayList<>();
+				for (int c : cArray)
+					cList.add(new Integer(c));
+				o = cList;
+			} else if (elementType.getCanonicalName().equals("long")) {
+				long[] cArray = (long[]) o;
+				List<Long> cList = new ArrayList<>();
+				for (long c : cArray)
+					cList.add(new Long(c));
+				o = cList;
+			} else if (elementType.getCanonicalName().equals("float")) {
+				float[] cArray = (float[]) o;
+				List<Float> cList = new ArrayList<>();
+				for (float c : cArray)
+					cList.add(new Float(c));
+				o = cList;
+			} else if (elementType.getCanonicalName().equals("double")) {
+				double[] cArray = (double[]) o;
+				List<Double> cList = new ArrayList<>();
+				for (double c : cArray)
+					cList.add(new Double(c));
+				o = cList;
+			} else if (elementType.getCanonicalName().equals("boolean")) {
+				boolean[] cArray = (boolean[]) o;
+				List<Boolean> cList = new ArrayList<>();
+				for (boolean c : cArray)
+					cList.add(new Boolean(c));
+				o = cList;
+			} else if (!elementType.getCanonicalName().equals("byte")) {
+				// Covert arrays of objects
+				Object[] cArray = (Object[]) o;
+				List<Object> cList = new ArrayList<>();
+				for (Object c : cArray)
+					cList.add(c);
+				o = cList;
+			}
+		}
+
+		// If a Collection but not a List
+		if ((o instanceof Collection) && !(o instanceof List)) {
+			List<Object> cList = new ArrayList<>();
+			cList.addAll((Collection<Object>) o);
+			o = cList;
+		}
+		// Next check lists.
+		if (o instanceof List) {
+			if (currentDepth < 1)
+				throw new ReferenceServiceException(
+						"Cannot register list at depth " + currentDepth);
+			List<?> l = (List<?>) o;
+			/*
+			 * If the list is empty then register a new empty list of the
+			 * appropriate depth and return it
+			 */
+			if (l.isEmpty())
+				try {
+					return listService.registerEmptyList(currentDepth, context)
+							.getId();
+				} catch (ListServiceException lse) {
+					throw new ReferenceServiceException(lse);
+				}
+			/*
+			 * Otherwise construct a new list of T2Reference and register it,
+			 * calling the getNameForObject method on all children of the list
+			 * to construct the list of references
+			 */
+			else {
+				List<T2Reference> references = new ArrayList<>();
+				for (Object item : l)
+					/*
+					 * Recursively call this method with a depth one lower than
+					 * the current depth
+					 */
+					references.add(getNameForObject(item, currentDepth - 1,
+							useConverterSPI, context));
+				try {
+					return listService.registerList(references, context)
+							.getId();
+				} catch (ListServiceException lse) {
+					throw new ReferenceServiceException(lse);
+				}
+			}
+		} else {
+			/*
+			 * Neither a list nor an already identified object, first thing is
+			 * to engage the converters if enabled. Only engage if we don't
+			 * already have a Throwable or an ExternalReferenceSPI instance
+			 */
+			if (useConverterSPI && (o instanceof Throwable == false)
+					&& (o instanceof ExternalReferenceSPI == false)) {
+				if (currentDepth != 0)
+					throw new ReferenceServiceException(
+							"Cannot register object " + o + " at depth "
+									+ currentDepth);
+
+				for (ValueToReferenceConverterSPI converter : converters)
+					if (converter.canConvert(o, context))
+						try {
+							o = converter.convert(o, context);
+							break;
+						} catch (ValueToReferenceConversionException vtrce) {
+							/*
+							 * Fail, but that doesn't matter at the moment as
+							 * there may be more converters to try.
+							 * 
+							 * TODO - log this!
+							 */
+						}
+			}
+			/*
+			 * If the object is neither a Throwable nor an ExternalReferenceSPI
+			 * instance at this point we should fail the registration process,
+			 * this means either that the conversion process wasn't enabled or
+			 * that it failed to map the object type correctly.
+			 */
+			if (!(o instanceof Throwable)
+					&& !(o instanceof ExternalReferenceSPI))
+				throw new ReferenceServiceException(
+						"Failed to register object "
+								+ o
+								+ ", found a type '"
+								+ o.getClass().getCanonicalName()
+								+ "' which cannot currently be registered with the reference manager");
+
+			// Have either a Throwable or an ExternalReferenceSPI
+			if (o instanceof Throwable)
+				// Wrap in an ErrorDocument and return the ID
+				try {
+					ErrorDocument doc = errorDocumentService.registerError(
+							(Throwable) o, currentDepth, context);
+					return doc.getId();
+				} catch (ErrorDocumentServiceException edse) {
+					throw new ReferenceServiceException(edse);
+				}
+			if (o instanceof ExternalReferenceSPI) {
+				if (currentDepth != 0)
+					throw new ReferenceServiceException(
+							"Cannot register external references at depth "
+									+ currentDepth);
+				try {
+					Set<ExternalReferenceSPI> references = new HashSet<ExternalReferenceSPI>();
+					references.add((ExternalReferenceSPI) o);
+					ReferenceSet rs = referenceSetService.registerReferenceSet(
+							references, context);
+					return rs.getId();
+				} catch (ReferenceSetServiceException rsse) {
+					throw new ReferenceServiceException(rsse);
+				}
+			}
+		}
+		throw new ReferenceServiceException(
+				"Should never see this, reference registration"
+						+ " logic has fallen off the end of the"
+						+ " world, check the code!");
+	}
+
+	/**
+	 * Perform recursive identifier resolution, building a collection structure
+	 * of Identified objects, any collection elements being IdentifiedLists of
+	 * Identified subclasses. If the id has depth 0 this will just return the
+	 * Identified to which that id refers.
+	 * 
+	 * @param id
+	 *            the T2Reference to resolve
+	 * @param ensureTypes
+	 *            a set of ExternalReferenceSPI classes, this is used to augment
+	 *            any resolved ReferenceSet instances to ensure that each one
+	 *            has at least one of the specified types. If augmentation is
+	 *            not required this can be set to null.
+	 * @param context
+	 *            the ReferenceContext to use to resolve this and any
+	 *            recursively resolved identifiers
+	 *            <p>
+	 *            If the context is null a new EmptyReferenceContext is inserted
+	 *            in its place.
+	 * @return fully resolved Identified subclass - this is either a (recursive)
+	 *         IdentifiedList of Identified, a ReferenceSet or an ErrorDocument
+	 * @throws ReferenceServiceException
+	 *             if any problems occur during resolution
+	 */
+	@Override
+	public Identified resolveIdentifier(T2Reference id,
+			Set<Class<ExternalReferenceSPI>> ensureTypes,
+			ReferenceContext context) throws ReferenceServiceException {
+		checkServices();
+		if (context == null)
+			context = new EmptyReferenceContext();
+		switch (id.getReferenceType()) {
+		case ReferenceSet:
+			try {
+				ReferenceSet rs;
+				if (ensureTypes == null)
+					rs = referenceSetService.getReferenceSet(id);
+				else
+					rs = referenceSetService.getReferenceSetWithAugmentation(
+							id, ensureTypes, context);
+				if (rs == null)
+					throw new ReferenceServiceException(
+							"Could not find ReferenceSet " + id);
+				return rs;
+			} catch (ReferenceSetServiceException rsse) {
+				throw new ReferenceServiceException(rsse);
+			}
+
+		case ErrorDocument:
+			try {
+				ErrorDocument ed = errorDocumentService.getError(id);
+				if (ed == null)
+					throw new ReferenceServiceException(
+							"Could not find ErrorDocument " + id);
+				return ed;
+			} catch (ErrorDocumentServiceException edse) {
+				throw new ReferenceServiceException(edse);
+			}
+
+		case IdentifiedList:
+			try {
+				IdentifiedList<T2Reference> idList = listService.getList(id);
+				if (idList == null)
+					throw new ReferenceServiceException(
+							"Could not find IdentifiedList " + id);
+				/*
+				 * Construct a new list, and populate with the result of
+				 * resolving each ID in turn
+				 */
+				IdentifiedArrayList<Identified> newList = new IdentifiedArrayList<>();
+				for (T2Reference item : idList)
+					newList.add(resolveIdentifier(item, ensureTypes, context));
+				newList.setTypedId(getAsImpl(id));
+				return newList;
+			} catch (ListServiceException lse) {
+				throw new ReferenceServiceException(lse);
+			}
+
+		default:
+			throw new ReferenceServiceException("Unsupported ID type : "
+					+ id.getReferenceType());
+		}
+	}
+
+	@Override
+	public Object renderIdentifier(T2Reference id, Class<?> leafClass,
+			ReferenceContext context) throws ReferenceServiceException {
+		// Check we have the services installed
+		checkServices();
+
+		// Insert an empty context if context was null
+		if (context == null)
+			context = new EmptyReferenceContext();
+		// Reject if the source reference contains errors
+		if (id.containsErrors())
+			throw new ReferenceServiceException(
+					"Can't render an identifier which contains errors to a POJO");
+
+		/*
+		 * Attempt to find an appropriate StreamToValueConverterSPI instance to
+		 * build the specified class
+		 */
+		StreamToValueConverterSPI<?> converter = null;
+		if (valueBuilders != null)
+			for (StreamToValueConverterSPI<?> stvc : valueBuilders) {
+				Class<?> builtClass = stvc.getPojoClass();
+				if (leafClass.isAssignableFrom(builtClass)) {
+					converter = stvc;
+					break;
+				}
+			}
+		if (converter == null)
+			log.warn("No stream->value converters found for type '"
+					+ leafClass.getCanonicalName() + "'");
+
+		// Render the identifier
+		return renderIdentifierInner(id, leafClass, context, converter);
+	}
+
+	private Object renderIdentifierInner(T2Reference id, Class<?> leafClass,
+			ReferenceContext context, StreamToValueConverterSPI<?> converter)
+			throws ReferenceServiceException {
+		checkServices();
+
+		switch (id.getReferenceType()) {
+		case IdentifiedList:
+			try {
+				IdentifiedList<T2Reference> idList = listService.getList(id);
+				if (idList == null)
+					throw new ReferenceServiceException(
+							"Could not find IdentifiedList " + id);
+				List<Object> result = new ArrayList<>();
+				for (T2Reference child : idList)
+					result.add(renderIdentifierInner(child, leafClass, context,
+							converter));
+				return result;
+			} catch (ListServiceException lse) {
+				throw new ReferenceServiceException(lse);
+			}
+
+		case ReferenceSet:
+			try {
+				ReferenceSet rs = referenceSetService.getReferenceSet(id);
+				if (rs == null)
+					throw new ReferenceServiceException(
+							"Could not find ReferenceSet " + id);
+				// Check that there are references in the set
+				if (rs.getExternalReferences().isEmpty())
+					throw new ReferenceServiceException(
+							"Can't render an empty reference set to a POJO");
+				/*
+				 * If we can't directly map to an appropriate value keep track
+				 * of the cheapest reference from which to try to build the pojo
+				 * from a stream
+				 */
+				ExternalReferenceSPI cheapestReference = null;
+				float cheapestReferenceCost = MAX_VALUE;
+				for (ExternalReferenceSPI ers : rs.getExternalReferences()) {
+					if (ers instanceof ValueCarryingExternalReference<?>) {
+						ValueCarryingExternalReference<?> vcer = (ValueCarryingExternalReference<?>) ers;
+						if (leafClass.isAssignableFrom(vcer.getValueType()))
+							return vcer.getValue();
+					}
+					// Got here so this wasn't an appropriate value type
+					if (cheapestReference == null
+							|| ers.getResolutionCost() < cheapestReferenceCost) {
+						cheapestReference = ers;
+						cheapestReferenceCost = ers.getResolutionCost();
+					}
+				}
+				if (converter != null && cheapestReference != null)
+					try (InputStream stream = cheapestReference
+							.openStream(context)) {
+						return converter.renderFrom(stream,
+								cheapestReference.getDataNature(),
+								cheapestReference.getCharset());
+					}
+			} catch (Exception e) {
+				throw new ReferenceServiceException(e);
+			}
+			throw new ReferenceServiceException(
+					"No converter found, and reference set didn't contain"
+							+ " an appropriate value carrying reference, cannot render to POJO");
+
+		default:
+			throw new ReferenceServiceException("Unsupported ID type : "
+					+ id.getReferenceType());
+		}
+	}
+
+	/**
+	 * Initiates a traversal of the specified t2reference, traversing to
+	 * whatever level of depth is required such that all identifiers returned
+	 * within the iterator have the specified depth. The context (i.e. the index
+	 * path from the originally specified reference to each reference within the
+	 * iteration) is included through use of the ContextualizedT2Reference
+	 * wrapper class
+	 * 
+	 * @param source
+	 *            the T2Reference from which to traverse. In general this is the
+	 *            root of a collection structure.
+	 * @param desiredDepth
+	 *            the desired depth of all returned T2References, must be less
+	 *            than or equal to that of the source reference.
+	 * @throws ReferenceServiceException
+	 *             if unable to create the iterator for some reason. Note that
+	 *             implementations are free to lazily perform the iteration so
+	 *             this method may succeed but the iterator produced can fail
+	 *             when used. If the iterator fails it will do so by throwing
+	 *             one of the underlying sub-service exceptions.
+	 */
+	@Override
+	public Iterator<ContextualizedT2Reference> traverseFrom(T2Reference source,
+			int desiredDepth) throws ReferenceServiceException {
+		checkServices();
+		if (desiredDepth < 0)
+			throw new ReferenceServiceException(
+					"Cannot traverse to a negative depth");
+		List<ContextualizedT2Reference> workingSet = new ArrayList<>();
+		workingSet.add(new ContextualizedT2ReferenceImpl(source, new int[0]));
+		int currentDepth = source.getDepth();
+		while (currentDepth > desiredDepth) {
+			List<ContextualizedT2Reference> newSet = new ArrayList<>();
+			for (ContextualizedT2Reference ci : workingSet) {
+				T2ReferenceImpl ref = (T2ReferenceImpl) ci.getReference();
+				switch (ref.getReferenceType()) {
+				case IdentifiedList:
+					try {
+						int position = 0;
+						for (T2Reference child : getListService().getList(ref))
+							newSet.add(new ContextualizedT2ReferenceImpl(child,
+									addIndex(ci.getIndex(), position++)));
+					} catch (ListServiceException lse) {
+						throw new ReferenceServiceException(lse);
+					}
+					break;
+				case ReferenceSet:
+					throw new ReferenceServiceException(
+							"Should never be trying to drill inside a data document identifier");
+				case ErrorDocument:
+					newSet.add(new ContextualizedT2ReferenceImpl(ref
+							.getDeeperErrorReference(), addIndex(ci.getIndex(),
+							0)));
+					break;
+				default:
+					throw new ReferenceServiceException(
+							"Fallen off end of case statement, unknown reference type!");
+				}
+			}
+			currentDepth--;
+			workingSet = newSet;
+		}
+		return workingSet.iterator();
+	}
+
+	/**
+	 * Append to an int[]
+	 * 
+	 * @param current
+	 *            current int[]
+	 * @param head
+	 *            new int item to append to the current array
+	 * @return new array of int with the head added
+	 */
+	private static int[] addIndex(int[] current, int head) {
+		int[] result = new int[current.length + 1];
+		System.arraycopy(current, 0, result, 0, current.length);
+		result[current.length] = head;
+		return result;
+	}
+
+	/**
+	 * Parse the reference contained in the string and return a
+	 * {@link T2Reference} with the correct properties
+	 */
+	@Override
+	public T2Reference referenceFromString(String reference) {
+		T2ReferenceImpl newRef = new T2ReferenceImpl();
+		Map<String, String> parseRef = parseRef(reference);
+		newRef.setNamespacePart(parseRef.get("namespace"));
+		newRef.setLocalPart(parseRef.get("localPart"));
+		String type = parseRef.get("type");
+		if (type.equals("ref")) {
+			newRef.setReferenceType(ReferenceSet);
+		} else if (type.equals("list")) {
+			newRef.setReferenceType(IdentifiedList);
+			newRef.setContainsErrors(Boolean.parseBoolean(parseRef.get("error")));
+			newRef.setDepth(Integer.parseInt(parseRef.get("depth")));
+		} else if (type.equals("error")) {
+			newRef.setContainsErrors(true);
+			newRef.setReferenceType(ErrorDocument);
+			newRef.setDepth(Integer.parseInt(parseRef.get("depth")));
+		} else {
+			return null;
+			// should throw an error
+		}
+
+		return newRef;
+	}
+
+	/**
+	 * Parse the reference and return a map with localPart, namespace, depth,
+	 * contains errors and the type
+	 * 
+	 * @param ref
+	 * @return
+	 */
+	private Map<String, String> parseRef(String ref) {
+		String[] split = ref.split("\\?");
+		/*
+		 * get the bit before and after the final '/' ie. the local part and the
+		 * depth, there might not be a split1[1] since it might not be a list
+		 */
+		String[] split2 = split[1].split("/");
+		// get the t2:abc:// and the namespace
+		String[] split3 = split[0].split("//");
+		// get the t2 bit and the reference type bit
+		String[] split4 = split3[0].split(":");
+
+		Map<String, String> refPartsMap = new HashMap<String, String>();
+		refPartsMap.put("type", split4[1]);
+		refPartsMap.put("namespace", split3[1]);
+		refPartsMap.put("localPart", split2[0]);
+
+		if (refPartsMap.get("type").equals("list")) {
+			refPartsMap.put("error", split2[1]);
+			refPartsMap.put("depth", split2[2]);
+		}
+		if (refPartsMap.get("type").equals("error"))
+			refPartsMap.put("depth", split2[1]);
+
+		return refPartsMap;
+
+	}
+
+	@Override
+	public boolean delete(List<T2Reference> references)
+			throws ReferenceServiceException {
+		for (T2Reference reference : references)
+			delete(reference);
+		return true;
+	}
+
+	@Override
+	public boolean delete(T2Reference reference)
+			throws ReferenceServiceException {
+		switch (reference.getReferenceType()) {
+		case IdentifiedList:
+			return listService.delete(reference);
+		case ReferenceSet:
+			return referenceSetService.delete(reference);
+		case ErrorDocument:
+			return errorDocumentService.delete(reference);
+		default:
+			throw new ReferenceServiceException("Unknown reference type!");
+		}
+	}
+
+	@Override
+	public void deleteReferencesForWorkflowRun(String workflowRunId)
+			throws ReferenceServiceException {
+		String errorString = "";
+		try {
+			listService.deleteIdentifiedListsForWorkflowRun(workflowRunId);
+		} catch (ReferenceServiceException resex) {
+			errorString += "Failed to delete lists for workflow run: "
+					+ workflowRunId + ".";
+		}
+		try {
+			referenceSetService
+					.deleteReferenceSetsForWorkflowRun(workflowRunId);
+		} catch (ReferenceServiceException resex) {
+			errorString += "Failed to delete reference sets for workflow run: "
+					+ workflowRunId + ".";
+		}
+		try {
+			errorDocumentService
+					.deleteErrorDocumentsForWorkflowRun(workflowRunId);
+		} catch (ReferenceServiceException resex) {
+			errorString += "Failed to delete error documents for workflow run: "
+					+ workflowRunId + ".";
+		}
+		if (!errorString.equals(""))
+			throw new ReferenceServiceException(errorString);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetAugmentorImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetAugmentorImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetAugmentorImpl.java
new file mode 100644
index 0000000..994d104
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetAugmentorImpl.java
@@ -0,0 +1,461 @@
+/*
+* 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.reference.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+import org.apache.taverna.reference.ExternalReferenceBuilderSPI;
+import org.apache.taverna.reference.ExternalReferenceSPI;
+import org.apache.taverna.reference.ExternalReferenceTranslatorSPI;
+import org.apache.taverna.reference.ReferenceContext;
+import org.apache.taverna.reference.ReferenceSet;
+import org.apache.taverna.reference.ReferenceSetAugmentationException;
+import org.apache.taverna.reference.ReferenceSetAugmentor;
+import org.apache.taverna.reference.ReferenceSetAugmentorCallback;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementation of ReferenceSetAugmentor using Dijkstra's shortest path
+ * algorithm over a type graph built from SPI instance registries of reference
+ * builders and reference translators.
+ * 
+ * @author Tom Oinn
+ */
+public class ReferenceSetAugmentorImpl implements ReferenceSetAugmentor {
+	private final Logger log = Logger
+			.getLogger(ReferenceSetAugmentorImpl.class);
+
+	/**
+	 * A list of ExternalReferenceBuilderSPI instances used to construct
+	 * ExternalReferenceSPI instances from byte streams
+	 */
+	private List<ExternalReferenceBuilderSPI<?>> builders;
+
+	/**
+	 * A list of ExternalReferenceTranslatorSPI instances used to construct
+	 * ExternalReferenceSPI instances from existing ExternalReferenceSPI
+	 * instances.
+	 */
+	private List<ExternalReferenceTranslatorSPI<?, ?>> translators;
+
+	private boolean cacheValid = false;
+
+	private final Set<Class<ExternalReferenceSPI>> knownReferenceTypes = new HashSet<>();
+	@SuppressWarnings("rawtypes")
+	private final Map<Class<ExternalReferenceSPI>, Set<ExternalReferenceTranslatorSPI>> adjacencySets = new HashMap<>();
+	private final Map<Class<ExternalReferenceSPI>, ShortestPathSolver> solvers = new HashMap<>();
+
+	/**
+	 * Default constructor to make life easier when using Spring. To be
+	 * functional this implementation should be injected with InstanceRegistry
+	 * implementations containing lists of known implementations of the
+	 * ExternalReferenceBuilderSPI and ExternalReferenceTranslatorSPI
+	 * interfaces.
+	 */
+	public ReferenceSetAugmentorImpl() {
+		super();
+	}
+
+	public void buildersUpdated(Object service, Map<?, ?> properties) {
+		cacheValid = false;
+	}
+
+	public void translatorsUpdated(Object service, Map<?, ?> properties) {
+		cacheValid = false;
+	}
+
+	/**
+	 * Inject a list containing all known implementations of
+	 * ExternalReferenceBuilderSPI.
+	 * 
+	 * @throws IllegalStateException
+	 *             if this has already been set, the instance registries should
+	 *             only be set on bean construction.
+	 */
+	public synchronized void setBuilders(
+			List<ExternalReferenceBuilderSPI<?>> builders) {
+		if (this.builders != null) {
+			log.error("Builder registry already injected, invalid operation");
+			throw new IllegalStateException(
+					"Can't inject the external reference builder registry "
+							+ "multiple times.");
+		}
+
+		this.builders = builders;
+		if (log.isDebugEnabled()) {
+			log.debug("* Builders injected :");
+			int counter = 0;
+			for (ExternalReferenceBuilderSPI<?> builder : builders)
+				log.debug("*   " + (++counter) + ") "
+						+ builder.getClass().getSimpleName() + ", builds "
+						+ builder.getReferenceType().getSimpleName());
+		}
+		cacheValid = false;
+	}
+
+	/**
+	 * Inject a list containing all known implementations of
+	 * ExternalReferenceTranslatorSPI.
+	 * 
+	 * @throws IllegalStateException
+	 *             if this has already been set, the instance registries should
+	 *             only be set on bean construction.
+	 */
+	public synchronized void setTranslators(
+			List<ExternalReferenceTranslatorSPI<?, ?>> translators) {
+		if (this.translators == null) {
+			this.translators = translators;
+			if (log.isDebugEnabled()) {
+				log.debug("* Translators injected :");
+				int counter = 0;
+				for (ExternalReferenceTranslatorSPI<?, ?> translator : translators)
+					log.debug("*   "
+							+ (++counter)
+							+ ") "
+							+ translator.getClass().getSimpleName()
+							+ ", translates "
+							+ translator.getSourceReferenceType()
+									.getSimpleName()
+							+ " to "
+							+ translator.getTargetReferenceType()
+									.getSimpleName());
+			}
+			cacheValid = false;
+		} else {
+			log.error("Translator registry already injected, invalid operation");
+			throw new IllegalStateException(
+					"Can't inject the translator registry multiple times.");
+		}
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	protected synchronized final void update() {
+		if (builders == null || translators == null || cacheValid)
+			return;
+		log.debug("# Refreshing shortest path cache");
+		knownReferenceTypes.clear();
+		solvers.clear();
+		adjacencySets.clear();
+		for (ExternalReferenceBuilderSPI erb : builders)
+			knownReferenceTypes.add(erb.getReferenceType());
+		for (ExternalReferenceTranslatorSPI ert : translators) {
+			knownReferenceTypes.add(ert.getSourceReferenceType());
+			knownReferenceTypes.add(ert.getTargetReferenceType());
+			getNeighbours(ert.getTargetReferenceType()).add(ert);
+		}
+		for (Class<ExternalReferenceSPI> type : knownReferenceTypes)
+			try {
+				solvers.put(type, new ShortestPathSolver(type));
+			} catch (Throwable t) {
+				log.error(t);
+				if (t instanceof RuntimeException)
+					throw (RuntimeException) t;
+			}
+		log.debug("# Path cache refresh done");
+		cacheValid = true;
+	}
+
+	@SuppressWarnings("rawtypes")
+	Set<ExternalReferenceTranslatorSPI> getNeighbours(
+			Class<ExternalReferenceSPI> node) {
+		Set<ExternalReferenceTranslatorSPI> adjacentTo = adjacencySets
+				.get(node);
+		if (adjacentTo != null)
+			return adjacentTo;
+
+		HashSet<ExternalReferenceTranslatorSPI> neighbours = new HashSet<>();
+		adjacencySets.put(node, neighbours);
+		return neighbours;
+	}
+
+	@Override
+	public final Set<ExternalReferenceSPI> augmentReferenceSet(
+			ReferenceSet references,
+			Set<Class<ExternalReferenceSPI>> targetReferenceTypes,
+			ReferenceContext context) throws ReferenceSetAugmentationException {
+		synchronized (this) {
+			if (!cacheValid)
+				update();
+		}
+
+		// Synchronize on the reference set itself
+		synchronized (references) {
+			/*
+			 * First check whether we actually need to modify the reference set
+			 * at all - it's perfectly valid to call the augmentor when nothing
+			 * actually needs to be done (ideally you wouldn't do this, but it's
+			 * likely to happen)
+			 */
+			for (ExternalReferenceSPI er : references.getExternalReferences())
+				if (targetReferenceTypes.contains(er.getClass()))
+					return new HashSet<>();
+
+			// Need to perform augmentation if we reach this point
+			List<TranslationPath> candidatePaths = new ArrayList<>();
+			for (Class<ExternalReferenceSPI> target : targetReferenceTypes) {
+				ShortestPathSolver solver = solvers.get(target);
+				if (solver == null) {
+					solver = new ShortestPathSolver(target);
+					solvers.put(target, solver);
+				}
+				if (solver != null)
+					for (TranslationPath path : solver.getTranslationPaths()) {
+						for (ExternalReferenceSPI er : references
+								.getExternalReferences())
+							if (er.getClass().equals(path.getSourceType()))
+								candidatePaths.add(path);
+						for (TranslationPath dereferenceBasedPath : path
+								.getDereferenceBasedPaths(references))
+							candidatePaths.add(dereferenceBasedPath);
+					}
+			}
+			/*
+			 * Now add candidate paths to represent a no-translator 'direct from
+			 * byte stream source' path for each target type compatible
+			 * reference builder
+			 */
+			for (ExternalReferenceBuilderSPI<?> builder : builders)
+				if (targetReferenceTypes.contains(builder.getReferenceType()))
+					/*
+					 * The builder can construct one of the target types, add
+					 * paths for all possible pairs of 'de-reference existing
+					 * reference' and the builder
+					 */
+					for (ExternalReferenceSPI er : references
+							.getExternalReferences()) {
+						TranslationPath newPath = new TranslationPath();
+						newPath.setBuilders(builders);
+						newPath.setInitialBuilder(builder);
+						newPath.setSourceReference(er);
+						candidatePaths.add(newPath);
+					}
+
+			/*
+			 * Got a list of candidate paths sorted by estimated overall path
+			 * cost
+			 */
+			Collections.sort(candidatePaths);
+			if (log.isDebugEnabled()) {
+				log.debug("Found "
+						+ candidatePaths.size()
+						+ " contextual translation path(s) including builder based :");
+				int counter = 0;
+				for (TranslationPath path : candidatePaths)
+					log.debug("  " + (++counter) + ") " + path.toString());
+			}
+
+			if (candidatePaths.isEmpty()) {
+				log.warn("No candidate paths found for augmentation");
+				throw new ReferenceSetAugmentationException(
+						"No candidate translation paths were found");
+			}
+
+			log.debug("Performing augmentation :");
+			int counter = 0;
+			for (TranslationPath path : candidatePaths)
+				try {
+					counter++;
+					Set<ExternalReferenceSPI> newReferences = path
+							.doTranslation(references, context);
+					if (log.isDebugEnabled())
+						log.debug("  Success (" + counter + "), created "
+								+ printRefSet(newReferences));
+					return newReferences;
+				} catch (Exception ex) {
+					log.debug("  Failed (" + counter + ")");
+					log.trace(ex);
+					// Use next path...
+				}
+			log.warn("  No paths succeeded, augmentation failed");
+			throw new ReferenceSetAugmentationException(
+					"All paths threw exceptions, can't perform augmentation");
+		}
+	}
+
+	private String printRefSet(Set<ExternalReferenceSPI> set) {
+		StringBuilder sb = new StringBuilder("[");
+		String sep = "";
+		for (ExternalReferenceSPI ref : set) {
+			sb.append(sep).append(ref.toString());
+			sep = ",";
+		}
+		return sb.append("]").toString();
+	}
+
+	@Override
+	public final void augmentReferenceSetAsynch(final ReferenceSet references,
+			final Set<Class<ExternalReferenceSPI>> targetReferenceTypes,
+			final ReferenceContext context,
+			final ReferenceSetAugmentorCallback callback)
+			throws ReferenceSetAugmentationException {
+		Runnable r = new Runnable() {
+			@Override
+			public void run() {
+				try {
+					callback.augmentationCompleted(augmentReferenceSet(
+							references, targetReferenceTypes, context));
+				} catch (ReferenceSetAugmentationException rsae) {
+					callback.augmentationFailed(rsae);
+				}
+			}
+		};
+		executeRunnable(r);
+	}
+
+	/**
+	 * Schedule a runnable for execution - current naive implementation uses a
+	 * new thread and executes immediately, but this is where any thread pool
+	 * logic would go if we wanted to add that.
+	 * 
+	 * @param r
+	 */
+	private void executeRunnable(Runnable r) {
+		new Thread(r).start();
+	}
+
+	class ShortestPathSolver {
+		private Map<Class<ExternalReferenceSPI>, Class<ExternalReferenceSPI>> predecessors;
+		private Map<Class<ExternalReferenceSPI>, ExternalReferenceTranslatorSPI<?, ?>> translators;
+		private Map<Class<ExternalReferenceSPI>, Float> shortestDistances;
+		private final Comparator<Class<ExternalReferenceSPI>> shortestDistanceComparator = new Comparator<Class<ExternalReferenceSPI>>() {
+			@Override
+			public int compare(Class<ExternalReferenceSPI> left,
+					Class<ExternalReferenceSPI> right) {
+				float shortestDistanceLeft = shortestDistances.get(left);
+				float shortestDistanceRight = shortestDistances.get(right);
+				if (shortestDistanceLeft > shortestDistanceRight)
+					return +1;
+				if (shortestDistanceLeft < shortestDistanceRight)
+					return -1;
+				return left.getCanonicalName().compareTo(
+						right.getCanonicalName());
+			}
+		};
+		private final PriorityQueue<Class<ExternalReferenceSPI>> unsettledNodes = new PriorityQueue<>(
+				10, shortestDistanceComparator);
+		private final Set<Class<ExternalReferenceSPI>> settledNodes = new HashSet<>();
+
+		private final List<TranslationPath> translationPaths = new ArrayList<>();
+
+		public List<TranslationPath> getTranslationPaths() {
+			return this.translationPaths;
+		}
+
+		public ShortestPathSolver(Class<ExternalReferenceSPI> targetType) {
+			log.debug("# Constructing shortest paths to '"
+					+ targetType.getSimpleName() + "'");
+			predecessors = new HashMap<>();
+			translators = new HashMap<>();
+			shortestDistances = new HashMap<>();
+			setShortestDistance(targetType, 0.0f);
+			unsettledNodes.add(targetType);
+			while (!unsettledNodes.isEmpty()) {
+				Class<ExternalReferenceSPI> u = extractMin();
+				settledNodes.add(u);
+				relaxNeighbours(u);
+			}
+			for (Class<ExternalReferenceSPI> c : settledNodes)
+				if (!c.equals(targetType)) {
+					// Don't calculate a path to itself!
+					TranslationPath p = new TranslationPath();
+					p.setBuilders(builders);
+					Class<ExternalReferenceSPI> node = c;
+					while (predecessors.get(node) != null) {
+						p.pathSteps().add(translators.get(node));
+						// Recurse, should terminate at the target type
+						node = predecessors.get(node);
+					}
+					translationPaths.add(p);
+				}
+			Collections.sort(translationPaths);
+			if (translationPaths.isEmpty())
+				log.debug("#   no paths discovered, type not reachable through translation");
+			else if (log.isDebugEnabled()) {
+				log.debug("#   found " + translationPaths.size()
+						+ " distinct path(s) :");
+				int counter = 0;
+				for (TranslationPath path : translationPaths)
+					log.debug("#     " + (++counter) + ") " + path);
+			}
+		}
+
+		@SuppressWarnings({ "unchecked", "rawtypes" })
+		private void relaxNeighbours(Class<ExternalReferenceSPI> u) {
+			log.trace("#     relaxing node " + u.getSimpleName());
+			Set<Class<ExternalReferenceSPI>> alreadySeen = new HashSet<>();
+			for (ExternalReferenceTranslatorSPI ert : getNeighbours(u)) {
+				// all the translators that translate *to* u
+				Class<ExternalReferenceSPI> v = ert.getSourceReferenceType();
+				log.trace("#     translator found from from '" + v + "' : "
+						+ ert.getClass().getSimpleName());
+				if (!alreadySeen.contains(v) && !isSettled(v)) {
+					/*
+					 * Avoid duplicate edges, always take the first one where
+					 * such duplicates exist
+					 */
+					alreadySeen.add(v);
+					if (getShortestDistance(v) > getShortestDistance(u)
+							+ ert.getTranslationCost()) {
+						setShortestDistance(
+								v,
+								getShortestDistance(u)
+										+ ert.getTranslationCost());
+						setPredecessor(v, u, ert);
+						unsettledNodes.add(v);
+					}
+				}
+			}
+		}
+
+		private boolean isSettled(Class<ExternalReferenceSPI> node) {
+			return settledNodes.contains(node);
+		}
+
+		private void setShortestDistance(Class<ExternalReferenceSPI> node,
+				float distance) {
+			shortestDistances.put(node, distance);
+		}
+
+		private float getShortestDistance(Class<ExternalReferenceSPI> node) {
+			Float d = shortestDistances.get(node);
+			return (d == null) ? Float.MAX_VALUE : d;
+		}
+
+		private Class<ExternalReferenceSPI> extractMin() {
+			return unsettledNodes.poll();
+		}
+
+		private void setPredecessor(Class<ExternalReferenceSPI> child,
+				Class<ExternalReferenceSPI> parent,
+				ExternalReferenceTranslatorSPI<?, ?> translator) {
+			predecessors.put(child, parent);
+			translators.put(child, translator);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetImpl.java
new file mode 100644
index 0000000..785a552
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetImpl.java
@@ -0,0 +1,122 @@
+/*
+* 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.reference.impl;
+
+import java.util.Set;
+
+import org.apache.taverna.reference.ExternalReferenceSPI;
+import org.apache.taverna.reference.ReferenceSet;
+import org.apache.taverna.reference.h3.HibernateMappedEntity;
+
+/**
+ * An implementation of ReferenceSet with the additional methods and metadata
+ * required by Hibernate3 to allow it to be persisted in a relational store. As
+ * with everything else in this package you shouldn't be using this class
+ * directly! Instead of this class you should use the registration methods on
+ * {@link org.apache.taverna.reference.ReferenceSetService}, implementations of
+ * that interface will handle the construction of ReferenceSet implementations
+ * (including this one).
+ * 
+ * @author Tom Oinn
+ */
+public class ReferenceSetImpl extends AbstractEntityImpl implements
+		ReferenceSet, HibernateMappedEntity {
+	private Set<ExternalReferenceSPI> externalReferences;
+	private Long approximateSizeInBytes = new Long(-1);
+	
+	/**
+	 * Construct a new ReferenceSetImpl with the given set of external
+	 * references and identifier.
+	 * 
+	 * @param references
+	 *            the set of ExternalReferenceSPI which this reference set
+	 *            should contain initially
+	 * @param id
+	 *            the T2Reference to use, must be an instance of
+	 *            ReferenceSetT2ReferenceImpl so hibernate can make use of it as
+	 *            a compound primary key component
+	 */
+	public ReferenceSetImpl(Set<ExternalReferenceSPI> references,
+			T2ReferenceImpl id) {
+		setTypedId(id);
+		this.externalReferences = references;
+		
+		//  Should be at least one - otherwise we cannot calculate the data size
+		if (externalReferences != null && externalReferences.size() > 0) {
+			// Just take the first ExternalReferenceSPI returned
+			ExternalReferenceSPI externalReferenceSPI = externalReferences
+					.toArray(new ExternalReferenceSPI[0])[0];
+			approximateSizeInBytes = externalReferenceSPI
+					.getApproximateSizeInBytes();
+		}
+	}
+
+	/**
+	 * Default constructor, used by Hibernate when reconstructing this bean from
+	 * the database. If you call this directly from your code you must then call
+	 * both {@link #setExternalReferences(Set)} and
+	 * {@link #setId(T2ReferenceImpl)} before any use of the reference set. If
+	 * you're not writing the reference manager implementation you shouldn't be
+	 * using this class anyway.
+	 */
+	public ReferenceSetImpl() {
+		//
+	}
+
+	/**
+	 * For debugging purposes, prints a summary of the contents and identifier
+	 * of this reference set.
+	 * 
+	 * @return human readable string representation of this object. This is not
+	 *         regarded as 'stable' and should not be parsed for any reason!
+	 */
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(getId()).append(" [").append(externalReferences.size())
+				.append("]\n");
+		for (ExternalReferenceSPI ref : externalReferences)
+			sb.append("  ").append(ref).append("\n");
+		return sb.toString();
+
+	}
+
+	@Override
+	public Set<ExternalReferenceSPI> getExternalReferences() {
+		return externalReferences;
+	}
+
+	/**
+	 * This method is only ever called from within Hibernate, and is used to
+	 * initialize the set of external references.
+	 */
+	public void setExternalReferences(Set<ExternalReferenceSPI> newReferences) {
+		this.externalReferences = newReferences;
+	}
+
+	public void setApproximateSizeInBytes(Long sizeInBytes) {
+		this.approximateSizeInBytes = sizeInBytes;
+	}
+
+	@Override
+	public Long getApproximateSizeInBytes() {
+		return approximateSizeInBytes;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetServiceImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetServiceImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetServiceImpl.java
new file mode 100644
index 0000000..0a50883
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/ReferenceSetServiceImpl.java
@@ -0,0 +1,152 @@
+/*
+* 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.reference.impl;
+
+import static org.apache.taverna.reference.impl.T2ReferenceImpl.getAsImpl;
+
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import org.apache.taverna.reference.DaoException;
+import org.apache.taverna.reference.ExternalReferenceSPI;
+import org.apache.taverna.reference.ReferenceContext;
+import org.apache.taverna.reference.ReferenceServiceException;
+import org.apache.taverna.reference.ReferenceSet;
+import org.apache.taverna.reference.ReferenceSetAugmentationException;
+import org.apache.taverna.reference.ReferenceSetService;
+import org.apache.taverna.reference.ReferenceSetServiceException;
+import org.apache.taverna.reference.T2Reference;
+
+/**
+ * Implementation of ReferenceSetService, inject with an appropriate
+ * ReferenceSetDao to enable. Implements translation functionality as long as an
+ * appropriate ReferenceSetAugmentor implementation is injected.
+ * 
+ * @author Tom Oinn
+ */
+public class ReferenceSetServiceImpl extends AbstractReferenceSetServiceImpl
+		implements ReferenceSetService {
+	@Override
+	public ReferenceSet getReferenceSet(T2Reference id)
+			throws ReferenceSetServiceException {
+		checkDao();
+		try {
+			return referenceSetDao.get(id);
+		} catch (DaoException de) {
+			throw new ReferenceSetServiceException(de);
+		}
+	}
+
+	private Map<URI,Object> locks = new WeakHashMap<>();
+
+	private Object getLock(T2Reference id) {
+		URI uri = id.toUri();
+		synchronized (locks) {
+			Object lock = locks.get(uri);
+			if (lock == null) {
+				lock = new Object();
+				locks.put(uri, lock);
+			}
+			return lock;
+		}
+	}
+
+	@Override
+	public ReferenceSet getReferenceSetWithAugmentation(T2Reference id,
+			Set<Class<ExternalReferenceSPI>> ensureTypes,
+			ReferenceContext context) throws ReferenceSetServiceException {
+		checkDao();
+		checkAugmentor();
+		if (context == null)
+			context = new EmptyReferenceContext();
+		// Obtain the reference set
+
+		try {
+			/*
+			 * Synchronize on the reference set, should ensure that we don't
+			 * have multiple concurrent translations assuming that Hibernate
+			 * retrieves the same entity each time. Except we have to
+			 * synchronize on the reference, and in fact we have to synchronize
+			 * on the URI form.
+			 */
+			synchronized (getLock(id)) {
+				ReferenceSet rs = getReferenceSet(id);
+				Set<ExternalReferenceSPI> newReferences = referenceSetAugmentor
+						.augmentReferenceSet(rs, ensureTypes, context);
+				if (!newReferences.isEmpty()) {
+					/*
+					 * Write back changes to the store if we got here, this can
+					 * potentially throw an unsupported operation exception in
+					 * which case we have to fail the augmentation.
+					 */
+					try {
+						rs.getExternalReferences().addAll(newReferences);
+					} catch (RuntimeException re) {
+						throw new ReferenceSetAugmentationException(
+								"Can't add new references back into existing reference set instance");
+					}
+					referenceSetDao.update(rs);
+				}
+				return rs;
+			}
+		} catch (ReferenceSetAugmentationException rsae) {
+			throw new ReferenceSetServiceException(rsae);
+		}
+	}
+
+	@Override
+	public ReferenceSet registerReferenceSet(
+			Set<ExternalReferenceSPI> references, ReferenceContext context)
+			throws ReferenceSetServiceException {
+		checkDao();
+		checkGenerator();
+		
+		ReferenceSetImpl rsi = new ReferenceSetImpl(new HashSet<>(references),
+				getAsImpl(t2ReferenceGenerator
+						.nextReferenceSetReference(context)));
+
+		try {
+			referenceSetDao.store(rsi);
+			return rsi;
+		} catch (DaoException de) {
+			throw new ReferenceSetServiceException(de);
+		}
+	}
+
+	@Override
+	public boolean delete(T2Reference reference)
+			throws ReferenceServiceException {
+		checkDao();
+		ReferenceSet set = referenceSetDao.get(reference);
+		if (set == null)
+			return false;
+		return referenceSetDao.delete(set);
+	}
+
+	@Override
+	public void deleteReferenceSetsForWorkflowRun(String workflowRunId)
+			throws ReferenceServiceException {
+		checkDao();
+		referenceSetDao.deleteReferenceSetsForWFRun(workflowRunId);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleCacheProviderImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleCacheProviderImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleCacheProviderImpl.java
new file mode 100644
index 0000000..5ea2e82
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleCacheProviderImpl.java
@@ -0,0 +1,56 @@
+/*
+* 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.reference.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.taverna.reference.Identified;
+import org.apache.taverna.reference.ReferenceServiceCacheProvider;
+import org.apache.taverna.reference.T2Reference;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Completely naive cache provider that just stores everything in a map. This
+ * <em>will</em> run out of memory as it makes no attempt to evict old items,
+ * it's really just here as a test!
+ * 
+ * @author Tom Oinn
+ */
+public class SimpleCacheProviderImpl implements ReferenceServiceCacheProvider {
+	private final Logger log = Logger.getLogger(SimpleCacheProviderImpl.class);
+	private Map<T2Reference, Identified> cache = new HashMap<>();
+
+	@Override
+	public Identified get(T2Reference id) {
+		if (log.isDebugEnabled())
+			log.debug("Get " + id.toString() + " (" + cache.containsKey(id)
+					+ ")");
+		return cache.get(id);
+	}
+
+	@Override
+	public void put(Identified i) {
+		if (log.isDebugEnabled())
+			log.debug("Put " + i.getId().toString());
+		cache.put(i.getId(), i);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleT2ReferenceGenerator.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleT2ReferenceGenerator.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleT2ReferenceGenerator.java
new file mode 100644
index 0000000..4be90b5
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/SimpleT2ReferenceGenerator.java
@@ -0,0 +1,62 @@
+/*
+* 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.reference.impl;
+
+import org.apache.taverna.reference.T2ReferenceGenerator;
+
+/**
+ * Simple implementation of T2ReferenceGenerator intended to be injected into
+ * the service layers for integration testing. Exposes a namespace property
+ * which can be configured through spring and allocates local parts based on an
+ * integer counter - this is only guaranteed to be unique within a single
+ * instance of this object so isn't suitable for real production use. For proper
+ * usage use an implementation tied to the backing store you're putting t2
+ * reference objects into.
+ * 
+ * @author Tom Oinn
+ */
+public class SimpleT2ReferenceGenerator extends AbstractT2ReferenceGenerator implements T2ReferenceGenerator {
+	private String namespace = "testNS";
+	private String localPrefix = "test";
+	private int counter = 0;
+
+	/**
+	 * Set the namespace for identifiers generated by this class as a string
+	 * 
+	 * @param newNamespace
+	 *            the namespace to use
+	 */
+	public void setNamespace(String newNamespace) {
+		this.namespace = newNamespace;
+	}
+
+	/**
+	 * Get the namespace for identifiers generated by this class
+	 */
+	@Override
+	public String getNamespace() {
+		return namespace;
+	}
+
+	@Override
+	protected synchronized String getNextLocalPart() {
+		return localPrefix + (counter++);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/StackTraceElementBeanImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/StackTraceElementBeanImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/StackTraceElementBeanImpl.java
new file mode 100644
index 0000000..12158f5
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/StackTraceElementBeanImpl.java
@@ -0,0 +1,76 @@
+/*
+* 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.reference.impl;
+
+import org.apache.taverna.reference.StackTraceElementBean;
+import org.apache.taverna.reference.h3.HibernateComponentClass;
+
+/**
+ * Simple bean implementation of StackTraceElement for Hibernate
+ * 
+ * @author Tom Oinn
+ */
+public class StackTraceElementBeanImpl implements StackTraceElementBean,
+		HibernateComponentClass {
+	private String className;
+	private String fileName;
+	private String methodName;
+	private int lineNumber;
+
+	public StackTraceElementBeanImpl() {
+		//
+	}
+
+	@Override
+	public String getClassName() {
+		return this.className;
+	}
+
+	public void setClassName(String className) {
+		this.className = className;
+	}
+
+	@Override
+	public String getFileName() {
+		return this.fileName;
+	}
+
+	public void setFileName(String fileName) {
+		this.fileName = fileName;
+	}
+
+	@Override
+	public int getLineNumber() {
+		return lineNumber;
+	}
+
+	public void setLineNumber(int lineNumber) {
+		this.lineNumber = lineNumber;
+	}
+
+	@Override
+	public String getMethodName() {
+		return this.methodName;
+	}
+
+	public void setMethodName(String methodName) {
+		this.methodName = methodName;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceImpl.java
new file mode 100644
index 0000000..2f0b385
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceImpl.java
@@ -0,0 +1,287 @@
+/*
+* 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.reference.impl;
+
+import static org.apache.taverna.reference.T2ReferenceType.ErrorDocument;
+
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.UUID;
+
+import org.apache.taverna.reference.T2Reference;
+import org.apache.taverna.reference.T2ReferenceType;
+import org.apache.taverna.reference.h3.HibernateComponentClass;
+
+import org.apache.log4j.Logger;
+
+/**
+ * An implementation of T2Reference specific to the ReferenceSetImpl. This is
+ * needed because ReferenceSetImpl uses a component based primary key driven
+ * from the namespace and local parts of T2Reference. This in turn means we can
+ * query hibernate directly with a T2Reference instance in the data access
+ * object. Because this is only used as a component (i.e. a value type) we don't
+ * need to define a hibernate mapping file for it.
+ * 
+ * @author Tom Oinn
+ * @author David Withers
+ */
+public class T2ReferenceImpl implements T2Reference, Serializable, HibernateComponentClass {
+	private static Logger logger = Logger.getLogger(T2ReferenceImpl.class);
+
+	private static final long serialVersionUID = 8363330461158750319L;
+	private String localPart;
+	private String namespacePart;
+	private long localMostSigBits, localLeastSigBits;
+	private boolean containsErrors = false;
+	private T2ReferenceType referenceType = T2ReferenceType.ReferenceSet;
+	private int depth = 0;
+	private transient URI cachedURI;
+
+	public T2ReferenceImpl() {
+		// Default constructor for Hibernate et al
+	}
+
+	/**
+	 * Construct a deep copy of the given T2Reference
+	 * 
+	 * @param source
+	 *            T2Reference to copy
+	 */
+	private T2ReferenceImpl(T2Reference source) {
+		super();
+		setNamespacePart(source.getNamespacePart());
+		setLocalPart(source.getLocalPart());
+		setContainsErrors(source.containsErrors());
+		setReferenceType(source.getReferenceType());
+		setDepth(source.getDepth());
+	}
+
+	public static T2ReferenceImpl getAsImpl(T2Reference source) {
+		if (source instanceof T2ReferenceImpl)
+			return (T2ReferenceImpl) source;
+		return new T2ReferenceImpl(source);
+	}
+
+	/**
+	 * Return whether the identified entity either is or contains errors
+	 */
+	@Override
+	public boolean containsErrors() {
+		return this.containsErrors;
+	}
+
+	/**
+	 * Property accessor for Hibernate, complies with java bean spec
+	 */
+	public boolean getContainsErrors() {
+		return this.containsErrors;
+	}
+
+	/**
+	 * Get the depth of the entity referred to by this reference
+	 */
+	@Override
+	public int getDepth() {
+		return this.depth;
+	}
+
+	/**
+	 * Get the local part of the URI for this reference
+	 */
+	@Override
+	public String getLocalPart() {
+		if (localPart == null) {
+			UUID localPartUUID = new UUID(localMostSigBits, localLeastSigBits);
+			return localPartUUID.toString();
+		}
+		return localPart;
+	}
+
+	/**
+	 * Get the namespace part of the URI for this reference
+	 */
+	@Override
+	public String getNamespacePart() {
+		return namespacePart;
+	}
+
+	/**
+	 * Get the type of the entity to which this reference refers
+	 */
+	@Override
+	public T2ReferenceType getReferenceType() {
+		return referenceType;
+	}
+
+	/**
+	 * This method is only ever called from within Hibernate when
+	 * re-constructing the identifier component to set the namespace part of the
+	 * identifier.
+	 */
+	public synchronized void setNamespacePart(String namespacePart) {
+		this.namespacePart = namespacePart;
+	}
+
+	/**
+	 * This method is only ever called from within Hibernate when
+	 * re-constructing the identifier component to set the local part of the
+	 * identifier.
+	 */
+	public synchronized void setLocalPart(String localPart) {
+		try {
+			UUID namespacePartUUID = UUID.fromString(localPart);
+			localMostSigBits = namespacePartUUID.getMostSignificantBits();
+			localLeastSigBits = namespacePartUUID.getLeastSignificantBits();
+			this.localPart = null;
+		} catch (IllegalArgumentException e) {
+			this.localPart = localPart;
+		}
+	}
+
+	/**
+	 * This method is only ever called from within Hibernate when
+	 * re-constructing the identifier component to set the depth of the
+	 * identifier.
+	 */
+	public synchronized void setDepth(int depth) {
+		this.depth = depth;
+	}
+
+	/**
+	 * This method is only ever called from within Hibernate when
+	 * re-constructing the identifier component to set the error property of the
+	 * identifier.
+	 */
+	public synchronized void setContainsErrors(boolean containsErrors) {
+		this.containsErrors = containsErrors;
+	}
+
+	/**
+	 * This method is only ever called from within Hibernate when
+	 * re-constructing the identifier component to set the reference type
+	 * property of the identifier.
+	 */
+	public synchronized void setReferenceType(T2ReferenceType type) {
+		this.referenceType = type;
+	}
+
+	/**
+	 * By default when printing an identifier we use {@link #toUri()}.
+	 * {@link java.net.URI#toASCIIString() toASCIIString()}
+	 */
+	@Override
+	public String toString() {
+		return toUri().toASCIIString();
+	}
+
+	/**
+	 * Drill inside an error document reference to get the error one deeper than
+	 * this as long as it is at least depth 1.
+	 */
+	T2ReferenceImpl getDeeperErrorReference() {
+		if (!getReferenceType().equals(ErrorDocument))
+			throw new AssertionError(
+					"Attempt to get a deeper reference on something that isn't an error ref");
+		if (getDepth() == 0)
+			throw new AssertionError(
+					"Error identifier already has depth 0, cannot decrease");
+
+		T2ReferenceImpl result = new T2ReferenceImpl();
+		result.setContainsErrors(true);
+		result.setDepth(getDepth() - 1);
+		result.setLocalPart(getLocalPart());
+		result.setNamespacePart(getNamespacePart());
+		result.setReferenceType(ErrorDocument);
+		return result;
+	}
+
+	/**
+	 * Returns the identifier expressed as a {@link java.net.URI URI},
+	 * constructed based on the reference type. For references to ReferenceSet
+	 * this is
+	 * <code>new URI("t2:ref//" + namespacePart + "?" + localPart)</code>
+	 * leading to URIs of the form <code>t2:ref//namespace?local</code>
+	 */
+	@Override
+	public synchronized URI toUri() {
+		try {
+			if (cachedURI == null)
+				switch (referenceType) {
+				case ReferenceSet:
+					cachedURI = new URI("t2:ref//" + getNamespacePart() + "?"
+							+ getLocalPart());
+				case IdentifiedList:
+					cachedURI = new URI("t2:list//" + getNamespacePart() + "?"
+							+ getLocalPart() + "/" + containsErrors + "/"
+							+ depth);
+				case ErrorDocument:
+					cachedURI = new URI("t2:error//" + getNamespacePart() + "?"
+							+ getLocalPart() + "/" + depth);
+				}
+		} catch (URISyntaxException e) {
+			logger.error("Unable to create URI", e);
+		}
+		return cachedURI;
+	}
+
+	@Override
+	public int hashCode() {
+		int result = 1;
+		result = 31 * result + depth;
+		result = 31 * result + (int) (localLeastSigBits ^ (localLeastSigBits >>> 32));
+		result = 31 * result + (int) (localMostSigBits ^ (localMostSigBits >>> 32));
+		result = 31 * result + ((localPart == null) ? 0 : localPart.hashCode());
+		result = 31 * result + ((namespacePart == null) ? 0 : namespacePart.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		T2ReferenceImpl other = (T2ReferenceImpl) obj;
+		if (depth != other.depth)
+			return false;
+		if (localLeastSigBits != other.localLeastSigBits)
+			return false;
+		if (localMostSigBits != other.localMostSigBits)
+			return false;
+		if (localPart == null) {
+			if (other.localPart != null)
+				return false;
+		} else if (!localPart.equals(other.localPart))
+			return false;
+		if (namespacePart == null) {
+			if (other.namespacePart != null)
+				return false;
+		} else if (!namespacePart.equals(other.namespacePart))
+			return false;
+		return true;
+	}
+
+	public synchronized String getCompactForm() {
+		return getNamespacePart() + ":" + getLocalPart() + ":" + getDepth();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceListImpl.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceListImpl.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceListImpl.java
new file mode 100644
index 0000000..2b0a449
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/T2ReferenceListImpl.java
@@ -0,0 +1,74 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.reference.impl;
+
+import java.util.List;
+
+import org.apache.taverna.reference.T2Reference;
+import org.apache.taverna.reference.h3.HibernateMappedEntity;
+
+/**
+ * Simple extension of
+ * <code>{@link IdentifiedArrayList IdentifiedArrayList&lt;T2Reference&gt;}</code>
+ * exposing get and set methods for the list contents so we can map it in
+ * hibernate.
+ * 
+ * @author Tom Oinn
+ */
+public class T2ReferenceListImpl extends IdentifiedArrayList<T2Reference>
+		implements HibernateMappedEntity {
+	public T2ReferenceListImpl() {
+		super();
+	}
+
+	/**
+	 * This is only called from Hibernate, outside of test code, so is
+	 * relatively safe to leave unchecked.
+	 */
+	@SuppressWarnings("rawtypes")
+	public List getListContents() {
+		return this.listDelegate;
+	}
+
+	/**
+	 * This is only called from Hibernate, outside of test code, so is
+	 * relatively safe to leave unchecked.
+	 */
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	public void setListContents(List newList) {
+		this.listDelegate = newList;
+	}
+
+	/**
+	 * Print the contents of this list for vaguely human readable debug
+	 * purposes.
+	 */
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(getId()).append("\n");
+		int counter = 0;
+		for (T2Reference ref : listDelegate)
+			sb.append("  ").append(++counter).append(") ").append(ref)
+					.append("\n");
+		return sb.toString();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateErrorDocumentDao.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateErrorDocumentDao.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateErrorDocumentDao.java
new file mode 100644
index 0000000..38a6988
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateErrorDocumentDao.java
@@ -0,0 +1,154 @@
+/*
+* 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.reference.impl;
+
+import static org.apache.taverna.reference.T2ReferenceType.ErrorDocument;
+
+import java.util.List;
+
+import org.apache.taverna.reference.DaoException;
+import org.apache.taverna.reference.ErrorDocument;
+import org.apache.taverna.reference.ErrorDocumentDao;
+import org.apache.taverna.reference.T2Reference;
+import org.apache.taverna.reference.annotations.DeleteIdentifiedOperation;
+import org.apache.taverna.reference.annotations.GetIdentifiedOperation;
+import org.apache.taverna.reference.annotations.PutIdentifiedOperation;
+
+import org.hibernate.Query;
+import org.hibernate.SessionFactory;
+
+/**
+ * An implementation of ErrorDocumentDao based on raw hibernate session factory
+ * injection and running within a spring managed context through auto-proxy
+ * generation. To use this in spring inject a property 'sessionFactory' with
+ * either a {@link org.springframework.orm.hibernate3.LocalSessionFactoryBean
+ * LocalSessionFactoryBean} or the equivalent class from the T2Platform module
+ * to add SPI based implementation discovery and mapping. To use outside of
+ * Spring ensure you call the setSessionFactory(..) method before using this
+ * (but really, use it from Spring, so much easier).
+ * <p>
+ * Methods in this Dao require transactional support
+ * 
+ * @author Tom Oinn
+ */
+public class TransactionalHibernateErrorDocumentDao implements ErrorDocumentDao {
+	private static final String GET_ERRORS_FOR_RUN = "FROM ErrorDocumentImpl WHERE namespacePart = :workflow_run_id";
+	private SessionFactory sessionFactory;
+
+	public void setSessionFactory(SessionFactory sessionFactory) {
+		this.sessionFactory = sessionFactory;
+	}
+
+	/**
+	 * Fetch an ErrorDocument list by id
+	 * 
+	 * @param ref
+	 *            the T2Reference to fetch
+	 * @return a retrieved identified list of T2 references
+	 * @throws DaoException
+	 *             if the supplied reference is of the wrong type or if
+	 *             something goes wrong fetching the data or connecting to the
+	 *             database
+	 */
+	@Override
+	@GetIdentifiedOperation
+	public ErrorDocument get(T2Reference ref) throws DaoException {
+		if (ref == null)
+			throw new DaoException(
+					"Supplied reference is null, can't retrieve.");
+		if (!ref.getReferenceType().equals(ErrorDocument))
+			throw new DaoException(
+					"This dao can only retrieve reference of type T2Reference.ErrorDocument");
+		if (!(ref instanceof T2ReferenceImpl))
+			throw new DaoException(
+					"Reference must be an instance of T2ReferenceImpl");
+
+		try {
+			return (ErrorDocumentImpl) sessionFactory.getCurrentSession().get(
+					ErrorDocumentImpl.class,
+					((T2ReferenceImpl) ref).getCompactForm());
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+
+	@Override
+	@PutIdentifiedOperation
+	public void store(ErrorDocument theDocument) throws DaoException {
+		if (theDocument.getId() == null)
+			throw new DaoException(
+					"Supplied error document set has a null ID, allocate "
+							+ "an ID before calling the store method in the dao.");
+		if (!theDocument.getId().getReferenceType().equals(ErrorDocument))
+			throw new DaoException("Strangely the list ID doesn't have type "
+					+ "T2ReferenceType.ErrorDocument, something has probably "
+					+ "gone badly wrong somewhere earlier!");
+		if (!(theDocument instanceof ErrorDocumentImpl))
+			throw new DaoException(
+					"Supplied ErrorDocument not an instance of ErrorDocumentImpl");
+
+		try {
+			sessionFactory.getCurrentSession().save(theDocument);
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+
+	@Override
+	@DeleteIdentifiedOperation
+	public boolean delete(ErrorDocument theDocument) throws DaoException {
+		if (theDocument.getId() == null)
+			throw new DaoException(
+					"Supplied error document set has a null ID, allocate "
+							+ "an ID before calling the store method in the dao.");
+		if (!theDocument.getId().getReferenceType().equals(ErrorDocument))
+			throw new DaoException("Strangely the list ID doesn't have type "
+					+ "T2ReferenceType.ErrorDocument, something has probably "
+					+ "gone badly wrong somewhere earlier!");
+		if (!(theDocument instanceof ErrorDocumentImpl))
+			throw new DaoException(
+					"Supplied ErrorDocument not an instance of ErrorDocumentImpl");
+
+		try {
+			sessionFactory.getCurrentSession().delete(theDocument);
+			return true;
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	@DeleteIdentifiedOperation
+	public synchronized void deleteErrorDocumentsForWFRun(String workflowRunId)
+			throws DaoException {
+		try {
+			// Select all ErrorDocuments for this wf run
+			Query selectQuery = sessionFactory.getCurrentSession().createQuery(
+					GET_ERRORS_FOR_RUN);
+			selectQuery.setString("workflow_run_id", workflowRunId);
+			List<ErrorDocument> errorDocuments = selectQuery.list();
+			for (ErrorDocument errorDocument : errorDocuments)
+				delete(errorDocument);
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/5f1ddb71/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateListDao.java
----------------------------------------------------------------------
diff --git a/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateListDao.java b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateListDao.java
new file mode 100644
index 0000000..3cc6561
--- /dev/null
+++ b/taverna-reference-impl/src/main/java/org/apache/taverna/reference/impl/TransactionalHibernateListDao.java
@@ -0,0 +1,153 @@
+/*
+* 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.reference.impl;
+
+import static org.apache.taverna.reference.T2ReferenceType.IdentifiedList;
+
+import java.util.List;
+
+import org.apache.taverna.reference.DaoException;
+import org.apache.taverna.reference.IdentifiedList;
+import org.apache.taverna.reference.ListDao;
+import org.apache.taverna.reference.T2Reference;
+import org.apache.taverna.reference.annotations.DeleteIdentifiedOperation;
+import org.apache.taverna.reference.annotations.GetIdentifiedOperation;
+import org.apache.taverna.reference.annotations.PutIdentifiedOperation;
+
+import org.hibernate.Query;
+import org.hibernate.SessionFactory;
+
+/**
+ * An implementation of ListDao based on based on raw hibernate session factory
+ * injection and running within a spring managed context through auto-proxy
+ * generation. To use this in spring inject a property 'sessionFactory' with
+ * either a {@link org.springframework.orm.hibernate3.LocalSessionFactoryBean
+ * LocalSessionFactoryBean} or the equivalent class from the T2Platform module
+ * to add SPI based implementation discovery and mapping. To use outside of
+ * Spring ensure you call the setSessionFactory(..) method before using this
+ * (but really, use it from Spring, so much easier).
+ * <p>
+ * Methods in this Dao require transactional support
+ * 
+ * @author Tom Oinn
+ */
+public class TransactionalHibernateListDao implements ListDao {
+	private static final String GET_REFLISTS_FOR_RUN = "FROM T2ReferenceListImpl WHERE namespacePart = :workflow_run_id";
+	private SessionFactory sessionFactory;
+
+	public void setSessionFactory(SessionFactory sessionFactory) {
+		this.sessionFactory = sessionFactory;
+	}
+
+	/**
+	 * Fetch a t2reference list by id
+	 * 
+	 * @param ref
+	 *            the T2Reference to fetch
+	 * @return a retrieved identified list of T2 references
+	 * @throws DaoException
+	 *             if the supplied reference is of the wrong type or if
+	 *             something goes wrong fetching the data or connecting to the
+	 *             database
+	 */
+	@Override
+	@GetIdentifiedOperation
+	public IdentifiedList<T2Reference> get(T2Reference ref) throws DaoException {
+		if (ref == null)
+			throw new DaoException(
+					"Supplied reference is null, can't retrieve.");
+		if (!ref.getReferenceType().equals(IdentifiedList))
+			throw new DaoException(
+					"This dao can only retrieve reference of type T2Reference.IdentifiedList");
+		if (!(ref instanceof T2ReferenceImpl))
+			throw new DaoException(
+					"Reference must be an instance of T2ReferenceImpl");
+
+		try {
+			return (T2ReferenceListImpl) sessionFactory.getCurrentSession()
+					.get(T2ReferenceListImpl.class,
+							((T2ReferenceImpl) ref).getCompactForm());
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+
+	@Override
+	@PutIdentifiedOperation
+	public void store(IdentifiedList<T2Reference> theList) throws DaoException {
+		if (theList.getId() == null)
+			throw new DaoException("Supplied list set has a null ID, allocate "
+					+ "an ID before calling the store method in the dao.");
+		if (!theList.getId().getReferenceType().equals(IdentifiedList))
+			throw new DaoException("Strangely the list ID doesn't have type "
+					+ "T2ReferenceType.IdentifiedList, something has probably "
+					+ "gone badly wrong somewhere earlier!");
+		if (!(theList instanceof T2ReferenceListImpl))
+			throw new DaoException(
+					"Supplied identifier list not an instance of T2ReferenceList");
+
+		try {
+			sessionFactory.getCurrentSession().save(theList);
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+
+	@Override
+	public boolean delete(IdentifiedList<T2Reference> theList)
+			throws DaoException {
+		if (theList.getId() == null)
+			throw new DaoException("Supplied list set has a null ID, allocate "
+					+ "an ID before calling the store method in the dao.");
+		if (!theList.getId().getReferenceType().equals(IdentifiedList))
+			throw new DaoException("Strangely the list ID doesn't have type "
+					+ "T2ReferenceType.IdentifiedList, something has probably "
+					+ "gone badly wrong somewhere earlier!");
+		if (!(theList instanceof T2ReferenceListImpl))
+			throw new DaoException(
+					"Supplied identifier list not an instance of T2ReferenceList");
+
+		try {
+			sessionFactory.getCurrentSession().delete(theList);
+			return true;
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	@DeleteIdentifiedOperation
+	public synchronized void deleteIdentifiedListsForWFRun(String workflowRunId)
+			throws DaoException {
+		try {
+			// Select all T2Reference lists for this wf run
+			Query selectQuery = sessionFactory.getCurrentSession().createQuery(
+					GET_REFLISTS_FOR_RUN);
+			selectQuery.setString("workflow_run_id", workflowRunId);
+			List<IdentifiedList<T2Reference>> referenceLists = selectQuery
+					.list();
+			for (IdentifiedList<T2Reference> referenceList : referenceLists)
+				delete(referenceList);
+		} catch (Exception ex) {
+			throw new DaoException(ex);
+		}
+	}
+}