/*
* #%L
* JBossOSGi Resolver API
* %%
* Copyright (C) 2010 - 2012 JBoss by Red Hat
* %%
* 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.
* #L%
*/
package org.jboss.osgi.resolver.spi;
import static org.jboss.osgi.resolver.ResolverLogger.LOGGER;
import static org.jboss.osgi.resolver.ResolverMessages.MESSAGES;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jboss.osgi.resolver.XBundle;
import org.jboss.osgi.resolver.XBundleRevision;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.resource.Resource;
/**
* The default implementation for {@link ResolverHook} functionality.
*
* @author thomas.diesler@jboss.com
* @since 01-Feb-2013
*/
public class ResolverHookProcessor {
private static ThreadLocal<ResolverHookProcessor> processorAssociation = new ThreadLocal<ResolverHookProcessor>();
private final BundleContext syscontext;
private List<ResolverHookRegistration> registrations;
private Collection<BundleRevision> candidates;
public interface SingletonLocator {
Collection<BundleCapability> findCollisionCandidates(BundleCapability icap);
}
public ResolverHookProcessor(BundleContext syscontext, Collection<XBundle> unresolved) {
this.syscontext = syscontext;
// Get the set of unresolved candidates
candidates = new ArrayList<BundleRevision>();
if (unresolved != null) {
for (XBundle bundle : unresolved) {
candidates.add(bundle.getBundleRevision());
}
}
candidates = new RemoveOnlyCollection<BundleRevision>(candidates);
// Get the set of registered {@link ResolverFactory}
Collection<ServiceReference<ResolverHookFactory>> srefs = null;
try {
srefs = syscontext.getServiceReferences(ResolverHookFactory.class, null);
} catch (InvalidSyntaxException e) {
// ignore
}
// The hooks are called in ranking order
List<ServiceReference<ResolverHookFactory>> sorted = new ArrayList<ServiceReference<ResolverHookFactory>>(srefs);
Collections.reverse(sorted);
// Create list of {@link ResolverHook} from {@link ResolverFactory}
for (ServiceReference<ResolverHookFactory> sref : sorted) {
if (registrations == null) {
registrations = new ArrayList<ResolverHookRegistration>();
}
registrations.add(new ResolverHookRegistration(sref));
}
}
public static ResolverHookProcessor getCurrentProcessor() {
return processorAssociation.get();
}
public boolean hasResolverHooks() {
return registrations != null;
}
public boolean hasBundleRevision(BundleRevision brev) {
return candidates != null && candidates.contains(brev);
}
public boolean hasResource(Resource res) {
return candidates != null && candidates.contains(res);
}
public void begin(Collection<? extends Resource> mandatory, Collection<? extends Resource> optional) {
// Set the thread association
processorAssociation.set(this);
// Get the initial set of trigger bundles
Collection<BundleRevision> triggers = new ArrayList<BundleRevision>();
addTriggers(mandatory, triggers);
addTriggers(optional, triggers);
triggers = new RemoveOnlyCollection<BundleRevision>(triggers);
// Create a {@link ResolverHook} for each factory
if (registrations != null) {
for (ResolverHookRegistration hookreg : registrations) {
try {
ResolverHookFactory hookFactory = syscontext.getService(hookreg.sref);
if (hookreg.isRegistered()) {
hookreg.hook = hookFactory.begin(triggers);
}
} catch (RuntimeException ex) {
hookreg.lastException = ex;
throw new ResolverHookException(ex);
}
}
}
}
private void addTriggers(Collection<? extends Resource> resources, Collection<BundleRevision> triggers) {
if (resources != null) {
for (Resource res : resources) {
XBundleRevision brev = (XBundleRevision) res;
if (brev.getBundle().getState() != Bundle.UNINSTALLED) {
triggers.add(brev);
}
}
}
}
public void filterResolvable() {
if (registrations != null) {
for (ResolverHookRegistration hookreg : registrations) {
try {
ResolverHook hook = hookreg.getResolverHook();
if (hook != null && hookreg.lastException == null) {
Collection<BundleRevision> before = new HashSet<BundleRevision>(candidates);
hook.filterResolvable(candidates);
for (BundleRevision aux : before) {
if (!candidates.contains(aux)) {
LOGGER.debugf("ResolverHook filtered resolvable: %s", aux);
}
}
}
} catch (RuntimeException ex) {
hookreg.lastException = ex;
throw new ResolverHookException(ex);
}
}
}
}
public void filterSingletonCollisions(SingletonLocator locator) {
if (registrations != null) {
// Collect the singleton capabilities from all candidate revisions
Map<BundleCapability, Collection<BundleCapability>> collisionCandidates = new HashMap<BundleCapability, Collection<BundleCapability>>();
for (BundleRevision brev : candidates) {
List<BundleCapability> bcaps = brev.getDeclaredCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
if (bcaps.size() == 1) {
BundleCapability bcap = bcaps.get(0);
if (Boolean.parseBoolean(bcap.getDirectives().get(Constants.SINGLETON_DIRECTIVE))) {
populateCollisionCandidates(bcap, collisionCandidates, locator);
}
}
}
for (Map.Entry<BundleCapability, Collection<BundleCapability>> entry : collisionCandidates.entrySet()) {
BundleCapability bcap = entry.getKey();
Collection<BundleCapability> collisions = entry.getValue();
// Collect the already resolved capabilities for reverse checking
Collection<BundleCapability> reverse = new HashSet<BundleCapability>();
for (BundleCapability aux : collisions) {
BundleRevision resource = aux.getResource();
BundleWiring wiring = resource.getBundle().adapt(BundleWiring.class);
if (wiring != null) {
reverse.add(aux);
}
}
// Remove the resolution candidate if we still have collisions
if (!processCollisionCandidates(bcap, collisions) || !processReverseCollisionCandidates(bcap, reverse)) {
BundleRevision brev = bcap.getResource();
LOGGER.debugf("ResolverHook found singleton collision of %s with %s", bcap, collisions);
LOGGER.debugf("ResolverHook removed resolution candidate %s", brev);
candidates.remove(brev);
}
}
}
}
private boolean processReverseCollisionCandidates(BundleCapability bcap, Collection<BundleCapability> reverse) {
for (BundleCapability aux : reverse) {
Collection<BundleCapability> collisions = new HashSet<BundleCapability>(Collections.singleton(bcap));
if (!processCollisionCandidates(aux, new RemoveOnlyCollection<BundleCapability>(collisions))) {
return false;
}
}
return true;
}
private boolean processCollisionCandidates(BundleCapability bcap, Collection<BundleCapability> collisions) {
// Remove the collisions that will not resolve because they are no candidates
for (Iterator<BundleCapability> iterator = collisions.iterator(); iterator.hasNext();) {
BundleRevision brev = iterator.next().getResource();
if (brev.getBundle().getState() == Bundle.INSTALLED && !candidates.contains(brev)) {
iterator.remove();
}
}
// Call the registered {@link ResolverHook}s
for (ResolverHookRegistration hookreg : registrations) {
try {
ResolverHook hook = hookreg.getResolverHook();
if (hook != null && hookreg.lastException == null) {
hook.filterSingletonCollisions(bcap, collisions);
}
} catch (RuntimeException ex) {
hookreg.lastException = ex;
throw new ResolverHookException(ex);
}
}
return collisions.isEmpty();
}
private void populateCollisionCandidates(BundleCapability bcap, Map<BundleCapability, Collection<BundleCapability>> map, SingletonLocator locator) {
if (!map.containsKey(bcap)) {
Collection<BundleCapability> candidates = locator.findCollisionCandidates(bcap);
map.put(bcap, new RemoveOnlyCollection<BundleCapability>(candidates));
}
}
public void filterMatches(BundleRequirement breq, Collection<BundleCapability> matching) {
if (registrations != null) {
for (ResolverHookRegistration hookreg : registrations) {
try {
ResolverHook hook = hookreg.getResolverHook();
if (hook != null && hookreg.lastException == null) {
Collection<BundleCapability> before = new HashSet<BundleCapability>(matching);
hook.filterMatches(breq, matching);
for (BundleCapability aux : before) {
if (!matching.contains(aux)) {
LOGGER.debugf("ResolverHook filtered match: %s", aux);
}
}
}
} catch (RuntimeException ex) {
hookreg.lastException = ex;
throw new ResolverHookException(ex);
}
}
}
}
public void end() {
ResolverHookException endException = null;
try {
// Call end on every {@link ResolverHook}
if (registrations != null) {
for (ResolverHookRegistration hookreg : registrations) {
try {
ResolverHook hook = hookreg.getResolverHook();
if (hook != null) {
hook.end();
}
} catch (RuntimeException ex) {
hookreg.lastException = ex;
if (endException == null) {
endException = new ResolverHookException(ex);
}
}
}
}
} finally {
// Clear the thread association
processorAssociation.remove();
}
// [TODO] The TCK expects to see the exception thrown in end() which would
// shadow a previous exception comming from filterResolvable
if (endException != null)
throw endException;
}
class ResolverHookRegistration {
private final ServiceReference<ResolverHookFactory> sref;
private RuntimeException lastException;
private ResolverHook hook;
ResolverHookRegistration(ServiceReference<ResolverHookFactory> sref) {
this.sref = sref;
}
boolean isRegistered() {
return syscontext.getService(sref) != null;
}
ResolverHook getResolverHook() {
if (!isRegistered()) {
throw MESSAGES.illegalStateResolverHookUnregistered(sref);
}
return hook;
}
}
}