/* * 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.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent; import org.eclipse.equinox.region.Region; import org.eclipse.equinox.region.RegionDigraph; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.hooks.bundle.EventHook; import org.osgi.framework.wiring.BundleRevision; import org.osgi.resource.Resource; public class BundleEventHook implements EventHook { private final Activator activator; private final ConcurrentHashMap<Bundle, BundleRevision> bundleToRevision; private boolean active; private List<BundleEvent> events; public BundleEventHook() { activator = Activator.getInstance(); bundleToRevision = new ConcurrentHashMap<Bundle, BundleRevision>(); } @Override public void event(BundleEvent event, Collection<BundleContext> contexts) { if ((event.getType() & (BundleEvent.INSTALLED | BundleEvent.UNINSTALLED)) == 0) return; // Protect against deadlock when the bundle event hook receives an // event before subsystems has fully initialized, in which case the // events are queued and processed once initialization is complete. synchronized (this) { if (!active) { if (events == null) events = new ArrayList<BundleEvent>(); events.add(event); return; } } handleEvent(event); } // Events must be processed in order. Don't allow events to go through // synchronously before all pending events have been processed. synchronized void activate() { active = true; processPendingEvents(); } synchronized void deactivate() { active = false; } synchronized void processPendingEvents() { if (events == null) return; for (BundleEvent event : events) handleEvent(event); events = null; } private Subsystems getSubsystems() { return activator.getSubsystems(); } /* * Note that because some events may be processed asynchronously, we can no * longer rely on the guarantees that a synchronous event brings. For * example, bundle revisions adapted from bundles included in events may be * null. */ private void handleEvent(BundleEvent event) { switch (event.getType()) { case BundleEvent.INSTALLED: handleInstalledEvent(event); break; // TODO I think updates will play a role here as well. Need to keep // track of the most current bundle revision? case BundleEvent.UNINSTALLED: handleUninstalledEvent(event); break; } } /* * This method guards against an uninstalled origin bundle. Guards against a * null bundle revision are done elsewhere. It is assumed the bundle * revision is never null once we get here. */ private void handleExplicitlyInstalledBundleBundleContext(BundleRevision originRevision, BundleRevision bundleRevision) { /* * The newly installed bundle must become a constituent of all the Subsystems of which the bundle * whose context was used to perform the install is a constituent (OSGI.enterprise spec. 134.10.1.1). */ Collection<BasicSubsystem> subsystems = getSubsystems().getSubsystemsReferencing(originRevision); boolean bundleRevisionInstalled=false; for (BasicSubsystem s : subsystems) { for (Resource constituent : s.getConstituents()) { if (constituent instanceof BundleConstituent) { BundleRevision rev = ((BundleConstituent) constituent).getRevision(); if (originRevision.equals(rev)) { Utils.installResource(bundleRevision, s); bundleRevisionInstalled=true; } } } } /* if the bundle is not made constituent of any subsystem then make it constituent of root */ if (!bundleRevisionInstalled) { Utils.installResource(bundleRevision, getSubsystems().getRootSubsystem()); } } private void handleExplicitlyInstalledBundleRegionDigraph(Bundle origin, BundleRevision bundleRevision) { // The bundle needs to be associated with the scoped subsystem of // the region used to install the bundle. RegionDigraph digraph = activator.getRegionDigraph(); Region region = digraph.getRegion(origin); for (BasicSubsystem s : getSubsystems().getSubsystems()) { if ((s.isApplication() || s.isComposite()) && region.equals(s.getRegion())) { Utils.installResource(bundleRevision, s); return; } } throw new IllegalStateException("No subsystem found for bundle " + bundleRevision + " in region " + region); } private void handleInstalledEvent(BundleEvent event) { Bundle origin = event.getOrigin(); BundleRevision originRevision = origin.adapt(BundleRevision.class); Bundle bundle = event.getBundle(); BundleRevision bundleRevision = bundle.adapt(BundleRevision.class); if (bundleRevision == null) { // The event is being processed asynchronously and the installed // bundle has been uninstalled. Nothing we can do. return; } bundleToRevision.put(bundle, bundleRevision); // Only handle explicitly installed bundles. An explicitly installed // bundle is a bundle that was installed using some other bundle's // BundleContext or using RegionDigraph. if (ThreadLocalSubsystem.get() != null // Region context bundles must be treated as explicit installations. || bundleRevision.getSymbolicName().startsWith(Constants.RegionContextBundleSymbolicNamePrefix)) { return; } // Indicate that a bundle is being explicitly installed on this thread. // This protects against attempts to resolve the bundle as part of // processing the explicit installation. ThreadLocalBundleRevision.set(bundleRevision); try { if ("org.eclipse.equinox.region".equals(origin.getSymbolicName())) { // The bundle was installed using RegionDigraph. handleExplicitlyInstalledBundleRegionDigraph(origin, bundleRevision); } else { // The bundle was installed using some other bundle's BundleContext. handleExplicitlyInstalledBundleBundleContext(originRevision, bundleRevision); } } finally { // Always remove the bundle so that it can be resolved no matter // what happens here. ThreadLocalBundleRevision.remove(); } } @SuppressWarnings("unchecked") private void handleUninstalledEvent(BundleEvent event) { Bundle bundle = event.getBundle(); BundleRevision revision = bundleToRevision.remove(bundle); if (ThreadLocalSubsystem.get() != null || (revision == null ? false : // Region context bundles must be treated as explicit installations. revision.getSymbolicName().startsWith(Constants.RegionContextBundleSymbolicNamePrefix))) { return; } Collection<BasicSubsystem> subsystems; if (revision == null) { // The bundle was installed while the bundle event hook was unregistered. Object[] o = activator.getSubsystems().getSubsystemsByBundle(bundle); if (o == null) return; revision = (BundleRevision)o[0]; subsystems = (Collection<BasicSubsystem>)o[1]; } else { subsystems = activator.getSubsystems().getSubsystemsByConstituent(new BundleConstituent(null, revision)); } for (BasicSubsystem subsystem : subsystems) { ResourceUninstaller.newInstance(revision, subsystem).uninstall(); } } }