blob: 14a6df5dac68c067f3cd02760da741639d13c21f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.framework;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.osgi.container.*;
import org.eclipse.osgi.container.Module.State;
import org.eclipse.osgi.framework.util.ArrayMap;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.internal.serviceregistry.*;
import org.eclipse.osgi.report.resolution.ResolutionReport;
import org.eclipse.osgi.storage.Storage;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
import org.osgi.framework.hooks.bundle.CollisionHook;
import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.wiring.*;
class OSGiFrameworkHooks {
static final String collisionHookName = CollisionHook.class.getName();
private final CoreResolverHookFactory resolverHookFactory;
private final ModuleCollisionHook collisionHook;
OSGiFrameworkHooks(EquinoxContainer container, Storage storage) {
resolverHookFactory = new CoreResolverHookFactory(container, storage);
collisionHook = new BundleCollisionHook(container);
}
public ResolverHookFactory getResolverHookFactory() {
return resolverHookFactory;
}
public ModuleCollisionHook getModuleCollisionHook() {
return collisionHook;
}
static class BundleCollisionHook implements ModuleCollisionHook {
final Debug debug;
final EquinoxContainer container;
public BundleCollisionHook(EquinoxContainer container) {
this.container = container;
this.debug = container.getConfiguration().getDebug();
}
@Override
public void filterCollisions(int operationType, Module target, Collection<Module> collisionCandidates) {
switch (container.getConfiguration().BSN_VERSION) {
case EquinoxConfiguration.BSN_VERSION_SINGLE : {
return;
}
case EquinoxConfiguration.BSN_VERSION_MULTIPLE : {
collisionCandidates.clear();
return;
}
case EquinoxConfiguration.BSN_VERSION_MANAGED : {
Bundle targetBundle = target.getBundle();
ArrayMap<Bundle, Module> candidateBundles = new ArrayMap<>(collisionCandidates.size());
for (Module module : collisionCandidates) {
candidateBundles.put(module.getBundle(), module);
}
notifyCollisionHooks(operationType, targetBundle, candidateBundles);
collisionCandidates.retainAll(candidateBundles.getValues());
return;
}
default :
throw new IllegalStateException("Bad configuration: " + container.getConfiguration().BSN_VERSION); //$NON-NLS-1$
}
}
private void notifyCollisionHooks(final int operationType, final Bundle target, Collection<Bundle> collisionCandidates) {
// Note that collision hook results are honored for the system bundle.
final Collection<Bundle> shrinkable = new ShrinkableCollection<>(collisionCandidates);
if (System.getSecurityManager() == null) {
notifyCollisionHooksPriviledged(operationType, target, shrinkable);
} else {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
notifyCollisionHooksPriviledged(operationType, target, shrinkable);
return null;
}
});
}
}
void notifyCollisionHooksPriviledged(final int operationType, final Bundle target, final Collection<Bundle> collisionCandidates) {
if (debug.DEBUG_HOOKS) {
Debug.println("notifyCollisionHooks(" + operationType + ", " + target + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
ServiceRegistry registry = container.getServiceRegistry();
if (registry != null) {
registry.notifyHooksPrivileged(new HookContext() {
public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
if (hook instanceof CollisionHook) {
((CollisionHook) hook).filterCollisions(operationType, target, collisionCandidates);
}
}
public String getHookClassName() {
return collisionHookName;
}
public String getHookMethodName() {
return "filterCollisions"; //$NON-NLS-1$
}
@Override
public boolean skipRegistration(ServiceRegistration<?> hookRegistration) {
return false;
}
});
}
}
}
/**
* This class encapsulates the delegation to ResolverHooks that are registered with the service
* registry. This way the resolver implementation only has to call out to a single hook
* which does all the necessary service registry lookups.
*
* This class is not thread safe and expects external synchronization.
*
*/
static class CoreResolverHookFactory implements ResolverHookFactory {
// need a tuple to hold the service reference and hook object
// do not use a map for performance reasons; no need to hash based on a key.
static class HookReference {
public HookReference(ServiceReferenceImpl<ResolverHookFactory> reference, ResolverHook hook, BundleContextImpl context) {
this.reference = reference;
this.hook = hook;
this.context = context;
}
final ServiceReferenceImpl<ResolverHookFactory> reference;
final ResolverHook hook;
final BundleContextImpl context;
}
final Debug debug;
final EquinoxContainer container;
final Storage storage;
volatile boolean inInit = false;
public CoreResolverHookFactory(EquinoxContainer container, Storage storage) {
this.container = container;
this.debug = container.getConfiguration().getDebug();
this.storage = storage;
}
void handleHookException(Throwable t, Object hook, String method) {
if (debug.DEBUG_HOOKS) {
Debug.println(hook.getClass().getName() + "." + method + "() exception:"); //$NON-NLS-1$ //$NON-NLS-2$
if (t != null)
Debug.printStackTrace(t);
}
String message = NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, hook.getClass().getName(), method);
throw new RuntimeException(message, new BundleException(message, BundleException.REJECTED_BY_HOOK, t));
}
private ServiceReferenceImpl<ResolverHookFactory>[] getHookReferences(final ServiceRegistry registry, final BundleContextImpl context) {
return AccessController.doPrivileged(new PrivilegedAction<ServiceReferenceImpl<ResolverHookFactory>[]>() {
@Override
public ServiceReferenceImpl<ResolverHookFactory>[] run() {
try {
@SuppressWarnings("unchecked")
ServiceReferenceImpl<ResolverHookFactory>[] result = (ServiceReferenceImpl<ResolverHookFactory>[]) registry.getServiceReferences(context, ResolverHookFactory.class.getName(), null, false);
return result;
} catch (InvalidSyntaxException e) {
// cannot happen; no filter
return null;
}
}
});
}
public ResolverHook begin(Collection<BundleRevision> triggers) {
if (debug.DEBUG_HOOKS) {
Debug.println("ResolverHook.begin"); //$NON-NLS-1$
}
ModuleContainer mContainer = storage.getModuleContainer();
Module systemModule = mContainer == null ? null : mContainer.getModule(0);
ServiceRegistry registry = container.getServiceRegistry();
if (registry == null || systemModule == null) {
return new CoreResolverHook(Collections.<HookReference> emptyList(), systemModule);
}
BundleContextImpl context = (BundleContextImpl) EquinoxContainer.secureAction.getContext(systemModule.getBundle());
ServiceReferenceImpl<ResolverHookFactory>[] refs = getHookReferences(registry, context);
List<HookReference> hookRefs = refs == null ? Collections.<CoreResolverHookFactory.HookReference> emptyList() : new ArrayList<CoreResolverHookFactory.HookReference>(refs.length);
if (refs != null) {
for (ServiceReferenceImpl<ResolverHookFactory> hookRef : refs) {
ResolverHookFactory factory = EquinoxContainer.secureAction.getService(hookRef, context);
if (factory != null) {
try {
ResolverHook hook = factory.begin(triggers);
if (hook != null)
hookRefs.add(new HookReference(hookRef, hook, context));
} catch (Throwable t) {
// need to force an end call on the ResolverHooks we got and release them
try {
new CoreResolverHook(hookRefs, systemModule).end();
} catch (Throwable endError) {
// we are already in failure mode; just continue
}
handleHookException(t, factory, "begin"); //$NON-NLS-1$
}
}
}
}
return new CoreResolverHook(hookRefs, systemModule);
}
class CoreResolverHook implements ResolutionReport.Listener, ResolverHook {
private final List<HookReference> hooks;
private final Module systemModule;
private volatile ResolutionReport resolutionReport;
CoreResolverHook(List<HookReference> hooks, Module systemModule) {
this.hooks = hooks;
this.systemModule = systemModule;
}
public void filterResolvable(Collection<BundleRevision> candidates) {
if (debug.DEBUG_HOOKS) {
Debug.println("ResolverHook.filterResolvable(" + candidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (isBootInit()) {
// only allow the system bundle and its fragments resolve during boot up and init
for (Iterator<BundleRevision> iCandidates = candidates.iterator(); iCandidates.hasNext();) {
BundleRevision revision = iCandidates.next();
if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) == 0) {
// host bundle; check if it is the system bundle
if (revision.getBundle().getBundleId() != 0) {
iCandidates.remove();
}
}
// just leave all fragments. Only the ones that are system bundle fragments will resolve
// since we removed all the other possible hosts.
}
}
if (hooks.isEmpty())
return;
candidates = new ShrinkableCollection<>(candidates);
for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
HookReference hookRef = iHooks.next();
if (hookRef.reference.getBundle() == null) {
handleHookException(null, hookRef.hook, "filterResolvable"); //$NON-NLS-1$
} else {
try {
hookRef.hook.filterResolvable(candidates);
} catch (Throwable t) {
handleHookException(t, hookRef.hook, "filterResolvable"); //$NON-NLS-1$
}
}
}
}
private boolean isBootInit() {
return systemModule == null || !Module.RESOLVED_SET.contains(systemModule.getState()) || (systemModule.getState().equals(State.STARTING) && inInit);
}
public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {
if (debug.DEBUG_HOOKS) {
Debug.println("ResolverHook.filterSingletonCollisions(" + singleton + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (hooks.isEmpty())
return;
collisionCandidates = new ShrinkableCollection<>(collisionCandidates);
for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
HookReference hookRef = iHooks.next();
if (hookRef.reference.getBundle() == null) {
handleHookException(null, hookRef.hook, "filterSingletonCollisions"); //$NON-NLS-1$
} else {
try {
hookRef.hook.filterSingletonCollisions(singleton, collisionCandidates);
} catch (Throwable t) {
handleHookException(t, hookRef.hook, "filterSingletonCollisions"); //$NON-NLS-1$
}
}
}
}
public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
if (debug.DEBUG_HOOKS) {
Debug.println("ResolverHook.filterMatches(" + requirement + ", " + candidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (hooks.isEmpty())
return;
candidates = new ShrinkableCollection<>(candidates);
for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
HookReference hookRef = iHooks.next();
if (hookRef.reference.getBundle() == null) {
handleHookException(null, hookRef.hook, "filterMatches"); //$NON-NLS-1$
} else {
try {
hookRef.hook.filterMatches(requirement, candidates);
} catch (Throwable t) {
handleHookException(t, hookRef.hook, "filterMatches"); //$NON-NLS-1$
}
}
}
}
public void end() {
if (debug.DEBUG_HOOKS) {
Debug.println("ResolverHook.end"); //$NON-NLS-1$
}
if (hooks.isEmpty())
return;
try {
HookReference missingHook = null;
Throwable endError = null;
HookReference endBadHook = null;
for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
HookReference hookRef = iHooks.next();
// We do not remove unregistered services here because we are going to remove all of them at the end
if (hookRef.reference.getBundle() == null) {
if (missingHook == null)
missingHook = hookRef;
} else {
try {
if (hookRef.hook instanceof ResolutionReport.Listener)
((ResolutionReport.Listener) hookRef.hook).handleResolutionReport(resolutionReport);
hookRef.hook.end();
} catch (Throwable t) {
// Must continue on to the next hook.end method
// save the error for throwing at the end
if (endError == null) {
endError = t;
endBadHook = hookRef;
}
}
}
}
if (missingHook != null)
handleHookException(null, missingHook.hook, "end"); //$NON-NLS-1$
if (endError != null)
handleHookException(endError, endBadHook.hook, "end"); //$NON-NLS-1$
} finally {
for (HookReference hookRef : hooks) {
hookRef.context.ungetService(hookRef.reference);
}
hooks.clear();
}
}
@Override
public void handleResolutionReport(ResolutionReport report) {
resolutionReport = report;
}
}
}
public void initBegin() {
resolverHookFactory.inInit = true;
}
public void initEnd() {
resolverHookFactory.inInit = false;
}
}