blob: 6a518d543e99f17e2a30e997736694be44216178 [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.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
import org.apache.felix.utils.version.VersionRange;
import org.apache.felix.utils.version.VersionTable;
import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Conditional;
import org.apache.karaf.features.Dependency;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesNamespaces;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.Library;
import org.apache.karaf.features.ScopeFilter;
import org.apache.karaf.features.internal.download.DownloadCallback;
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.repository.BaseRepository;
import org.apache.karaf.features.internal.resolver.CapabilityImpl;
import org.apache.karaf.features.internal.resolver.FeatureResource;
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.ResourceUtils;
import org.apache.karaf.features.internal.resolver.SimpleFilter;
import org.apache.karaf.features.internal.service.Overrides;
import org.osgi.framework.BundleException;
import org.osgi.framework.Version;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import static java.util.jar.JarFile.MANIFEST_NAME;
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.resolver.ResourceUtils.addIdentityRequirement;
import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureRequirement;
import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
import static org.eclipse.equinox.region.RegionFilter.VISIBLE_ALL_NAMESPACE;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
import static org.osgi.resource.Namespace.REQUIREMENT_FILTER_DIRECTIVE;
public class Subsystem extends ResourceImpl {
private static final String ALL_FILTER = "(|(!(all=*))(all=*))";
private static final String SUBSYSTEM_FILTER = String.format("(%s=%s)", CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM);
private static final String FEATURE_FILTER = String.format("(%s=%s)", CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE);
private static final String SUBSYSTEM_OR_FEATURE_FILTER = String.format("(|%s%s)", SUBSYSTEM_FILTER, FEATURE_FILTER);
// Everything is visible
private static final Map<String, Set<String>> SHARE_ALL_POLICY =
Collections.singletonMap(
VISIBLE_ALL_NAMESPACE,
Collections.singleton(ALL_FILTER));
// Nothing (but systems) is visible
private static final Map<String, Set<String>> SHARE_NONE_POLICY =
Collections.singletonMap(
IDENTITY_NAMESPACE,
Collections.singleton(SUBSYSTEM_FILTER));
private final String name;
private final boolean acceptDependencies;
private final Subsystem parent;
private final Feature feature;
private final boolean mandatory;
private final List<Subsystem> children = new ArrayList<>();
private final Map<String, Set<String>> importPolicy;
private final Map<String, Set<String>> exportPolicy;
private final List<Resource> installable = new ArrayList<>();
private final Map<String, DependencyInfo> dependencies = new HashMap<>();
private final List<Requirement> dependentFeatures = new ArrayList<>();
private final List<String> bundles = new ArrayList<>();
public Subsystem(String name) {
super(name, TYPE_SUBSYSTEM, Version.emptyVersion);
this.name = name;
this.parent = null;
this.acceptDependencies = true;
this.feature = null;
this.importPolicy = SHARE_NONE_POLICY;
this.exportPolicy = SHARE_NONE_POLICY;
this.mandatory = true;
}
public Subsystem(String name, Feature feature, Subsystem parent, boolean mandatory) {
super(name, TYPE_SUBSYSTEM, Version.emptyVersion);
this.name = name;
this.parent = parent;
this.acceptDependencies = feature.getScoping() != null && feature.getScoping().acceptDependencies();
this.feature = feature;
this.mandatory = mandatory;
if (feature.getScoping() != null) {
this.importPolicy = createPolicy(feature.getScoping().getImports());
this.importPolicy.put(IDENTITY_NAMESPACE, Collections.singleton(SUBSYSTEM_OR_FEATURE_FILTER));
this.exportPolicy = createPolicy(feature.getScoping().getExports());
this.exportPolicy.put(IDENTITY_NAMESPACE, Collections.singleton(SUBSYSTEM_OR_FEATURE_FILTER));
} else {
this.importPolicy = SHARE_ALL_POLICY;
this.exportPolicy = SHARE_ALL_POLICY;
}
addIdentityRequirement(this,
feature.getName(),
TYPE_FEATURE,
new VersionRange(VersionTable.getVersion(feature.getVersion()), true));
}
public Subsystem(String name, Subsystem parent, boolean acceptDependencies, boolean mandatory) {
super(name, TYPE_SUBSYSTEM, Version.emptyVersion);
this.name = name;
this.parent = parent;
this.acceptDependencies = acceptDependencies;
this.feature = null;
this.mandatory = mandatory;
this.importPolicy = SHARE_ALL_POLICY;
this.exportPolicy = SHARE_NONE_POLICY;
}
public List<Resource> getInstallable() {
return installable;
}
public String getName() {
return name;
}
public Subsystem getParent() {
return parent;
}
public Collection<Subsystem> getChildren() {
return children;
}
public Subsystem getChild(String name) {
for (Subsystem child : children) {
if (child.getName().equals(name)) {
return child;
}
}
return null;
}
public boolean isAcceptDependencies() {
return acceptDependencies;
}
public Map<String, Set<String>> getImportPolicy() {
return importPolicy;
}
public Map<String, Set<String>> getExportPolicy() {
return exportPolicy;
}
public Feature getFeature() {
return feature;
}
public Subsystem createSubsystem(String name, boolean acceptDependencies) {
if (feature != null) {
throw new UnsupportedOperationException("Can not create application subsystems inside a feature subsystem");
}
// Create subsystem
String childName = getName() + "/" + name;
Subsystem as = new Subsystem(childName, this, acceptDependencies, true);
children.add(as);
// Add a requirement to force its resolution
ResourceUtils.addIdentityRequirement(this, childName, TYPE_SUBSYSTEM, (VersionRange) null);
// Add it to repo
installable.add(as);
return as;
}
public void addSystemResource(Resource resource) {
installable.add(resource);
}
public void requireFeature(String name, String range, boolean mandatory) {
if (mandatory) {
ResourceUtils.addIdentityRequirement(this, name, TYPE_FEATURE, range);
} else {
ResourceImpl res = new ResourceImpl();
ResourceUtils.addIdentityRequirement(res, name, TYPE_FEATURE, range, false);
dependentFeatures.addAll(res.getRequirements(null));
}
}
public void require(String requirement) throws BundleException {
int idx = requirement.indexOf(":");
String type, req;
if (idx >= 0) {
type = requirement.substring(0, idx);
req = requirement.substring(idx + 1);
} else {
type = "feature";
req = requirement;
}
switch (type) {
case "feature":
addRequirement(toFeatureRequirement(req));
break;
case "requirement":
addRequirement(req);
break;
case "bundle":
bundles.add(req);
break;
}
}
protected void addRequirement(String requirement) throws BundleException {
for (Requirement req : ResourceBuilder.parseRequirement(this, requirement)) {
Object range = req.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE);
if (range instanceof String) {
req.getAttributes().put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((String) range));
}
addRequirement(req);
}
}
public Map<String, BundleInfo> getBundleInfos() {
Map<String, BundleInfo> infos = new HashMap<>();
for (DependencyInfo di : dependencies.values()) {
infos.put(di.getLocation(), di);
}
return infos;
}
@SuppressWarnings("InfiniteLoopStatement")
public void build(Collection<Feature> features) throws Exception {
doBuild(features, true);
}
private void doBuild(Collection<Feature> features, boolean mandatory) throws Exception {
for (Subsystem child : children) {
child.doBuild(features, true);
}
if (feature != null) {
for (Dependency dep : feature.getDependencies()) {
Subsystem ss = this;
while (!ss.isAcceptDependencies()) {
ss = ss.getParent();
}
ss.requireFeature(dep.getName(), dep.getVersion(), false);
}
for (Conditional cond : feature.getConditional()) {
Feature fcond = cond.asFeature();
String ssName = this.name + "#" + (fcond.hasVersion() ? fcond.getName() + "-" + fcond.getVersion() : fcond.getName());
Subsystem fs = getChild(ssName);
if (fs == null) {
fs = new Subsystem(ssName, fcond, this, true);
fs.doBuild(features, false);
installable.add(fs);
children.add(fs);
}
}
}
List<Requirement> processed = new ArrayList<>();
while (true) {
List<Requirement> requirements = getRequirements(IDENTITY_NAMESPACE);
requirements.addAll(dependentFeatures);
requirements.removeAll(processed);
if (requirements.isEmpty()) {
break;
}
for (Requirement requirement : requirements) {
String name = (String) requirement.getAttributes().get(IDENTITY_NAMESPACE);
String type = (String) requirement.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
VersionRange range = (VersionRange) requirement.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE);
if (TYPE_FEATURE.equals(type)) {
for (Feature feature : features) {
if (feature.getName().equals(name)
&& (range == null || range.contains(VersionTable.getVersion(feature.getVersion())))) {
if (feature != this.feature) {
String ssName = this.name + "#" + (feature.hasVersion() ? feature.getName() + "-" + feature.getVersion() : feature.getName());
Subsystem fs = getChild(ssName);
if (fs == null) {
fs = new Subsystem(ssName, feature, this, mandatory && !SubsystemResolveContext.isOptional(requirement));
fs.build(features);
installable.add(fs);
children.add(fs);
}
}
}
}
}
processed.add(requirement);
}
}
}
public Set<String> collectPrerequisites() {
Set<String> prereqs = new HashSet<>();
doCollectPrerequisites(prereqs);
return prereqs;
}
private void doCollectPrerequisites(Set<String> prereqs) {
for (Subsystem child : children) {
child.doCollectPrerequisites(prereqs);
}
if (feature != null) {
for (Dependency dep : feature.getDependencies()) {
if (dep.isPrerequisite()) {
prereqs.add(dep.toString());
}
}
}
}
@SuppressWarnings("InfiniteLoopStatement")
public void downloadBundles(DownloadManager manager,
Set<String> overrides,
String featureResolutionRange,
final String serviceRequirements, RepositoryManager repos) throws Exception {
for (Subsystem child : children) {
child.downloadBundles(manager, overrides, featureResolutionRange, serviceRequirements, repos);
}
final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>();
final Downloader downloader = manager.createDownloader();
final Map<BundleInfo, Conditional> infos = new HashMap<>();
if (feature != null) {
for (Conditional cond : feature.getConditional()) {
for (final BundleInfo bi : cond.getBundles()) {
infos.put(bi, cond);
}
}
for (BundleInfo bi : feature.getBundles()) {
infos.put(bi, null);
}
}
boolean removeServiceRequirements;
if (FeaturesService.SERVICE_REQUIREMENTS_DISABLE.equals(serviceRequirements)) {
removeServiceRequirements = true;
} else if (feature != null && FeaturesService.SERVICE_REQUIREMENTS_DEFAULT.equals(serviceRequirements)) {
removeServiceRequirements = !FeaturesNamespaces.URI_1_3_0.equals(feature.getNamespace())
&& !FeaturesNamespaces.URI_1_4_0.equals(feature.getNamespace());
} else {
removeServiceRequirements = false;
}
for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) {
final BundleInfo bi = entry.getKey();
final String loc = bi.getLocation();
downloader.download(loc, new DownloadCallback() {
@Override
public void downloaded(StreamProvider provider) throws Exception {
ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements);
bundles.put(loc, res);
}
});
}
for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) {
final String loc = bundle.getName();
downloader.download(loc, new DownloadCallback() {
@Override
public void downloaded(StreamProvider provider) throws Exception {
ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements);
bundles.put(loc, res);
}
});
}
for (String override : overrides) {
final String loc = Overrides.extractUrl(override);
downloader.download(loc, new DownloadCallback() {
@Override
public void downloaded(StreamProvider provider) throws Exception {
ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements);
bundles.put(loc, res);
}
});
}
if (feature != null) {
for (Library library : feature.getLibraries()) {
if (library.isExport()) {
final String loc = library.getLocation();
downloader.download(loc, new DownloadCallback() {
@Override
public void downloaded(StreamProvider provider) throws Exception {
ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements);
bundles.put(loc, res);
}
});
}
}
}
downloader.await();
Overrides.override(bundles, overrides);
if (feature != null) {
// Add conditionals
Map<Conditional, Resource> resConds = new HashMap<>();
for (Conditional cond : feature.getConditional()) {
FeatureResource resCond = FeatureResource.build(feature, cond, featureResolutionRange, bundles);
addIdentityRequirement(this, resCond, false);
addIdentityRequirement(resCond, this, true);
installable.add(resCond);
resConds.put(cond, resCond);
}
// Add features
FeatureResource resFeature = FeatureResource.build(feature, featureResolutionRange, bundles);
addIdentityRequirement(resFeature, this);
installable.add(resFeature);
// Add dependencies
for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) {
final BundleInfo bi = entry.getKey();
final String loc = bi.getLocation();
final Conditional cond = entry.getValue();
ResourceImpl res = bundles.get(loc);
int sl = bi.getStartLevel() <= 0 ? feature.getStartLevel() : bi.getStartLevel();
if (bi.isDependency()) {
addDependency(res, false, bi.isStart(), sl);
} else {
doAddDependency(res, cond == null, bi.isStart(), sl);
}
if (cond != null) {
addIdentityRequirement(res, resConds.get(cond), true);
}
}
for (Library library : feature.getLibraries()) {
if (library.isExport()) {
final String loc = library.getLocation();
ResourceImpl res = bundles.get(loc);
addDependency(res, false, false, 0);
}
}
for (String uri : feature.getResourceRepositories()) {
BaseRepository repo = repos.getRepository(feature.getRepositoryUrl(), uri);
for (Resource resource : repo.getResources()) {
ResourceImpl res = cloneResource(resource);
addDependency(res, false, true, 0);
}
}
}
for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) {
final String loc = bundle.getName();
boolean dependency = Boolean.parseBoolean(bundle.getAttribute("dependency"));
boolean start = bundle.getAttribute("start") == null || Boolean.parseBoolean(bundle.getAttribute("start"));
int startLevel = 0;
try {
startLevel = Integer.parseInt(bundle.getAttribute("start-level"));
} catch (NumberFormatException e) {
// Ignore
}
if (dependency) {
addDependency(bundles.get(loc), false, start, startLevel);
} else {
doAddDependency(bundles.get(loc), true, start, startLevel);
addIdentityRequirement(this, bundles.get(loc));
}
}
// Compute dependencies
for (DependencyInfo info : dependencies.values()) {
installable.add(info.resource);
addIdentityRequirement(info.resource, this, info.mandatory);
}
}
ResourceImpl cloneResource(Resource resource) {
ResourceImpl res = new ResourceImpl();
for (Capability cap : resource.getCapabilities(null)) {
res.addCapability(new CapabilityImpl(res, cap.getNamespace(),
new HashMap<>(cap.getDirectives()), new HashMap<>(cap.getAttributes())));
}
for (Requirement req : resource.getRequirements(null)) {
SimpleFilter sf;
if (req instanceof RequirementImpl) {
sf = ((RequirementImpl) req).getFilter();
} else if (req.getDirectives().containsKey(REQUIREMENT_FILTER_DIRECTIVE)) {
sf = SimpleFilter.parse(req.getDirectives().get(REQUIREMENT_FILTER_DIRECTIVE));
} else {
sf = SimpleFilter.convert(req.getAttributes());
}
res.addRequirement(new RequirementImpl(res, req.getNamespace(),
new HashMap<>(req.getDirectives()), new HashMap<>(req.getAttributes()), sf));
}
return res;
}
Map<String, String> getMetadata(StreamProvider provider) throws IOException {
try (
InputStream is = provider.open();
) {
ZipInputStream zis = new ZipInputStream(is);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (MANIFEST_NAME.equals(entry.getName())) {
Attributes attributes = new Manifest(zis).getMainAttributes();
Map<java.lang.String, java.lang.String> headers = new HashMap<java.lang.String, java.lang.String>();
for (Map.Entry attr : attributes.entrySet()) {
headers.put(attr.getKey().toString(), attr.getValue().toString());
}
return headers;
}
}
}
throw new IllegalArgumentException("Resource " + provider.getUrl() + " does not contain a manifest");
}
void addDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) {
if (isAcceptDependencies()) {
doAddDependency(resource, mandatory, start, startLevel);
} else {
parent.addDependency(resource, mandatory, start, startLevel);
}
}
private void doAddDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) {
String id = ResolverUtil.getSymbolicName(resource) + "|" + ResolverUtil.getVersion(resource);
DependencyInfo info = dependencies.get(id);
if (info == null) {
info = new DependencyInfo();
dependencies.put(id, info);
}
info.resource = resource;
info.mandatory |= mandatory;
info.start |= start;
if (info.startLevel > 0 && startLevel > 0) {
info.startLevel = Math.min(info.startLevel, startLevel);
} else {
info.startLevel = Math.max(info.startLevel, startLevel);
}
}
class DependencyInfo implements BundleInfo {
ResourceImpl resource;
boolean mandatory;
boolean start;
int startLevel;
@Override
public boolean isStart() {
return start;
}
@Override
public int getStartLevel() {
return startLevel;
}
@Override
public String getLocation() {
return getUri(resource);
}
@Override
public boolean isDependency() {
return !mandatory;
}
}
Map<String, Set<String>> createPolicy(List<? extends ScopeFilter> filters) {
Map<String, Set<String>> policy = new HashMap<>();
for (ScopeFilter filter : filters) {
addToMapSet(policy, filter.getNamespace(), filter.getFilter());
}
return policy;
}
ResourceImpl createResource(String uri, Map<String, String> headers, boolean removeServiceRequirements) throws Exception {
try {
return ResourceBuilder.build(uri, headers, removeServiceRequirements);
} catch (BundleException e) {
throw new Exception("Unable to create resource for bundle " + uri, e);
}
}
@Override
public String toString() {
return getName();
}
}