You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 11:35:18 UTC

[03/58] [abbrv] incubator-taverna-plugin-component git commit: taverna-component-activity/

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/Component.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/Component.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/Component.java
new file mode 100644
index 0000000..4e8ac52
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/Component.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.component.registry;
+
+import static java.util.Collections.synchronizedSortedMap;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Version;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * A Component is a building block for creating Taverna workflows. Components
+ * and must comply with the ComponentProfile of their ComponentFamily.
+ * 
+ * @author David Withers
+ */
+public abstract class Component implements
+		net.sf.taverna.t2.component.api.Component {
+	private String name;
+	private String description;
+	private URL url;
+	/**
+	 * Mapping from version numbers to version implementations.
+	 */
+	protected SortedMap<Integer, Version> versionMap = new TreeMap<>();
+
+	protected Component(URL url) {
+		this.url = url;
+	}
+	
+	protected Component(String url) {
+		try {
+			this.url = new URL(url);
+		} catch (MalformedURLException e) {
+			// nothing
+		}
+	}
+
+	protected Component(File fileDir) {
+		try {
+			this.url = fileDir.toURI().toURL();
+		} catch (MalformedURLException e) {
+			// nothing
+		}
+	}
+
+	@Override
+	public final synchronized String getName() {
+		if (name == null)
+			name = internalGetName();
+		return name;
+	}
+
+	/**
+	 * The real implementation of the name fetching. Caching already handled.
+	 * 
+	 * @return The name of the component.
+	 */
+	protected abstract String internalGetName();
+
+	@Override
+	public final synchronized String getDescription() {
+		if (description == null)
+			description = internalGetDescription();
+		return description;
+	}
+
+	/**
+	 * The real implementation of the description fetching. Caching already
+	 * handled.
+	 * 
+	 * @return The description of the component.
+	 */
+	protected abstract String internalGetDescription();
+
+	@Override
+	public final SortedMap<Integer, Version> getComponentVersionMap() {
+		synchronized (versionMap) {
+			checkComponentVersionMap();
+			return synchronizedSortedMap(versionMap);
+		}
+	}
+
+	private void checkComponentVersionMap() {
+		if (versionMap.isEmpty())
+			populateComponentVersionMap();
+	}
+
+	/**
+	 * Create the contents of the {@link #versionMap} field.
+	 */
+	protected abstract void populateComponentVersionMap();
+
+	@Override
+	public final Version getComponentVersion(Integer version)
+			throws ComponentException {
+		synchronized (versionMap) {
+			checkComponentVersionMap();
+			return versionMap.get(version);
+		}
+	}
+
+	@Override
+	public final Version addVersionBasedOn(WorkflowBundle bundle,
+			String revisionComment) throws ComponentException {
+		Version result = internalAddVersionBasedOn(bundle, revisionComment);
+		synchronized (versionMap) {
+			checkComponentVersionMap();
+			versionMap.put(result.getVersionNumber(), result);
+		}
+		return result;
+	}
+
+	/**
+	 * Manufacture a new version of a component. Does not add to the overall
+	 * version map.
+	 * 
+	 * @param bundle
+	 *            The definition of the component.
+	 * @param revisionComment
+	 *            The description of the version.
+	 * @return The new version of the component.
+	 * @throws RegistryException
+	 */
+	protected abstract Version internalAddVersionBasedOn(WorkflowBundle bundle,
+			String revisionComment) throws ComponentException;
+
+	@Override
+	public final URL getComponentURL() {
+		return url;
+	}
+
+	@Override
+	public void delete() throws ComponentException {
+		getFamily().removeComponent(this);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentFamily.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentFamily.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentFamily.java
new file mode 100644
index 0000000..7339b10
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentFamily.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.component.registry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.taverna.t2.component.api.Component;
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Registry;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.api.profile.Profile;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * A ComponentFamily is a collection of Components that share the same
+ * ComponentProfile.
+ * 
+ * @author David Withers
+ */
+public abstract class ComponentFamily implements
+		net.sf.taverna.t2.component.api.Family {
+	private Registry parentRegistry;
+	private String name;
+	private String description;
+	private Profile componentProfile;
+	private ComponentUtil util;
+
+	protected Map<String, Component> componentCache = new HashMap<>();
+
+	public ComponentFamily(Registry componentRegistry, ComponentUtil util) {
+		this.parentRegistry = componentRegistry;
+		this.util = util;
+	}
+
+	@Override
+	public Registry getComponentRegistry() {
+		return parentRegistry;
+	}
+
+	@Override
+	public final synchronized String getName() {
+		if (name == null) {
+			name = internalGetName();
+		}
+		return name;
+	}
+
+	protected abstract String internalGetName();
+
+	@Override
+	public final synchronized String getDescription() {
+		if (description == null) {
+			description = internalGetDescription();
+		}
+		return description;
+	}
+
+	protected abstract String internalGetDescription();
+
+	@Override
+	public final synchronized Profile getComponentProfile()
+			throws ComponentException {
+		if (componentProfile == null)
+			componentProfile = internalGetComponentProfile();
+		if (componentProfile == null) {
+			Profile baseProfile = util.getBaseProfile();
+			if (baseProfile != null) {
+				return baseProfile;
+			}
+		}
+		return componentProfile;
+	}
+
+	protected abstract Profile internalGetComponentProfile()
+			throws ComponentException;
+
+	@Override
+	public final List<Component> getComponents() throws ComponentException {
+		checkComponentCache();
+		return new ArrayList<>(componentCache.values());
+	}
+
+	private void checkComponentCache() throws ComponentException {
+		synchronized (componentCache) {
+			if (componentCache.isEmpty())
+				populateComponentCache();
+		}
+	}
+
+	protected abstract void populateComponentCache() throws ComponentException;
+
+	@Override
+	public final Component getComponent(String componentName)
+			throws ComponentException {
+		checkComponentCache();
+		return componentCache.get(componentName);
+	}
+
+	@Override
+	public final Version createComponentBasedOn(String componentName,
+			String description, WorkflowBundle bundle) throws ComponentException {
+		if (componentName == null)
+			throw new ComponentException("Component name must not be null");
+		if (bundle == null)
+			throw new ComponentException("workflow must not be null");
+		checkComponentCache();
+		if (componentCache.containsKey(componentName))
+			throw new ComponentException("Component name already used");
+		Version version = internalCreateComponentBasedOn(componentName,
+				description, bundle);
+		synchronized (componentCache) {
+			Component c = version.getComponent();
+			componentCache.put(componentName, c);
+		}
+		return version;
+	}
+
+	protected abstract Version internalCreateComponentBasedOn(
+			String componentName, String description, WorkflowBundle bundle)
+			throws ComponentException;
+
+	@Override
+	public final void removeComponent(Component component)
+			throws ComponentException {
+		if (component != null) {
+			checkComponentCache();
+			synchronized (componentCache) {
+				componentCache.remove(component.getName());
+			}
+			internalRemoveComponent(component);
+		}
+	}
+
+	protected abstract void internalRemoveComponent(Component component)
+			throws ComponentException;
+
+	@Override
+	public void delete() throws ComponentException {
+		getComponentRegistry().removeComponentFamily(this);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentImplementationCache.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentImplementationCache.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentImplementationCache.java
new file mode 100644
index 0000000..2283295
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentImplementationCache.java
@@ -0,0 +1,65 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.component.registry;
+
+import static java.lang.System.currentTimeMillis;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Version;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class ComponentImplementationCache {
+	private class Entry {
+		WorkflowBundle implementation;
+		long timestamp;
+	}
+	private final long VALIDITY = 15 * 60 * 1000;
+	private final Logger logger = getLogger(ComponentImplementationCache.class);
+	private final Map<Version.ID, Entry> cache = new WeakHashMap<>();
+	private ComponentUtil utils;
+
+	public void setComponentUtil(ComponentUtil utils) {
+		this.utils = utils;
+	}
+
+	public WorkflowBundle getImplementation(Version.ID id) throws ComponentException {
+		long now = currentTimeMillis();
+		synchronized (id) {
+			Entry entry = cache.get(id);
+			if (entry != null && entry.timestamp >= now)
+				return entry.implementation;
+			logger.info("before calculate component version for " + id);
+			Version componentVersion;
+			try {
+				componentVersion = utils.getVersion(id);
+			} catch (RuntimeException e) {
+				if (entry != null)
+					return entry.implementation;
+				throw new ComponentException(e.getMessage(), e);
+			}
+			logger.info("calculated component version for " + id + " as "
+					+ componentVersion.getVersionNumber() + "; retrieving dataflow");
+			WorkflowBundle implementation = componentVersion.getImplementation();
+			//DataflowValidationReport report = implementation.checkValidity();
+			//logger.info("component version " + id + " incomplete:"
+			//		+ report.isWorkflowIncomplete() + " valid:"
+			//		+ report.isValid());
+			entry = new Entry();
+			entry.implementation = implementation;
+			entry.timestamp = now + VALIDITY;
+			return cache.put(id, entry).implementation;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentRegistry.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentRegistry.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentRegistry.java
new file mode 100644
index 0000000..40d1346
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentRegistry.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.component.registry;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Family;
+import net.sf.taverna.t2.component.api.License;
+import net.sf.taverna.t2.component.api.SharingPolicy;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.api.profile.Profile;
+
+/**
+ * A ComponentRegistry contains ComponentFamilies and ComponentProfiles.
+ * 
+ * @author David Withers
+ */
+public abstract class ComponentRegistry implements
+		net.sf.taverna.t2.component.api.Registry {
+	protected Map<String, Family> familyCache = new HashMap<>();
+	protected List<Profile> profileCache = new ArrayList<>();
+	protected List<SharingPolicy> permissionCache = new ArrayList<>();
+	protected List<License> licenseCache = new ArrayList<>();
+
+	private URL registryBase;
+
+	protected ComponentRegistry(URL registryBase) throws ComponentException {
+		this.registryBase = registryBase;
+	}
+
+	protected ComponentRegistry(File fileDir) throws ComponentException {
+		try {
+			this.registryBase = fileDir.toURI().toURL();
+		} catch (MalformedURLException e) {
+			throw new ComponentException(e);
+		}
+	}
+
+	@Override
+	public final List<Family> getComponentFamilies() throws ComponentException {
+		checkFamilyCache();
+		return new ArrayList<Family>(familyCache.values());
+	}
+
+	private void checkFamilyCache() throws ComponentException {
+		synchronized (familyCache) {
+			if (familyCache.isEmpty())
+				populateFamilyCache();
+		}
+	}
+
+	protected abstract void populateFamilyCache() throws ComponentException;
+
+	@Override
+	public final Family getComponentFamily(String familyName)
+			throws ComponentException {
+		checkFamilyCache();
+		return familyCache.get(familyName);
+	}
+
+	@Override
+	public final Family createComponentFamily(String familyName,
+			Profile componentProfile, String description, License license,
+			SharingPolicy sharingPolicy) throws ComponentException {
+		if (familyName == null)
+			throw new ComponentException(
+					"Component family name must not be null");
+		if (componentProfile == null)
+			throw new ComponentException("Component profile must not be null");
+		if (getComponentFamily(familyName) != null)
+			throw new ComponentException("Component family already exists");
+
+		Family result = internalCreateComponentFamily(familyName,
+				componentProfile, description, license, sharingPolicy);
+		checkFamilyCache();
+		synchronized (familyCache) {
+			familyCache.put(familyName, result);
+		}
+		return result;
+	}
+
+	protected abstract Family internalCreateComponentFamily(String familyName,
+			Profile componentProfile, String description, License license,
+			SharingPolicy sharingPolicy) throws ComponentException;
+
+	@Override
+	public final void removeComponentFamily(Family componentFamily)
+			throws ComponentException {
+		if (componentFamily != null) {
+			checkFamilyCache();
+			synchronized (familyCache) {
+				familyCache.remove(componentFamily.getName());
+			}
+		  internalRemoveComponentFamily(componentFamily);
+		}
+	}
+
+	protected abstract void internalRemoveComponentFamily(Family componentFamily)
+			throws ComponentException;
+
+	@Override
+	public final URL getRegistryBase() {
+		return registryBase;
+	}
+
+	@Override
+	public final String getRegistryBaseString() {
+		String urlString = getRegistryBase().toString();
+		if (urlString.endsWith("/"))
+			urlString = urlString.substring(0, urlString.length() - 1);
+		return urlString;
+	}
+
+	private void checkProfileCache() throws ComponentException {
+		synchronized (profileCache) {
+			if (profileCache.isEmpty())
+				populateProfileCache();
+		}
+	}
+
+	protected abstract void populateProfileCache() throws ComponentException;
+
+	@Override
+	public final List<Profile> getComponentProfiles() throws ComponentException {
+		checkProfileCache();
+		return profileCache;
+	}
+
+	@Override
+	public final Profile getComponentProfile(String id)
+			throws ComponentException {
+		// TODO use a map instead of a *linear search*...
+		for (Profile p : getComponentProfiles())
+			if (p.getId().equals(id))
+				return p;
+		return null;
+	}
+
+	@Override
+	public final Profile addComponentProfile(Profile componentProfile,
+			License license, SharingPolicy sharingPolicy)
+			throws ComponentException {
+		if (componentProfile == null) {
+			throw new ComponentException("componentProfile is null");
+		}
+		Profile result = null;
+		checkProfileCache();
+		for (Profile p : getComponentProfiles())
+			if (p.getId().equals(componentProfile.getId())) {
+				result = p;
+				break;
+			}
+
+		if (result == null) {
+			result = internalAddComponentProfile(componentProfile, license,
+					sharingPolicy);
+			synchronized (profileCache) {
+				profileCache.add(result);
+			}
+		}
+		return result;
+	}
+
+	protected abstract Profile internalAddComponentProfile(
+			Profile componentProfile, License license,
+			SharingPolicy sharingPolicy) throws ComponentException;
+
+	private void checkPermissionCache() {
+		synchronized (permissionCache) {
+			if (permissionCache.isEmpty())
+				populatePermissionCache();
+		}
+	}
+
+	protected abstract void populatePermissionCache();
+
+	@Override
+	public final List<SharingPolicy> getPermissions() throws ComponentException {
+		checkPermissionCache();
+		return permissionCache;
+	}
+
+	private void checkLicenseCache() {
+		synchronized (licenseCache) {
+			if (licenseCache.isEmpty())
+				populateLicenseCache();
+		}
+	}
+
+	protected abstract void populateLicenseCache();
+
+	@Override
+	public final List<License> getLicenses() throws ComponentException {
+		checkLicenseCache();
+		return licenseCache;
+	}
+
+	protected License getLicenseByAbbreviation(String licenseString)
+			throws ComponentException {
+		checkLicenseCache();
+		for (License l : getLicenses())
+			if (l.getAbbreviation().equals(licenseString))
+				return l;
+		return null;
+	}
+
+	@Override
+	public abstract License getPreferredLicense() throws ComponentException;
+
+	@Override
+	public abstract Set<Version.ID> searchForComponents(String prefixString,
+			String text) throws ComponentException;
+
+	@Override
+	public String toString() {
+		String[] names = getClass().getName().split("\\.");
+		return names[names.length-1] + ": " + registryBase;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentUtil.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentUtil.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentUtil.java
new file mode 100644
index 0000000..4380d22
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentUtil.java
@@ -0,0 +1,113 @@
+package net.sf.taverna.t2.component.registry;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.component.api.Component;
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.ComponentFactory;
+import net.sf.taverna.t2.component.api.Family;
+import net.sf.taverna.t2.component.api.profile.Profile;
+import net.sf.taverna.t2.component.api.Registry;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.profile.BaseProfileLocator;
+import net.sf.taverna.t2.component.profile.ComponentProfileImpl;
+import net.sf.taverna.t2.component.registry.local.LocalComponentRegistryFactory;
+import net.sf.taverna.t2.component.registry.standard.NewComponentRegistryFactory;
+
+import org.springframework.beans.factory.annotation.Required;
+
+/**
+ * @author alanrw
+ * @author dkf
+ */
+public class ComponentUtil implements ComponentFactory {
+	private NewComponentRegistryFactory netLocator;
+	private BaseProfileLocator base;
+	private LocalComponentRegistryFactory fileLocator;
+
+	private final Map<String, Registry> cache = new HashMap<>();
+
+	@Required
+	public void setNetworkLocator(NewComponentRegistryFactory locator) {
+		this.netLocator = locator;
+	}
+
+	@Required
+	public void setFileLocator(LocalComponentRegistryFactory fileLocator) {
+		this.fileLocator = fileLocator;
+	}
+
+	@Required
+	public void setBaseLocator(BaseProfileLocator base) {
+		this.base = base;
+	}
+
+	@Override
+	public Registry getRegistry(URL registryBase) throws ComponentException {
+		Registry registry = cache.get(registryBase.toString());
+		if (registry != null)
+			return registry;
+
+		if (registryBase.getProtocol().startsWith("http")) {
+			if (!netLocator.verifyBase(registryBase))
+				throw new ComponentException(
+						"Unable to establish credentials for " + registryBase);
+			registry = netLocator.getComponentRegistry(registryBase);
+		} else
+			registry = fileLocator.getComponentRegistry(registryBase);
+		cache.put(registryBase.toString(), registry);
+		return registry;
+	}
+
+	@Override
+	public Family getFamily(URL registryBase, String familyName)
+			throws ComponentException {
+		return getRegistry(registryBase).getComponentFamily(familyName);
+	}
+
+	@Override
+	public Component getComponent(URL registryBase, String familyName,
+			String componentName) throws ComponentException {
+		return getRegistry(registryBase).getComponentFamily(familyName)
+				.getComponent(componentName);
+	}
+
+	@Override
+	public Version getVersion(URL registryBase, String familyName,
+			String componentName, Integer componentVersion)
+			throws ComponentException {
+		return getRegistry(registryBase).getComponentFamily(familyName)
+				.getComponent(componentName)
+				.getComponentVersion(componentVersion);
+	}
+
+	@Override
+	public Version getVersion(Version.ID ident) throws ComponentException {
+		return getVersion(ident.getRegistryBase(), ident.getFamilyName(),
+				ident.getComponentName(), ident.getComponentVersion());
+	}
+
+	@Override
+	public Component getComponent(Version.ID ident) throws ComponentException {
+		return getComponent(ident.getRegistryBase(), ident.getFamilyName(),
+				ident.getComponentName());
+	}
+
+	@Override
+	public Profile getProfile(URL url) throws ComponentException {
+		Profile p = new ComponentProfileImpl(url, base);
+		p.getProfileDocument(); // force immediate loading
+		return p;
+	}
+
+	@Override
+	public Profile getBaseProfile() throws ComponentException {
+		return base.getProfile();
+	}
+
+	public BaseProfileLocator getBaseProfileLocator() {
+		return base;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersion.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersion.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersion.java
new file mode 100644
index 0000000..8d6c443
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersion.java
@@ -0,0 +1,65 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.component.registry;
+
+import net.sf.taverna.t2.component.api.Component;
+import net.sf.taverna.t2.component.api.ComponentException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ * 
+ */
+public abstract class ComponentVersion implements
+		net.sf.taverna.t2.component.api.Version {
+	private Integer versionNumber;
+	private String description;
+	private Component component;
+
+	protected ComponentVersion(Component component) {
+		this.component = component;
+	}
+
+	@Override
+	public final synchronized Integer getVersionNumber() {
+		if (versionNumber == null)
+			versionNumber = internalGetVersionNumber();
+		return versionNumber;
+	}
+
+	protected abstract Integer internalGetVersionNumber();
+
+	@Override
+	public final synchronized String getDescription() {
+		if (description == null)
+			description = internalGetDescription();
+
+		return description;
+	}
+
+	protected abstract String internalGetDescription();
+
+	@Override
+	public final synchronized WorkflowBundle getImplementation()
+			throws ComponentException {
+		// Cached in dataflow cache
+		return internalGetImplementation();
+	}
+
+	protected abstract WorkflowBundle internalGetImplementation()
+			throws ComponentException;
+
+	@Override
+	public final Component getComponent() {
+		return component;
+	}
+
+	@Override
+	public ID getID() {
+		Component c = getComponent();
+		return new ComponentVersionIdentification(c.getRegistry()
+				.getRegistryBase(), c.getFamily().getName(), c.getName(),
+				getVersionNumber());
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersionIdentification.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersionIdentification.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersionIdentification.java
new file mode 100644
index 0000000..9115a32
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/ComponentVersionIdentification.java
@@ -0,0 +1,196 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.component.registry;
+
+import java.net.URL;
+
+import net.sf.taverna.t2.component.api.Family;
+import net.sf.taverna.t2.component.api.Registry;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.api.Version.ID;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class ComponentVersionIdentification implements
+		net.sf.taverna.t2.component.api.Version.ID {
+	private static final long serialVersionUID = 1768548650702925916L;
+	private URL registryBase;
+	private String familyName;
+	private String componentName;
+	private Integer componentVersion;
+
+	public ComponentVersionIdentification(URL registryBase, String familyName,
+			String componentName, Integer componentVersion) {
+		super();
+		this.registryBase = registryBase;
+		this.familyName = familyName;
+		this.componentName = componentName;
+		this.componentVersion = componentVersion;
+	}
+
+	public ComponentVersionIdentification(Registry registry, Family family,
+			net.sf.taverna.t2.component.api.Component component, Integer version) {
+		this(registry.getRegistryBase(), family.getName(), component.getName(), version);
+	}
+
+	public ComponentVersionIdentification(Version.ID toBeCopied) {
+		this.registryBase = toBeCopied.getRegistryBase();
+		this.familyName = toBeCopied.getFamilyName();
+		this.componentName = toBeCopied.getComponentName();
+		this.componentVersion = toBeCopied.getComponentVersion();
+	}
+
+	/**
+	 * @return the registryBase
+	 */
+	@Override
+	public URL getRegistryBase() {
+		return registryBase;
+	}
+
+	/**
+	 * @return the familyName
+	 */
+	@Override
+	public String getFamilyName() {
+		return familyName;
+	}
+
+	/**
+	 * @return the componentName
+	 */
+	@Override
+	public String getComponentName() {
+		return componentName;
+	}
+
+	/**
+	 * @return the componentVersion
+	 */
+	@Override
+	public Integer getComponentVersion() {
+		return componentVersion;
+	}
+
+	/**
+	 * @param componentVersion
+	 *            the componentVersion to set
+	 */
+	public void setComponentVersion(Integer componentVersion) {
+		this.componentVersion = componentVersion;
+	}
+
+	/**
+	 * @param registryBase
+	 *            the registryBase to set
+	 */
+	public void setRegistryBase(URL registryBase) {
+		this.registryBase = registryBase;
+	}
+
+	/**
+	 * @param familyName
+	 *            the familyName to set
+	 */
+	public void setFamilyName(String familyName) {
+		this.familyName = familyName;
+	}
+
+	/**
+	 * @param componentName
+	 *            the componentName to set
+	 */
+	public void setComponentName(String componentName) {
+		this.componentName = componentName;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((componentName == null) ? 0 : componentName.hashCode());
+		result = prime
+				* result
+				+ ((componentVersion == null) ? 0 : componentVersion.hashCode());
+		result = prime * result
+				+ ((familyName == null) ? 0 : familyName.hashCode());
+		result = prime * result
+				+ ((registryBase == null) ? 0 : registryBase.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;
+		ComponentVersionIdentification other = (ComponentVersionIdentification) obj;
+		if (componentName == null) {
+			if (other.componentName != null)
+				return false;
+		} else if (!componentName.equals(other.componentName))
+			return false;
+		if (componentVersion == null) {
+			if (other.componentVersion != null)
+				return false;
+		} else if (!componentVersion.equals(other.componentVersion))
+			return false;
+		if (familyName == null) {
+			if (other.familyName != null)
+				return false;
+		} else if (!familyName.equals(other.familyName))
+			return false;
+		if (registryBase == null) {
+			if (other.registryBase != null)
+				return false;
+		} else if (!registryBase.toString().equals(other.registryBase.toString()))
+			return false;
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return getComponentName() + " V. " + getComponentVersion()
+				+ " in family " + getFamilyName() + " on "
+				+ getRegistryBase().toExternalForm();
+	}
+
+	@Override
+	public boolean mostlyEqualTo(ID id) {
+		if (this == id)
+			return true;
+		if (id == null)
+			return false;
+		if (getClass() != id.getClass())
+			return false;
+		ComponentVersionIdentification other = (ComponentVersionIdentification) id;
+		if (componentName == null) {
+			if (other.componentName != null)
+				return false;
+		} else if (!componentName.equals(other.componentName))
+			return false;
+		if (familyName == null) {
+			if (other.familyName != null)
+				return false;
+		} else if (!familyName.equals(other.familyName))
+			return false;
+		if (registryBase == null) {
+			if (other.registryBase != null)
+				return false;
+		} else if (!registryBase.toString().equals(other.registryBase.toString()))
+			return false;
+		return true;
+	}
+
+	@Override
+	public boolean mostlyEqualTo(net.sf.taverna.t2.component.api.Component c) {
+		return mostlyEqualTo(new ComponentVersionIdentification(c.getRegistry(), c.getFamily(), c, 0));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponent.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponent.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponent.java
new file mode 100644
index 0000000..bfb1007
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponent.java
@@ -0,0 +1,134 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.component.registry.local;
+
+import static net.sf.taverna.t2.component.registry.local.LocalComponentRegistry.ENC;
+import static org.apache.commons.io.FileUtils.readFileToString;
+import static org.apache.commons.io.FileUtils.writeStringToFile;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Family;
+import net.sf.taverna.t2.component.api.Registry;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.registry.Component;
+import net.sf.taverna.t2.component.utils.SystemUtils;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ * 
+ */
+class LocalComponent extends Component {
+	static final String COMPONENT_FILENAME = "dataflow.t2flow";
+	private final File componentDir;
+	private final LocalComponentRegistry registry;
+	private final LocalComponentFamily family;
+	private static Logger logger = getLogger(LocalComponent.class);
+	private SystemUtils system;
+
+	public LocalComponent(File componentDir, LocalComponentRegistry registry,
+			LocalComponentFamily family, SystemUtils system) {
+		super(componentDir);
+		this.system = system;
+		this.componentDir = componentDir;
+		this.registry = registry;
+		this.family = family;
+	}
+
+	@Override
+	protected final Version internalAddVersionBasedOn(WorkflowBundle bundle,
+			String revisionComment) throws ComponentException {
+		Integer nextVersionNumber = 1;
+		try {
+			nextVersionNumber = getComponentVersionMap().lastKey() + 1;
+		} catch (NoSuchElementException e) {
+			// This is OK
+		}
+		File newVersionDir = new File(componentDir,
+				nextVersionNumber.toString());
+		newVersionDir.mkdirs();
+		LocalComponentVersion newComponentVersion = new LocalComponentVersion(
+				this, newVersionDir, system);
+		try {
+			system.saveBundle(bundle, new File(newVersionDir,
+					COMPONENT_FILENAME));
+		} catch (Exception e) {
+			throw new ComponentException("Unable to save component version", e);
+		}
+		File revisionCommentFile = new File(newVersionDir, "description");
+		try {
+			writeStringToFile(revisionCommentFile, revisionComment, ENC);
+		} catch (IOException e) {
+			throw new ComponentException("Could not write out description", e);
+		}
+
+		return newComponentVersion;
+	}
+
+	@Override
+	protected final String internalGetName() {
+		return componentDir.getName();
+	}
+
+	@Override
+	protected final void populateComponentVersionMap() {
+		for (File subFile : componentDir.listFiles())
+			try {
+				if (subFile.isDirectory())
+					versionMap.put(Integer.valueOf(subFile.getName()),
+							new LocalComponentVersion(this, subFile, system));
+			} catch (NumberFormatException e) {
+				// Ignore
+			}
+	}
+
+	@Override
+	public int hashCode() {
+		return 31 + ((componentDir == null) ? 0 : componentDir.hashCode());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LocalComponent other = (LocalComponent) obj;
+		if (componentDir == null)
+			return (other.componentDir == null);
+		return componentDir.equals(other.componentDir);
+	}
+
+	@Override
+	protected final String internalGetDescription() {
+		File descriptionFile = new File(componentDir, "description");
+		try {
+			if (descriptionFile.isFile())
+				return readFileToString(descriptionFile);
+		} catch (IOException e) {
+			logger.error("failed to get description from " + descriptionFile, e);
+		}
+		return "";
+	}
+
+	@Override
+	public Registry getRegistry() {
+		return registry;
+	}
+
+	@Override
+	public Family getFamily() {
+		return family;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentFamily.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentFamily.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentFamily.java
new file mode 100644
index 0000000..96a32ab
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentFamily.java
@@ -0,0 +1,141 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.component.registry.local;
+
+import static net.sf.taverna.t2.component.registry.local.LocalComponentRegistry.ENC;
+import static org.apache.commons.io.FileUtils.deleteDirectory;
+import static org.apache.commons.io.FileUtils.readFileToString;
+import static org.apache.commons.io.FileUtils.writeStringToFile;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.File;
+import java.io.IOException;
+
+import net.sf.taverna.t2.component.api.Component;
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.api.profile.Profile;
+import net.sf.taverna.t2.component.registry.ComponentFamily;
+import net.sf.taverna.t2.component.registry.ComponentUtil;
+import net.sf.taverna.t2.component.utils.SystemUtils;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ * 
+ */
+class LocalComponentFamily extends ComponentFamily {
+	private static Logger logger = getLogger(LocalComponentFamily.class);
+	private static final String PROFILE = "profile";
+
+	private final File componentFamilyDir;
+	private SystemUtils system;
+
+	public LocalComponentFamily(LocalComponentRegistry parentRegistry,
+			File componentFamilyDir, ComponentUtil util, SystemUtils system) {
+		super(parentRegistry, util);
+		this.componentFamilyDir = componentFamilyDir;
+		this.system = system;
+	}
+
+	@Override
+	protected final Profile internalGetComponentProfile()
+			throws ComponentException {
+		LocalComponentRegistry parentRegistry = (LocalComponentRegistry) getComponentRegistry();
+		File profileFile = new File(componentFamilyDir, PROFILE);
+		String profileName;
+		try {
+			profileName = readFileToString(profileFile, ENC);
+		} catch (IOException e) {
+			throw new ComponentException("Unable to read profile name", e);
+		}
+		for (Profile p : parentRegistry.getComponentProfiles())
+			if (p.getName().equals(profileName))
+				return p;
+		return null;
+	}
+
+	@Override
+	protected void populateComponentCache() throws ComponentException {
+		for (File subFile : componentFamilyDir.listFiles()) {
+			if (!subFile.isDirectory())
+				continue;
+			LocalComponent newComponent = new LocalComponent(subFile,
+					(LocalComponentRegistry) getComponentRegistry(), this,
+					system);
+			componentCache.put(newComponent.getName(), newComponent);
+		}
+	}
+
+	@Override
+	protected final String internalGetName() {
+		return componentFamilyDir.getName();
+	}
+
+	@Override
+	protected final Version internalCreateComponentBasedOn(
+			String componentName, String description, WorkflowBundle bundle)
+			throws ComponentException {
+		File newSubFile = new File(componentFamilyDir, componentName);
+		if (newSubFile.exists())
+			throw new ComponentException("Component already exists");
+		newSubFile.mkdirs();
+		File descriptionFile = new File(newSubFile, "description");
+		try {
+			writeStringToFile(descriptionFile, description, ENC);
+		} catch (IOException e) {
+			throw new ComponentException("Could not write out description", e);
+		}
+		LocalComponent newComponent = new LocalComponent(newSubFile,
+				(LocalComponentRegistry) getComponentRegistry(), this, system);
+
+		return newComponent.addVersionBasedOn(bundle, "Initial version");
+	}
+
+	@Override
+	public int hashCode() {
+		return 31 + ((componentFamilyDir == null) ? 0 : componentFamilyDir
+				.hashCode());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LocalComponentFamily other = (LocalComponentFamily) obj;
+		if (componentFamilyDir == null)
+			return (other.componentFamilyDir == null);
+		return componentFamilyDir.equals(other.componentFamilyDir);
+	}
+
+	@Override
+	protected final String internalGetDescription() {
+		File descriptionFile = new File(componentFamilyDir, "description");
+		try {
+			if (descriptionFile.isFile())
+				return readFileToString(descriptionFile);
+		} catch (IOException e) {
+			logger.error("failed to get description from " + descriptionFile, e);
+		}
+		return "";
+	}
+
+	@Override
+	protected final void internalRemoveComponent(Component component)
+			throws ComponentException {
+		File componentDir = new File(componentFamilyDir, component.getName());
+		try {
+			deleteDirectory(componentDir);
+		} catch (IOException e) {
+			throw new ComponentException("Unable to delete component", e);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistry.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistry.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistry.java
new file mode 100644
index 0000000..9fcc19a
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistry.java
@@ -0,0 +1,205 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.component.registry.local;
+
+import static org.apache.commons.io.FileUtils.deleteDirectory;
+import static org.apache.commons.io.FileUtils.writeStringToFile;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.Set;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Family;
+import net.sf.taverna.t2.component.api.License;
+import net.sf.taverna.t2.component.api.SharingPolicy;
+import net.sf.taverna.t2.component.api.Version;
+import net.sf.taverna.t2.component.api.profile.Profile;
+import net.sf.taverna.t2.component.profile.ComponentProfileImpl;
+import net.sf.taverna.t2.component.registry.ComponentRegistry;
+import net.sf.taverna.t2.component.registry.ComponentUtil;
+import net.sf.taverna.t2.component.utils.SystemUtils;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A component registry implemented using the local file system. Note that the
+ * components it contains are <i>not</i> shareable.
+ * 
+ * @author alanrw
+ */
+class LocalComponentRegistry extends ComponentRegistry {
+	private static final Logger logger = getLogger(LocalComponentRegistry.class);
+	static final String ENC = "utf-8";
+	private ComponentUtil util;
+	private SystemUtils system;
+	private File baseDir;
+
+	public LocalComponentRegistry(File registryDir, ComponentUtil util,
+			SystemUtils system) throws ComponentException {
+		super(registryDir);
+		baseDir = registryDir;
+		this.util = util;
+		this.system = system;
+	}
+
+	@Override
+	public Family internalCreateComponentFamily(String name,
+			Profile componentProfile, String description, License license,
+			SharingPolicy sharingPolicy) throws ComponentException {
+		File newFamilyDir = new File(getComponentFamiliesDir(), name);
+		newFamilyDir.mkdirs();
+		File profileFile = new File(newFamilyDir, "profile");
+		try {
+			writeStringToFile(profileFile, componentProfile.getName(), ENC);
+		} catch (IOException e) {
+			throw new ComponentException("Could not write out profile", e);
+		}
+		File descriptionFile = new File(newFamilyDir, "description");
+		try {
+			writeStringToFile(descriptionFile, description, ENC);
+		} catch (IOException e) {
+			throw new ComponentException("Could not write out description", e);
+		}
+		return new LocalComponentFamily(this, newFamilyDir, util, system);
+	}
+
+	@Override
+	protected void populateFamilyCache() throws ComponentException {
+		File familiesDir = getComponentFamiliesDir();
+		for (File subFile : familiesDir.listFiles()) {
+			if (!subFile.isDirectory())
+				continue;
+			LocalComponentFamily newFamily = new LocalComponentFamily(this,
+					subFile, util, system);
+			familyCache.put(newFamily.getName(), newFamily);
+		}
+	}
+
+	@Override
+	protected void populateProfileCache() throws ComponentException {
+		File profilesDir = getComponentProfilesDir();
+		for (File subFile : profilesDir.listFiles())
+			if (subFile.isFile() && (!subFile.isHidden())
+					&& subFile.getName().endsWith(".xml"))
+				try {
+					profileCache.add(new LocalComponentProfile(subFile));
+				} catch (MalformedURLException e) {
+					logger.error("Unable to read profile", e);
+				}
+	}
+
+	@Override
+	protected void internalRemoveComponentFamily(Family componentFamily)
+			throws ComponentException {
+		try {
+			deleteDirectory(new File(getComponentFamiliesDir(),
+					componentFamily.getName()));
+		} catch (IOException e) {
+			throw new ComponentException("Unable to delete component family", e);
+		}
+	}
+
+	private File getBaseDir() {
+		baseDir.mkdirs();
+		return baseDir;
+	}
+
+	private File getComponentFamiliesDir() {
+		File componentFamiliesDir = new File(getBaseDir(), "componentFamilies");
+		componentFamiliesDir.mkdirs();
+		return componentFamiliesDir;
+	}
+
+	private File getComponentProfilesDir() {
+		File componentProfilesDir = new File(getBaseDir(), "componentProfiles");
+		componentProfilesDir.mkdirs();
+		return componentProfilesDir;
+	}
+
+	@Override
+	public Profile internalAddComponentProfile(Profile componentProfile,
+			License license, SharingPolicy sharingPolicy)
+			throws ComponentException {
+		String name = componentProfile.getName().replaceAll("\\W+", "")
+				+ ".xml";
+		String inputString = componentProfile.getXML();
+		File outputFile = new File(getComponentProfilesDir(), name);
+		try {
+			writeStringToFile(outputFile, inputString);
+		} catch (IOException e) {
+			throw new ComponentException("Unable to save profile", e);
+		}
+
+		try {
+			return new LocalComponentProfile(outputFile);
+		} catch (MalformedURLException e) {
+			throw new ComponentException("Unable to create profile", e);
+		}
+	}
+
+	@Override
+	public int hashCode() {
+		return 31 + ((baseDir == null) ? 0 : baseDir.hashCode());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LocalComponentRegistry other = (LocalComponentRegistry) obj;
+		if (baseDir == null)
+			return (other.baseDir == null);
+		return baseDir.equals(other.baseDir);
+	}
+
+	@Override
+	public void populatePermissionCache() {
+		return;
+	}
+
+	@Override
+	public void populateLicenseCache() {
+		return;
+	}
+
+	@Override
+	public License getPreferredLicense() {
+		return null;
+	}
+
+	@Override
+	public Set<Version.ID> searchForComponents(String prefixString, String text)
+			throws ComponentException {
+		throw new ComponentException("Local registries cannot be searched yet");
+	}
+
+	@Override
+	public String getRegistryTypeName() {
+		return "File System";
+	}
+
+	class LocalComponentProfile extends ComponentProfileImpl {
+		URI uri;
+
+		LocalComponentProfile(File file) throws MalformedURLException,
+				ComponentException {
+			super(LocalComponentRegistry.this, file.toURI(), util
+					.getBaseProfileLocator());
+			uri = file.toURI();
+		}
+
+		@Override
+		public String toString() {
+			return "Local Component Profile[" + uri + "]";
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistryFactory.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistryFactory.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistryFactory.java
new file mode 100644
index 0000000..c56fe52
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentRegistryFactory.java
@@ -0,0 +1,45 @@
+package net.sf.taverna.t2.component.registry.local;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Registry;
+import net.sf.taverna.t2.component.registry.ComponentUtil;
+import net.sf.taverna.t2.component.utils.SystemUtils;
+
+import org.springframework.beans.factory.annotation.Required;
+
+public class LocalComponentRegistryFactory {
+	private final Map<File, Registry> registries = new HashMap<>();
+	private ComponentUtil util;
+	private SystemUtils system;
+
+	@Required
+	public void setComponentUtil(ComponentUtil util) {
+		this.util = util;
+	}
+
+	@Required
+	public void setSystemUtils(SystemUtils system) {
+		this.system = system;
+	}
+
+	public synchronized Registry getComponentRegistry(File registryDir)
+			throws ComponentException {
+		if (!registries.containsKey(registryDir))
+			registries.put(registryDir, new LocalComponentRegistry(registryDir,
+					util, system));
+		return registries.get(registryDir);
+	}
+
+	public Registry getComponentRegistry(URL componentRegistryBase)
+			throws ComponentException {
+		@SuppressWarnings("deprecation")
+		String hackedPath = URLDecoder.decode(componentRegistryBase.getPath());
+		return getComponentRegistry(new File(hackedPath));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentVersion.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentVersion.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentVersion.java
new file mode 100644
index 0000000..74a7389
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/local/LocalComponentVersion.java
@@ -0,0 +1,94 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.component.registry.local;
+
+import static java.lang.Integer.parseInt;
+import static net.sf.taverna.t2.component.registry.local.LocalComponent.COMPONENT_FILENAME;
+import static org.apache.commons.io.FileUtils.readFileToString;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.registry.ComponentVersion;
+import net.sf.taverna.t2.component.utils.SystemUtils;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ * 
+ */
+class LocalComponentVersion extends ComponentVersion {
+	private static Logger logger = getLogger(LocalComponentVersion.class);
+
+	private final File componentVersionDir;
+	private SystemUtils system;
+
+	protected LocalComponentVersion(LocalComponent component,
+			File componentVersionDir, SystemUtils system) {
+		super(component);
+		this.componentVersionDir = componentVersionDir;
+		this.system = system;
+	}
+
+	@Override
+	protected final String internalGetDescription() {
+		File descriptionFile = new File(componentVersionDir, "description");
+		try {
+			if (descriptionFile.isFile())
+				return readFileToString(descriptionFile);
+		} catch (IOException e) {
+			logger.error("failed to get description from " + descriptionFile, e);
+		}
+		return "";
+	}
+
+	@Override
+	protected final Integer internalGetVersionNumber() {
+		return parseInt(componentVersionDir.getName());
+	}
+
+	@Override
+	protected final WorkflowBundle internalGetImplementation()
+			throws ComponentException {
+		File filename = new File(componentVersionDir, COMPONENT_FILENAME);
+		try {
+			return system.getBundle(filename);
+		} catch (Exception e) {
+			logger.error(
+					"failed to get component realization from " + filename, e);
+			throw new ComponentException("Unable to open dataflow", e);
+		}
+	}
+
+	@Override
+	public int hashCode() {
+		return 31 + ((componentVersionDir == null) ? 0 : componentVersionDir
+				.hashCode());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LocalComponentVersion other = (LocalComponentVersion) obj;
+		if (componentVersionDir == null)
+			return (other.componentVersionDir == null);
+		return componentVersionDir.equals(other.componentVersionDir);
+	}
+
+	@Override
+	public URL getHelpURL() {
+		return null;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/Client.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/Client.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/Client.java
new file mode 100644
index 0000000..61f9997
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/Client.java
@@ -0,0 +1,637 @@
+package net.sf.taverna.t2.component.registry.standard;
+
+import static java.lang.Math.min;
+import static java.lang.String.format;
+import static java.lang.System.getProperty;
+import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
+import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+import static java.net.URLEncoder.encode;
+import static javax.xml.bind.DatatypeConverter.printBase64Binary;
+import static net.sf.taverna.t2.component.registry.ClientVersion.VERSION;
+import static org.apache.commons.io.IOUtils.copy;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.registry.standard.Client.MyExperimentConnector.ServerResponse;
+import net.sf.taverna.t2.component.registry.standard.annotations.Unused;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+class Client {
+	private static final String API_VERIFICATION_RESOURCE = "/component-profiles.xml";
+	private static final String WHOAMI = "/whoami.xml";
+	private static final String PLUGIN_USER_AGENT = "Taverna2-Component-plugin/"
+			+ VERSION + " Java/" + getProperty("java.version");
+	private static final int MESSAGE_TRIM_LENGTH = 512;
+	private static final Logger logger = getLogger(Client.class);
+	private final MyExperimentConnector http;
+	private final URL registryBase;
+	private final JAXBContext jaxbContext;
+	private final CredentialManager cm;
+
+	Client(JAXBContext context, URL repository, CredentialManager cm)
+			throws ComponentException {
+		this(context, repository, true, cm);
+	}
+
+	Client(JAXBContext context, URL repository, boolean tryLogIn,
+			CredentialManager cm) throws ComponentException {
+		this.cm = cm;
+		this.registryBase = repository;
+		this.jaxbContext = context;
+		this.http = new MyExperimentConnector(tryLogIn);
+		logger.info("instantiated client connection engine to " + repository);
+	}
+
+	public boolean verify() {
+		try {
+			String url = url(API_VERIFICATION_RESOURCE);
+			logger.info("API verification: HEAD for " + url);
+			return http.HEAD(url).getCode() == HTTP_OK;
+		} catch (Exception e) {
+			logger.info("failed to connect to " + registryBase, e);
+			return false;
+		}
+	}
+
+	private String url(String uri, String... arguments)
+			throws MalformedURLException, UnsupportedEncodingException {
+		StringBuilder uriBuilder = new StringBuilder(uri);
+		for (String queryElement : arguments) {
+			String[] bits = queryElement.split("=", 2);
+			uriBuilder.append(uriBuilder.indexOf("?") < 0 ? "?" : "&")
+					.append(bits[0]).append('=')
+					.append(encode(bits[1], "UTF-8"));
+		}
+		return new URL(registryBase, uriBuilder.toString()).toString();
+	}
+
+	private Marshaller getMarshaller() throws JAXBException {
+		return jaxbContext.createMarshaller();
+	}
+
+	/**
+	 * Does an HTTP GET against the configured repository.
+	 * 
+	 * @param clazz
+	 *            The JAXB-annotated class that the result is supposed to be
+	 *            instantiated into.
+	 * @param uri
+	 *            The path part of the URI within the repository.
+	 * @param query
+	 *            The strings to put into the query part. Each should be in
+	 *            <tt>key=value</tt> form.
+	 * @return The deserialized response object.
+	 * @throws ComponentException
+	 *             If anything goes wrong.
+	 */
+	public <T> T get(Class<T> clazz, String uri, String... query)
+			throws ComponentException {
+		try {
+			int redirectCounter = 0;
+
+			String url = url(uri, query);
+			ServerResponse response;
+			do {
+				if (redirectCounter++ > 5)
+					throw new ComponentException("too many redirects!");
+				logger.info("GET of " + url);
+				response = http.GET(url);
+				if (response.isFailure())
+					throw new ComponentException(
+							"Unable to perform request (%d): %s",
+							response.getCode(), response.getError());
+			} while ((url = response.getLocation()) != null);
+			return response.getResponse(clazz);
+
+		} catch (ComponentException e) {
+			throw e;
+		} catch (MalformedURLException e) {
+			throw new ComponentException("Problem constructing resource URL", e);
+		} catch (JAXBException e) {
+			throw new ComponentException("Problem when unmarshalling response",
+					e);
+		} catch (Exception e) {
+			throw new ComponentException("Problem when sending request", e);
+		}
+	}
+
+	/**
+	 * Does an HTTP POST against the configured repository.
+	 * 
+	 * @param clazz
+	 *            The JAXB-annotated class that the result is supposed to be
+	 *            instantiated into.
+	 * @param elem
+	 *            The JAXB element to post to the resource.
+	 * @param uri
+	 *            The path part of the URI within the repository.
+	 * @param query
+	 *            The strings to put into the query part. Each should be in
+	 *            <tt>key=value</tt> form.
+	 * @return The deserialized response object.
+	 * @throws ComponentException
+	 *             If anything goes wrong.
+	 */
+	public <T> T post(Class<T> clazz, JAXBElement<?> elem, String uri,
+			String... query) throws ComponentException {
+		try {
+
+			String url = url(uri, query);
+			logger.info("POST to " + url);
+			StringWriter sw = new StringWriter();
+			getMarshaller().marshal(elem, sw);
+			if (logger.isDebugEnabled())
+				logger.info("About to post XML document:\n" + sw);
+			ServerResponse response = http.POST(url, sw);
+			if (response.isFailure())
+				throw new ComponentException(
+						"Unable to perform request (%d): %s",
+						response.getCode(), response.getError());
+			if (response.getLocation() != null)
+				return get(clazz, response.getLocation());
+			return response.getResponse(clazz);
+
+		} catch (ComponentException e) {
+			throw e;
+		} catch (MalformedURLException e) {
+			throw new ComponentException("Problem constructing resource URL", e);
+		} catch (JAXBException e) {
+			throw new ComponentException("Problem when marshalling request", e);
+		} catch (Exception e) {
+			throw new ComponentException("Problem when sending request", e);
+		}
+	}
+
+	/**
+	 * Does an HTTP PUT against the configured repository.
+	 * 
+	 * @param clazz
+	 *            The JAXB-annotated class that the result is supposed to be
+	 *            instantiated into.
+	 * @param elem
+	 *            The JAXB element to post to the resource.
+	 * @param uri
+	 *            The path part of the URI within the repository.
+	 * @param query
+	 *            The strings to put into the query part. Each should be in
+	 *            <tt>key=value</tt> form.
+	 * @return The deserialized response object.
+	 * @throws ComponentException
+	 *             If anything goes wrong.
+	 */
+	@Unused
+	public <T> T put(Class<T> clazz, JAXBElement<?> elem, String uri,
+			String... query) throws ComponentException {
+		try {
+
+			String url = url(uri, query);
+			logger.info("PUT to " + url);
+			StringWriter sw = new StringWriter();
+			getMarshaller().marshal(elem, sw);
+			if (logger.isDebugEnabled())
+				logger.info("About to put XML document:\n" + sw);
+			ServerResponse response = http.PUT(url, sw);
+			if (response.isFailure())
+				throw new ComponentException(
+						"Unable to perform request (%d): %s",
+						response.getCode(), response.getError());
+			if (response.getLocation() != null)
+				return get(clazz, response.getLocation());
+			return response.getResponse(clazz);
+
+		} catch (ComponentException e) {
+			throw e;
+		} catch (MalformedURLException e) {
+			throw new ComponentException("Problem constructing resource URL", e);
+		} catch (JAXBException e) {
+			throw new ComponentException("Problem when marshalling request", e);
+		} catch (Exception e) {
+			throw new ComponentException("Problem when sending request", e);
+		}
+	}
+
+	/**
+	 * Does an HTTP DELETE against the configured repository.
+	 * 
+	 * @param uri
+	 *            The path part of the URI within the repository.
+	 * @param query
+	 *            The strings to put into the query part. Each should be in
+	 *            <tt>key=value</tt> form.
+	 * @throws ComponentException
+	 *             If anything goes wrong.
+	 */
+	public void delete(String uri, String... query) throws ComponentException {
+		ServerResponse response;
+		try {
+
+			String url = url(uri, query);
+			logger.info("DELETE of " + url);
+			response = http.DELETE(url);
+
+		} catch (MalformedURLException e) {
+			throw new ComponentException("Problem constructing resource URL", e);
+		} catch (Exception e) {
+			throw new ComponentException("Unable to perform request", e);
+		}
+		if (response.isFailure())
+			throw new ComponentException("Unable to perform request (%d): %s",
+					response.getCode(), response.getError());
+	}
+
+	private String getCredentials(String urlString, boolean mandatory)
+			throws CMException, UnsupportedEncodingException {
+		final URI serviceURI = URI.create(urlString);
+
+		if (mandatory || cm.hasUsernamePasswordForService(serviceURI)) {
+			UsernamePassword userAndPass = cm.getUsernameAndPasswordForService(
+					serviceURI, true, null);
+			// Check for user didn't log in...
+			if (userAndPass == null)
+				return null;
+			return printBase64Binary(format("%s:%s", userAndPass.getUsername(),
+					userAndPass.getPasswordAsString()).getBytes("UTF-8"));
+		}
+		return null;
+	}
+
+	private void clearCredentials(String baseURL) throws CMException {
+		for (URI uri : cm.getServiceURIsForAllUsernameAndPasswordPairs())
+			if (uri.toString().startsWith(baseURL))
+				cm.deleteUsernameAndPasswordForService(uri);
+	}
+
+	private static Document getDocumentFromStream(InputStream inputStream)
+			throws SAXException, IOException, ParserConfigurationException {
+		DocumentBuilder db = DocumentBuilderFactory.newInstance()
+				.newDocumentBuilder();
+		Document doc;
+		try (InputStream is = new BufferedInputStream(inputStream)) {
+			if (!logger.isDebugEnabled())
+				doc = db.parse(is);
+			else {
+				ByteArrayOutputStream baos = new ByteArrayOutputStream();
+				copy(is, baos);
+				String response = baos.toString("UTF-8");
+				logger.info("response message follows\n"
+						+ response.substring(0,
+								min(MESSAGE_TRIM_LENGTH, response.length())));
+				doc = db.parse(new ByteArrayInputStream(baos.toByteArray()));
+			}
+		}
+		return doc;
+	}
+
+	class MyExperimentConnector {
+		// authentication settings (and the current user)
+		private String authString = null;
+
+		private void tryLogIn(boolean mandatory) throws ComponentException {
+			// check if the stored credentials are valid
+			ServerResponse response = null;
+			try {
+				String userPass = getCredentials(registryBase.toString(),
+						mandatory);
+				if (userPass == null)
+					logger.debug("no credentials available for " + registryBase);
+				else {
+					// set the system to the "logged in" state from INI file properties
+					authString = userPass;
+					response = GET(registryBase.toString() + WHOAMI);
+				}
+			} catch (Exception e) {
+				authString = null;
+				logger.debug("failed when verifying login credentials", e);
+			}
+
+			if (response == null || response.getCode() != HTTP_OK)
+				try {
+					if (response != null)
+						throw new ComponentException("failed to log in: "
+								+ response.getError());
+				} finally {
+					try {
+						authString = null;
+						clearCredentials(registryBase.toString());
+					} catch (Exception e) {
+						logger.debug("failed to clear credentials", e);
+					}
+				}
+			if (authString != null)
+				logger.debug("logged in to repository successfully");
+		}
+
+		MyExperimentConnector(boolean tryLogIn) throws ComponentException {
+			if (tryLogIn)
+				tryLogIn(false);
+		}
+
+		// getter for the current status
+		private boolean isLoggedIn() {
+			return authString != null;
+		}
+
+		private HttpURLConnection connect(String method, String strURL)
+				throws MalformedURLException, IOException {
+			HttpURLConnection conn = (HttpURLConnection) new URL(strURL)
+					.openConnection();
+			conn.setRequestMethod(method);
+			if (method.equals("POST") || method.equals("PUT"))
+				conn.setDoOutput(true);
+			conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+			if (authString != null)
+				conn.setRequestProperty("Authorization", "Basic " + authString);
+			return conn;
+		}
+
+		private boolean elevate() throws ComponentException {
+			tryLogIn(true);
+			return isLoggedIn();
+		}
+
+		/**
+		 * Generic method to execute GET requests to myExperiment server.
+		 * 
+		 * @param url
+		 *            The URL on myExperiment to issue GET request to.
+		 * @return An object containing XML Document with server's response body
+		 *         and a response code. Response body XML document might be null
+		 *         if there was an error or the user wasn't authorised to
+		 *         perform a certain action. Response code will always be set.
+		 * @throws Exception
+		 */
+		public ServerResponse GET(String url) throws Exception {
+			if (!isLoggedIn())
+				logger.warn("not logged in");
+			return receiveServerResponse(connect("GET", url), url, true, false);
+		}
+
+		/**
+		 * Generic method to execute GET requests to myExperiment server.
+		 * 
+		 * @param url
+		 *            The URL on myExperiment to issue GET request to.
+		 * @return An object containing XML Document with server's response body
+		 *         and a response code. Response body XML document might be null
+		 *         if there was an error or the user wasn't authorised to
+		 *         perform a certain action. Response code will always be set.
+		 * @throws Exception
+		 */
+		public ServerResponse HEAD(String url) throws Exception {
+			if (!isLoggedIn())
+				logger.warn("not logged in");
+			return receiveServerResponse(connect("HEAD", url), url, false, true);
+		}
+
+		/**
+		 * Generic method to execute GET requests to myExperiment server.
+		 * 
+		 * @param url
+		 *            The URL on myExperiment to POST to.
+		 * @param xmlDataBody
+		 *            Body of the XML data to be POSTed to strURL.
+		 * @return An object containing XML Document with server's response body
+		 *         and a response code. Response body XML document might be null
+		 *         if there was an error or the user wasn't authorised to
+		 *         perform a certain action. Response code will always be set.
+		 * @throws Exception
+		 */
+		public ServerResponse POST(String url, Object xmlDataBody)
+				throws Exception {
+			if (!isLoggedIn() && !elevate())
+				return null;
+
+			HttpURLConnection conn = connect("POST", url);
+			sendXmlBody(xmlDataBody, conn);
+			return receiveServerResponse(conn, url, false, false);
+		}
+
+		/**
+		 * Generic method to execute DELETE requests to myExperiment server.
+		 * This is only to be called when a user is logged in.
+		 * 
+		 * @param url
+		 *            The URL on myExperiment to direct DELETE request to.
+		 * @return An object containing XML Document with server's response body
+		 *         and a response code. Response body XML document might be null
+		 *         if there was an error or the user wasn't authorised to
+		 *         perform a certain action. Response code will always be set.
+		 * @throws Exception
+		 */
+		public ServerResponse DELETE(String url) throws Exception {
+			if (!isLoggedIn() && !elevate())
+				return null;
+			return receiveServerResponse(connect("DELETE", url), url, true,
+					false);
+		}
+
+		@Unused
+		public ServerResponse PUT(String url, Object xmlDataBody)
+				throws Exception {
+			if (!isLoggedIn() && !elevate())
+				return null;
+
+			HttpURLConnection conn = connect("PUT", url);
+			sendXmlBody(xmlDataBody, conn);
+			return receiveServerResponse(conn, url, false, false);
+		}
+
+		/**
+		 * Factoring out of how to write a body.
+		 * 
+		 * @param xmlDataBody
+		 *            What to write (an {@link InputStream}, a {@link Reader} or
+		 *            an object that will have it's {@link Object#toString()
+		 *            toString()} method called.
+		 * @param conn
+		 *            Where to write it to.
+		 * @throws IOException
+		 *             If anything goes wrong. The <code>conn</code> will be
+		 *             disconnected in the case of a failure.
+		 */
+		private void sendXmlBody(Object xmlDataBody, HttpURLConnection conn)
+				throws IOException {
+			try {
+				conn.setRequestProperty("Content-Type", "application/xml");
+				if (xmlDataBody instanceof InputStream)
+					copy((InputStream) xmlDataBody, conn.getOutputStream());
+				else
+					try (OutputStreamWriter out = new OutputStreamWriter(
+							conn.getOutputStream())) {
+						if (xmlDataBody instanceof Reader)
+							copy((Reader) xmlDataBody, out);
+						else
+							out.write(xmlDataBody.toString());
+					}
+			} catch (IOException e) {
+				conn.disconnect();
+				throw e;
+			}
+		}
+
+		/**
+		 * A common method for retrieving myExperiment server's response for all
+		 * types of requests.
+		 * 
+		 * @param conn
+		 *            Instance of the established URL connection to poll for
+		 *            server's response.
+		 * @param url
+		 *            The URL on myExperiment with which the connection is
+		 *            established.
+		 * @param isGETrequest
+		 *            Flag for identifying type of the request. True when the
+		 *            current connection executes GET request; false when it
+		 *            executes a POST request.
+		 * @return An object containing XML Document with server's response body
+		 *         and a response code. Response body XML document might be null
+		 *         if there was an error or the user wasn't authorised to
+		 *         perform a certain action. Response code will always be set.
+		 */
+		private ServerResponse receiveServerResponse(HttpURLConnection conn,
+				String url, boolean isGETrequest, boolean isHEADrequest)
+				throws Exception {
+			try {
+				switch (conn.getResponseCode()) {
+				case HTTP_OK:
+					/*
+					 * data retrieval was successful - parse the response XML
+					 * and return it along with response code
+					 */
+					if (isHEADrequest)
+						return new ServerResponse(conn.getResponseCode(), null,
+								null);
+					return new ServerResponse(conn.getResponseCode(), null,
+							getDocumentFromStream(conn.getInputStream()));
+				case HTTP_NO_CONTENT:
+					return new ServerResponse(HTTP_OK, null, null);
+
+				case HttpURLConnection.HTTP_CREATED:
+				case HttpURLConnection.HTTP_MOVED_PERM:
+				case HttpURLConnection.HTTP_MOVED_TEMP:
+				case HttpURLConnection.HTTP_SEE_OTHER:
+				case HttpURLConnection.HTTP_USE_PROXY:
+					return new ServerResponse(conn.getResponseCode(),
+							conn.getHeaderField("Location"), null);
+
+				case HTTP_BAD_REQUEST:
+				case HTTP_FORBIDDEN:
+					/*
+					 * this was a bad XML request - need full XML response to
+					 * retrieve the error message from it; Java throws
+					 * IOException if getInputStream() is used when non HTTP_OK
+					 * response code was received - hence can use
+					 * getErrorStream() straight away to fetch the error
+					 * document
+					 */
+					return new ServerResponse(conn.getResponseCode(), null,
+							getDocumentFromStream(conn.getErrorStream()));
+
+				case HTTP_UNAUTHORIZED:
+					// this content is not authorised for current user
+					logger.warn("non-authorised request to " + url + "\n"
+							+ IOUtils.toString(conn.getErrorStream()));
+					return new ServerResponse(conn.getResponseCode(), null,
+							null);
+
+				case HTTP_NOT_FOUND:
+					if (isHEADrequest)
+						return new ServerResponse(conn.getResponseCode(), null,
+								null);
+					throw new FileNotFoundException("no such resource: " + url);
+				default:
+					// unexpected response code - raise an exception
+					throw new IOException(
+							format("Received unexpected HTTP response code (%d) while %s %s",
+									conn.getResponseCode(),
+									(isGETrequest ? "fetching data at"
+											: "posting data to"), url));
+				}
+			} finally {
+				conn.disconnect();
+			}
+		}
+
+		class ServerResponse {
+			private final int responseCode;
+			private final String responseLocation;
+			private final Document responseBody;
+
+			ServerResponse(int responseCode, String responseLocation,
+					Document responseBody) {
+				this.responseCode = responseCode;
+				this.responseBody = responseBody;
+				this.responseLocation = responseLocation;
+			}
+
+			public int getCode() {
+				return responseCode;
+			}
+
+			public boolean isFailure() {
+				return responseCode >= HTTP_BAD_REQUEST;
+			}
+
+			public String getLocation() {
+				return responseLocation;
+			}
+
+			public <T> T getResponse(Class<T> clazz) throws JAXBException {
+				return jaxbContext.createUnmarshaller()
+						.unmarshal(responseBody.getDocumentElement(), clazz)
+						.getValue();
+			}
+
+			/**
+			 * Returns contents of the "reason" field of the error message.
+			 */
+			public String getError() {
+				if (responseBody != null) {
+					Node reasonElement = responseBody.getDocumentElement()
+							.getElementsByTagName("reason").item(0);
+					if (reasonElement != null) {
+						String reason = reasonElement.getTextContent();
+						if (!reason.isEmpty())
+							return reason;
+					}
+				}
+				return format("unknown reason (%d)", responseCode);
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/e15f9c85/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/NewComponent.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/NewComponent.java b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/NewComponent.java
new file mode 100644
index 0000000..a3f9536
--- /dev/null
+++ b/taverna-component-activity/src/main/java/net/sf/taverna/t2/component/registry/standard/NewComponent.java
@@ -0,0 +1,220 @@
+package net.sf.taverna.t2.component.registry.standard;
+
+import static java.lang.String.format;
+import static net.sf.taverna.t2.component.registry.standard.NewComponentRegistry.logger;
+import static net.sf.taverna.t2.component.registry.standard.Policy.getPolicy;
+import static net.sf.taverna.t2.component.utils.SystemUtils.getElementString;
+import static net.sf.taverna.t2.component.utils.SystemUtils.getValue;
+
+import java.lang.ref.SoftReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.IllegalFormatException;
+
+import net.sf.taverna.t2.component.api.ComponentException;
+import net.sf.taverna.t2.component.api.Family;
+import net.sf.taverna.t2.component.api.License;
+import net.sf.taverna.t2.component.api.Registry;
+import net.sf.taverna.t2.component.api.SharingPolicy;
+import net.sf.taverna.t2.component.registry.Component;
+import net.sf.taverna.t2.component.registry.ComponentVersion;
+import net.sf.taverna.t2.component.registry.api.ComponentType;
+import net.sf.taverna.t2.component.registry.api.Description;
+import net.sf.taverna.t2.component.utils.SystemUtils;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+class NewComponent extends Component {
+	static final String ELEMENTS = "title,description";
+	static final String EXTRA = "license-type,permissions";
+
+	private final SystemUtils system;
+	final NewComponentRegistry registry;
+	final NewComponentFamily family;
+	private final String id;
+	private final String title;
+	private final String description;
+	private final String resource;
+
+	NewComponent(NewComponentRegistry registry, NewComponentFamily family,
+			Description cd, SystemUtils system) throws ComponentException {
+		super(cd.getUri());
+		this.system = system;
+		this.registry = registry;
+		this.family = family;
+		id = cd.getId().trim();
+		title = getElementString(cd, "title");
+		description = getElementString(cd, "description");
+		resource = cd.getResource();
+	}
+
+	NewComponent(NewComponentRegistry registry, NewComponentFamily family,
+			ComponentType ct, SystemUtils system) {
+		super(ct.getUri());
+		this.system = system;
+		this.registry = registry;
+		this.family = family;
+		id = ct.getId().trim();
+		title = ct.getTitle().trim();
+		description = ct.getDescription().trim();
+		resource = ct.getResource();
+	}
+
+	public ComponentType getCurrent(String elements) throws ComponentException {
+		return registry.getComponentById(id, null, elements);
+	}
+
+	@Override
+	protected String internalGetName() {
+		return title;
+	}
+
+	@Override
+	protected String internalGetDescription() {
+		return description;
+	}
+
+	@Override
+	protected void populateComponentVersionMap() {
+		try {
+			for (Description d : getCurrent("versions").getVersions()
+					.getWorkflow())
+				versionMap.put(d.getVersion(), new Version(d.getVersion(),
+						getValue(d)));
+		} catch (ComponentException e) {
+			logger.warn("failed to retrieve version list: " + e.getMessage());
+		}
+	}
+
+	@Override
+	protected Version internalAddVersionBasedOn(WorkflowBundle bundle,
+			String revisionComment) throws ComponentException {
+		/*
+		 * Only fetch the license and sharing policy now; user might have
+		 * updated them on the site and we want to duplicate.
+		 */
+		ComponentType ct = getCurrent(EXTRA);
+		License license = registry.getLicense(getValue(ct.getLicenseType())
+				.trim());
+		SharingPolicy sharingPolicy = getPolicy(ct.getPermissions());
+
+		return (Version) registry.createComponentVersionFrom(this, title,
+				revisionComment, bundle, license, sharingPolicy);
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof NewComponent) {
+			NewComponent other = (NewComponent) o;
+			return registry.equals(other.registry) && id.equals(other.id);
+		}
+		return false;
+	}
+
+	public String getResourceLocation() {
+		return resource;
+	}
+
+	private static final int BASEHASH = NewComponent.class.hashCode();
+
+	@Override
+	public int hashCode() {
+		return BASEHASH ^ registry.hashCode() ^ id.hashCode();
+	}
+
+	class Version extends ComponentVersion {
+		private int version;
+		private String description;
+		private String location;
+		private SoftReference<WorkflowBundle> bundleRef;
+
+		private static final String htmlPageTemplate = "%1$s/workflows/%2$s/versions/%3$s.html";
+
+		protected Version(Integer version, String description, WorkflowBundle bundle) {
+			super(NewComponent.this);
+			this.version = version;
+			this.description = description;
+			this.bundleRef = new SoftReference<>(bundle);
+		}
+
+		protected Version(Integer version, String description) {
+			super(NewComponent.this);
+			this.version = version;
+			this.description = description;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof Version) {
+				Version other = (Version) o;
+				return version == other.version
+						&& NewComponent.this.equals(other.getComponent());
+			}
+			return false;
+		}
+
+		@Override
+		public int hashCode() {
+			return NewComponent.this.hashCode() ^ (version << 16)
+					^ (version >> 16);
+		}
+
+		@Override
+		protected Integer internalGetVersionNumber() {
+			return version;
+		}
+
+		@Override
+		protected String internalGetDescription() {
+			return description;
+		}
+
+		private String getLocationUri() throws ComponentException {
+			if (location == null)
+				location = registry.getComponentById(id, version,
+						"content-uri").getContentUri();
+			return location;
+		}
+
+		@Override
+		protected synchronized WorkflowBundle internalGetImplementation()
+				throws ComponentException {
+			if (bundleRef == null || bundleRef.get() == null) {
+				String contentUri = getLocationUri();
+				try {
+					WorkflowBundle result = system.getBundleFromUri(contentUri
+							+ "?version=" + version);
+					bundleRef = new SoftReference<>(result);
+					return result;
+				} catch (Exception e) {
+					throw new ComponentException("Unable to open dataflow", e);
+				}
+			}
+			return bundleRef.get();
+		}
+
+		@Override
+		public URL getHelpURL() {
+			try {
+				return new URL(format(htmlPageTemplate,
+						registry.getRegistryBaseString(), getId(), version));
+			} catch (IllegalFormatException | MalformedURLException e) {
+				logger.error(e);
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public Registry getRegistry() {
+		return registry;
+	}
+
+	@Override
+	public Family getFamily() {
+		return family;
+	}
+}