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:06:22 UTC

[2/5] incubator-taverna-osgi git commit: Revert "temporarily empty repository"

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-osgi-schemas/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-osgi-schemas/pom.xml b/taverna-osgi-schemas/pom.xml
new file mode 100644
index 0000000..853ff16
--- /dev/null
+++ b/taverna-osgi-schemas/pom.xml
@@ -0,0 +1,27 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.osgi</groupId>
+		<artifactId>taverna-osgi</artifactId>
+		<version>0.2.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-osgi-schemas</artifactId>
+	<name>Apache Taverna OSGi XML Schemas</name>
+	<packaging>bundle</packaging>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.jvnet.jaxb2.maven2</groupId>
+				<artifactId>maven-jaxb2-plugin</artifactId>
+				<executions>
+					<execution>
+						<goals>
+							<goal>generate</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-osgi-schemas/src/main/resources/ApplicationPlugin.xsd
----------------------------------------------------------------------
diff --git a/taverna-osgi-schemas/src/main/resources/ApplicationPlugin.xsd b/taverna-osgi-schemas/src/main/resources/ApplicationPlugin.xsd
new file mode 100644
index 0000000..180c8c2
--- /dev/null
+++ b/taverna-osgi-schemas/src/main/resources/ApplicationPlugin.xsd
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<xs:schema xmlns="http://ns.taverna.org.uk/2013/application/plugin"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:versions="http://ns.taverna.org.uk/2013/application/versions"
+	xmlns:ap="http://ns.taverna.org.uk/2013/application/profile"
+	xmlns:plugin="http://ns.taverna.org.uk/2013/application/plugin"
+	xmlns:jxb="http://java.sun.com/xml/ns/jaxb" jxb:version="1.0"
+	targetNamespace="http://ns.taverna.org.uk/2013/application/plugin"
+	elementFormDefault="qualified">
+
+	<xs:annotation>
+		<xs:appinfo>
+			<jxb:schemaBindings>
+				<jxb:package name="uk.org.taverna.commons.plugin.xml.jaxb" />
+			</jxb:schemaBindings>
+		</xs:appinfo>
+	</xs:annotation>
+
+	<xs:import namespace="http://ns.taverna.org.uk/2013/application/versions"
+		schemaLocation="ApplicationVersions.xsd" />
+
+	<xs:import namespace="http://ns.taverna.org.uk/2013/application/profile"
+		schemaLocation="ApplicationProfile.xsd" />
+
+	<xs:element name="pluginInfo">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="id" type="xs:string" />
+				<xs:element name="name" type="xs:string" />
+				<xs:element name="description" type="xs:string" />
+				<xs:element name="organization" type="xs:string" />
+				<xs:element name="version" type="versions:semanticVersion" />
+				<xs:element name="bundle" type="ap:bundleInfo" minOccurs="0" maxOccurs="unbounded" />
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:complexType name="pluginVersions">
+		<xs:complexContent>
+			<xs:extension base="versions:versions">
+				<xs:sequence>
+					<xs:element name="organization" type="xs:string" />
+					<xs:element name="pluginSiteUrl" type="xs:string" minOccurs="0" />
+				</xs:sequence>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+
+	<xs:element name="plugins">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="plugin" type="pluginVersions"
+					minOccurs="1" maxOccurs="unbounded">
+					<xs:unique name="versionUnique">
+						<xs:selector
+							xpath="versions:previousVersion/versions:version|versions:latestVersion/versions:version" />
+						<xs:field xpath="." />
+					</xs:unique>
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+		<xs:unique name="versionsIdUnique">
+			<xs:selector xpath="plugin:versions/versions:id" />
+			<xs:field xpath="." />
+		</xs:unique>
+	</xs:element>
+
+</xs:schema>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-osgi-schemas/src/main/resources/ApplicationProfile.xsd
----------------------------------------------------------------------
diff --git a/taverna-osgi-schemas/src/main/resources/ApplicationProfile.xsd b/taverna-osgi-schemas/src/main/resources/ApplicationProfile.xsd
new file mode 100644
index 0000000..78073a2
--- /dev/null
+++ b/taverna-osgi-schemas/src/main/resources/ApplicationProfile.xsd
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<xs:schema xmlns="http://ns.taverna.org.uk/2013/application/profile"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:versions="http://ns.taverna.org.uk/2013/application/versions"
+	xmlns:ap="http://ns.taverna.org.uk/2013/application/profile" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
+	jxb:version="1.0" targetNamespace="http://ns.taverna.org.uk/2013/application/profile"
+	elementFormDefault="qualified">
+
+	<xs:annotation>
+		<xs:appinfo>
+			<jxb:schemaBindings>
+				<jxb:package name="uk.org.taverna.commons.profile.xml.jaxb" />
+			</jxb:schemaBindings>
+		</xs:appinfo>
+	</xs:annotation>
+
+	<xs:import namespace="http://ns.taverna.org.uk/2013/application/versions"
+		schemaLocation="ApplicationVersions.xsd" />
+
+	<xs:complexType name="frameworkConfiguration">
+		<xs:attribute name="name" type="xs:string" use="required" />
+		<xs:attribute name="value" type="xs:string" use="required" />
+	</xs:complexType>
+
+	<xs:complexType name="bundleInfo">
+		<xs:sequence>
+			<xs:element name="fileName" type="xs:anyURI" minOccurs="0" />
+		</xs:sequence>
+		<xs:attribute name="symbolicName" type="xs:string" use="required" />
+		<xs:attribute name="version" type="versions:semanticVersion"
+			use="required" />
+	</xs:complexType>
+
+	<xs:complexType name="updates">
+		<xs:sequence>
+			<xs:element name="updateSite" type="xs:anyURI" />
+			<xs:element name="updatesFile" type="xs:string" />
+			<xs:element name="libDirectory" type="xs:string" />
+			<xs:element name="pluginSite" type="xs:string" />
+			<xs:element name="pluginsFile" type="xs:string" />
+		</xs:sequence>
+	</xs:complexType>
+
+	<xs:element name="applicationProfile">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="id" type="xs:string" />
+				<xs:element name="name" type="xs:string" />
+				<xs:element name="version" type="versions:semanticVersion" />
+				<xs:element name="updates" type="updates" />
+				<xs:element name="frameworkConfiguration" type="frameworkConfiguration"
+					minOccurs="0" maxOccurs="unbounded" />
+				<xs:element name="bundle" type="bundleInfo" minOccurs="0" maxOccurs="unbounded" />
+			</xs:sequence>
+		</xs:complexType>
+		<xs:unique name="symbolicNameUnique">
+			<xs:selector xpath="ap:bundles/ap:bundle"></xs:selector>
+			<xs:field xpath="@symbolicName"></xs:field>
+		</xs:unique>
+	</xs:element>
+
+	<xs:element name="updateSite">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="versions" type="versions:versions">
+					<xs:unique name="versionUnique">
+						<xs:selector
+							xpath="versions:previousVersion/versions:version|versions:latestVersion/versions:version" />
+						<xs:field xpath="." />
+					</xs:unique>
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+
+</xs:schema>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-osgi-schemas/src/main/resources/ApplicationVersions.xsd
----------------------------------------------------------------------
diff --git a/taverna-osgi-schemas/src/main/resources/ApplicationVersions.xsd b/taverna-osgi-schemas/src/main/resources/ApplicationVersions.xsd
new file mode 100644
index 0000000..706423c
--- /dev/null
+++ b/taverna-osgi-schemas/src/main/resources/ApplicationVersions.xsd
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<xs:schema xmlns="http://ns.taverna.org.uk/2013/application/versions"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:versions="http://ns.taverna.org.uk/2013/application/versions"
+	xmlns:jxb="http://java.sun.com/xml/ns/jaxb" jxb:version="1.0"
+	targetNamespace="http://ns.taverna.org.uk/2013/application/versions"
+	elementFormDefault="qualified">
+
+	<xs:annotation>
+		<xs:appinfo>
+			<jxb:schemaBindings>
+				<jxb:package name="uk.org.taverna.commons.versions.xml.jaxb" />
+			</jxb:schemaBindings>
+		</xs:appinfo>
+	</xs:annotation>
+
+	<xs:simpleType name="semanticVersion">
+		<xs:annotation>
+			<xs:documentation>A semantic version.</xs:documentation>
+		</xs:annotation>
+		<xs:restriction base="xs:string">
+			<xs:pattern value="[0-9]+\.[0-9]+(\.[0-9]+(\.[0-9A-Za-z_-]+)?)?" />
+		</xs:restriction>
+	</xs:simpleType>
+
+	<xs:complexType name="version">
+		<xs:sequence>
+			<xs:element name="version" type="semanticVersion" />
+			<xs:element name="file" type="xs:string" />
+		</xs:sequence>
+	</xs:complexType>
+
+	<xs:complexType name="versions">
+		<xs:sequence>
+			<xs:element name="id" type="xs:string" />
+			<xs:element name="name" type="xs:string" />
+			<xs:element name="description" type="xs:string" />
+			<xs:element name="latestVersion" type="version" minOccurs="1"
+				maxOccurs="1" />
+			<xs:element name="previousVersion" type="version"
+				minOccurs="0" maxOccurs="unbounded" />
+		</xs:sequence>
+	</xs:complexType>
+
+</xs:schema>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-api/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-plugin-api/pom.xml b/taverna-plugin-api/pom.xml
new file mode 100644
index 0000000..1e7ed02
--- /dev/null
+++ b/taverna-plugin-api/pom.xml
@@ -0,0 +1,29 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.osgi</groupId>
+		<artifactId>taverna-osgi</artifactId>
+		<version>0.2.0-incubating-SNAPSHOT</version>
+	</parent>
+	<packaging>bundle</packaging>
+	<artifactId>taverna-plugin-api</artifactId>
+	<name>Apache Taverna Plugin API</name>
+	<dependencies>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-osgi-schemas</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+	</dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/Plugin.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/Plugin.java b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/Plugin.java
new file mode 100644
index 0000000..e9ea0a4
--- /dev/null
+++ b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/Plugin.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin;
+
+import java.io.File;
+import java.util.Set;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+
+/**
+ * A plugin adds functionality to the application by providing implementations of application
+ * services.
+ *
+ * @author David Withers
+ */
+public interface Plugin {
+
+	public static enum State {
+		UNINSTALLED, INSTALLED, STARTED, STOPPED
+	}
+
+	public String getId();
+
+	public String getName();
+
+	public String getDescription();
+
+	public String getOrganization();
+
+	public Version getVersion();
+
+	/**
+	 * Returns the state of the plugin.
+	 *
+	 * @return the state of the plugin
+	 */
+	public State getState();
+
+	/**
+	 * Starts the plugin and sets the state to STARTED.
+	 * <p>
+	 * If the plugin state is STARTED this method will have no effect.
+	 * <p>
+	 * All plugin bundles are not currently started will be started.
+	 *
+	 * @throws PluginException
+	 *             if the plugin state is UNINSTALLED or any of the plugin bundles cannot be started
+	 */
+	public void start() throws PluginException;
+
+	/**
+	 * Stops the plugin and sets the state to STOPPED.
+	 * <p>
+	 * If the plugin state is not STARTED this method will have no effect.
+	 * <p>
+	 * All plugin bundles not used elsewhere will be stopped.
+	 *
+	 * @throws PluginException
+	 *             if any of the plugin bundles cannot be stopped
+	 */
+	public void stop() throws PluginException;
+
+	public void uninstall() throws PluginException;
+
+	public File getFile();
+
+	public Set<Bundle> getBundles();
+
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginManager.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginManager.java b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginManager.java
new file mode 100644
index 0000000..e9b2f83
--- /dev/null
+++ b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginManager.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin;
+
+import java.io.File;
+import java.util.List;
+
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * Manages installing plugins and checking for plugin updates.
+ *
+ * @author David Withers
+ */
+public interface PluginManager {
+
+	public static final String EVENT_TOPIC_ROOT = "uk/org/taverna/commons/plugin/PluginManager/";
+	public static final String PLUGIN_INSTALLED = EVENT_TOPIC_ROOT + "PLUGIN_INSTALLED";
+	public static final String PLUGIN_UNINSTALLED = EVENT_TOPIC_ROOT + "PLUGIN_UNINSTALLED";
+	public static final String UPDATES_AVAILABLE = EVENT_TOPIC_ROOT + "UPDATES_AVAILABLE";
+
+	/**
+	 * Loads plugins from the system and user plugin directories.
+	 * <p>
+	 * If the plugins are not already installed they will be installed and started.
+	 *
+	 * @throws PluginException
+	 */
+	public void loadPlugins() throws PluginException;
+
+	/**
+	 * Check if there are new versions of installed plugins available.
+	 * <p>
+	 * If updates are available and event with topic {@link UPDATES_AVAILABLE} will be posted.
+	 *
+	 * @throws PluginException
+	 */
+	public void checkForUpdates() throws PluginException;
+
+	/**
+	 * Returns updated versions of installed plugins.
+	 * <p>
+	 * Only plugins that the user has permission to update are returned.
+	 *
+	 * @return
+	 */
+	public List<PluginVersions> getPluginUpdates() throws PluginException;
+
+	/**
+	 * Returns new plugins available from all plugin sites.
+	 *
+	 * @return new plugins available from all plugin sites.
+	 * @throws PluginException
+	 */
+	public List<PluginVersions> getAvailablePlugins() throws PluginException;
+
+	/**
+	 * Returns all the installed plugins.
+	 *
+	 * @return
+	 * @throws PluginException
+	 */
+	public List<Plugin> getInstalledPlugins() throws PluginException;
+
+	/**
+	 * Installs a plugin from a plugin file.
+	 *
+	 * @param pluginFile
+	 *            the file to install the plugin from
+	 * @return the installed plugin
+	 * @throws PluginException
+	 */
+	public Plugin installPlugin(File pluginFile) throws PluginException;
+
+	/**
+	 * Installs a plugin from an update site.
+	 *
+	 * @param pluginSiteURL
+	 * @param pluginFile
+	 * @return
+	 * @throws PluginException
+	 */
+	public Plugin installPlugin(String pluginSiteURL, String pluginFile) throws PluginException;
+
+	public Plugin updatePlugin(PluginVersions pluginVersions) throws PluginException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSite.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSite.java b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSite.java
new file mode 100644
index 0000000..54f8e3b
--- /dev/null
+++ b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSite.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin;
+
+/**
+ * A plugin site specifies the location of a site that contains plugins.
+ * <p>
+ * There are two types of plugin site:
+ * <dl>
+ * <dt>SYSTEM</dt>
+ * <dd>plugin sites specified by the application profile</dd>
+ * <dt>USER</dt>
+ * <dd>plugin sites that can be added and removed by the user</dd>
+ * </dl>
+ *
+ * @author David Withers
+ */
+public interface PluginSite {
+
+	public static enum PluginSiteType {
+		SYSTEM, USER
+	};
+
+	/**
+	 * Returns the name of the plugin site.
+	 *
+	 * @return the name of the plugin site
+	 */
+	public String getName();
+
+	/**
+	 * Returns the URL of the plugin site.
+	 *
+	 * @return the URL of the plugin site
+	 */
+	public String getUrl();
+
+	/**
+	 * Returns the type of the plugin site.
+	 * <p>
+	 * The type is either {@code SYSTEM} or {@code USER}
+	 *
+	 * @return the type of the plugin site
+	 */
+	public PluginSiteType getType();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSiteManager.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSiteManager.java b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSiteManager.java
new file mode 100644
index 0000000..8cd32b5
--- /dev/null
+++ b/taverna-plugin-api/src/main/java/uk/org/taverna/commons/plugin/PluginSiteManager.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin;
+
+import java.net.URL;
+import java.util.List;
+
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * Manages plugin sites.
+ *
+ * @author David Withers
+ */
+public interface PluginSiteManager {
+
+	/**
+	 * Returns all the managed plugin sites.
+	 * <p>
+	 * If there are no plugin sites an empty list is returned.
+	 *
+	 * @return all the managed plugin sites
+	 * @throws PluginException
+	 */
+	public List<PluginSite> getPluginSites();
+
+	/**
+	 * Contacts the plugin site at the specified URL and return a new plugin site.
+	 *
+	 * @param pluginSiteURL the plugin site URL
+	 * @throws PluginException if there is a problem contacting the plugin site
+	 */
+	public PluginSite createPluginSite(URL pluginSiteURL) throws PluginException;
+
+	/**
+	 * Adds a plugin site.
+	 * <p>
+	 * If the plugin site already exists this method does nothing.
+	 *
+	 * @param pluginSite the plugin site to add
+	 * @throws PluginException
+	 */
+	public void addPluginSite(PluginSite pluginSite) throws PluginException;
+
+	/**
+	 * Removes a plugin site.
+	 * <p>
+	 * If the plugin site does not exist this method does nothing.
+	 *
+	 * @param pluginSite the plugin site to remove
+	 * @throws PluginException
+	 */
+	public void removePluginSite(PluginSite pluginSite) throws PluginException;
+
+	/**
+	 * Returns all the plugins available at the specified plugin site.
+	 * <p>
+	 * If no plugins are available an empty list is returned.
+	 *
+	 * @param pluginSite
+	 *            the plugin site to contact
+	 * @return all the plugins available at the specified plugin site
+	 * @throws PluginException
+	 *             if there is a plroblem contacting the plugin site
+	 */
+	public List<PluginVersions> getPlugins(PluginSite pluginSite) throws PluginException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/pom.xml b/taverna-plugin-impl/pom.xml
new file mode 100644
index 0000000..488e777
--- /dev/null
+++ b/taverna-plugin-impl/pom.xml
@@ -0,0 +1,81 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.osgi</groupId>
+		<artifactId>taverna-osgi</artifactId>
+		<version>0.2.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-plugin-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Apache Taverna Plugin Implementation</name>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<configuration>
+					<instructions>
+						<Import-Package>uk.org.taverna.commons.plugin;provide:=true,*</Import-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-plugin-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-download-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-osgi-schemas</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<version>${log4j.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>${commons.io.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>${junit.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-core</artifactId>
+			<version>${mockito.version}</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginDirectoryWatcher.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginDirectoryWatcher.java b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginDirectoryWatcher.java
new file mode 100644
index 0000000..9419d4e
--- /dev/null
+++ b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginDirectoryWatcher.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import java.io.File;
+
+import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
+import org.apache.commons.io.monitor.FileAlterationMonitor;
+import org.apache.commons.io.monitor.FileAlterationObserver;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+
+/**
+ * Watches a plugin directory and adds or removes plugins when plugin files are added or removed
+ * from the directory.
+ * 
+ * @author David Withers
+ */
+public class PluginDirectoryWatcher extends FileAlterationListenerAdaptor {
+
+	private static final Logger logger = Logger.getLogger(PluginDirectoryWatcher.class);
+
+	private final PluginManagerImpl pluginManager;
+	private final File directory;
+
+	private FileAlterationMonitor monitor;
+
+	public PluginDirectoryWatcher(PluginManagerImpl pluginManager, File directory) {
+		this.pluginManager = pluginManager;
+		this.directory = directory;
+		FileAlterationObserver observer = new FileAlterationObserver(directory);
+		observer.addListener(this);
+		monitor = new FileAlterationMonitor();
+		monitor.addObserver(observer);
+	}
+
+	/**
+	 * Starts watching the plugin directory.
+	 * 
+	 * @throws PluginException
+	 */
+	public void start() throws PluginException {
+		try {
+			monitor.start();
+		} catch (Exception e) {
+			throw new PluginException(String.format("Error starting watch on %1$s.",
+					directory.getAbsolutePath()), e);
+		}
+	}
+
+	/**
+	 * Stops watching the plugin directory.
+	 * 
+	 * @throws PluginException
+	 */
+	public void stop() throws PluginException {
+		try {
+			monitor.stop();
+		} catch (Exception e) {
+			throw new PluginException(String.format("Error stopping watch on %1$s.",
+					directory.getAbsolutePath()), e);
+		}
+	}
+
+	@Override
+	public void onFileCreate(File file) {
+		try {
+			Plugin plugin = pluginManager.installPlugin(file);
+			plugin.start();
+		} catch (PluginException e) {
+			logger.warn("Error loading plugin file " + file, e);
+		}
+	}
+
+	@Override
+	public void onFileChange(File file) {
+		onFileDelete(file);
+		onFileCreate(file);
+	}
+
+	@Override
+	public void onFileDelete(File file) {
+		pluginManager.uninstallPlugin(file);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginImpl.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginImpl.java b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginImpl.java
new file mode 100644
index 0000000..5f31472
--- /dev/null
+++ b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginImpl.java
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import static uk.org.taverna.commons.plugin.Plugin.State.STARTED;
+import static uk.org.taverna.commons.plugin.Plugin.State.STOPPED;
+import static uk.org.taverna.commons.plugin.Plugin.State.UNINSTALLED;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginInfo;
+
+/**
+ * @author David Withers
+ */
+public class PluginImpl implements Plugin {
+
+	private static final Logger logger = Logger.getLogger(PluginImpl.class);
+
+	private PluginManagerImpl pluginManager;
+
+	private State state = UNINSTALLED;
+
+	private File file;
+	private String id, name, description, organization;
+	private Version version;
+	private Set<Bundle> bundles = new HashSet<Bundle>();
+
+	public PluginImpl(PluginManagerImpl pluginManager, File file, PluginInfo pluginInfo) {
+		this.pluginManager = pluginManager;
+		this.file = file;
+		id = pluginInfo.getId();
+		name = pluginInfo.getName();
+		description = pluginInfo.getDescription();
+		organization = pluginInfo.getOrganization();
+		version = Version.parseVersion(pluginInfo.getVersion());
+	}
+
+	@Override
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public String getOrganization() {
+		return organization;
+	}
+
+	@Override
+	public Version getVersion() {
+		return version;
+	}
+
+	@Override
+	public State getState() {
+		return state;
+	}
+
+	void setState(State state) {
+		this.state = state;
+	}
+
+	@Override
+	public void start() throws PluginException {
+		if (state == STARTED) {
+			return;
+		}
+		if (state == UNINSTALLED) {
+			throw new PluginException("Cannot start an uninstalled plugin");
+		}
+		List<Bundle> startedBundles = new ArrayList<Bundle>();
+		for (Bundle bundle : getBundles()) {
+			if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) {
+				if (bundle.getState() != Bundle.ACTIVE) {
+					try {
+						bundle.start();
+						startedBundles.add(bundle);
+					} catch (BundleException e) {
+						// clean up by stopping bundles already started
+						for (Bundle startedBundle : startedBundles) {
+							try {
+								startedBundle.stop();
+							} catch (BundleException ex) {
+								logger.warn("Error unistalling bundle", ex);
+							}
+						}
+						throw new PluginException(String.format("Error starting bundle %1$s",
+								bundle.getSymbolicName()), e);
+					}
+				}
+			}
+		}
+	}
+
+	@Override
+	public void stop() throws PluginException {
+		if (state == STARTED) {
+			List<Plugin> installedPlugins = pluginManager.getInstalledPlugins();
+			for (Bundle bundle : getBundles()) {
+				// check if bundle is used by other plugins
+				boolean bundleUsed = false;
+				for (Plugin installedPlugin : installedPlugins) {
+					if (!installedPlugin.equals(this) && installedPlugin.getState() == STARTED) {
+						if (installedPlugin.getBundles().contains(bundle)) {
+							bundleUsed = true;
+							break;
+						}
+					}
+				}
+				if (!bundleUsed) {
+					try {
+						logger.info("Stopping bundle " + bundle.getSymbolicName());
+						bundle.stop();
+					} catch (BundleException e) {
+						logger.warn(
+								String.format("Error stopping bundle %1$s for plugin %2$s",
+										bundle.getSymbolicName(), getName()), e);
+					}
+				}
+			}
+			state = STOPPED;
+		}
+	}
+
+	@Override
+	public void uninstall() throws PluginException {
+		if (state != UNINSTALLED) {
+			pluginManager.uninstallPlugin(this);
+			state = UNINSTALLED;
+		}
+	}
+
+	@Override
+	public File getFile() {
+		return file;
+	}
+
+	@Override
+	public Set<Bundle> getBundles() {
+		return bundles;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginManagerImpl.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginManagerImpl.java b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginManagerImpl.java
new file mode 100644
index 0000000..364551c
--- /dev/null
+++ b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginManagerImpl.java
@@ -0,0 +1,466 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.log4j.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+import uk.org.taverna.commons.download.DownloadException;
+import uk.org.taverna.commons.download.DownloadManager;
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.Plugin.State;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.PluginSite;
+import uk.org.taverna.commons.plugin.PluginSiteManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginInfo;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+import uk.org.taverna.commons.profile.xml.jaxb.BundleInfo;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * PluginManager implementation.
+ *
+ * @author David Withers
+ */
+public class PluginManagerImpl implements PluginManager {
+
+	private static final String DIGEST_ALGORITHM = "MD5";
+	private static final String PLUGIN_FILE_NAME = "META-INF/taverna/plugin.xml";
+
+	private static final Logger logger = Logger.getLogger(PluginManagerImpl.class);
+
+	private EventAdmin eventAdmin;
+	private ApplicationConfiguration applicationConfiguration;
+	private BundleContext bundleContext;
+	private DownloadManager downloadManager;
+	private PluginSiteManager pluginSiteManager;
+
+	private Map<String, Plugin> installedPlugins = new TreeMap<String, Plugin>();
+	private Map<String, PluginVersions> availablePlugins = new TreeMap<String, PluginVersions>();
+	private Map<String, PluginVersions> pluginUpdates = new TreeMap<String, PluginVersions>();
+
+	private boolean updateAvailablePlugins = true;
+
+	private Map<File, PluginDirectoryWatcher> pluginDirectoryWatchers = new HashMap<File, PluginDirectoryWatcher>();
+
+	private Set<Bundle> installedBundles = new HashSet<Bundle>();
+
+	private Unmarshaller unmarshaller;
+
+	public PluginManagerImpl() throws PluginException {
+		try {
+			JAXBContext jaxbContext = JAXBContext.newInstance(PluginInfo.class);
+			unmarshaller = jaxbContext.createUnmarshaller();
+		} catch (JAXBException e) {
+			throw new PluginException("Error creating JAXBContext", e);
+		}
+	}
+
+	@Override
+	public void checkForUpdates() throws PluginException {
+		boolean updatesFound = false;
+		synchronized (pluginUpdates) {
+			pluginUpdates.clear();
+			for (PluginSite pluginSite : pluginSiteManager.getPluginSites()) {
+				List<PluginVersions> plugins = pluginSiteManager.getPlugins(pluginSite);
+				for (PluginVersions plugin : plugins) {
+					if (installedPlugins.containsKey(plugin.getId())) {
+						Plugin installedPlugin = installedPlugins.get(plugin.getId());
+						if (installedPlugin.getFile().canWrite()) {
+							Version latestVersion = Version.parseVersion(plugin.getLatestVersion()
+									.getVersion());
+							if (latestVersion.compareTo(installedPlugin.getVersion()) > 0) {
+								pluginUpdates.put(plugin.getId(), plugin);
+								updatesFound = true;
+							}
+						}
+					}
+				}
+			}
+		}
+		if (updatesFound) {
+			postEvent(PluginManager.UPDATES_AVAILABLE);
+		}
+	}
+
+	@Override
+	public List<PluginVersions> getPluginUpdates() throws PluginException {
+		synchronized (pluginUpdates) {
+			return new ArrayList<PluginVersions>(pluginUpdates.values());
+		}
+	}
+
+	@Override
+	public void loadPlugins() throws PluginException {
+		loadPlugins(applicationConfiguration.getSystemPluginDir());
+		loadPlugins(applicationConfiguration.getUserPluginDir());
+	}
+
+	@Override
+	public List<PluginVersions> getAvailablePlugins() throws PluginException {
+		if (updateAvailablePlugins) {
+			synchronized (availablePlugins) {
+				availablePlugins = new HashMap<String, PluginVersions>();
+				for (PluginSite pluginSite : pluginSiteManager.getPluginSites()) {
+					List<PluginVersions> plugins = pluginSiteManager.getPlugins(pluginSite);
+					for (PluginVersions plugin : plugins) {
+						if (!installedPlugins.containsKey(plugin.getId())) {
+							availablePlugins.put(plugin.getId(), plugin);
+						}
+					}
+				}
+			}
+			updateAvailablePlugins = false;
+		}
+		return new ArrayList<PluginVersions>(availablePlugins.values());
+	}
+
+	@Override
+	public List<Plugin> getInstalledPlugins() throws PluginException {
+		return new ArrayList<Plugin>(installedPlugins.values());
+	}
+
+	@Override
+	public Plugin installPlugin(File pluginFile) throws PluginException {
+		// check if already installed
+		synchronized (installedPlugins) {
+			for (Plugin plugin : installedPlugins.values()) {
+				if (plugin.getFile().equals(pluginFile)) {
+					return plugin;
+				}
+			}
+			// check plugin file
+			if (pluginFile.exists()) {
+				new PluginException(String.format("Plugin file %1$s does not exist", pluginFile));
+			}
+			if (pluginFile.isFile()) {
+				new PluginException(String.format("Plugin file %1$s is not a file", pluginFile));
+			}
+			if (!pluginFile.canRead()) {
+				new PluginException(String.format("Plugin file %1$s is not readable", pluginFile));
+			}
+			// install plugin from plugin file
+			logger.info(String.format("Installing plugin from '%s'", pluginFile));
+			JarFile jarFile;
+			try {
+				jarFile = new JarFile(pluginFile);
+			} catch (IOException e) {
+				throw new PluginException(String.format("Error reading plugin file %1$s",
+						pluginFile), e);
+			}
+			Plugin plugin = installPlugin(jarFile);
+			installedPlugins.put(plugin.getId(), plugin);
+			availablePlugins.remove(plugin.getId());
+			postEvent(PluginManager.PLUGIN_INSTALLED);
+			return plugin;
+		}
+	}
+
+	@Override
+	public Plugin installPlugin(String pluginSiteURL, String pluginFileName) throws PluginException {
+		File pluginFile = getPluginFile(pluginSiteURL, pluginFileName);
+		return installPlugin(pluginFile);
+	}
+
+	@Override
+	public Plugin updatePlugin(PluginVersions pluginVersions) throws PluginException {
+		String pluginId = pluginVersions.getId();
+		String pluginSiteUrl = pluginVersions.getPluginSiteUrl();
+		String pluginFile = pluginVersions.getLatestVersion().getFile();
+		Plugin plugin = installedPlugins.get(pluginId);
+		plugin.stop();
+		Plugin newPlugin;
+		try {
+			newPlugin = installPlugin(pluginSiteUrl, pluginFile);
+		} catch (PluginException e) {
+			plugin.start();
+			throw new PluginException("Failed to update plugin " + pluginId, e);
+		}
+		synchronized (pluginUpdates) {
+			pluginUpdates.remove(pluginId);
+		}
+		uninstallPlugin(plugin);
+		return newPlugin;
+	}
+
+	void uninstallPlugin(File pluginFile) {
+		synchronized (installedPlugins) {
+			for (Plugin plugin : installedPlugins.values()) {
+				if (plugin.getFile().equals(pluginFile)) {
+					uninstallPlugin(plugin);
+					break;
+				}
+			}
+		}
+	}
+
+	void uninstallPlugin(Plugin plugin) {
+		synchronized (installedPlugins) {
+			if (installedPlugins.containsKey(plugin.getId())) {
+				for (Bundle bundle : plugin.getBundles()) {
+					if (installedBundles.contains(bundle)) {
+						// check if bundle is used by other plugins
+						boolean bundleInUse = false;
+						for (Plugin installedPlugin : installedPlugins.values()) {
+							if (!installedPlugin.equals(plugin)) {
+								if (installedPlugin.getBundles().contains(bundle)) {
+									bundleInUse = true;
+									break;
+								}
+							}
+						}
+						if (!bundleInUse) {
+							try {
+								logger.info("Uninstalling bundle " + bundle.getSymbolicName());
+								bundle.uninstall();
+								installedBundles.remove(bundle);
+								System.out.println("Remove " + bundle.getSymbolicName());
+							} catch (BundleException e) {
+								logger.warn(String.format(
+										"Error uninstalling bundle %1$s for plugin %2$s",
+										bundle.getSymbolicName(), plugin.getName()), e);
+							}
+						}
+					}
+				}
+				installedPlugins.remove(plugin.getId());
+				pluginUpdates.remove(plugin.getId());
+				updateAvailablePlugins = true;
+				postEvent(PluginManager.PLUGIN_UNINSTALLED);
+			}
+		}
+	}
+
+	public void loadPlugins(File pluginDir) throws PluginException {
+		if (checkPluginDirectory(pluginDir, false)) {
+			for (File pluginFile : pluginDir.listFiles()) {
+				if (pluginFile.isFile() && pluginFile.canRead() && !pluginFile.isHidden()) {
+					try {
+						installPlugin(pluginFile).start();
+					} catch (PluginException e) {
+						logger.warn(String.format("Error loading plugin from '%s'", pluginFile), e);
+					}
+				}
+			}
+		}
+		startWatchingPluginDirectory(pluginDir);
+	}
+
+	private Plugin installPlugin(JarFile jarFile) throws PluginException {
+		PluginInfo pluginInfo = getPluginInfo(jarFile);
+
+		PluginImpl plugin = new PluginImpl(this, new File(jarFile.getName()), pluginInfo);
+
+		// check bundles exist in jar
+		for (BundleInfo bundleInfo : pluginInfo.getBundle()) {
+			// find the bundle in the plugin jar
+			JarEntry entry = jarFile.getJarEntry(bundleInfo.getFileName());
+			if (entry == null) {
+				throw new PluginException(String.format(
+						"Plugin file '%1$s' does not contain bundle file '%2$s'.",
+						jarFile.getName(), bundleInfo.getFileName()));
+			}
+		}
+
+		// install plugin bundles
+		Set<Bundle> pluginBundles = plugin.getBundles();
+		for (BundleInfo bundleInfo : pluginInfo.getBundle()) {
+			Bundle installedBundle = getInstalledBundle(bundleInfo);
+			if (installedBundle == null) {
+				// install the bundle from the jar
+				JarEntry entry = jarFile.getJarEntry(bundleInfo.getFileName());
+				String bundleURL = jarEntryToURL(jarFile, entry);
+				try {
+					Bundle bundle = bundleContext.installBundle(bundleURL);
+					pluginBundles.add(bundle);
+					installedBundles.add(bundle);
+					System.out.println("Add " + bundle.getSymbolicName());
+				} catch (BundleException e) {
+					// clean up by removing bundles already installed
+					for (Bundle bundle : pluginBundles) {
+						try {
+							bundle.uninstall();
+							installedBundles.remove(bundle);
+						} catch (BundleException ex) {
+							logger.warn("Error unistalling bundle", ex);
+						}
+					}
+					throw new PluginException(String.format("Error installing bundle file %1$s",
+							bundleURL), e);
+				}
+			} else {
+				pluginBundles.add(installedBundle);
+			}
+		}
+		plugin.setState(State.INSTALLED);
+		return plugin;
+	}
+
+	private Bundle getInstalledBundle(BundleInfo bundleInfo) {
+		for (Bundle installedBundle : bundleContext.getBundles()) {
+			if (installedBundle.getSymbolicName().equals(bundleInfo.getSymbolicName())) {
+				org.osgi.framework.Version installedVersion = installedBundle.getVersion();
+				if (installedVersion
+						.equals(new org.osgi.framework.Version(bundleInfo.getVersion()))) {
+					return installedBundle;
+				}
+			}
+		}
+		return null;
+	}
+
+	public PluginInfo getPluginInfo(JarFile jarFile) throws PluginException {
+		// TODO check manifest for non standard plugin info file
+		JarEntry pluginEntry = jarFile.getJarEntry(PLUGIN_FILE_NAME);
+		if (pluginEntry == null) {
+			throw new PluginException(String.format(
+					"Plugin file '%1$s' does not contain a %2$s file.", jarFile.getName(),
+					PLUGIN_FILE_NAME));
+		}
+		try {
+			InputStream inputStream = jarFile.getInputStream(pluginEntry);
+			return (PluginInfo) unmarshaller.unmarshal(inputStream);
+		} catch (JAXBException e) {
+			throw new PluginException(String.format("Error reading plugin file %1$s from %2$s",
+					pluginEntry, jarFile.getName()), e);
+		} catch (IOException e) {
+			throw new PluginException(String.format("Error reading plugin file %1$s from %2$s",
+					pluginEntry, jarFile.getName()), e);
+		}
+	}
+
+	private File getPluginFile(String pluginSiteURL, String pluginFileName) throws PluginException {
+		File pluginFile = new File(getPluginDirectory(), pluginFileName);
+		String pluginFileURL = pluginSiteURL + "/" + pluginFileName;
+		try {
+			downloadManager.download(new URL(pluginFileURL), pluginFile, DIGEST_ALGORITHM);
+		} catch (DownloadException e) {
+			throw new PluginException("Error downloading plugin file " + pluginFile, e);
+		} catch (MalformedURLException e) {
+			throw new PluginException("Invalid plugin file URL " + pluginFileURL, e);
+		}
+		return pluginFile;
+	}
+
+	public void startWatchingPluginDirectory(File pluginDir) throws PluginException {
+		if (!pluginDirectoryWatchers.containsKey(pluginDir)) {
+			pluginDirectoryWatchers.put(pluginDir, new PluginDirectoryWatcher(this, pluginDir));
+		}
+		pluginDirectoryWatchers.get(pluginDir).start();
+	}
+
+	public void stopWatchingPluginDirectory(File pluginDir) throws PluginException {
+		if (pluginDirectoryWatchers.containsKey(pluginDir)) {
+			pluginDirectoryWatchers.get(pluginDir).stop();
+		}
+	}
+
+	private File getPluginDirectory() throws PluginException {
+		File systemPluginsDir = applicationConfiguration.getSystemPluginDir();
+		if (checkPluginDirectory(systemPluginsDir, true)) {
+			return systemPluginsDir;
+		}
+		File userPluginsDir = applicationConfiguration.getUserPluginDir();
+		if (checkPluginDirectory(userPluginsDir, true)) {
+			return userPluginsDir;
+		}
+		throw new PluginException("No plugin directory avaliable");
+	}
+
+	public void setEventAdmin(EventAdmin eventAdmin) {
+		this.eventAdmin = eventAdmin;
+	}
+
+	public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+	}
+
+	public void setBundleContext(BundleContext bundleContext) {
+		this.bundleContext = bundleContext;
+	}
+
+	public void setDownloadManager(DownloadManager downloadManager) {
+		this.downloadManager = downloadManager;
+	}
+
+	public void setPluginSiteManager(PluginSiteManager pluginSiteManager) {
+		this.pluginSiteManager = pluginSiteManager;
+	}
+
+	private boolean checkPluginDirectory(File pluginDirectory, boolean checkWritable) {
+		if (pluginDirectory == null) {
+			return false;
+		}
+		if (!pluginDirectory.exists()) {
+			logger.debug(String.format("Plugin directory %1$s does not exist", pluginDirectory));
+			return false;
+		}
+		if (!pluginDirectory.isDirectory()) {
+			logger.warn(String.format("Plugin directory %1$s is not a directory", pluginDirectory));
+			return false;
+		}
+		if (!pluginDirectory.canRead()) {
+			logger.debug(String.format("Plugin directory %1$s is not readable", pluginDirectory));
+			return false;
+		}
+		if (checkWritable && !pluginDirectory.canWrite()) {
+			logger.debug(String.format("Plugin directory %1$s is not writeable", pluginDirectory));
+			return false;
+		}
+		return true;
+	}
+
+	private String jarEntryToURL(JarFile jarFile, JarEntry jarEntry) {
+		File file = new File(jarFile.getName());
+		return "jar:" + file.toURI() + "!/" + jarEntry.getName();
+	}
+
+	private void postEvent(String topic) {
+		Event event = new Event(topic, new HashMap());
+		eventAdmin.postEvent(event);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteImpl.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteImpl.java b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteImpl.java
new file mode 100644
index 0000000..6458341
--- /dev/null
+++ b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteImpl.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import uk.org.taverna.commons.plugin.PluginSite;
+
+/**
+ * PluginSite implementation.
+ *
+ * @author David Withers
+ */
+public class PluginSiteImpl implements PluginSite {
+
+	private String name, url;
+
+	private PluginSiteType type;
+
+	public PluginSiteImpl() {
+	}
+
+	public PluginSiteImpl(String name, String url) {
+		this(name, url, PluginSiteType.USER);
+	}
+
+	public PluginSiteImpl(String name, String url, PluginSiteType type) {
+		this.name = name;
+		this.url = url;
+		this.type = type;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public String getUrl() {
+		return url;
+	}
+
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	@Override
+	public PluginSiteType getType() {
+		return type;
+	}
+
+	public void setType(PluginSiteType type) {
+		this.type = type;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImpl.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImpl.java b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImpl.java
new file mode 100644
index 0000000..1634d98
--- /dev/null
+++ b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImpl.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.download.DownloadException;
+import uk.org.taverna.commons.download.DownloadManager;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginSite;
+import uk.org.taverna.commons.plugin.PluginSite.PluginSiteType;
+import uk.org.taverna.commons.plugin.PluginSiteManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+import uk.org.taverna.commons.plugin.xml.jaxb.Plugins;
+import uk.org.taverna.commons.profile.xml.jaxb.Updates;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * PluginSiteManager implementation.
+ *
+ * @author David Withers
+ */
+public class PluginSiteManagerImpl implements PluginSiteManager {
+
+	private static final String PLUGIN_SITES_FILE = "plugin-sites.xml";
+	private static final String DIGEST_ALGORITHM = "MD5";
+	private static final String PLUGINS_FILE = "plugins.xml";
+
+	private static final Logger logger = Logger.getLogger(PluginSiteManagerImpl.class);
+
+	private ApplicationConfiguration applicationConfiguration;
+	private DownloadManager downloadManager;
+
+	private Unmarshaller unmarshaller;
+	private Marshaller marshaller;
+
+	private List<PluginSite> pluginSites;
+
+	public PluginSiteManagerImpl() throws PluginException {
+		try {
+			JAXBContext jaxbContext = JAXBContext.newInstance(Plugins.class, PluginSites.class);
+			unmarshaller = jaxbContext.createUnmarshaller();
+			marshaller = jaxbContext.createMarshaller();
+		} catch (JAXBException e) {
+			throw new PluginException("Error creating JAXBContext", e);
+		}
+	}
+
+	@Override
+	public List<PluginSite> getPluginSites() {
+		if (pluginSites == null) {
+			readPluginSitesFile();
+			if (pluginSites == null) {
+				pluginSites = new ArrayList<PluginSite>();
+				pluginSites.addAll(getSystemPluginSites());
+			}
+		}
+		return pluginSites;
+	}
+
+	@Override
+	public PluginSite createPluginSite(URL pluginSiteURL) throws PluginException {
+		try {
+			File tempFile = File.createTempFile("plugins", null);
+			tempFile.deleteOnExit();
+			URL pluginFileURL = new URL(pluginSiteURL + "/" + PLUGINS_FILE);
+			downloadManager.download(pluginFileURL, tempFile, DIGEST_ALGORITHM);
+			return new PluginSiteImpl("", pluginSiteURL.toExternalForm());
+		} catch (MalformedURLException e) {
+			throw new PluginException(String.format("Invalid plugin site URL %1$s", pluginSiteURL), e);
+		} catch (DownloadException e) {
+			throw new PluginException(String.format("Error contacting plugin site at %1$s", pluginSiteURL), e);
+		} catch (IOException e) {
+			throw new PluginException(String.format("Error contacting plugin site at %1$s", pluginSiteURL), e);
+		}
+	}
+
+	@Override
+	public void addPluginSite(PluginSite pluginSite) throws PluginException {
+		getPluginSites().add(pluginSite);
+		writePluginSitesFile();
+	}
+
+	@Override
+	public void removePluginSite(PluginSite pluginSite) throws PluginException {
+		getPluginSites().remove(pluginSite);
+		writePluginSitesFile();
+	}
+
+	@Override
+	public List<PluginVersions> getPlugins(PluginSite pluginSite) throws PluginException {
+		List<PluginVersions> plugins = new ArrayList<PluginVersions>();
+		try {
+			URL pluginSiteURL = new URL(pluginSite.getUrl() + "/" + PLUGINS_FILE);
+			File pluginsFile = new File(getDataDirectory(), PLUGINS_FILE);
+			downloadManager.download(pluginSiteURL, pluginsFile, DIGEST_ALGORITHM);
+			Plugins pluginsXML = (Plugins) unmarshaller.unmarshal(pluginsFile);
+			for (PluginVersions plugin : pluginsXML.getPlugin()) {
+				plugin.setPluginSiteUrl(pluginSite.getUrl());
+				plugins.add(plugin);
+			}
+		} catch (MalformedURLException e) {
+			throw new PluginException(String.format("Plugin site %1$s has an invalid location",
+					pluginSite.getName()), e);
+		} catch (DownloadException e) {
+			throw new PluginException(String.format("Error downloading from plugin site %1$s",
+					pluginSite.getName()), e);
+		} catch (JAXBException e) {
+			throw new PluginException(String.format("Error getting plugins from plugin site %1$s",
+					pluginSite.getName()), e);
+		}
+		return plugins;
+	}
+
+	private List<PluginSite> getSystemPluginSites() {
+		List<PluginSite> systemPluginSites = new ArrayList<PluginSite>();
+		Updates updates = applicationConfiguration.getApplicationProfile().getUpdates();
+		systemPluginSites
+				.add(new PluginSiteImpl("", updates.getPluginSite(), PluginSiteType.SYSTEM));
+		return systemPluginSites;
+	}
+
+	private void writePluginSitesFile() {
+		File pluginSitesFile = new File(getDataDirectory(), PLUGIN_SITES_FILE);
+		try {
+			marshaller.marshal(pluginSites, pluginSitesFile);
+		} catch (JAXBException e) {
+			logger.error("Error writing file " + pluginSitesFile, e);
+		}
+	}
+
+	private void readPluginSitesFile() {
+		File pluginSitesFile = new File(getDataDirectory(), PLUGIN_SITES_FILE);
+		if (pluginSitesFile.exists()) {
+			try {
+				pluginSites = new ArrayList<PluginSite>();
+				PluginSites pluginSitesStore = (PluginSites) unmarshaller
+						.unmarshal(pluginSitesFile);
+				for (PluginSiteImpl pluginSiteImpl : pluginSitesStore.getPluginSites()) {
+					pluginSites.add(pluginSiteImpl);
+				}
+			} catch (JAXBException e) {
+				logger.error("Error reading file " + pluginSitesFile, e);
+			}
+		}
+	}
+
+	private File getDataDirectory() {
+		return new File(applicationConfiguration.getApplicationHomeDir(), "plugin-data");
+	}
+
+	public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+		this.applicationConfiguration = applicationConfiguration;
+	}
+
+	public void setDownloadManager(DownloadManager downloadManager) {
+		this.downloadManager = downloadManager;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSites.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSites.java b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSites.java
new file mode 100644
index 0000000..1e5203f
--- /dev/null
+++ b/taverna-plugin-impl/src/main/java/uk/org/taverna/commons/plugin/impl/PluginSites.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ *
+ *
+ * @author David Withers
+ */
+@XmlRootElement
+public class PluginSites {
+
+    private List<PluginSiteImpl> pluginSites;
+
+	public List<PluginSiteImpl> getPluginSites() {
+		if (pluginSites == null) {
+			pluginSites = new ArrayList<PluginSiteImpl>();
+		}
+		return pluginSites;
+	}
+
+	public void setPluginSites(List<PluginSiteImpl> pluginSites) {
+		this.pluginSites = pluginSites;
+	}
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context-osgi.xml
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context-osgi.xml b/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context-osgi.xml
new file mode 100644
index 0000000..cd235f5
--- /dev/null
+++ b/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                                 http://www.springframework.org/schema/beans/spring-beans.xsd
+                                 http://www.springframework.org/schema/osgi
+                                 http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+	<service ref="pluginSiteManager" interface="uk.org.taverna.commons.plugin.PluginSiteManager" />
+
+	<reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+	<reference id="downloadManager" interface="uk.org.taverna.commons.download.DownloadManager" />
+
+	<reference id="eventAdmin" interface="org.osgi.service.event.EventAdmin" />
+
+</beans:beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context.xml
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context.xml b/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context.xml
new file mode 100644
index 0000000..7315a1b
--- /dev/null
+++ b/taverna-plugin-impl/src/main/resources/META-INF/spring/plugin-context.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgix="http://www.springframework.org/schema/osgi-compendium"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/osgi-compendium
+                           http://www.springframework.org/schema/osgi-compendium/spring-osgi-compendium.xsd">
+
+	<!-- <osgix:cm-properties id="cfg.with.defaults" persistent-id="uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl">
+		<prop key="checkIntervalSeconds">300</prop>
+	</osgix:cm-properties> -->
+
+	<bean id="pluginManager" class="uk.org.taverna.commons.plugin.impl.PluginManagerImpl">
+		<property name="eventAdmin" ref="eventAdmin" />
+		<property name="applicationConfiguration" ref="applicationConfiguration" />
+		<property name="bundleContext" ref="bundleContext" />
+		<property name="downloadManager" ref="downloadManager" />
+		<property name="pluginSiteManager">
+			<ref local="pluginSiteManager" />
+		</property>
+
+	</bean>
+
+	<bean id="pluginSiteManager"
+		class="uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl">
+		<property name="applicationConfiguration" ref="applicationConfiguration" />
+		<property name="downloadManager" ref="downloadManager" />
+		<!-- <osgix:managed-properties persistent-id="uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl" /> -->
+	</bean>
+
+</beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteImplTest.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteImplTest.java b/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteImplTest.java
new file mode 100644
index 0000000..02ccb6e
--- /dev/null
+++ b/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteImplTest.java
@@ -0,0 +1,72 @@
+package uk.org.taverna.commons.plugin.impl;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.commons.plugin.PluginSite.PluginSiteType;
+
+public class PluginSiteImplTest {
+
+	PluginSiteImpl pluginSiteImpl;
+	String name, url;
+	PluginSiteType type;
+
+	@Before
+	public void setUp() throws Exception {
+		name = "test name";
+		url = "test url";
+		type = PluginSiteType.SYSTEM;
+		pluginSiteImpl = new PluginSiteImpl(name, url, type);
+	}
+
+	@Test
+	public void testPluginSiteImpl() {
+		pluginSiteImpl = new PluginSiteImpl();
+	}
+
+	@Test
+	public void testPluginSiteImplStringStringPluginSiteType() {
+		pluginSiteImpl = new PluginSiteImpl(null, null, null);
+		pluginSiteImpl = new PluginSiteImpl("", "", PluginSiteType.USER);
+	}
+
+	@Test
+	public void testGetName() {
+		assertEquals(name, pluginSiteImpl.getName());
+		assertEquals(name, pluginSiteImpl.getName());
+	}
+
+	@Test
+	public void testSetName() {
+		pluginSiteImpl.setName("name");
+		assertEquals("name", pluginSiteImpl.getName());
+	}
+
+	@Test
+	public void testGetUrl() {
+		assertEquals(url, pluginSiteImpl.getUrl());
+		assertEquals(url, pluginSiteImpl.getUrl());
+	}
+
+	@Test
+	public void testSetUrl() {
+		pluginSiteImpl.setName("http://www.example.com/");
+		assertEquals("http://www.example.com/", pluginSiteImpl.getName());
+	}
+
+	@Test
+	public void testGetType() {
+		assertEquals(type, pluginSiteImpl.getType());
+		assertEquals(type, pluginSiteImpl.getType());
+	}
+
+	@Test
+	public void testSetType() {
+		pluginSiteImpl.setType(PluginSiteType.USER);
+		assertEquals(PluginSiteType.USER, pluginSiteImpl.getType());
+
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImplTest.java
----------------------------------------------------------------------
diff --git a/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImplTest.java b/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImplTest.java
new file mode 100644
index 0000000..0ca9485
--- /dev/null
+++ b/taverna-plugin-impl/src/test/java/uk/org/taverna/commons/plugin/impl/PluginSiteManagerImplTest.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.plugin.impl;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.commons.download.DownloadException;
+import uk.org.taverna.commons.download.DownloadManager;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ *
+ *
+ * @author David Withers
+ */
+@Ignore
+public class PluginSiteManagerImplTest {
+
+	private PluginSiteManagerImpl pluginSiteManager;
+	private ApplicationConfiguration applicationConfiguration;
+	private DownloadManager downloadManager;
+
+	/**
+	 * @throws java.lang.Exception
+	 */
+	@Before
+	public void setUp() throws Exception {
+		pluginSiteManager = new PluginSiteManagerImpl();
+		applicationConfiguration = mock(ApplicationConfiguration.class);
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#PluginSiteManagerImpl()}.
+	 * @throws Exception 
+	 */
+	@Test
+	public void testPluginSiteManagerImpl() throws Exception {
+		new PluginSiteManagerImpl();
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#getPluginSites()}.
+	 */
+	@Test
+	public void testGetPluginSites() {
+		fail("Not yet implemented");
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#createPluginSite(java.net.URL)}.
+	 * @throws DownloadException
+	 */
+	@Test
+	public void testCreatePluginSite() throws Exception {
+		downloadManager = mock(DownloadManager.class);
+		doNothing().when(downloadManager).download(new URL("file:///"), null, "");
+
+		pluginSiteManager.setDownloadManager(downloadManager);
+
+		pluginSiteManager.createPluginSite(new URL("file:///"));
+
+	}
+
+	@Test(expected=PluginException.class)
+	public void testCreatePluginSiteDownloadException() throws Exception {
+		downloadManager = mock(DownloadManager.class);
+		doThrow(DownloadException.class).when(downloadManager).download(new URL("file:///"), null, "");
+
+		pluginSiteManager.setDownloadManager(downloadManager);
+
+		pluginSiteManager.createPluginSite(new URL("file:///"));
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#addPluginSite(uk.org.taverna.commons.plugin.PluginSite)}.
+	 */
+	@Test
+	public void testAddPluginSite() {
+		fail("Not yet implemented");
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#removePluginSite(uk.org.taverna.commons.plugin.PluginSite)}.
+	 */
+	@Test
+	public void testRemovePluginSite() {
+		fail("Not yet implemented");
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#getPlugins(uk.org.taverna.commons.plugin.PluginSite)}.
+	 */
+	@Test
+	public void testGetPlugins() {
+		fail("Not yet implemented");
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#setApplicationConfiguration(uk.org.taverna.configuration.app.ApplicationConfiguration)}.
+	 */
+	@Test
+	public void testSetApplicationConfiguration() {
+		fail("Not yet implemented");
+	}
+
+	/**
+	 * Test method for {@link uk.org.taverna.commons.plugin.impl.PluginSiteManagerImpl#setDownloadManager(uk.org.taverna.commons.download.DownloadManager)}.
+	 */
+	@Test
+	public void testSetDownloadManager() {
+		fail("Not yet implemented");
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-update-api/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-update-api/pom.xml b/taverna-update-api/pom.xml
new file mode 100644
index 0000000..2b70a7d
--- /dev/null
+++ b/taverna-update-api/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.osgi</groupId>
+		<artifactId>taverna-osgi</artifactId>
+		<version>0.2.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-update-api</artifactId>
+	<packaging>bundle</packaging>
+	<name>Apache Taverna Update API</name>
+</project>

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

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-update-api/src/main/java/uk/org/taverna/commons/update/UpdateManager.java
----------------------------------------------------------------------
diff --git a/taverna-update-api/src/main/java/uk/org/taverna/commons/update/UpdateManager.java b/taverna-update-api/src/main/java/uk/org/taverna/commons/update/UpdateManager.java
new file mode 100644
index 0000000..29415cc
--- /dev/null
+++ b/taverna-update-api/src/main/java/uk/org/taverna/commons/update/UpdateManager.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package uk.org.taverna.commons.update;
+
+/**
+ * Manager for updating Tavana Applications.
+ *
+ * @author David Withers
+ */
+public interface UpdateManager {
+
+	public boolean checkForUpdates() throws UpdateException;
+
+	public boolean update() throws UpdateException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-osgi/blob/c9bb093a/taverna-update-impl/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-update-impl/pom.xml b/taverna-update-impl/pom.xml
new file mode 100644
index 0000000..ee081a2
--- /dev/null
+++ b/taverna-update-impl/pom.xml
@@ -0,0 +1,63 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.osgi</groupId>
+		<artifactId>taverna-osgi</artifactId>
+		<version>0.2.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-update-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>Apache Taverna Update Implementation</name>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<configuration>
+					<instructions>
+						<Import-Package>uk.org.taverna.commons.update;provide:=true,*</Import-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-update-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-download-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-osgi-schemas</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.parent.groupId}</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>${commons.io.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>${osgi.core.version}</version>
+		</dependency>
+	</dependencies>
+</project>