You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by jw...@apache.org on 2016/01/28 20:27:50 UTC

svn commit: r1727424 - in /aries/trunk/subsystem: subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/ subsystem-itests/src/test/java/org/apache/aries/subsystem/...

Author: jwross
Date: Thu Jan 28 19:27:49 2016
New Revision: 1727424

URL: http://svn.apache.org/viewvc?rev=1727424&view=rev
Log:
[ARIES-1383] Provide option to disable the provisioning of dependencies at install time.

As part of the implemented locking strategy, three locks are used. The Global Read/Write Lock (GRWL) is used to ensure thread safety among all operations: install, install
dependencies, resolve, start, stop, and uninstall. The write lock is acquired for install, install dependencies, and uninstall. The read lock is acquired for resolve, start, and stop.
The Global Mutual Exlusion Lock (GMEL) is used to prevent cycle deadlocks when acquiring the state change locks of individual subsystems. Every subsystem has a Local State Change Lock
(LSCL). These locks are used to prevent more than one state change operation at a time to occur for the same subsystem.

A condition exists for the GMEL. The condition is used in order to notify waiting threads that the LSCL of at least one subsystem was released and that at least one thread may now be
able to proceed. Threads that fail to acquire the LSCL of a subsystem while holding the GMEL will release all currently held locks and wait for the condition to apply. This is
necessary because, when starting, the target subsystem must be locked before any other affected subsystems since the latter may only be computed after dependencies, which may include
other subsystems, are installed.

The consequences of this strategy is that installs and uninstalls are synchronous while resolutions, starts, and stops are asynchronous as long as the same subsystem is not affected.
It may be possible to create an even more granular locking mechanism in the future if dictated by performance requirements.

Added:
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java
Modified:
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java Thu Jan 28 19:27:49 2016
@@ -32,6 +32,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.aries.subsystem.AriesSubsystem;
 import org.apache.aries.subsystem.core.archive.AriesProvisionDependenciesDirective;
@@ -621,6 +622,11 @@ public class BasicSubsystem implements R
 		}
 	}
 	
+	private final ReentrantLock stateChangeLock = new ReentrantLock();
+	ReentrantLock stateChangeLock() {
+		return stateChangeLock;
+	}
+	
 	private String getDeploymentManifestHeaderValue(String name) {
 		DeploymentManifest manifest = getDeploymentManifest();
 		if (manifest == null)
@@ -754,8 +760,8 @@ public class BasicSubsystem implements R
 		}
 	}
 	
-	void computeDependenciesPostInstallation() throws IOException {
-		resource.computeDependencies(null);
+	void computeDependenciesPostInstallation(Coordination coordination) throws IOException {
+		resource.computeDependencies(null, coordination);
 		ProvisionResourceHeader header = resource.computeProvisionResourceHeader();
 		setDeploymentManifest(
 				new DeploymentManifest.Builder()

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java Thu Jan 28 19:27:49 2016
@@ -41,57 +41,68 @@ public class InstallAction implements Pr
 	
 	@Override
 	public BasicSubsystem run() {
-		State state = parent.getState();
-	    if (State.INSTALLING.equals(state)) {
-	        throw new SubsystemException("A child subsystem may not be installed while the parent is in the INSTALLING state");
-	    }
-		// Initialization of a null coordination must be privileged and,
-		// therefore, occur in the run() method rather than in the constructor.
-		Coordination coordination = Utils.createCoordination(parent);
+		// Doesn't appear to be any need of protecting against re-entry in the
+		// case of installation.
 		BasicSubsystem result = null;
+		// Acquire the global write lock to prevent all other operations until
+		// the installation is complete. There is no need to hold any other locks.
+		LockingStrategy.writeLock();
 		try {
-			TargetRegion region = new TargetRegion(parent);
-			SubsystemResource ssr = new SubsystemResource(location, content, parent);
-			result = Activator.getInstance().getSubsystems().getSubsystemByLocation(location);
-			if (result != null) {
-				checkLifecyclePermission(result);
-				if (!region.contains(result))
-					throw new SubsystemException("Location already exists but existing subsystem is not part of target region: " + location);
-				if (!(result.getSymbolicName().equals(ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName())
-						&& result.getVersion().equals(ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion())
-						&& result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType())))
-					throw new SubsystemException("Location already exists but symbolic name, version, and type are not the same: " + location);
-				return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install();
+			State state = parent.getState();
+			if (State.INSTALLING.equals(state)) {
+				throw new SubsystemException("A child subsystem may not be installed while the parent is in the INSTALLING state");
 			}
-			result = (BasicSubsystem)region.find(
-					ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName(), 
-					ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion());
-			if (result != null) {
+			// Initialization of a null coordination must be privileged and,
+			// therefore, occur in the run() method rather than in the constructor.
+			Coordination coordination = Utils.createCoordination(parent);
+			try {
+				TargetRegion region = new TargetRegion(parent);
+				SubsystemResource ssr = new SubsystemResource(location, content, parent, coordination);
+				result = Activator.getInstance().getSubsystems().getSubsystemByLocation(location);
+				if (result != null) {
+					if (!region.contains(result))
+						throw new SubsystemException("Location already exists but existing subsystem is not part of target region: " + location);
+					if (!(result.getSymbolicName().equals(ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName())
+							&& result.getVersion().equals(ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion())
+							&& result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType())))
+						throw new SubsystemException("Location already exists but symbolic name, version, and type are not the same: " + location);
+				}
+				else {
+					result = (BasicSubsystem)region.find(
+							ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName(), 
+							ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion());
+					if (result != null) {
+						if (!result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType()))
+							throw new SubsystemException("Subsystem already exists in target region but has a different type: " + location);
+					}
+					else {
+						result = new BasicSubsystem(ssr, deploymentManifest);
+					}
+				}
 				checkLifecyclePermission(result);
-				if (!result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType()))
-					throw new SubsystemException("Subsystem already exists in target region but has a different type: " + location);
 				return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install();
 			}
-			result = new BasicSubsystem(ssr, deploymentManifest);
-			checkLifecyclePermission(result);
-			return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install();
-		}
-		catch (Throwable t) {
-			coordination.fail(t);
-		}
-		finally {
-			try {
-				coordination.end();
+			catch (Throwable t) {
+				coordination.fail(t);
 			}
-			catch (CoordinationException e) {
-				Throwable t = e.getCause();
-				if (t instanceof SubsystemException)
-					throw (SubsystemException)t;
-				if (t instanceof SecurityException)
-					throw (SecurityException)t;
-				throw new SubsystemException(t);
+			finally {
+				try {
+					coordination.end();
+				}
+				catch (CoordinationException e) {
+					Throwable t = e.getCause();
+					if (t instanceof SubsystemException)
+						throw (SubsystemException)t;
+					if (t instanceof SecurityException)
+						throw (SecurityException)t;
+					throw new SubsystemException(t);
+				}
 			}
 		}
+		finally {
+			// Release the global write lock.
+			LockingStrategy.writeUnlock();
+		}
 		return result;
 	}
 	

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java?rev=1727424&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java Thu Jan 28 19:27:49 2016
@@ -0,0 +1,164 @@
+package org.apache.aries.subsystem.core.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.osgi.service.subsystem.Subsystem;
+import org.osgi.service.subsystem.SubsystemException;
+
+public class LockingStrategy {
+	private static final int TRY_LOCK_TIME = 30000;
+	private static final TimeUnit TRY_LOCK_TIME_UNIT = TimeUnit.MILLISECONDS;
+	
+	/*
+	 * A mutual exclusion lock used when acquiring the state change locks of
+	 * a collection of subsystems in order to prevent cycle deadlocks.
+	 */
+	private static final ReentrantLock lock = new ReentrantLock();
+	/*
+	 * Used when the state change lock of a subsystem cannot be acquired. All
+	 * other state change locks are released while waiting. The condition is met
+	 * whenever the state change lock of one or more subsystems is released.
+	 */
+	private static final Condition condition = lock.newCondition();
+	
+	/*
+	 * Allow only one of the following operations to be executing at the same 
+	 * time.
+	 * 
+	 * (1) Install
+	 * (2) Install Dependencies
+	 * (3) Uninstall
+	 * 
+	 * Allow any number of the following operations to be executing at the same
+	 * time.
+	 * 
+	 * (1) Resolve
+	 * (2) Start
+	 * (3) Stop
+	 */
+	private static final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
+	
+	private static final ThreadLocal<Map<Subsystem.State, Set<BasicSubsystem>>> local = new ThreadLocal<Map<Subsystem.State, Set<BasicSubsystem>>>() {
+		@Override
+		protected Map<Subsystem.State, Set<BasicSubsystem>> initialValue() {
+			return new HashMap<Subsystem.State, Set<BasicSubsystem>>();
+		}
+	};
+	
+	public static void lock() {
+		try {
+			if (!lock.tryLock(TRY_LOCK_TIME, TRY_LOCK_TIME_UNIT)) {
+				throw new SubsystemException("Unable to acquire the global mutual exclusion lock in time.");
+			}
+		}
+		catch (InterruptedException e) {
+			throw new SubsystemException(e);
+		}
+	}
+	
+	public static void unlock() {
+		lock.unlock();
+	}
+	
+	public static void lock(Collection<BasicSubsystem> subsystems) {
+		Collection<BasicSubsystem> locked = new ArrayList<BasicSubsystem>(subsystems.size());
+		try {
+			while (locked.size() < subsystems.size()) {
+				for (BasicSubsystem subsystem : subsystems) {
+					if (!subsystem.stateChangeLock().tryLock()) {
+						unlock(locked);
+						locked.clear();
+						if (!LockingStrategy.condition.await(TRY_LOCK_TIME, TimeUnit.SECONDS)) {
+							throw new SubsystemException("Unable to acquire the state change lock in time: " + subsystem);
+						}
+						break;
+					}
+					locked.add(subsystem);
+				}
+			}
+		}
+		catch (InterruptedException e) {
+			unlock(locked);
+			throw new SubsystemException(e);
+		}
+	}
+	
+	public static void unlock(Collection<BasicSubsystem> subsystems) {
+		for (BasicSubsystem subsystem : subsystems) {
+			subsystem.stateChangeLock().unlock();
+		}
+		signalAll();
+	}
+	
+	private static void signalAll() {
+		lock();
+		try {
+			condition.signalAll();
+		}
+		finally {
+			unlock();
+		}
+	}
+	
+	public static boolean set(Subsystem.State state, BasicSubsystem subsystem) {
+		Map<Subsystem.State, Set<BasicSubsystem>> map = local.get();
+		Set<BasicSubsystem> subsystems = map.get(state);
+		if (subsystems == null) {
+			subsystems = new HashSet<BasicSubsystem>();
+			map.put(state, subsystems);
+			local.set(map);
+		}
+		if (subsystems.contains(subsystem)) {
+			return false;
+		}
+		subsystems.add(subsystem);
+		return true;
+	}
+	
+	public static void unset(Subsystem.State state, BasicSubsystem subsystem) {
+		Map<Subsystem.State, Set<BasicSubsystem>> map = local.get();
+		Set<BasicSubsystem> subsystems = map.get(state);
+		if (subsystems != null) {
+			subsystems.remove(subsystem);
+		}
+	}
+	
+	public static void readLock() {
+		try {
+			if (!rwlock.readLock().tryLock(TRY_LOCK_TIME, TRY_LOCK_TIME_UNIT)) {
+				throw new SubsystemException("Unable to acquire the global read lock in time.");
+			}
+		}
+		catch (InterruptedException e) {
+			throw new SubsystemException(e);
+		}
+	}
+	
+	public static void readUnlock() {
+		rwlock.readLock().unlock();
+	}
+	
+	public static void writeLock() {
+		try {
+			if (!rwlock.writeLock().tryLock(TRY_LOCK_TIME, TRY_LOCK_TIME_UNIT)) {
+				throw new SubsystemException("Unable to acquire the global write lock in time.");
+			}
+		}
+		catch (InterruptedException e) {
+			throw new SubsystemException(e);
+		}
+	}
+	
+	public static void writeUnlock() {
+		rwlock.writeLock().unlock();
+	}
+}

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java Thu Jan 28 19:27:49 2016
@@ -15,6 +15,7 @@ package org.apache.aries.subsystem.core.
 
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.security.AccessController;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -27,6 +28,7 @@ import org.apache.aries.subsystem.core.a
 import org.apache.aries.subsystem.core.archive.SubsystemTypeHeader;
 import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent;
 import org.apache.aries.subsystem.core.internal.DependencyCalculator.MissingCapability;
+import org.apache.aries.subsystem.core.internal.StartAction.Restriction;
 import org.apache.aries.subsystem.core.repository.Repository;
 import org.eclipse.equinox.region.Region;
 import org.osgi.framework.BundleException;
@@ -42,6 +44,7 @@ import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 import org.osgi.resource.Wiring;
 import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.subsystem.Subsystem.State;
 import org.osgi.service.subsystem.SubsystemException;
 
 public class ResolveContext extends org.osgi.service.resolver.ResolveContext {
@@ -61,9 +64,40 @@ public class ResolveContext extends org.
 		repositoryServiceRepository = new RepositoryServiceRepository();
 		systemRepository = Activator.getInstance().getSystemRepository();
 	}
+	
+	private void installDependenciesOfRequirerIfNecessary(Requirement requirement) {
+		if (requirement == null) {
+			return;
+		}
+		Resource requirer = requirement.getResource();
+		if (resource.equals(requirer)) {
+			return;
+		}
+		Collection<BasicSubsystem> subsystems;
+		if (requirer instanceof BasicSubsystem) {
+			BasicSubsystem subsystem = (BasicSubsystem)requirer;
+			subsystems = Collections.singletonList(subsystem);
+		}
+		else if (requirer instanceof BundleRevision) {
+			BundleRevision revision = (BundleRevision)requirer;
+			BundleConstituent constituent = new BundleConstituent(null, revision);
+			subsystems = Activator.getInstance().getSubsystems().getSubsystemsByConstituent(constituent);
+		}
+		else {
+			return;
+		}
+		for (BasicSubsystem subsystem : subsystems) {
+			if (Utils.isProvisionDependenciesInstall(subsystem) 
+					|| !State.INSTALLING.equals(subsystem.getState())) {
+				continue;
+			}
+			AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, Restriction.INSTALL_ONLY));
+		}
+	}
 
 	@Override
 	public List<Capability> findProviders(Requirement requirement) {
+		installDependenciesOfRequirerIfNecessary(requirement);
 		ArrayList<Capability> result = new ArrayList<Capability>();
 		try {
 			// Only check the system repository for osgi.ee and osgi.native
@@ -160,7 +194,8 @@ public class ResolveContext extends org.
 	}
 
 	private boolean addDependenciesFromSystemRepository(Requirement requirement, List<Capability> capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
-		return addDependencies(systemRepository, requirement, capabilities, true);
+		boolean result = addDependencies(systemRepository, requirement, capabilities, true);
+		return result;
 	}
 
 	private void addValidCapabilities(Collection<Capability> from, Collection<Capability> to, Requirement requirement, boolean validate) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java Thu Jan 28 19:27:49 2016
@@ -19,7 +19,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -57,155 +57,283 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class StartAction extends AbstractAction {
-	private static final Logger logger = LoggerFactory.getLogger(StartAction.class);
+	public static enum Restriction {
+		NONE,
+		INSTALL_ONLY,
+		RESOLVE_ONLY
+	}
 	
-	private static final ThreadLocal<Set<Subsystem>> subsystemsStartingOnCurrentThread = new ThreadLocal<Set<Subsystem>>() {
-		@Override
-		protected Set<Subsystem> initialValue() {
-			return new HashSet<Subsystem>();
-		}
-	};
+	private static final Logger logger = LoggerFactory.getLogger(StartAction.class);
 
 	private final Coordination coordination;
 	private final BasicSubsystem instigator;
-	private final boolean resolveOnly;
+	private final Restriction restriction;
 
 	public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target) {
-		this(instigator, requestor, target, false);
+		this(instigator, requestor, target, Restriction.NONE);
 	}
 
-	public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, boolean resolveOnly) {
-		this(instigator, requestor, target, null, resolveOnly);
+	public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Restriction restriction) {
+		this(instigator, requestor, target, null, restriction);
 	}
 	
 	public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination) {
-		this(instigator, requestor, target, coordination, false);
+		this(instigator, requestor, target, coordination, Restriction.NONE);
 	}
 	
-	public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination, boolean resolveOnly) {
+	public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination, Restriction restriction) {
 		super(requestor, target, false);
 		this.instigator = instigator;
 		this.coordination = coordination;
-		this.resolveOnly = resolveOnly;
+		this.restriction = restriction;
 	}
 	
-	private Object doRun() {
-		// TODO We now support circular dependencies so a sane locking strategy
-		// is required now more than ever before. Needs to be much more granular
-		// (and complex) than this. Perhaps something along the lines of a state
-		// change lock per subsystem then use a global lock only while acquiring
-		// the necessary state change locks.
-		synchronized (StartAction.class) {
-			State state = target.getState();
-		    // The following states are illegal.
-		    if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLED, State.UNINSTALLING).contains(state))
-		        throw new SubsystemException("Cannot start from state " + state);
-		    // The following states must wait with the exception of INSTALLING
-		    // combined with apache-aries-provision-dependencies:=resolve.
-		    if ((State.INSTALLING.equals(state) 
-		    		&& Utils.isProvisionDependenciesInstall(target))
-		    	            || EnumSet.of(State.RESOLVING, State.STARTING, State.STOPPING).contains(state)) {
-		        waitForStateChange(state);
-		        return new StartAction(instigator, requestor, target, coordination).run();
-		    }
-		    // The following states mean the requested state has already been attained.
-		    if (State.ACTIVE.equals(state))
-		        return null;
-		    // Always start if target is content of requestor.
-		    if (!Utils.isContent(requestor, target)) {
-		        // Always start if target is a dependency of requestor.
-		        if (!Utils.isDependency(requestor, target)) {
-		            // Always start if instigator equals target (explicit start).
-		            if (!instigator.equals(target)) {
-		                // Don't start if instigator is root (restart) and target is not ready.
-		                if (instigator.isRoot() && !target.isReadyToStart()) {
-		                    return null;
-		                }
-		            }
-		        }
-		    }
-		    Coordination coordination = this.coordination;
-		    if (coordination == null) {
-		        coordination = Utils.createCoordination(target);
-		    }
-		    try {
-		    	// If necessary, install the dependencies.
-		    	if (State.INSTALLING.equals(target.getState()) && 
-		    			!Utils.isProvisionDependenciesInstall(target)) {
-		    		// The following line is necessary in order to ensure that
-		    		// the export sharing policies of composites are in place
-		    		// for capability validation.
-		    		setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(coordination);
-		    		Collection<Subsystem> subsystems = new ArrayList<Subsystem>();
-		    		subsystems.addAll(Activator.getInstance().getSubsystems().getChildren(target));
-		    		subsystems.addAll(target.getParents());
-		    		for (Subsystem subsystem : subsystems) {
-		    			if (State.INSTALLING.equals(subsystem.getState())) {
-		    				BasicSubsystem bs = (BasicSubsystem)subsystem;
-		    				bs.computeDependenciesPostInstallation();
-		    	            new InstallDependencies().install(bs, null, coordination);
-		    	            bs.setState(State.INSTALLED);
-		    			}
-		    		}
-		    		target.computeDependenciesPostInstallation();
-		            new InstallDependencies().install(target, null, coordination);
-		            target.setState(State.INSTALLED);
-		    	}
-		        // Resolve if necessary.
-		        if (State.INSTALLED.equals(target.getState()))
-		            resolve(target, coordination);
-		        if (resolveOnly)
-		            return null;
-		        target.setState(State.STARTING);
-		        // TODO Need to hold a lock here to guarantee that another start
-		        // operation can't occur when the state goes to RESOLVED.
-		        // Start the subsystem.
-		        List<Resource> resources = new ArrayList<Resource>(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target));
-		        SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader();
-		        if (header != null)
-		            Collections.sort(resources, new StartResourceComparator(header));
-		        for (Resource resource : resources)
-		            startResource(resource, coordination);
-		        target.setState(State.ACTIVE);
-		    } catch (Throwable t) {
-		        coordination.fail(t);
-		        // TODO Need to reinstate complete isolation by disconnecting the
-		        // region and transition to INSTALLED.
-		    } finally {
-		        try {
-		            // Don't end the coordination if the subsystem being started
-		            // (i.e. the target) did not begin it.
-		            if (coordination.getName().equals(Utils.computeCoordinationName(target)))
-		                coordination.end();
-		        } catch (CoordinationException e) {
-		        	// If the target's state is INSTALLING then installing the
-		        	// dependencies failed, in which case we want to leave it as is.
-		        	if (!State.INSTALLING.equals(target.getState())) {
-		        		target.setState(State.RESOLVED);
-		        	}
-		            Throwable t = e.getCause();
-		            if (t instanceof SubsystemException)
-		                throw (SubsystemException)t;
-		            throw new SubsystemException(t);
-		        }
-		    }
-		    return null;
+	private static boolean isTargetStartable(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target) {
+		State state = target.getState();
+	    // The following states are illegal.
+	    if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLED).contains(state))
+	        throw new SubsystemException("Cannot start from state " + state);
+	    // The following states mean the requested state has already been attained.
+	    if (State.ACTIVE.equals(state))
+	        return false;
+		// Always start if target is content of requestor.
+	    if (!Utils.isContent(requestor, target)) {
+	        // Always start if target is a dependency of requestor.
+	        if (!Utils.isDependency(requestor, target)) {
+	            // Always start if instigator equals target (explicit start).
+	            if (!instigator.equals(target)) {
+	                // Don't start if instigator is root (restart) and target is not ready.
+	                if (instigator.isRoot() && !target.isReadyToStart()) {
+	                    return false;
+	                }
+	            }
+	        }
+	    }
+	    return true;
+	}
+	
+	private void installDependencies(BasicSubsystem target, Coordination coordination) throws Exception {
+		for (Subsystem parent : target.getParents()) {
+			AccessController.doPrivileged(new StartAction(instigator, target, (BasicSubsystem)parent, coordination, Restriction.INSTALL_ONLY));
+		}
+		installDependencies(Collections.<Subsystem>singletonList(target), coordination);
+		for (Subsystem child : Activator.getInstance().getSubsystems().getChildren(target)) {
+			AccessController.doPrivileged(new StartAction(instigator, target, (BasicSubsystem)child, coordination, Restriction.INSTALL_ONLY));
+		}
+	}
+	
+	private static void installDependencies(Collection<Subsystem> subsystems, Coordination coordination) throws Exception {
+		for (Subsystem subsystem : subsystems) {
+			if (State.INSTALLING.equals(subsystem.getState())) {
+				BasicSubsystem bs = (BasicSubsystem)subsystem;
+				bs.computeDependenciesPostInstallation(coordination);
+				new InstallDependencies().install(bs, null, coordination);
+				bs.setState(State.INSTALLED);
+			}
+		}
+	}
+	
+	private Coordination createCoordination() {
+		Coordination coordination = this.coordination;
+	    if (coordination == null) {
+	        coordination = Utils.createCoordination(target);
+	    }
+	    return coordination;
+	}
+	
+	private static LinkedHashSet<BasicSubsystem> computeAffectedSubsystems(BasicSubsystem target) {
+		LinkedHashSet<BasicSubsystem> result = new LinkedHashSet<BasicSubsystem>();
+		Subsystems subsystems = Activator.getInstance().getSubsystems();
+		for (Resource dep : subsystems.getResourcesReferencedBy(target)) {
+			if (dep instanceof BasicSubsystem 
+					&& !subsystems.getChildren(target).contains(dep)) {
+				result.add((BasicSubsystem)dep);
+			}
+			else if (dep instanceof BundleRevision) {
+				BundleConstituent constituent = new BundleConstituent(null, (BundleRevision)dep);
+				if (!target.getConstituents().contains(constituent)) {
+					for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent(
+							new BundleConstituent(null, (BundleRevision)dep))) {
+						result.add(constituentOf);
+					}
+				}
+			}
 		}
+		for (Subsystem child : subsystems.getChildren(target)) {
+			result.add((BasicSubsystem)child);
+		}
+		for (Resource resource : target.getResource().getSharedContent()) {
+			for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent(
+					resource instanceof BundleRevision ? new BundleConstituent(null, (BundleRevision)resource) : resource)) {
+				result.add(constituentOf);
+			}
+		}
+		result.add(target);
+		return result;
 	}
 
 	@Override
 	public Object run() {
-		Set<Subsystem> subsystems = subsystemsStartingOnCurrentThread.get();
-		if (subsystems.contains(target)) {
+		// Protect against re-entry now that cycles are supported.
+		if (!LockingStrategy.set(State.STARTING, target)) {
 			return null;
 		}
-		subsystems.add(target);
-	    try {
-	    	return doRun();
-	    }
-	    finally {
-	    	subsystems.remove(target);
-	    }
+		try {
+			Collection<BasicSubsystem> subsystems;
+			// We are now protected against re-entry.
+			// If necessary, install the dependencies.
+	    	if (State.INSTALLING.equals(target.getState()) && !Utils.isProvisionDependenciesInstall(target)) {
+	    		// Acquire the global write lock while installing dependencies.
+				LockingStrategy.writeLock();
+				try {
+					// We are now protected against installs, starts, stops, and uninstalls.
+		    		// We need a separate coordination when installing 
+					// dependencies because cleaning up the temporary export 
+					// sharing policies must be done while holding the write lock.
+		    		Coordination c = Utils.createCoordination(target);
+		    		try {
+		    			installDependencies(target, c);
+		    			// Associated subsystems must be computed after all dependencies 
+						// are installed because some of the dependencies may be 
+						// subsystems. This is safe to do while only holding the read
+						// lock since we know that nothing can be added or removed.
+						subsystems = computeAffectedSubsystems(target);
+						for (BasicSubsystem subsystem : subsystems) {
+							if (State.INSTALLING.equals(subsystem.getState())
+									&& !Utils.isProvisionDependenciesInstall(subsystem)) {
+								installDependencies(subsystem, c);
+							}
+						}
+						// Downgrade to the read lock in order to prevent 
+		    			// installs and uninstalls but allow starts and stops.
+						LockingStrategy.readLock();
+		    		}
+		    		catch (Throwable t) {
+		    			c.fail(t);
+		    		}
+		    		finally {
+		    			// This will clean up the temporary export sharing
+		    			// policies. Must be done while holding the write lock.
+		    			c.end();
+		    		}
+				}
+				finally {
+					// Release the global write lock as soon as possible.
+					LockingStrategy.writeUnlock();
+				}
+	    	}
+	    	else {
+	    		// Acquire the read lock in order to prevent installs and
+	    		// uninstalls but allow starts and stops.
+	    		LockingStrategy.readLock();
+	    	}
+	    	try {
+	    		// We now hold the read lock and are protected against installs
+	    		// and uninstalls.
+	    		if (Restriction.INSTALL_ONLY.equals(restriction)) {
+					return null;
+				}
+	    		// Compute associated subsystems here in case (1) they weren't
+	    		// computed previously while holding the write lock or (2) they
+	    		// were computed previously and more were subsequently added. 
+				// This is safe to do while only holding the read lock since we
+				// know that nothing can be added or removed.
+				subsystems = computeAffectedSubsystems(target);
+				// Acquire the global mutual exclusion lock while acquiring the
+				// state change locks of affected subsystems.
+				LockingStrategy.lock();
+				try {
+					// We are now protected against cycles.
+					// Acquire the state change locks of affected subsystems.
+					LockingStrategy.lock(subsystems);
+				}
+				finally {
+					// Release the global mutual exclusion lock as soon as possible.
+					LockingStrategy.unlock();
+				}
+				Coordination coordination = this.coordination;
+				try {
+					coordination = createCoordination();
+					// We are now protected against other starts and stops of the affected subsystems.
+					if (!isTargetStartable(instigator, requestor, target)) {
+						return null;
+					}
+					
+					// Resolve if necessary.
+					if (State.INSTALLED.equals(target.getState()))
+						resolve(instigator, target, target, coordination, subsystems);
+					if (Restriction.RESOLVE_ONLY.equals(restriction))
+						return null;
+					target.setState(State.STARTING);
+					// Be sure to set the state back to RESOLVED if starting fails.
+					coordination.addParticipant(new Participant() {
+						@Override
+						public void ended(Coordination coordination) throws Exception {
+							// Nothing.
+						}
+
+						@Override
+						public void failed(Coordination coordination) throws Exception {
+							target.setState(State.RESOLVED);
+						}
+					});
+					for (BasicSubsystem subsystem : subsystems) {
+						if (!target.equals(subsystem)) {
+							startSubsystemResource(subsystem, coordination);
+						}
+					}
+					List<Resource> resources = new ArrayList<Resource>(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target));
+					SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader();
+					if (header != null)
+						Collections.sort(resources, new StartResourceComparator(header));
+					for (Resource resource : resources)
+						startResource(resource, coordination);
+					target.setState(State.ACTIVE);
+					
+				}
+				catch (Throwable t) {
+					// We catch exceptions and fail the coordination here to
+					// ensure we are still holding the state change locks when
+					// the participant sets the state to RESOLVED.
+					coordination.fail(t);
+				}
+				finally {
+					try {
+						// Don't end a coordination that was not begun as part
+						// of this start action.
+						if (coordination.getName().equals(Utils.computeCoordinationName(target))) {
+							coordination.end();
+						}
+					}
+					finally {
+						// Release the state change locks of affected subsystems.
+						LockingStrategy.unlock(subsystems);
+					}
+				}
+	    	}
+	    	finally {
+				// Release the read lock.
+				LockingStrategy.readUnlock();
+			}
+		}
+		catch (CoordinationException e) {
+			Throwable t = e.getCause();
+			if (t == null) {
+				throw new SubsystemException(e);
+			}
+			if (t instanceof SecurityException) {
+				throw (SecurityException)t;
+			}
+			if (t instanceof SubsystemException) {
+				throw (SubsystemException)t;
+			}
+			throw new SubsystemException(t);
+		}
+		finally {
+			// Protection against re-entry no longer required.
+			LockingStrategy.unset(State.STARTING, target);
+		}
+		return null;
 	}
 
 	private static Collection<Bundle> getBundles(BasicSubsystem subsystem) {
@@ -233,41 +361,21 @@ public class StartAction extends Abstrac
 			subsystem.setState(State.RESOLVED);
 	}
 	
-	private static void resolveSubsystems(BasicSubsystem subsystem, Coordination coordination) {
-		//resolve dependencies to ensure framework resolution succeeds
-		Subsystems subsystems = Activator.getInstance().getSubsystems();
-		for (Resource dep : subsystems.getResourcesReferencedBy(subsystem)) {
-			if (dep instanceof BasicSubsystem 
-					&& !subsystems.getChildren(subsystem).contains(dep)) {
-				resolveSubsystem((BasicSubsystem)dep, coordination);
-			}
-			else if (dep instanceof BundleRevision
-					&& !subsystem.getConstituents().contains(dep)) {
-				for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent(
-						new BundleConstituent(null, (BundleRevision)dep))) {
-					resolveSubsystem(constituentOf, coordination);
-				}
-			}
-		}
-		for (Subsystem child : subsystems.getChildren(subsystem)) {
-			resolveSubsystem((BasicSubsystem)child, coordination);
-		}
-		for (Resource resource : subsystem.getResource().getSharedContent()) {
-			for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent(
-					resource instanceof BundleRevision ? new BundleConstituent(null, (BundleRevision)resource) : resource)) {
-				resolveSubsystem(constituentOf, coordination);
-			}
+	private static void resolveSubsystems(BasicSubsystem instigator, BasicSubsystem target, Coordination coordination, Collection<BasicSubsystem> subsystems) throws Exception {
+		for (BasicSubsystem subsystem : subsystems) {
+			resolveSubsystem(instigator, target, subsystem, coordination);
 		}
 	}
 	
-	private static void resolveSubsystem(BasicSubsystem subsystem, Coordination coordination) {
+	private static void resolveSubsystem(BasicSubsystem instigator, BasicSubsystem target, BasicSubsystem subsystem, Coordination coordination) throws Exception {
 		State state = subsystem.getState();
 		if (State.INSTALLED.equals(state)) {
-			AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, coordination, true));
-		}
-		else if (State.INSTALLING.equals(state)
-				&& !Utils.isProvisionDependenciesInstall(subsystem)) {
-			AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, coordination, true));
+			if (target.equals(subsystem)) {
+				resolve(instigator, target, subsystem, coordination, Collections.<BasicSubsystem>emptyList());
+			}
+			else {
+				AccessController.doPrivileged(new StartAction(instigator, target, subsystem, coordination, Restriction.RESOLVE_ONLY));
+			}
 		}
 	}
 	
@@ -282,7 +390,7 @@ public class StartAction extends Abstrac
 		}
 	}
 
-	private static void resolve(BasicSubsystem subsystem, Coordination coordination) {
+	private static void resolve(BasicSubsystem instigator, BasicSubsystem target, BasicSubsystem subsystem, Coordination coordination, Collection<BasicSubsystem> subsystems) {
 		emitResolvingEvent(subsystem);
 		try {
 			// The root subsystem should follow the same event pattern for
@@ -291,7 +399,7 @@ public class StartAction extends Abstrac
 			// actually doing the resolution work.
 			if (!subsystem.isRoot()) {
 				setExportIsolationPolicy(subsystem, coordination);
-				resolveSubsystems(subsystem, coordination);
+				resolveSubsystems(instigator, target, coordination, subsystems);
 				resolveBundles(subsystem);
 			}
 			emitResolvedEvent(subsystem);
@@ -491,8 +599,11 @@ public class StartAction extends Abstrac
         return false;
     }
 
-	private void startSubsystemResource(Resource resource, Coordination coordination) throws IOException {
+	private void startSubsystemResource(Resource resource, final Coordination coordination) throws IOException {
 		final BasicSubsystem subsystem = (BasicSubsystem)resource;
+		if (!isTargetStartable(instigator, target, subsystem)) {
+			 return;
+		}
 		// Subsystems that are content resources of another subsystem must have
 		// their autostart setting set to started.
 		if (Utils.isContent(this.target, subsystem))
@@ -560,7 +671,7 @@ public class StartAction extends Abstrac
 		logger.error(diagnostics.toString());
 	}
 	
-	private static void setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(Coordination coordination) throws InvalidSyntaxException {
+	static void setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(Coordination coordination) throws InvalidSyntaxException {
 		for (BasicSubsystem subsystem : Activator.getInstance().getSubsystems().getSubsystems()) {
 			if (!State.INSTALLING.equals(subsystem.getState())
 					|| Utils.isProvisionDependenciesInstall(subsystem)) {

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java Thu Jan 28 19:27:49 2016
@@ -17,6 +17,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 
 import org.apache.aries.subsystem.ContentHandler;
@@ -42,54 +43,102 @@ public class StopAction extends Abstract
 
 	@Override
 	public Object run() {
-		checkRoot();
-		State state = target.getState();
-		if (EnumSet.of(State.INSTALLED, State.RESOLVED).contains(state))
+		// Protect against re-entry now that cycles are supported.
+		if (!LockingStrategy.set(State.STOPPING, target)) {
 			return null;
-		else if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLING, State.UNINSTALLED).contains(state))
-			throw new IllegalStateException("Cannot stop from state " + state);
-		else if (State.INSTALLING.equals(state) && !Utils.isProvisionDependenciesInstall(target)) {
-			return null;
-		}
-		else if (EnumSet.of(State.INSTALLING, State.RESOLVING, State.STARTING, State.STOPPING).contains(state)) {
-			waitForStateChange(state);
-			return new StopAction(requestor, target, disableRootCheck).run();
 		}
-		target.setState(State.STOPPING);
-		List<Resource> resources = new ArrayList<Resource>(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target));
-		SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader();
-		if (header != null) {
-			Collections.sort(resources, new StartResourceComparator(target.getSubsystemManifest().getSubsystemContentHeader()));
-			Collections.reverse(resources);
-		}
-		for (Resource resource : resources) {
-			// Don't stop the region context bundle.
-			if (Utils.isRegionContextBundle(resource))
-				continue;
+		try {
+			// We are now protected against re-entry.
+			// Acquire the global read lock to prevent installs and uninstalls
+			// but allow starts and stops.
+			LockingStrategy.readLock();
 			try {
-				stopResource(resource);
+				// We are now protected against installs and uninstalls.
+				checkRoot();
+				// Compute affected subsystems. This is safe to do while only 
+				// holding the read lock since we know that nothing can be added 
+				// or removed.
+				LinkedHashSet<BasicSubsystem> subsystems = new LinkedHashSet<BasicSubsystem>();
+				subsystems.add(target);
+				List<Resource> resources = new ArrayList<Resource>(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target));
+				for (Resource resource : resources) {
+					if (resource instanceof BasicSubsystem) {
+						subsystems.add((BasicSubsystem)resource);
+					}
+				}
+				// Acquire the global mutual exclusion lock while acquiring the
+				// state change locks of affected subsystems.
+				LockingStrategy.lock();
+				try {
+					// We are now protected against cycles.
+					// Acquire the state change locks of affected subsystems.
+					LockingStrategy.lock(subsystems);
+				}
+				finally {
+					// Release the global mutual exclusion lock as soon as possible.
+					LockingStrategy.unlock();
+				}
+				try {
+					// We are now protected against other starts and stops of the affected subsystems.
+					State state = target.getState();
+					if (EnumSet.of(State.INSTALLED, State.INSTALLING, State.RESOLVED).contains(state)) {
+						// INSTALLING is included because a subsystem may
+						// persist in this state without being locked when
+						// apache-aries-provision-dependencies:=resolve.
+						return null;
+					}
+					else if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLED).contains(state)) {
+						throw new IllegalStateException("Cannot stop from state " + state);
+					}
+					target.setState(State.STOPPING);
+					SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader();
+					if (header != null) {
+						Collections.sort(resources, new StartResourceComparator(target.getSubsystemManifest().getSubsystemContentHeader()));
+						Collections.reverse(resources);
+					}
+					for (Resource resource : resources) {
+						// Don't stop the region context bundle.
+						if (Utils.isRegionContextBundle(resource))
+							continue;
+						try {
+							stopResource(resource);
+						}
+						catch (Exception e) {
+							logger.error("An error occurred while stopping resource " + resource + " of subsystem " + target, e);
+						}
+					}
+					// TODO Can we automatically assume it actually is resolved?
+					target.setState(State.RESOLVED);
+					try {
+						synchronized (target) {
+							target.setDeploymentManifest(new DeploymentManifest(
+									target.getDeploymentManifest(),
+									null,
+									target.isAutostart(),
+									target.getSubsystemId(),
+									SubsystemIdentifier.getLastId(),
+									target.getLocation(),
+									false,
+									false));
+						}
+					}
+					catch (Exception e) {
+						throw new SubsystemException(e);
+					}
+				}
+				finally {
+					// Release the state change locks of affected subsystems.
+					LockingStrategy.unlock(subsystems);
+				}
 			}
-			catch (Exception e) {
-				logger.error("An error occurred while stopping resource " + resource + " of subsystem " + target, e);
-			}
-		}
-		// TODO Can we automatically assume it actually is resolved?
-		target.setState(State.RESOLVED);
-		try {
-			synchronized (target) {
-				target.setDeploymentManifest(new DeploymentManifest(
-						target.getDeploymentManifest(),
-						null,
-						target.isAutostart(),
-						target.getSubsystemId(),
-						SubsystemIdentifier.getLastId(),
-						target.getLocation(),
-						false,
-						false));
+			finally {
+				// Release the read lock.
+				LockingStrategy.readUnlock();
 			}
 		}
-		catch (Exception e) {
-			throw new SubsystemException(e);
+		finally {
+			// Protection against re-entry no longer required.
+			LockingStrategy.unset(State.STOPPING, target);
 		}
 		return null;
 	}

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java Thu Jan 28 19:27:49 2016
@@ -87,18 +87,18 @@ public class SubsystemResource implement
 	private final Collection<Resource> sharedContent = new HashSet<Resource>();
 	private final Collection<Resource> sharedDependencies = new HashSet<Resource>();
 
-	public SubsystemResource(String location, IDirectory content, BasicSubsystem parent) throws URISyntaxException, IOException, ResolutionException, BundleException, InvalidSyntaxException {
-		this(new RawSubsystemResource(location, content, parent), parent);
+	public SubsystemResource(String location, IDirectory content, BasicSubsystem parent, Coordination coordination) throws URISyntaxException, IOException, ResolutionException, BundleException, InvalidSyntaxException {
+		this(new RawSubsystemResource(location, content, parent), parent, coordination);
 	}
 
-	public SubsystemResource(RawSubsystemResource resource, BasicSubsystem parent) throws IOException, BundleException, InvalidSyntaxException, URISyntaxException {
+	public SubsystemResource(RawSubsystemResource resource, BasicSubsystem parent, Coordination coordination) throws IOException, BundleException, InvalidSyntaxException, URISyntaxException {
 		this.parent = parent;
 		this.resource = resource;
 		computeContentResources(resource.getDeploymentManifest());
 		capabilities = computeCapabilities();
 		if (this.getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isInstall()) {
 		    /* compute dependencies now only if we intend to provision them during install */
-	        computeDependencies(resource.getDeploymentManifest());		    
+	        computeDependencies(resource.getDeploymentManifest(), coordination);		    
 		}
 		deploymentManifest = computeDeploymentManifest();
 	}
@@ -122,7 +122,7 @@ public class SubsystemResource implement
 		capabilities = computeCapabilities();
         if (getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isInstall()) {
             /* compute dependencies if we intend to provision them during install */
-            computeDependencies(resource.getDeploymentManifest());          
+            computeDependencies(resource.getDeploymentManifest(), null);          
         }
 	}
 
@@ -367,9 +367,9 @@ public class SubsystemResource implement
 		}
 	}
 
-	void computeDependencies(DeploymentManifest manifest) {
+	void computeDependencies(DeploymentManifest manifest, Coordination coordination) {
 	    if (manifest == null) {
-	        computeDependencies(getSubsystemManifest());
+	        computeDependencies(getSubsystemManifest(), coordination);
 	    }
 	    else {
 	        ProvisionResourceHeader header = manifest.getProvisionResourceHeader();
@@ -384,9 +384,13 @@ public class SubsystemResource implement
 	    }
 	}
 
-	private void computeDependencies(SubsystemManifest manifest)  {
+	private void computeDependencies(SubsystemManifest manifest, Coordination coordination)  {
 		SubsystemContentHeader contentHeader = manifest.getSubsystemContentHeader();
 		try {
+			// The following line is necessary in order to ensure that the
+			// export sharing policies of composites are in place for capability
+			// validation.
+			StartAction.setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(coordination);
 			Map<Resource, List<Wire>> resolution = Activator.getInstance().getResolver().resolve(createResolveContext());
 			setImportIsolationPolicy(resolution);
 			for (Map.Entry<Resource, List<Wire>> entry : resolution.entrySet()) {

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java Thu Jan 28 19:27:49 2016
@@ -83,15 +83,15 @@ public class SubsystemResourceInstaller
 		}
 		Comparator<Resource> comparator = new InstallResourceComparator();
 		// Install dependencies first if appropriate...
-		if (Utils.isProvisionDependenciesInstall(subsystem)) {
-		    new InstallDependencies().install(subsystem, this.subsystem, coordination);
+		if (!subsystem.isRoot() && Utils.isProvisionDependenciesInstall(subsystem)) {
+			new InstallDependencies().install(subsystem, this.subsystem, coordination);
 		}
 		// ...followed by content.
 		// Simulate installation of shared content so that necessary relationships are established.
-        for (Resource content : subsystem.getResource().getSharedContent()) {
-            ResourceInstaller.newInstance(coordination, content, subsystem).install();
-        }
-        // Now take care of the installable content.
+		for (Resource content : subsystem.getResource().getSharedContent()) {
+			ResourceInstaller.newInstance(coordination, content, subsystem).install();
+		}
+		// Now take care of the installable content.
 		if (State.INSTALLING.equals(subsystem.getState())) {
 			List<Resource> installableContent = new ArrayList<Resource>(subsystem.getResource().getInstallableContent());
 			Collections.sort(installableContent, comparator);
@@ -101,7 +101,7 @@ public class SubsystemResourceInstaller
 		// Only brand new subsystems should have acquired the INSTALLING state,
 		// in which case an INSTALLED event must be propagated.
 		if (State.INSTALLING.equals(subsystem.getState()) && 
-		        Utils.isProvisionDependenciesInstall(subsystem)) {
+				Utils.isProvisionDependenciesInstall(subsystem)) {
 			subsystem.setState(State.INSTALLED);
 		}
 		else {
@@ -112,7 +112,7 @@ public class SubsystemResourceInstaller
 	}
 
 	private BasicSubsystem installRawSubsystemResource(RawSubsystemResource resource) throws Exception {
-		SubsystemResource subsystemResource = new SubsystemResource(resource, provisionTo);
+		SubsystemResource subsystemResource = new SubsystemResource(resource, provisionTo, coordination);
 		return installSubsystemResource(subsystemResource);
 	}
 

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java Thu Jan 28 19:27:49 2016
@@ -24,22 +24,35 @@ public class UninstallAction extends Abs
 	
 	@Override
 	public Object run() {
-		checkValid();
-		checkRoot();
-		State state = target.getState();
-		if (EnumSet.of(State.UNINSTALLED).contains(state))
+		// Protect against re-entry now that cycles are supported.
+		if (!LockingStrategy.set(State.STOPPING, target)) {
 			return null;
-		else if ((State.INSTALLING.equals(state) && Utils.isProvisionDependenciesInstall(target))
-				|| EnumSet.of(State.RESOLVING, State.STARTING, State.STOPPING, State.UNINSTALLING).contains(state)) {
-			waitForStateChange(state);
-			target.uninstall();
 		}
-		else if (state.equals(State.ACTIVE)) {
-			new StopAction(requestor, target, disableRootCheck).run();
-			target.uninstall();
+		try {
+			// Acquire the global write lock to prevent all other operations until
+			// the installation is complete. There is no need to hold any other locks.
+			LockingStrategy.writeLock();
+			try {
+				checkRoot();
+				checkValid();
+				State state = target.getState();
+				if (EnumSet.of(State.UNINSTALLED).contains(state)) {
+					return null;
+				}
+				if (state.equals(State.ACTIVE)) {
+					new StopAction(requestor, target, disableRootCheck).run();
+				}
+				ResourceUninstaller.newInstance(requestor, target).uninstall();
+			}
+			finally {
+				// Release the global write lock.
+				LockingStrategy.writeUnlock();
+			}
+		}
+		finally {
+			// Protection against re-entry no longer required.
+			LockingStrategy.unset(State.STOPPING, target);
 		}
-		else
-			ResourceUninstaller.newInstance(requestor, target).uninstall();
 		return null;
 	}
 }

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java Thu Jan 28 19:27:49 2016
@@ -30,6 +30,7 @@ import java.util.Set;
 import org.apache.aries.subsystem.core.archive.DynamicImportPackageHeader;
 import org.apache.aries.subsystem.core.archive.DynamicImportPackageRequirement;
 import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent;
+import org.apache.aries.subsystem.core.internal.StartAction.Restriction;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph.FilteredRegion;
 import org.eclipse.equinox.region.RegionDigraphVisitor;
@@ -111,7 +112,7 @@ public class WovenClassListener implemen
 			// package imports to the sharing policy in order to minimize 
 			// unpredictable wirings. Resolving the scoped subsystem will also
 			// resolve all of the unscoped subsystems in the region.
-			AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, true));
+			AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, Restriction.RESOLVE_ONLY));
 		}
 		Bundle systemBundle = context.getBundle(org.osgi.framework.Constants.SYSTEM_BUNDLE_LOCATION);
 		FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);

Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java (original)
+++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java Thu Jan 28 19:27:49 2016
@@ -1443,7 +1443,7 @@ public class Aries1383Test extends Subsy
 		assertConstituent(f1, "b2");
 		startSubsystem(f1, false);
 		stoppableSubsystems.add(f1);
-		assertState(State.RESOLVED, f2);
+		assertState(EnumSet.of(State.RESOLVED, State.ACTIVE), f2);
 		assertConstituent(s1, "b4");
 		assertConstituent(s1, "b5");
 		assertConstituent(s1, "b6");
@@ -1821,7 +1821,33 @@ public class Aries1383Test extends Subsy
 	
 	public static class TestServiceImpl implements TestService {}
 	
-	public static class TestServiceActivator implements BundleActivator {
+	public static class TestServiceClientActivator implements BundleActivator {
+		@Override
+		public void start(BundleContext context) throws Exception {
+			ServiceReference<TestService> ref = null;
+			for (int i = 0; i < 80; i++) { // 20 seconds with 250ms sleep.
+				ref = context.getServiceReference(TestService.class);
+				if (ref == null) {
+					Thread.sleep(250);
+					continue;
+				}
+				break;
+			}
+			try {
+				TestService service = context.getService(ref);
+				service.getClass();
+			}
+			finally {
+				context.ungetService(ref);
+			}
+		}
+
+		@Override
+		public void stop(BundleContext context) throws Exception {
+		}
+	}
+	
+	public static class TestServiceImplActivator implements BundleActivator {
 		private ServiceRegistration<TestService> reg;
 		
 		@Override
@@ -2309,7 +2335,7 @@ public class Aries1383Test extends Subsy
 														.importPackage("org.osgi.framework")
 														.requireBundle("api")
 														.clazz(TestServiceImpl.class)
-														.activator(TestServiceActivator.class)
+														.activator(TestServiceImplActivator.class)
 														.build())
 										.build(),
 								false);
@@ -2397,4 +2423,297 @@ public class Aries1383Test extends Subsy
 		startFutures.get(1).get();
 		startFutures.get(2).get();
 	}
+	
+	@Test
+	public void testComApiComImplComClient() throws Exception {
+		Subsystem root = getRootSubsystem();
+		final Subsystem shared = installSubsystem(
+				root,
+				"shared", 
+				new SubsystemArchiveBuilder()
+						.symbolicName("shared")
+						.type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE 
+								+ ';' 
+								+ AriesProvisionDependenciesDirective.RESOLVE.toString()
+								+ ';'
+								+ SubsystemConstants.PROVISION_POLICY_DIRECTIVE
+								+ ":="
+								+ SubsystemConstants.PROVISION_POLICY_ACCEPT_DEPENDENCIES)
+						.importPackage("org.osgi.framework")
+						.build(),
+				false
+		);
+		uninstallableSubsystems.add(shared);
+		shared.start();
+		stoppableSubsystems.add(shared);
+		@SuppressWarnings("unchecked")
+		Callable<Subsystem>[] installCallables = new Callable[] {
+				new Callable<Subsystem>() {
+					@Override
+					public Subsystem call() throws Exception {
+						Subsystem result = installSubsystem(
+								shared,
+								"client", 
+								new SubsystemArchiveBuilder()
+										.symbolicName("client")
+										.type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE)
+										.content("client;version=\"[0,0]\"")
+										.importPackage("org.osgi.framework")
+										.requireCapability("osgi.service;filter:=\"(objectClass="
+												+ TestService.class.getName()
+												+ ")\";effective:=active")
+										.importService(TestService.class.getName())
+										.requireBundle("api,impl")
+										.bundle(
+												"client", 
+												new BundleArchiveBuilder()
+														.symbolicName("client")
+														.importPackage("org.osgi.framework")
+														.requireCapability("osgi.service;filter:=\"(objectClass="
+																+ TestService.class.getName()
+																+ ")\";effective:=active")
+														.requireBundle("api,impl")
+														.activator(TestServiceClientActivator.class)
+														.build())
+										.build(),
+								false);
+						return result;
+					}
+					
+				},
+				new Callable<Subsystem>() {
+					@Override
+					public Subsystem call() throws Exception {
+						Subsystem result = installSubsystem(
+								shared,
+								"impl", 
+								new SubsystemArchiveBuilder()
+										.symbolicName("impl")
+										.type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE)
+										.content("impl;version=\"[0,0]\"")
+										.provideCapability("osgi.service;objectClass:List<String>=\"" 
+												+ TestService.class.getName() 
+												+ "\"")
+										.exportService(TestService.class.getName())
+										.importPackage("org.osgi.framework")
+										.requireBundle("api")
+										.provideCapability("osgi.wiring.bundle;osgi.wiring.bundle=impl;bundle-version=0")
+										.bundle(
+												"impl", 
+												new BundleArchiveBuilder()
+														.symbolicName("impl")
+														.provideCapability("osgi.service;objectClass:List<String>=\"" 
+																+ TestService.class.getName() 
+																+ "\"")
+														.importPackage("org.osgi.framework")
+														.requireBundle("api")
+														.clazz(TestServiceImpl.class)
+														.activator(TestServiceImplActivator.class)
+														.build())
+										.build(),
+								false);
+						return result;
+					}
+					
+				},
+				new Callable<Subsystem>() {
+					@Override
+					public Subsystem call() throws Exception {
+						Subsystem result = installSubsystem(
+								shared,
+								"api", 
+								new SubsystemArchiveBuilder()
+										.symbolicName("api")
+										.type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE)
+										.content("api;version=\"[0,0]\"")
+										.exportPackage("org.apache.aries.subsystem.itests.defect")
+										.provideCapability("osgi.wiring.bundle;osgi.wiring.bundle=api;bundle-version=0")
+										.bundle(
+												"api", 
+												new BundleArchiveBuilder()
+														.symbolicName("api")
+														.exportPackage("org.apache.aries.subsystem.itests.defect")
+														.clazz(TestService.class)
+														.build())
+										.build(),
+								false);
+						return result;
+					}
+					
+				}
+		};
+		ExecutorService executor = Executors.newFixedThreadPool(3);
+		List<Future<Subsystem>> installFutures = executor.invokeAll(Arrays.asList(installCallables));
+		final Subsystem client = installFutures.get(0).get();
+		final Subsystem impl = installFutures.get(1).get();
+		final Subsystem api = installFutures.get(2).get();
+		@SuppressWarnings("unchecked")
+		Callable<Void>[] startCallables = new Callable[] {
+			new Callable<Void>() {
+				@Override
+				public Void call() throws Exception {
+					client.start();
+					assertEvent(client, State.INSTALLED, subsystemEvents.poll(client.getSubsystemId(), 5000));
+					assertEvent(client, State.RESOLVING, subsystemEvents.poll(client.getSubsystemId(), 5000));
+					assertEvent(client, State.RESOLVED, subsystemEvents.poll(client.getSubsystemId(), 5000));
+					assertEvent(client, State.STARTING, subsystemEvents.poll(client.getSubsystemId(), 5000));
+					assertEvent(client, State.ACTIVE, subsystemEvents.poll(client.getSubsystemId(), 5000));
+					return null;
+				}
+			},
+			new Callable<Void>() {
+				@Override
+				public Void call() throws Exception {
+					impl.start();
+					assertEvent(impl, State.INSTALLED, subsystemEvents.poll(impl.getSubsystemId(), 5000));
+					assertEvent(impl, State.RESOLVING, subsystemEvents.poll(impl.getSubsystemId(), 5000));
+					assertEvent(impl, State.RESOLVED, subsystemEvents.poll(impl.getSubsystemId(), 5000));
+					assertEvent(impl, State.STARTING, subsystemEvents.poll(impl.getSubsystemId(), 5000));
+					assertEvent(impl, State.ACTIVE, subsystemEvents.poll(impl.getSubsystemId(), 5000));
+					return null;
+				}
+			},
+			new Callable<Void>() {
+				@Override
+				public Void call() throws Exception {
+					api.start();
+					assertEvent(api, State.INSTALLED, subsystemEvents.poll(api.getSubsystemId(), 5000));
+					assertEvent(api, State.RESOLVING, subsystemEvents.poll(api.getSubsystemId(), 5000));
+					assertEvent(api, State.RESOLVED, subsystemEvents.poll(api.getSubsystemId(), 5000));
+					assertEvent(api, State.STARTING, subsystemEvents.poll(api.getSubsystemId(), 5000));
+					assertEvent(api, State.ACTIVE, subsystemEvents.poll(api.getSubsystemId(), 5000));
+					return null;
+				}
+			}
+		};
+		List<Future<Void>> startFutures = executor.invokeAll(Arrays.asList(startCallables));
+		startFutures.get(0).get();
+		startFutures.get(1).get();
+		startFutures.get(2).get();
+	}
+	
+	@Test
+	public void testAutoInstallDependenciesComposite() throws Exception {
+		Subsystem root = getRootSubsystem();
+		Subsystem b = installSubsystem(
+				root,
+				"b", 
+				new SubsystemArchiveBuilder()
+						.symbolicName("b")
+						.type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE 
+								+ ';' 
+								+ AriesProvisionDependenciesDirective.RESOLVE.toString())
+						.content("a;version=\"[0,0]\"")
+						.exportPackage("a")
+						.importPackage("b")
+						.bundle(
+								"a", 
+								new BundleArchiveBuilder()
+								.symbolicName("a")
+								.importPackage("b")
+								.exportPackage("a")
+								.build())
+						.bundle(
+								"b", 
+								new BundleArchiveBuilder()
+								.symbolicName("b")
+								.exportPackage("b")
+								.build())
+						.build(),
+				false
+		);
+		uninstallableSubsystems.add(b);
+		try {
+			Subsystem a = installSubsystem(
+					root,
+					"a", 
+					new SubsystemArchiveBuilder()
+							.symbolicName("a")
+							.type(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION)
+							.bundle(
+									"a", 
+									new BundleArchiveBuilder()
+									.symbolicName("a")
+									.importPackage("a")
+									.build())
+							.build(),
+					true
+			);
+			uninstallableSubsystems.add(a);
+			assertState(EnumSet.of(State.INSTALLED, State.RESOLVED), b);
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+			fail("Subsystem should have installed");
+		}
+	}
+	
+	@Test
+	public void testAutoInstallDependenciesFeature() throws Exception {
+		Subsystem root = getRootSubsystem();
+		Subsystem shared = installSubsystem(
+				root,
+				"shared", 
+				new SubsystemArchiveBuilder()
+						.symbolicName("shared")
+						.type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE 
+								+ ';' 
+								+ AriesProvisionDependenciesDirective.RESOLVE.toString()
+								+ ';'
+								+ SubsystemConstants.PROVISION_POLICY_DIRECTIVE
+								+ ":="
+								+ SubsystemConstants.PROVISION_POLICY_ACCEPT_DEPENDENCIES)
+						.build(),
+				false
+		);
+		uninstallableSubsystems.add(shared);
+		startSubsystem(shared, false);
+		Subsystem b = installSubsystem(
+				shared,
+				"b", 
+				new SubsystemArchiveBuilder()
+						.symbolicName("b")
+						.type(SubsystemConstants.SUBSYSTEM_TYPE_FEATURE)
+						.content("a")
+						.bundle(
+								"a", 
+								new BundleArchiveBuilder()
+								.symbolicName("a")
+								.importPackage("b")
+								.exportPackage("a")
+								.build())
+						.bundle(
+								"b", 
+								new BundleArchiveBuilder()
+								.symbolicName("b")
+								.exportPackage("b")
+								.build())
+						.build(),
+				false
+		);
+		try {
+			installSubsystem(
+					shared,
+					"a", 
+					new SubsystemArchiveBuilder()
+							.symbolicName("a")
+							.type(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION
+									+ ';' 
+									+ AriesProvisionDependenciesDirective.INSTALL.toString())
+							.bundle(
+									"a", 
+									new BundleArchiveBuilder()
+									.symbolicName("a")
+									.importPackage("a")
+									.build())
+							.build(),
+					true
+			);
+			assertState(EnumSet.of(State.INSTALLED, State.RESOLVED), b);
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+			fail("Subsystem should have installed");
+		}
+	}
 }

Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java?rev=1727424&r1=1727423&r2=1727424&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java (original)
+++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java Thu Jan 28 19:27:49 2016
@@ -49,6 +49,10 @@ public class SubsystemArchiveBuilder {
 		return header(Constants.EXPORT_PACKAGE, value);
 	}
 	
+	public SubsystemArchiveBuilder exportService(String value) {
+		return header(SubsystemConstants.SUBSYSTEM_EXPORTSERVICE, value);
+	}
+	
 	public SubsystemArchiveBuilder file(String name, InputStream value) {
 		bundle.add(name, value);
 		return this;
@@ -63,14 +67,22 @@ public class SubsystemArchiveBuilder {
 		return header(Constants.IMPORT_PACKAGE, value);
 	}
 	
-	public SubsystemArchiveBuilder requireBundle(String value) {
-		return header(Constants.REQUIRE_BUNDLE, value);
+	public SubsystemArchiveBuilder importService(String value) {
+		return header(SubsystemConstants.SUBSYSTEM_IMPORTSERVICE, value);
 	}
 	
 	public SubsystemArchiveBuilder provideCapability(String value) {
 		return header(Constants.PROVIDE_CAPABILITY, value);
 	}
 	
+	public SubsystemArchiveBuilder requireBundle(String value) {
+		return header(Constants.REQUIRE_BUNDLE, value);
+	}
+	
+	public SubsystemArchiveBuilder requireCapability(String value) {
+		return header(Constants.REQUIRE_CAPABILITY, value);
+	}
+	
 	public SubsystemArchiveBuilder subsystem(String name, InputStream value) {
 		return file(name + ESA_EXTENSION, value);
 	}