Author: jwross Date: Fri Sep 7 18:34:15 2012 New Revision: 1382116 URL: http://svn.apache.org/viewvc?rev=1382116&view=rev Log: ARIES-922: If a bundle is uninstalled from the root region while the bundle event hook is not registered but remains in persistent memory, root initialization will no longer fail with an NPE. The missing bundle is simply removed from memory.
A failure will still occur if the same thing happens for a non-root subsystem. I left it that way for now for fail-fast purposes since it would be very bad for someone to muck around with the deployed content of a non-root subsystem outside of the subsystems API. It might be reasonable in the future to make this more intelligent by checking to see if the missing resource is optional or trying to re-deploy it from a repository. Also added a new test. Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/archive/DeployedContentHeader.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/AriesSubsystem.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/RootSubsystemTest.java Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/archive/DeployedContentHeader.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/archive/DeployedContentHeader.java?rev=1382116&r1=1382115&r2=1382116&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/archive/DeployedContentHeader.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/archive/DeployedContentHeader.java Fri Sep 7 18:34:15 2012 @@ -85,6 +85,27 @@ public class DeployedContentHeader imple } @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof Clause)) + return false; + Clause that = (Clause)o; + return getSymbolicName().equals(that.getSymbolicName()) + && getDeployedVersion().equals(that.getDeployedVersion()) + && getType().equals(that.getType()); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + getSymbolicName().hashCode(); + result = 31 * result + getDeployedVersion().hashCode(); + result = 31 * result + getType().hashCode(); + return result; + } + + @Override public Attribute getAttribute(String name) { Parameter result = parameters.get(name); if (result instanceof Attribute) Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/AriesSubsystem.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/AriesSubsystem.java?rev=1382116&r1=1382115&r2=1382116&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/AriesSubsystem.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/AriesSubsystem.java Fri Sep 7 18:34:15 2012 @@ -358,6 +358,20 @@ public class AriesSubsystem implements R catch (Exception e) { throw new SubsystemException(e); } + Collection<DeployedContentHeader.Clause> missingResources = resource.getMissingResources(); + if (!missingResources.isEmpty()) { + if (isRoot()) + // We don't care if the root subsystem has missing resources + // because they are either (1) extraneous bundles outside of + // the subsystems API or (2) provisioned dependencies of + // other subsystems. Those that fall in the latter category + // will be detected by the dependent subsystems. + removedContent(missingResources); + else + // If a non-root subsystem has missing dependencies, let's + // fail fast for now. + throw new SubsystemException("Missing resources: " + missingResources); + } } return resource; } @@ -427,9 +441,17 @@ public class AriesSubsystem implements R DeployedContentHeader.Clause clause = header.getClause(resource); if (clause == null) return; + removedContent(Collections.singleton(clause)); + } + + void removedContent(Collection<DeployedContentHeader.Clause> content) { + DeploymentManifest manifest = getDeploymentManifest(); + DeployedContentHeader header = manifest.getDeployedContentHeader(); + if (header == null) + return; Collection<DeployedContentHeader.Clause> clauses = new ArrayList<DeployedContentHeader.Clause>(header.getClauses()); for (Iterator<DeployedContentHeader.Clause> i = clauses.iterator(); i.hasNext();) - if (clause.equals(i.next())) { + if (content.contains(i.next())) { i.remove(); break; } 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=1382116&r1=1382115&r2=1382116&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 Fri Sep 7 18:34:15 2012 @@ -46,6 +46,7 @@ import org.eclipse.equinox.region.Region import org.eclipse.equinox.region.RegionDigraph; import org.eclipse.equinox.region.RegionFilter; import org.eclipse.equinox.region.RegionFilterBuilder; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.InvalidSyntaxException; @@ -76,6 +77,7 @@ public class SubsystemResource implement private final Collection<Resource> installableContent = new HashSet<Resource>(); private final Collection<Resource> installableDependencies = new HashSet<Resource>(); private final Collection<Resource> mandatoryResources = new HashSet<Resource>(); + private final Collection<DeployedContentHeader.Clause> missingResources = new HashSet<DeployedContentHeader.Clause>(); private final Collection<Resource> optionalResources = new HashSet<Resource>(); private final AriesSubsystem parent; private final Repository preferredProviderRepository; @@ -159,6 +161,10 @@ public class SubsystemResource implement return resource.getLocation().getValue(); } + public Collection<DeployedContentHeader.Clause> getMissingResources() { + return missingResources; + } + public Collection<AriesSubsystem> getParents() { if (parent == null) { Header<?> header = getDeploymentManifest().getHeaders().get(DeploymentManifest.ARIESSUBSYSTEM_PARENTS); @@ -290,6 +296,10 @@ public class SubsystemResource implement sharedDependencies.add(resource); } + private void addMissingResource(DeployedContentHeader.Clause resource) { + missingResources.add(resource); + } + private void addValidCapabilities(Collection<Capability> from, Collection<Capability> to) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException { for (Capability c : from) if (isValid(c)) @@ -332,8 +342,9 @@ public class SubsystemResource implement for (DeployedContentHeader.Clause clause : header.getClauses()) { Resource resource = findContent(clause); if (resource == null) - throw new SubsystemException("Resource does not exist: " + clause); - addContentResource(resource); + addMissingResource(clause); + else + addContentResource(resource); } } } @@ -531,7 +542,10 @@ public class SubsystemResource implement if (resourceId != -1) { String type = clause.getType(); if (IdentityNamespace.TYPE_BUNDLE.equals(type) || IdentityNamespace.TYPE_FRAGMENT.equals(type)) { - return Activator.getInstance().getBundleContext().getBundle(0).getBundleContext().getBundle(resourceId).adapt(BundleRevision.class); + Bundle resource = Activator.getInstance().getBundleContext().getBundle(0).getBundleContext().getBundle(resourceId); + if (resource == null) + return null; + return resource.adapt(BundleRevision.class); } else return Activator.getInstance().getSubsystems().getSubsystemById(resourceId); Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/RootSubsystemTest.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/RootSubsystemTest.java?rev=1382116&r1=1382115&r2=1382116&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/RootSubsystemTest.java (original) +++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/RootSubsystemTest.java Fri Sep 7 18:34:15 2012 @@ -14,6 +14,7 @@ package org.apache.aries.subsystem.itests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; @@ -103,6 +104,34 @@ public class RootSubsystemTest extends S } @Test + public void testUninstallRootRegionBundleWithNoBundleEventHook() throws Exception { + // Install an extraneous bundle into the root region. The bundle will + // be recorded in the root subsystem's persistent memory. + Bundle bundleA = bundleContext.installBundle(new File(BUNDLE_A).toURI().toURL().toString()); + try { + Bundle core = getSubsystemCoreBundle(); + // Stop the subsystems bundle in order to unregister the bundle + // event hook. + core.stop(); + // Uninstall the bundle so it won't be there on restart. + bundleA.uninstall(); + try { + // Start the subsystems bundle and ensure the root subsystem + // recovers from the uninstalled bundle being in persistent + // memory. + core.start(); + } + catch (BundleException e) { + fail("Could not start subsystems bundle after uninstalling a root region bundle with no bundle event hook registered"); + } + } + finally { + if (Bundle.UNINSTALLED != bundleA.getState()) + bundleA.uninstall(); + } + } + + @Test public void testVersion() { assertEquals("Wrong root version", getRootSubsystem().getVersion(), Version.parseVersion("1.0.0")); }