blob: 3f83733640346972b50cb6bd6135164185620ce1 [file] [log] [blame]
/*
* 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.features.internal.region;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import org.apache.felix.utils.collections.DictionaryAsMap;
import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.internal.download.DownloadManager;
import org.apache.karaf.features.internal.download.Downloader;
import org.apache.karaf.features.internal.download.StreamProvider;
import org.apache.karaf.features.internal.resolver.BaseClause;
import org.apache.karaf.features.internal.resolver.CapabilityImpl;
import org.apache.karaf.features.internal.resolver.CapabilitySet;
import org.apache.karaf.features.internal.resolver.RequirementImpl;
import org.apache.karaf.features.internal.resolver.ResolverUtil;
import org.apache.karaf.features.internal.resolver.ResourceBuilder;
import org.apache.karaf.features.internal.resolver.ResourceImpl;
import org.apache.karaf.features.internal.resolver.SimpleFilter;
import org.apache.karaf.features.internal.util.JsonWriter;
import org.eclipse.equinox.internal.region.StandardRegionDigraph;
import org.eclipse.equinox.region.Region;
import org.eclipse.equinox.region.RegionDigraph;
import org.eclipse.equinox.region.RegionFilterBuilder;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.namespace.service.ServiceNamespace;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.resource.Wire;
import org.osgi.service.repository.Repository;
import org.osgi.service.resolver.Resolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_FEATURE;
import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
import static org.apache.karaf.features.internal.util.MapUtils.invert;
import static org.osgi.framework.Constants.PROVIDE_CAPABILITY;
import static org.osgi.framework.namespace.ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
import static org.osgi.framework.namespace.IdentityNamespace.TYPE_BUNDLE;
import static org.osgi.framework.namespace.IdentityNamespace.TYPE_FRAGMENT;
public class SubsystemResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(SubsystemResolver.class);
private DownloadManager manager;
private Resolver resolver;
private RegionDigraph digraph;
private Subsystem root;
private Map<Resource, List<Wire>> wiring;
// Cached computed results
private ResourceImpl environmentResource;
private Map<String, String> flatSubsystemsMap;
private Map<String, Set<Resource>> bundlesPerRegions;
private Map<Resource, String> bundles;
private Map<String, Set<Resource>> featuresPerRegions;
private Map<Resource, String> features;
private RegionDigraph flatDigraph;
private Map<String, Map<String, BundleInfo>> bundleInfos;
public SubsystemResolver(Resolver resolver, DownloadManager manager) {
this.resolver = resolver;
this.manager = manager;
}
public void prepare(
Collection<Feature> allFeatures,
Map<String, Set<String>> requirements,
Map<String, Set<BundleRevision>> system
) throws Exception {
// Build subsystems on the fly
for (Map.Entry<String, Set<String>> entry : requirements.entrySet()) {
String[] parts = entry.getKey().split("/");
if (root == null) {
root = new Subsystem(parts[0]);
} else if (!root.getName().equals(parts[0])) {
throw new IllegalArgumentException("Can not use multiple roots: " + root.getName() + ", " + parts[0]);
}
Subsystem ss = root;
for (int i = 1; i < parts.length; i++) {
ss = getOrCreateChild(ss, parts[i]);
}
for (String requirement : entry.getValue()) {
ss.require(requirement);
}
}
if (root == null) {
return;
}
// Pre-resolve
root.build(allFeatures);
// Add system resources
BundleRevision sysBundleRev = null;
boolean hasEeCap = false;
for (Map.Entry<String, Set<BundleRevision>> entry : system.entrySet()) {
Subsystem ss = null;
String[] parts = entry.getKey().split("/");
String path = parts[0];
if (path.equals(root.getName())) {
ss = root;
}
for (int i = 1; ss != null && i < parts.length; i++) {
path += "/" + parts[i];
ss = ss.getChild(path);
}
if (ss != null) {
ResourceImpl dummy = new ResourceImpl("dummy", "dummy", Version.emptyVersion);
for (BundleRevision res : entry.getValue()) {
// We need to explicitely provide service capabilities for bundles
// We use both actual services and services declared from the headers
// TODO: use actual services
Map<String, String> headers = new DictionaryAsMap<>(res.getBundle().getHeaders());
Resource tmp = ResourceBuilder.build(res.getBundle().getLocation(), headers);
for (Capability cap : tmp.getCapabilities(ServiceNamespace.SERVICE_NAMESPACE)) {
dummy.addCapability(new CapabilityImpl(dummy, cap.getNamespace(), cap.getDirectives(), cap.getAttributes()));
}
ss.addSystemResource(res);
for (Capability cap : res.getCapabilities(null)) {
hasEeCap |= cap.getNamespace().equals(EXECUTION_ENVIRONMENT_NAMESPACE);
}
if (res.getBundle().getBundleId() == 0) {
sysBundleRev = res;
}
}
ss.addSystemResource(dummy);
}
}
// Under Equinox, the osgi.ee capabilities are not provided by the system bundle
if (!hasEeCap && sysBundleRev != null) {
String provideCaps = sysBundleRev.getBundle().getHeaders().get(PROVIDE_CAPABILITY);
environmentResource = new ResourceImpl("environment", "karaf.environment", Version.emptyVersion);
environmentResource.addCapabilities(ResourceBuilder.parseCapability(environmentResource, provideCaps));
root.addSystemResource(environmentResource);
}
}
public Set<String> collectPrerequisites() throws Exception {
if (root != null) {
return root.collectPrerequisites();
}
return new HashSet<String>();
}
public Map<Resource, List<Wire>> resolve(
Set<String> overrides,
String featureResolutionRange,
String serviceRequirements,
final Repository globalRepository,
String outputFile) throws Exception {
if (root == null) {
return Collections.emptyMap();
}
// Download bundles
RepositoryManager repos = new RepositoryManager();
root.downloadBundles(manager, overrides, featureResolutionRange, serviceRequirements, repos);
// Populate digraph and resolve
digraph = new StandardRegionDigraph(null, null);
populateDigraph(digraph, root);
Downloader downloader = manager.createDownloader();
SubsystemResolveContext context = new SubsystemResolveContext(root, digraph, globalRepository, downloader, serviceRequirements);
if (outputFile != null) {
Map<String, Object> json = new HashMap<>();
if (globalRepository != null) {
json.put("globalRepository", toJson(globalRepository));
}
json.put("repository", toJson(context.getRepository()));
try {
wiring = resolver.resolve(context);
json.put("success", "true");
} catch (Exception e) {
json.put("exception", e.toString());
throw e;
} finally {
try (Writer writer = Files.newBufferedWriter(
Paths.get(outputFile),
StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
JsonWriter.write(writer, json, true);
}
}
} else {
wiring = resolver.resolve(context);
}
downloader.await();
// Remove wiring to the fake environment resource
if (environmentResource != null) {
for (List<Wire> wires : wiring.values()) {
for (Iterator<Wire> iterator = wires.iterator(); iterator.hasNext();) {
Wire wire = iterator.next();
if (wire.getProvider() == environmentResource) {
iterator.remove();
}
}
}
}
// Fragments are always wired to their host only, so create fake wiring to
// the subsystem the host is wired to
associateFragments();
return wiring;
}
private Object toJson(Repository repository) {
Requirement req = new RequirementImpl(
null,
IDENTITY_NAMESPACE,
Collections.<String, String>emptyMap(),
Collections.<String, Object>emptyMap(),
new SimpleFilter(null, null, SimpleFilter.MATCH_ALL));
Collection<Capability> identities = repository.findProviders(Collections.singleton(req)).get(req);
List<Object> resources = new ArrayList<>();
for (Capability identity : identities) {
String id = BaseClause.toString(null, identity.getNamespace(), identity.getAttributes(), identity.getDirectives());
resources.add(toJson(identity.getResource()));
}
return resources;
}
private Object toJson(Resource resource) {
Map<String, Object> obj = new HashMap<>();
List<Object> caps = new ArrayList<>();
List<Object> reqs = new ArrayList<>();
for (Capability cap : resource.getCapabilities(null)) {
caps.add(BaseClause.toString(null, cap.getNamespace(), cap.getAttributes(), cap.getDirectives()));
}
for (Requirement req : resource.getRequirements(null)) {
reqs.add(BaseClause.toString(null, req.getNamespace(), req.getAttributes(), req.getDirectives()));
}
obj.put("capabilities", caps);
obj.put("requirements", reqs);
return obj;
}
public Map<String, Map<String, BundleInfo>> getBundleInfos() {
if (bundleInfos == null) {
bundleInfos = new HashMap<>();
addBundleInfos(root);
}
return bundleInfos;
}
private void addBundleInfos(Subsystem subsystem) {
if (subsystem != null) {
String region = getFlatSubsystemsMap().get(subsystem.getName());
Map<String, BundleInfo> bis = bundleInfos.get(region);
if (bis == null) {
bis = new HashMap<>();
bundleInfos.put(region, bis);
}
bis.putAll(subsystem.getBundleInfos());
for (Subsystem child : subsystem.getChildren()) {
addBundleInfos(child);
}
}
}
public Map<String, StreamProvider> getProviders() {
return manager.getProviders();
}
public Map<Resource, List<Wire>> getWiring() {
return wiring;
}
public RegionDigraph getFlatDigraph() throws BundleException, InvalidSyntaxException {
if (flatDigraph == null) {
flatDigraph = new StandardRegionDigraph(null, null);
Map<String, String> flats = getFlatSubsystemsMap();
if (digraph != null) {
for (Region r : digraph.getRegions()) {
if (r.getName().equals(flats.get(r.getName()))) {
flatDigraph.createRegion(r.getName());
}
}
for (Region r : digraph.getRegions()) {
for (RegionDigraph.FilteredRegion fr : digraph.getEdges(r)) {
String rt = flats.get(r.getName());
String rh = flats.get(fr.getRegion().getName());
if (!rh.equals(rt)) {
Region tail = flatDigraph.getRegion(rt);
Region head = flatDigraph.getRegion(rh);
RegionFilterBuilder rfb = flatDigraph.createRegionFilterBuilder();
for (Map.Entry<String, Collection<String>> entry : fr.getFilter().getSharingPolicy().entrySet()) {
// Discard osgi.identity namespace
if (!IDENTITY_NAMESPACE.equals(entry.getKey())) {
for (String f : entry.getValue()) {
rfb.allow(entry.getKey(), f);
}
}
}
flatDigraph.connect(tail, rfb.build(), head);
}
}
}
}
}
return flatDigraph;
}
public Map<String, String> getFlatSubsystemsMap() {
if (flatSubsystemsMap == null) {
flatSubsystemsMap = new HashMap<>();
findSubsystemsToFlatten(root, flatSubsystemsMap);
}
return flatSubsystemsMap;
}
public Map<String, Set<Resource>> getBundlesPerRegions() {
if (bundlesPerRegions == null) {
bundlesPerRegions = invert(getBundles());
}
return bundlesPerRegions;
}
/**
*
* @return map of bundles and the region they are deployed in
*/
public Map<Resource, String> getBundles() {
if (bundles == null) {
String filter = String.format("(&(%s=*)(|(%s=%s)(%s=%s)))",
IDENTITY_NAMESPACE,
CAPABILITY_TYPE_ATTRIBUTE, TYPE_BUNDLE,
CAPABILITY_TYPE_ATTRIBUTE, TYPE_FRAGMENT);
SimpleFilter sf = SimpleFilter.parse(filter);
bundles = getResourceMapping(sf);
}
return bundles;
}
public Map<String, Set<Resource>> getFeaturesPerRegions() {
if (featuresPerRegions == null) {
featuresPerRegions = invert(getFeatures());
}
return featuresPerRegions;
}
public Map<Resource, String> getFeatures() {
if (features == null) {
SimpleFilter sf = createFilter(IDENTITY_NAMESPACE, "*",
CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE);
features = getResourceMapping(sf);
}
return features;
}
/**
*
* @param resourceFilter
* @return map from resource to region name
*/
private Map<Resource, String> getResourceMapping(SimpleFilter resourceFilter) {
Map<String, String> flats = getFlatSubsystemsMap();
Map<Resource, List<Wire>> wiring = getWiring();
Map<Resource, String> resources = new HashMap<>();
SimpleFilter sf = createFilter(IDENTITY_NAMESPACE, "*",
CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM);
if (wiring != null) {
for (Resource resource : wiring.keySet()) {
if (findMatchingCapability(resourceFilter, resource.getCapabilities(null)) != null) {
// Find the subsystem where this feature is installed
Wire wire = findMatchingWire(sf, wiring.get(resource));
if (wire != null) {
String region = (String) wire.getCapability().getAttributes().get(IDENTITY_NAMESPACE);
region = flats.get(region);
resources.put(resource, region);
}
}
}
}
return resources;
}
private void associateFragments() {
SimpleFilter sf = createFilter(IDENTITY_NAMESPACE, "*", CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM);
for (Map.Entry<Resource, List<Wire>> entry : wiring.entrySet()) {
final Resource resource = entry.getKey();
final Requirement requirement = getSubsystemRequirement(resource);
if (ResolverUtil.isFragment(resource)) {
List<Wire> wires = entry.getValue();
final Resource host = wires.get(0).getProvider();
final Wire wire = findMatchingWire(sf, wiring.get(host));
if (wire != null) {
wires.add(new Wire() {
@Override
public Capability getCapability() {
return wire.getCapability();
}
@Override
public Requirement getRequirement() {
return requirement;
}
@Override
public Resource getProvider() {
return wire.getProvider();
}
@Override
public Resource getRequirer() {
return resource;
}
});
}
}
}
}
private Requirement getSubsystemRequirement(Resource resource) {
for (Requirement requirement : resource.getRequirements(null)) {
if (IDENTITY_NAMESPACE.equals(requirement.getNamespace())
&& TYPE_SUBSYSTEM.equals(requirement.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE))) {
return requirement;
}
}
return null;
}
private Capability findMatchingCapability(SimpleFilter filter, Collection<Capability> caps) {
for (Capability cap : caps) {
if (CapabilitySet.matches(cap, filter)) {
return cap;
}
}
return null;
}
private Wire findMatchingWire(SimpleFilter filter, Collection<Wire> wires) {
for (Wire wire : wires) {
Capability cap = wire.getCapability();
if (CapabilitySet.matches(cap, filter)) {
return wire;
}
}
return null;
}
private SimpleFilter createFilter(String... s) {
Map<String, Object> attrs = new HashMap<>();
for (int i = 0; i < s.length - 1; i += 2) {
attrs.put(s[i], s[i + 1]);
}
return SimpleFilter.convert(attrs);
}
private void findSubsystemsToFlatten(Subsystem subsystem, Map<String, String> toFlatten) {
Subsystem nonFlat = subsystem;
while (isFlat(nonFlat)) {
nonFlat = nonFlat.getParent();
}
if (subsystem != null) {
toFlatten.put(subsystem.getName(), nonFlat.getName());
for (Subsystem child : subsystem.getChildren()) {
findSubsystemsToFlatten(child, toFlatten);
}
}
}
private boolean isFlat(Subsystem subsystem) {
if (subsystem == null || subsystem.getFeature() == null)
return false;
return subsystem.getFeature() != null && subsystem.getFeature().getScoping() == null;
}
private Subsystem getOrCreateChild(Subsystem ss, String name) {
Subsystem child = ss.getChild(name);
return child != null ? child : ss.createSubsystem(name, true);
}
private void populateDigraph(RegionDigraph digraph, Subsystem subsystem) throws BundleException, InvalidSyntaxException {
Region region = digraph.createRegion(subsystem.getName());
if (subsystem.getParent() != null) {
Region parent = digraph.getRegion(subsystem.getParent().getName());
digraph.connect(region, createRegionFilterBuilder(digraph, subsystem.getImportPolicy()).build(), parent);
digraph.connect(parent, createRegionFilterBuilder(digraph, subsystem.getExportPolicy()).build(), region);
}
for (Subsystem child : subsystem.getChildren()) {
populateDigraph(digraph, child);
}
}
private RegionFilterBuilder createRegionFilterBuilder(RegionDigraph digraph, Map<String, Set<String>> sharingPolicy) throws InvalidSyntaxException {
RegionFilterBuilder result = digraph.createRegionFilterBuilder();
for (Map.Entry<String, Set<String>> entry : sharingPolicy.entrySet()) {
for (String filter : entry.getValue()) {
result.allow(entry.getKey(), filter);
}
}
return result;
}
}