/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.karaf.bundle.core.internal; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.karaf.bundle.core.BundleInfo; import org.apache.karaf.bundle.core.BundleService; import org.apache.karaf.bundle.core.BundleState; import org.apache.karaf.bundle.core.BundleStateService; import org.apache.karaf.util.jaas.JaasHelper; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.wiring.BundleCapability; import org.osgi.framework.wiring.BundleRequirement; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleRevisions; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import org.osgi.framework.wiring.FrameworkWiring; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.lang.String.format; public class BundleServiceImpl implements BundleService { private static final String KARAF_SYSTEM_BUNDLES_START_LEVEL = "karaf.systemBundlesStartLevel"; private static Logger LOG = LoggerFactory.getLogger(BundleService.class); /** * The header key where we store the active wires when we enable DynamicImport=* */ private static final String ORIGINAL_WIRES = "Original-Wires"; private final BundleContext bundleContext; private final List<BundleStateService> stateServices = new CopyOnWriteArrayList<>(); public BundleServiceImpl(BundleContext bundleContext) { this.bundleContext = bundleContext; } public void registerBundleStateService(BundleStateService service) { stateServices.add(service); } public void unregisterBundleStateService(BundleStateService service) { stateServices.remove(service); } @Override public List<Bundle> selectBundles(List<String> ids, boolean defaultAllBundles) { return selectBundles(null, ids, defaultAllBundles); } @Override public List<Bundle> selectBundles(String context, List<String> ids, boolean defaultAllBundles) { return doSelectBundles(doGetBundleContext(context), ids, defaultAllBundles); } @Override public Bundle getBundle(String id) { return getBundle(null, id); } @Override public Bundle getBundle(String context, String id) { return doGetBundle(doGetBundleContext(context), id); } @Override public BundleInfo getInfo(Bundle bundle) { BundleState combinedState = BundleState.Unknown; for (BundleStateService stateService : this.stateServices) { BundleState extState = stateService.getState(bundle); if (extState != BundleState.Unknown) { combinedState = extState; } } return new BundleInfoImpl(bundle, combinedState); } @Override public String getDiag(Bundle bundle) { StringBuilder message = new StringBuilder(); for (BundleStateService bundleStateService : stateServices) { String part = bundleStateService.getDiag(bundle); if (part != null) { message.append(bundleStateService.getName()); message.append("\n"); message.append(part); } } if (bundle.getState() == Bundle.INSTALLED) { System.out.println("Unsatisfied Requirements:"); List<BundleRequirement> reqs = getUnsatisfiedRequirements(bundle, null); for (BundleRequirement req : reqs) { System.out.println(req); } } return message.toString(); } @Override public List<BundleRequirement> getUnsatisfiedRequirements(Bundle bundle, String namespace) { List<BundleRequirement> result = new ArrayList<>(); BundleRevision rev = bundle.adapt(BundleRevision.class); if (rev != null) { List<BundleRequirement> reqs = rev.getDeclaredRequirements(namespace); for (BundleRequirement req : reqs) { if (!canBeSatisfied(req)) { result.add(req); } } } return result; } @Override public int getSystemBundleThreshold() { int sbsl = 50; try { final String sbslProp = bundleContext.getProperty(KARAF_SYSTEM_BUNDLES_START_LEVEL); if (sbslProp != null) { sbsl = Integer.valueOf(sbslProp); } } catch (Exception ignore) { // ignore } return sbsl; } private BundleContext doGetBundleContext(String context) { if (context == null || context.trim().isEmpty()) { return bundleContext; } else { List<Bundle> bundles = new BundleSelectorImpl(bundleContext).selectBundles(Collections.singletonList(context), false); if (bundles.isEmpty()) { throw new IllegalArgumentException("Context " + context + " does not evaluate to a bundle"); } else if (bundles.size() > 1) { throw new IllegalArgumentException("Context " + context + " is ambiguous"); } BundleContext bundleContext = bundles.get(0).getBundleContext(); if (bundleContext == null) { throw new IllegalArgumentException("Context " + context + " is not resolved"); } return bundleContext; } } private Bundle doGetBundle(BundleContext bundleContext, String id) { List<Bundle> bundles = doSelectBundles(bundleContext, Collections.singletonList(id), false); if (bundles.isEmpty()) { throw new IllegalArgumentException("Bundle " + id + " does not match any bundle"); } else { List<Bundle> filtered = filter(bundles); if (filtered.isEmpty()) { throw new IllegalArgumentException("Access to bundle " + id + " is forbidden"); } else if (filtered.size() > 1) { throw new IllegalArgumentException("Multiple bundles matching " + id); } return filtered.get(0); } } private List<Bundle> doSelectBundles(BundleContext bundleContext, List<String> ids, boolean defaultAllBundles) { return filter(new BundleSelectorImpl(bundleContext).selectBundles(ids, defaultAllBundles)); } private List<Bundle> filter(List<Bundle> bundles) { if (JaasHelper.currentUserHasRole(BundleService.SYSTEM_BUNDLES_ROLE)) { return bundles; } int sbsl = getSystemBundleThreshold(); List<Bundle> filtered = new ArrayList<>(); for (Bundle bundle : bundles) { int level = bundle.adapt(BundleStartLevel.class).getStartLevel(); if (level >= sbsl) { filtered.add(bundle); } } return filtered; } private boolean canBeSatisfied(BundleRequirement req) { Bundle[] bundles = bundleContext.getBundles(); for (Bundle bundle : bundles) { BundleWiring wiring = bundle.adapt(BundleWiring.class); if (wiring != null) { List<BundleCapability> caps = wiring.getCapabilities(null); for (BundleCapability cap : caps) { if (req.matches(cap)) { return true; } } } } return false; } /* * Enable DynamicImport=* on the bundle */ public void enableDynamicImports(Bundle bundle) { String location = String.format("wrap:%s$" + "Bundle-UpdateLocation=%s&" + "DynamicImport-Package=*&" + "%s=%s&" + "overwrite=merge", bundle.getLocation(), bundle.getLocation(), ORIGINAL_WIRES, explode(getWiredBundles(bundle).keySet())); LOG.debug(format("Updating %s with URL %s", bundle, location)); try { URL url = new URL(location); bundle.update(url.openStream()); bundleContext.getBundle(0).adapt(FrameworkWiring.class).refreshBundles(Collections.singleton(bundle)); } catch (Exception e) { throw new RuntimeException("Error enabling dynamic imports on bundle" + bundle.getBundleId(), e); } } /* * Disable DynamicImport=* on the bundle * * At this time, we will also calculate the difference in package wiring for the bundle compared to * when we enabled the DynamicImport */ public void disableDynamicImports(Bundle bundle) { Set<String> current = getWiredBundles(bundle).keySet(); for (String original : bundle.getHeaders().get(ORIGINAL_WIRES).split(",")) { current.remove(original); } if (current.isEmpty()) { LOG.debug("No additional packages have been wired since dynamic import was enabled"); } else { LOG.debug("Additional packages wired since dynamic import was enabled"); for (String pkg : current) { LOG.debug("- " + pkg); } } try { bundle.update(); } catch (BundleException e) { throw new RuntimeException("Error disabling dynamic imports on bundle" + bundle.getBundleId(), e); } } /* * Explode a set of string values in to a ,-delimited string */ private String explode(Set<String> set) { StringBuilder result = new StringBuilder(); Iterator<String> it = set.iterator(); while (it.hasNext()) { result.append(it.next()); if (it.hasNext()) { result.append(","); } } if (result.length() == 0) { return "--none--"; } return result.toString(); } /* * Get the list of bundles from which the given bundle imports packages */ public Map<String, Bundle> getWiredBundles(Bundle bundle) { // the set of bundles from which the bundle imports packages Map<String, Bundle> exporters = new HashMap<>(); for (BundleRevision revision : bundle.adapt(BundleRevisions.class).getRevisions()) { BundleWiring wiring = revision.getWiring(); if (wiring != null) { List<BundleWire> wires = wiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE); if (wires != null) { for (BundleWire wire : wires) { if (wire.getProviderWiring().getBundle().getBundleId() != 0) { exporters.put(wire.getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString(), wire.getProviderWiring().getBundle()); } } } } } return exporters; } @Override public boolean isDynamicImport(Bundle bundle) { return bundle.getHeaders().get(ORIGINAL_WIRES) != null; } @Override public String getStatus(String id) { Bundle bundle = getBundle(id); return getState(bundle); } /** * Return a String representing current bundle state * * @param bundle the bundle * @return bundle state String */ private String getState(Bundle bundle) { switch (bundle.getState()) { case Bundle.UNINSTALLED: return "Uninstalled"; case Bundle.INSTALLED: return "Installed"; case Bundle.RESOLVED: return "Resolved"; case Bundle.STARTING: return "Starting"; case Bundle.STOPPING: return "Stopping"; case Bundle.ACTIVE: return "Active"; default: return "Unknown"; } } }