/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.aries.subsystem.core.internal; import java.io.IOException; import java.security.AccessController; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import org.apache.aries.subsystem.ContentHandler; import org.apache.aries.subsystem.core.archive.ExportPackageCapability; import org.apache.aries.subsystem.core.archive.ExportPackageHeader; import org.apache.aries.subsystem.core.archive.ProvideCapabilityCapability; import org.apache.aries.subsystem.core.archive.ProvideCapabilityHeader; import org.apache.aries.subsystem.core.archive.SubsystemContentHeader; import org.apache.aries.subsystem.core.archive.SubsystemExportServiceCapability; import org.apache.aries.subsystem.core.archive.SubsystemExportServiceHeader; import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent; import org.eclipse.equinox.region.Region; import org.eclipse.equinox.region.RegionFilter; import org.eclipse.equinox.region.RegionFilterBuilder; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.FrameworkWiring; import org.osgi.resource.Resource; import org.osgi.service.coordinator.Coordination; import org.osgi.service.coordinator.CoordinationException; import org.osgi.service.coordinator.Participant; import org.osgi.service.subsystem.Subsystem; import org.osgi.service.subsystem.Subsystem.State; import org.osgi.service.subsystem.SubsystemConstants; import org.osgi.service.subsystem.SubsystemException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StartAction extends AbstractAction { public static enum Restriction { NONE, INSTALL_ONLY, RESOLVE_ONLY } private static final Logger logger = LoggerFactory.getLogger(StartAction.class); private final Coordination coordination; private final BasicSubsystem instigator; private final Restriction restriction; public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target) { this(instigator, requestor, target, Restriction.NONE); } 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, Restriction.NONE); } public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination, Restriction restriction) { super(requestor, target, false); this.instigator = instigator; this.coordination = coordination; this.restriction = restriction; } 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 class AffectedResources { private final List<Resource> resources; private final Collection<BasicSubsystem> subsystemResources; AffectedResources(BasicSubsystem target) { LinkedHashSet<Resource> resources = new LinkedHashSet<Resource>(); LinkedHashSet<BasicSubsystem> subsystemResources = new LinkedHashSet<BasicSubsystem>(); Subsystems subsystems = Activator.getInstance().getSubsystems(); for (Resource dep : subsystems.getResourcesReferencedBy(target)) { if (dep instanceof BasicSubsystem && !subsystems.getChildren(target).contains(dep)) { subsystemResources.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))) { subsystemResources.add(constituentOf); } } } resources.add(dep); } for (Subsystem child : subsystems.getChildren(target)) { subsystemResources.add((BasicSubsystem)child); resources.add((BasicSubsystem)child); } for (Resource resource : target.getResource().getSharedContent()) { for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent( resource instanceof BundleRevision ? new BundleConstituent(null, (BundleRevision)resource) : resource)) { subsystemResources.add(constituentOf); } resources.add(resource); } subsystemResources.add(target); this.resources = new ArrayList<Resource>(resources); this.subsystemResources = subsystemResources; } List<Resource> resources() { return resources; } Collection<BasicSubsystem> subsystems() { return subsystemResources; } } private static AffectedResources computeAffectedResources(BasicSubsystem target) { return new AffectedResources(target); } @Override public Object run() { // Protect against re-entry now that cycles are supported. if (!Activator.getInstance().getLockingStrategy().set(State.STARTING, target)) { return null; } try { AffectedResources affectedResources; // 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. Activator.getInstance().getLockingStrategy().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. affectedResources = computeAffectedResources(target); for (BasicSubsystem subsystem : affectedResources.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. Activator.getInstance().getLockingStrategy().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. Activator.getInstance().getLockingStrategy().writeUnlock(); } } else { // Acquire the read lock in order to prevent installs and // uninstalls but allow starts and stops. Activator.getInstance().getLockingStrategy().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. affectedResources = computeAffectedResources(target); // Acquire the global mutual exclusion lock while acquiring the // state change locks of affected subsystems. Activator.getInstance().getLockingStrategy().lock(); try { // We are now protected against cycles. // Acquire the state change locks of affected subsystems. Activator.getInstance().getLockingStrategy().lock(affectedResources.subsystems()); } finally { // Release the global mutual exclusion lock as soon as possible. Activator.getInstance().getLockingStrategy().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, affectedResources.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); } }); SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader(); if (header != null) Collections.sort(affectedResources.resources(), new StartResourceComparator(header)); for (Resource resource : affectedResources.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. Activator.getInstance().getLockingStrategy().unlock(affectedResources.subsystems()); } } } finally { // Release the read lock. Activator.getInstance().getLockingStrategy().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. Activator.getInstance().getLockingStrategy().unset(State.STARTING, target); } return null; } private static Collection<Bundle> getBundles(BasicSubsystem subsystem) { Collection<Resource> constituents = Activator.getInstance().getSubsystems().getConstituents(subsystem); ArrayList<Bundle> result = new ArrayList<Bundle>(constituents.size()); for (Resource resource : constituents) { if (resource instanceof BundleRevision) result.add(((BundleRevision)resource).getBundle()); } result.trimToSize(); return result; } private static void emitResolvingEvent(BasicSubsystem subsystem) { // Don't propagate a RESOLVING event if this is a persisted subsystem // that is already RESOLVED. if (State.INSTALLED.equals(subsystem.getState())) subsystem.setState(State.RESOLVING); } private static void emitResolvedEvent(BasicSubsystem subsystem) { // No need to propagate a RESOLVED event if this is a persisted // subsystem already in the RESOLVED state. if (State.RESOLVING.equals(subsystem.getState())) subsystem.setState(State.RESOLVED); } 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 instigator, BasicSubsystem target, BasicSubsystem subsystem, Coordination coordination) throws Exception { State state = subsystem.getState(); if (State.INSTALLED.equals(state)) { if (target.equals(subsystem)) { resolve(instigator, target, subsystem, coordination, Collections.<BasicSubsystem>emptyList()); } else { AccessController.doPrivileged(new StartAction(instigator, target, subsystem, coordination, Restriction.RESOLVE_ONLY)); } } } private static void resolveBundles(BasicSubsystem subsystem) { FrameworkWiring frameworkWiring = Activator.getInstance().getBundleContext().getBundle(0) .adapt(FrameworkWiring.class); // TODO I think this is insufficient. Do we need both // pre-install and post-install environments for the Resolver? Collection<Bundle> bundles = getBundles(subsystem); if (!frameworkWiring.resolveBundles(bundles)) { handleFailedResolution(subsystem, bundles, frameworkWiring); } } 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 // state transitions as other subsystems. However, an unresolvable // root subsystem should have no effect, so there's no point in // actually doing the resolution work. if (!subsystem.isRoot()) { setExportIsolationPolicy(subsystem, coordination); resolveSubsystems(instigator, target, coordination, subsystems); resolveBundles(subsystem); } emitResolvedEvent(subsystem); } catch (Throwable t) { subsystem.setState(State.INSTALLED); if (t instanceof SubsystemException) throw (SubsystemException)t; throw new SubsystemException(t); } } private static void setExportIsolationPolicy(final BasicSubsystem subsystem, Coordination coordination) throws InvalidSyntaxException { if (!subsystem.isComposite()) return; final Region from = ((BasicSubsystem)subsystem.getParents().iterator().next()).getRegion(); final Region to = subsystem.getRegion(); RegionFilterBuilder builder = from.getRegionDigraph().createRegionFilterBuilder(); setExportIsolationPolicy(builder, subsystem.getDeploymentManifest().getExportPackageHeader(), subsystem); setExportIsolationPolicy(builder, subsystem.getDeploymentManifest().getProvideCapabilityHeader(), subsystem); setExportIsolationPolicy(builder, subsystem.getDeploymentManifest().getSubsystemExportServiceHeader(), subsystem); RegionFilter regionFilter = builder.build(); if (regionFilter.getSharingPolicy().isEmpty()) return; if (logger.isDebugEnabled()) logger.debug("Establishing region connection: from=" + from + ", to=" + to + ", filter=" + regionFilter); try { from.connectRegion(to, regionFilter); } catch (BundleException e) { // TODO Assume this means that the export sharing policy has already // been set. Bad assumption? return; } coordination.addParticipant(new Participant() { @Override public void ended(Coordination coordination) throws Exception { // It may be necessary to rollback the export sharing policy // even when the coordination did not fail. For example, this // might have been a subsystem whose export sharing policy was // set just in case it offered dependencies for some other // subsystem. unsetExportIsolationPolicyIfNecessary(); } @Override public void failed(Coordination coordination) throws Exception { // Nothing to do because a coordination is always ended. } private void unsetExportIsolationPolicyIfNecessary() throws BundleException, InvalidSyntaxException { if (!EnumSet.of(State.INSTALLING, State.INSTALLED).contains(subsystem.getState())) { // The subsystem is either RESOLVED or ACTIVE and therefore // does not require a rollback. return; } // The subsystem is either INSTALLING or INSTALLED and therefore // requires a rollback since the export sharing policy must only // be set upon entering the RESOLVED state. RegionUpdater updater = new RegionUpdater(from, to); updater.addRequirements(null); } }); } private static void setExportIsolationPolicy(RegionFilterBuilder builder, ExportPackageHeader header, BasicSubsystem subsystem) throws InvalidSyntaxException { if (header == null) return; String policy = RegionFilter.VISIBLE_PACKAGE_NAMESPACE; for (ExportPackageCapability capability : header.toCapabilities(subsystem)) { StringBuilder filter = new StringBuilder("(&"); for (Entry<String, Object> attribute : capability.getAttributes().entrySet()) filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')'); filter.append(')'); if (logger.isDebugEnabled()) logger.debug("Allowing " + policy + " of " + filter); builder.allow(policy, filter.toString()); } } private static void setExportIsolationPolicy(RegionFilterBuilder builder, ProvideCapabilityHeader header, BasicSubsystem subsystem) throws InvalidSyntaxException { if (header == null) return; for (ProvideCapabilityHeader.Clause clause : header.getClauses()) { ProvideCapabilityCapability capability = new ProvideCapabilityCapability(clause, subsystem); String policy = capability.getNamespace(); Set<Entry<String, Object>> entrySet = capability.getAttributes().entrySet(); StringBuilder filter = new StringBuilder(); if (entrySet.size() > 1) { filter.append("(&"); } for (Entry<String, Object> attribute : capability.getAttributes().entrySet()) { filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')'); } if (entrySet.size() > 1) { filter.append(')'); } if (logger.isDebugEnabled()) { logger.debug("Allowing policy {} with filter {}", policy, filter); } if (filter.length() == 0) { builder.allowAll(policy); } else { builder.allow(policy, filter.toString()); } } } private static void setExportIsolationPolicy(RegionFilterBuilder builder, SubsystemExportServiceHeader header, BasicSubsystem subsystem) throws InvalidSyntaxException { if (header == null) return; String policy = RegionFilter.VISIBLE_SERVICE_NAMESPACE; for (SubsystemExportServiceHeader.Clause clause : header.getClauses()) { SubsystemExportServiceCapability capability = new SubsystemExportServiceCapability(clause, subsystem); String filter = capability.getDirectives().get(SubsystemExportServiceCapability.DIRECTIVE_FILTER); if (logger.isDebugEnabled()) logger.debug("Allowing " + policy + " of " + filter); builder.allow(policy, filter); } } private void startBundleResource(Resource resource, Coordination coordination) throws BundleException { if (target.isRoot()) // Starting the root subsystem should not affect bundles within the // root region. return; if (Utils.isRegionContextBundle(resource)) // The region context bundle was persistently started elsewhere. return; final Bundle bundle = ((BundleRevision)resource).getBundle(); if ((bundle.getState() & (Bundle.STARTING | Bundle.ACTIVE)) != 0) return; if (logger.isDebugEnabled()) { int bundleStartLevel = bundle.adapt(BundleStartLevel.class).getStartLevel(); Bundle systemBundle=Activator.getInstance().getBundleContext().getBundle(0); int fwStartLevel = systemBundle.adapt(FrameworkStartLevel.class).getStartLevel(); logger.debug("StartAction: starting bundle " + bundle.getSymbolicName() + " " + bundle.getVersion().toString() + " bundleStartLevel=" + bundleStartLevel + " frameworkStartLevel=" + fwStartLevel); } bundle.start(Bundle.START_TRANSIENT | Bundle.START_ACTIVATION_POLICY); if (logger.isDebugEnabled()) { logger.debug("StartAction: bundle " + bundle.getSymbolicName() + " " + bundle.getVersion().toString() + " started correctly"); } if (coordination == null) return; coordination.addParticipant(new Participant() { public void ended(Coordination coordination) throws Exception { // noop } public void failed(Coordination coordination) throws Exception { bundle.stop(); } }); } private void startResource(Resource resource, Coordination coordination) throws BundleException, IOException { String type = ResourceHelper.getTypeAttribute(resource); if (SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION.equals(type) || SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE.equals(type) || SubsystemConstants.SUBSYSTEM_TYPE_FEATURE.equals(type)) { startSubsystemResource(resource, coordination); } else if (IdentityNamespace.TYPE_BUNDLE.equals(type)) { startBundleResource(resource, coordination); } else if (IdentityNamespace.TYPE_FRAGMENT.equals(type)) { // Fragments are not started. } else { if (!startCustomHandler(resource, type, coordination)) throw new SubsystemException("Unsupported resource type: " + type); } } private boolean startCustomHandler(Resource resource, String type, Coordination coordination) { ServiceReference<ContentHandler> customHandlerRef = CustomResources.getCustomContentHandler(target, type); if (customHandlerRef != null) { ContentHandler customHandler = target.getBundleContext().getService(customHandlerRef); if (customHandler != null) { try { customHandler.start(ResourceHelper.getSymbolicNameAttribute(resource), type, target, coordination); return true; } finally { target.getBundleContext().ungetService(customHandlerRef); } } } return false; } 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)) subsystem.setAutostart(true); new StartAction(instigator, target, subsystem, coordination).run(); if (coordination == null) return; coordination.addParticipant(new Participant() { public void ended(Coordination coordination) throws Exception { // noop } public void failed(Coordination coordination) throws Exception { new StopAction(target, subsystem, !subsystem.isRoot()).run(); } }); } private static void handleFailedResolution(BasicSubsystem subsystem, Collection<Bundle> bundles, FrameworkWiring wiring) { logFailedResolution(subsystem, bundles); throw new SubsystemException("Framework could not resolve the bundles: " + bundles); } private static void logFailedResolution(BasicSubsystem subsystem, Collection<Bundle> bundles) { //work out which bundles could not be resolved Collection<Bundle> unresolved = new ArrayList<Bundle>(); StringBuilder diagnostics = new StringBuilder(); diagnostics.append(String.format("Unable to resolve bundles for subsystem/version/id %s/%s/%s:\n", subsystem.getSymbolicName(), subsystem.getVersion(), subsystem.getSubsystemId())); String fmt = "%d : STATE %s : %s : %s : %s"; for(Bundle bundle:bundles){ if((bundle.getState() & Bundle.RESOLVED) != Bundle.RESOLVED) { unresolved.add(bundle); } String state = null; switch(bundle.getState()) { case Bundle.ACTIVE : state = "ACTIVE"; break; case Bundle.INSTALLED : state = "INSTALLED"; break; case Bundle.RESOLVED : state = "RESOLVED"; break; case Bundle.STARTING : state = "STARTING"; break; case Bundle.STOPPING : state = "STOPPING"; break; case Bundle.UNINSTALLED : state = "UNINSTALLED"; break; default : //convert common states to text otherwise default to just showing the ID state = "[" + Integer.toString(bundle.getState()) + "]"; break; } diagnostics.append(String.format(fmt, bundle.getBundleId(), state, bundle.getSymbolicName(), bundle.getVersion().toString(), bundle.getLocation())); diagnostics.append("\n"); } logger.error(diagnostics.toString()); } static void setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(Coordination coordination) throws InvalidSyntaxException { for (BasicSubsystem subsystem : Activator.getInstance().getSubsystems().getSubsystems()) { if (!State.INSTALLING.equals(subsystem.getState()) || Utils.isProvisionDependenciesInstall(subsystem)) { continue; } setExportIsolationPolicy(subsystem, coordination); } } }