package aQute.bnd.osgi.resource; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.osgi.framework.namespace.BundleNamespace; import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.namespace.NativeNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.namespace.service.ServiceNamespace; import org.osgi.resource.Capability; import org.osgi.resource.Namespace; import org.osgi.resource.Requirement; import org.osgi.resource.Resource; import org.osgi.service.repository.ContentNamespace; import aQute.bnd.build.model.EE; import aQute.bnd.header.Attrs; import aQute.bnd.header.OSGiHeader; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Domain; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.Verifier; import aQute.bnd.version.VersionRange; import aQute.lib.converter.Converter; import aQute.lib.filter.Filter; import aQute.libg.cryptography.SHA256; import aQute.libg.reporter.ReporterAdapter; import aQute.service.reporter.Reporter; public class ResourceBuilder { private final static String BUNDLE_MIME_TYPE = "application/vnd.osgi.bundle"; private final static String JAR_MIME_TYPE = "application/java-archive"; private final ResourceImpl resource = new ResourceImpl(); private final List<Capability> capabilities = new LinkedList<Capability>(); private final List<Requirement> requirements = new LinkedList<Requirement>(); private ReporterAdapter reporter = new ReporterAdapter(); private boolean built = false; public ResourceBuilder(Resource source) throws Exception { addCapabilities(source.getCapabilities(null)); addRequirements(source.getRequirements(null)); } public ResourceBuilder() {} public ResourceBuilder addCapability(Capability capability) throws Exception { CapReqBuilder builder = CapReqBuilder.clone(capability); return addCapability(builder); } public ResourceBuilder addCapability(CapReqBuilder builder) { if (built) throw new IllegalStateException("Resource already built"); addCapability0(builder); return this; } public Capability addCapability0(CapReqBuilder builder) { Capability cap = builder.setResource(resource).buildCapability(); capabilities.add(cap); return cap; } public ResourceBuilder addRequirement(Requirement requirement) throws Exception { if (requirement == null) return this; CapReqBuilder builder = CapReqBuilder.clone(requirement); return addRequirement(builder); } public ResourceBuilder addRequirement(CapReqBuilder builder) { if (builder == null) return this; if (built) throw new IllegalStateException("Resource already built"); Requirement req = builder.setResource(resource).buildRequirement(); requirements.add(req); return this; } public Resource build() { if (built) throw new IllegalStateException("Resource already built"); built = true; resource.setCapabilities(capabilities); resource.setRequirements(requirements); return resource; } public List<Capability> getCapabilities() { return capabilities; } /** * Parse the manifest and turn them into requirements & capabilities * * @param manifest The manifest to parse * @throws Exception */ public boolean addManifest(Domain manifest) throws Exception { // // Do the Bundle Identity Ns // int bundleManifestVersion = Integer.parseInt(manifest.get(Constants.BUNDLE_MANIFESTVERSION, "1")); Entry<String,Attrs> bsn = manifest.getBundleSymbolicName(); if (bsn == null) { reporter.warning("No BSN set, not a bundle"); return false; } boolean singleton = "true".equals(bsn.getValue().get(Constants.SINGLETON_DIRECTIVE + ":")); boolean fragment = manifest.getFragmentHost() != null; String versionString = manifest.getBundleVersion(); if (versionString == null) versionString = "0"; else if (!aQute.bnd.version.Version.isVersion(versionString)) throw new IllegalArgumentException("Invalid version in bundle " + bsn + ": " + versionString); aQute.bnd.version.Version version = aQute.bnd.version.Version.parseVersion(versionString); // // First the identity // CapReqBuilder identity = new CapReqBuilder(resource, IdentityNamespace.IDENTITY_NAMESPACE); identity.addAttribute(IdentityNamespace.IDENTITY_NAMESPACE, bsn.getKey()); identity.addAttribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, fragment ? IdentityNamespace.TYPE_FRAGMENT : IdentityNamespace.TYPE_BUNDLE); identity.addAttribute(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, version); if (singleton) { identity.addDirective(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE, "true"); } String copyright = manifest.get(Constants.BUNDLE_COPYRIGHT); if (copyright != null) { identity.addAttribute(IdentityNamespace.CAPABILITY_COPYRIGHT_ATTRIBUTE, copyright); } String description = manifest.get(Constants.BUNDLE_DESCRIPTION); if (description != null) { identity.addAttribute(IdentityNamespace.CAPABILITY_DESCRIPTION_ATTRIBUTE, description); } String docurl = manifest.get(Constants.BUNDLE_DOCURL); if (docurl != null) { identity.addAttribute(IdentityNamespace.CAPABILITY_DOCUMENTATION_ATTRIBUTE, docurl); } String license = manifest.get("Bundle-License"); if (license != null) { identity.addAttribute(IdentityNamespace.CAPABILITY_LICENSE_ATTRIBUTE, license); } addCapability(identity.buildCapability()); // // Now the provide bundle ns // if ((bundleManifestVersion >= 2) && (!fragment)) { CapReqBuilder provideBundle = new CapReqBuilder(resource, BundleNamespace.BUNDLE_NAMESPACE); provideBundle.addAttributesOrDirectives(bsn.getValue()); provideBundle.addAttribute(BundleNamespace.BUNDLE_NAMESPACE, bsn.getKey()); provideBundle.addAttribute(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version); addCapability(provideBundle.buildCapability()); } // // Import/Export service // @SuppressWarnings("deprecation") Parameters importServices = OSGiHeader.parseHeader(manifest.get(Constants.IMPORT_SERVICE)); addImportServices(importServices); @SuppressWarnings("deprecation") Parameters exportServices = OSGiHeader.parseHeader(manifest.get(Constants.EXPORT_SERVICE)); addExportServices(exportServices); // // Handle Require Bundle // Parameters requireBundle = manifest.getRequireBundle(); addRequireBundles(requireBundle); // // Handle Fragment Host // if (fragment) { Entry<String,Attrs> fragmentHost = manifest.getFragmentHost(); addFragmentHost(fragmentHost.getKey(), fragmentHost.getValue()); } else { addFragmentHostCap(bsn.getKey(), version); } // // Add the exported package. These need // to be converted to osgi.wiring.package ns // addExportPackages(manifest.getExportPackage()); // // Add the imported package. These need // to be converted to osgi.wiring.package ns // addImportPackages(manifest.getImportPackage()); // // Add the provided capabilities, they're easy! // addProvideCapabilities(manifest.getProvideCapability()); // // Add the required capabilities, they're also easy! // addRequireCapabilities(manifest.getRequireCapability()); // // Manage native code header // addRequirement(getNativeCode(manifest.getBundleNative())); return true; } public void addExportServices(Parameters exportServices) throws Exception { for (Map.Entry<String,Attrs> e : exportServices.entrySet()) { String service = Processor.removeDuplicateMarker(e.getKey()); CapabilityBuilder cb = new CapabilityBuilder(ServiceNamespace.SERVICE_NAMESPACE); cb.addAttributesOrDirectives(e.getValue()); cb.addAttribute(Constants.OBJECTCLASS, service); addCapability(cb); } } public void addImportServices(Parameters importServices) { for (Map.Entry<String,Attrs> e : importServices.entrySet()) { String service = Processor.removeDuplicateMarker(e.getKey()); boolean optional = Constants.RESOLUTION_OPTIONAL.equals(e.getValue().get("availability:")); boolean multiple = "true".equalsIgnoreCase(e.getValue().get("multiple:")); StringBuilder filter = new StringBuilder(); filter.append('(').append(Constants.OBJECTCLASS).append('=').append(service).append(')'); RequirementBuilder rb = new RequirementBuilder(ServiceNamespace.SERVICE_NAMESPACE); rb.addFilter(filter.toString()); rb.addDirective("effective", "active"); if (optional) rb.addDirective(ServiceNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, Constants.RESOLUTION_OPTIONAL); rb.addDirective(ServiceNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, multiple ? ServiceNamespace.CARDINALITY_MULTIPLE : ServiceNamespace.CARDINALITY_SINGLE); addRequirement(rb); } } /** * Caclulate the requirement from a native code header * * @param header the Bundle-NativeCode header or null * @return a Requirement Builder set to the requirements according tot he * core spec */ public RequirementBuilder getNativeCode(String header) throws Exception { if (header == null || header.isEmpty()) return null; Parameters bundleNative = OSGiHeader.parseHeader(header, null, new Parameters(true)); if (bundleNative.isEmpty()) return null; boolean optional = false; List<String> options = new LinkedList<String>(); RequirementBuilder rb = new RequirementBuilder(NativeNamespace.NATIVE_NAMESPACE); FilterBuilder sb = new FilterBuilder(); sb.or(); for (Entry<String,Attrs> entry : bundleNative.entrySet()) { String name = Processor.removeDuplicateMarker(entry.getKey()); if ("*".equals(name)) { optional = true; continue; } sb.and(); /* * • osname - Name of the operating system. The value of this * attribute must be the name of the operating system upon which the * native libraries run. A number of canonical names are defined in * Table 4.3. */ doOr(sb, "osname", NativeNamespace.CAPABILITY_OSNAME_ATTRIBUTE, entry.getValue()); /* * • processor - The processor architecture. The value of this * attribute must be the name of the processor architecture upon * which the native libraries run. A number of canonical names are * defined in Table 4.2. */ doOr(sb, "processor", NativeNamespace.CAPABILITY_PROCESSOR_ATTRIBUTE, entry.getValue()); /* * • language - The ISO code for a language. The value of this * attribute must be the name of the language for which the native * libraries have been localized. */ doOr(sb, "language", NativeNamespace.CAPABILITY_LANGUAGE_ATTRIBUTE, entry.getValue()); for (String key : entry.getValue().keySet()) { Object value = entry.getValue().getTyped(key); key = Processor.removeDuplicateMarker(key); switch (key) { case "osname" : case "processor" : case "language" : break; /* * • osversion - The operating system version. The value of * this attribute must be a version range as defined in * Version Ranges on page 36. */ case "osversion" : sb.eq(NativeNamespace.CAPABILITY_OSVERSION_ATTRIBUTE, value); break; /* * • selection-filter - A selection filter. The value of * this attribute must be a filter expression that in- * dicates if the native code clause should be selected or * not. */ case "selection-filter" : String filter = value.toString(); String validateFilter = Verifier.validateFilter(filter); if (validateFilter != null) { reporter.error("Invalid 'selection-filter' on Bundle-NativeCode %s", filter); } sb.literal(value.toString()); break; default : reporter.warning("Unknown attribute on Bundle-NativeCode header %s=%s", key, value); break; } } sb.endAnd(); } sb.endOr(); if (optional) rb.addDirective("resolution", "optional"); rb.addFilter(sb.toString()); return rb; } private static void doOr(FilterBuilder sb, String key, String attribute, Attrs attrs) throws Exception { sb.or(); while (attrs.containsKey(key)) { String[] names = Converter.cnv(String[].class, attrs.getTyped(key)); for (String name : names) { sb.eq(attribute, name); } key += "~"; } sb.endOr(); } /** * Add the Require-Bundle header * * @throws Exception */ public void addRequireBundles(Parameters requireBundle) throws Exception { for (Entry<String,Attrs> clause : requireBundle.entrySet()) { addRequireBundle(Processor.removeDuplicateMarker(clause.getKey()), clause.getValue()); } } public void addRequireBundle(String bsn, VersionRange range) throws Exception { Attrs attrs = new Attrs(); attrs.put("bundle-version", range.toString()); addRequireBundle(bsn, attrs); } public void addRequireBundle(String bsn, Attrs attrs) throws Exception { CapReqBuilder rbb = new CapReqBuilder(resource, BundleNamespace.BUNDLE_NAMESPACE); rbb.addDirectives(attrs); StringBuilder filter = new StringBuilder(); filter.append("(").append(BundleNamespace.BUNDLE_NAMESPACE).append("=").append(bsn).append(")"); String v = attrs.get(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); if (v != null && VersionRange.isOSGiVersionRange(v)) { VersionRange range = VersionRange.parseOSGiVersionRange(v); filter.insert(0, "(&"); filter.append(toBundleVersionFilter(range)); filter.append(")"); } rbb.addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); addRequirement(rbb.buildRequirement()); } Object toBundleVersionFilter(VersionRange range) { return range.toFilter().replaceAll(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); } void addFragmentHostCap(String bsn, aQute.bnd.version.Version version) throws Exception { CapReqBuilder rbb = new CapReqBuilder(resource, HostNamespace.HOST_NAMESPACE); rbb.addAttribute(HostNamespace.HOST_NAMESPACE, bsn); rbb.addAttribute(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version); addCapability(rbb.buildCapability()); } public void addFragmentHost(String bsn, Attrs attrs) throws Exception { CapReqBuilder rbb = new CapReqBuilder(resource, HostNamespace.HOST_NAMESPACE); rbb.addDirectives(attrs); StringBuilder filter = new StringBuilder(); filter.append("(").append(HostNamespace.HOST_NAMESPACE).append("=").append(bsn).append(")"); String v = attrs.get(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); if (v != null && VersionRange.isOSGiVersionRange(v)) { VersionRange range = VersionRange.parseOSGiVersionRange(v); filter.insert(0, "(&"); filter.append(range.toFilter(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)); filter.append(")"); } rbb.addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); addRequirement(rbb.buildRequirement()); } public void addRequireCapabilities(Parameters required) throws Exception { for (Entry<String,Attrs> clause : required.entrySet()) { String namespace = Processor.removeDuplicateMarker(clause.getKey()); addRequireCapability(namespace, Processor.removeDuplicateMarker(clause.getKey()), clause.getValue()); } } public void addRequireCapability(String namespace, String name, Attrs attrs) throws Exception { CapReqBuilder req = new CapReqBuilder(resource, namespace); req.addAttributesOrDirectives(attrs); addRequirement(req.buildRequirement()); } public List<Capability> addProvideCapabilities(Parameters capabilities) throws Exception { List<Capability> added = new ArrayList<>(); for (Entry<String,Attrs> clause : capabilities.entrySet()) { String namespace = Processor.removeDuplicateMarker(clause.getKey()); Attrs attrs = clause.getValue(); Capability addedCapability = addProvideCapability(namespace, attrs); added.add(addedCapability); } return added; } public List<Capability> addProvideCapabilities(String clauses) throws Exception { return addProvideCapabilities(new Parameters(clauses, reporter)); } public Capability addProvideCapability(String namespace, Attrs attrs) throws Exception { CapReqBuilder capb = new CapReqBuilder(resource, namespace); capb.addAttributesOrDirectives(attrs); return addCapability0(capb); } /** * Add Exported Packages * * @throws Exception */ public void addExportPackages(Parameters exports) throws Exception { for (Entry<String,Attrs> clause : exports.entrySet()) { String pname = Processor.removeDuplicateMarker(clause.getKey()); Attrs attrs = clause.getValue(); addExportPackage(pname, attrs); } } public void addEE(EE ee) throws Exception { addExportPackages(ee.getPackages()); EE[] compatibles = ee.getCompatible(); addExecutionEnvironment(ee); for (EE compatible : compatibles) { addExecutionEnvironment(compatible); } } public void addExportPackage(String packageName, Attrs attrs) throws Exception { CapReqBuilder capb = new CapReqBuilder(resource, PackageNamespace.PACKAGE_NAMESPACE); capb.addAttributesOrDirectives(attrs); if (!attrs.containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE)) { capb.addAttribute(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion); } capb.addAttribute(PackageNamespace.PACKAGE_NAMESPACE, packageName); addCapability(capb); } /** * Add imported packages * * @throws Exception */ public void addImportPackages(Parameters imports) throws Exception { for (Entry<String,Attrs> clause : imports.entrySet()) { String pname = Processor.removeDuplicateMarker(clause.getKey()); Attrs attrs = clause.getValue(); addImportPackage(pname, attrs); } } public Requirement addImportPackage(String pname, Attrs attrs) throws Exception { CapReqBuilder reqb = new CapReqBuilder(resource, PackageNamespace.PACKAGE_NAMESPACE); reqb.addDirectives(attrs); reqb.addFilter(PackageNamespace.PACKAGE_NAMESPACE, pname, attrs.getVersion(), attrs); Requirement requirement = reqb.buildRequirement(); addRequirement(requirement); return requirement; } // Correct version according to R5 specification section 3.4.1 // BREE J2SE-1.4 ==> osgi.ee=JavaSE, version:Version=1.4 // See bug 329, https://github.com/bndtools/bnd/issues/329 public void addExecutionEnvironment(EE ee) throws Exception { CapReqBuilder builder = new CapReqBuilder(resource, ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE); builder.addAttribute(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, ee.getCapabilityName()); builder.addAttribute(ExecutionEnvironmentNamespace.CAPABILITY_VERSION_ATTRIBUTE, ee.getCapabilityVersion()); addCapability(builder); // Compatibility with old version... builder = new CapReqBuilder(resource, ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE); builder.addAttribute(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, ee.getEEName()); addCapability(builder); } public void addAllExecutionEnvironments(EE ee) throws Exception { addExportPackages(ee.getPackages()); addExecutionEnvironment(ee); for (EE compatibleEE : ee.getCompatible()) { addExecutionEnvironment(compatibleEE); } } public void copyCapabilities(Set<String> ignoreNamespaces, Resource r) throws Exception { for (Capability c : r.getCapabilities(null)) { if (ignoreNamespaces.contains(c.getNamespace())) continue; addCapability(c); } } public void addCapabilities(List<Capability> capabilities) throws Exception { if (capabilities == null || capabilities.isEmpty()) return; for (Capability c : capabilities) addCapability(c); } public void addRequirement(List<Requirement> requirements) throws Exception { if (requirements == null || requirements.isEmpty()) return; for (Requirement rq : requirements) addRequirement(rq); } public void addRequirements(List<Requirement> requires) throws Exception { for (Requirement req : requires) { addRequirement(req); } } public List<Capability> findCapabilities(String ns, String filter) throws Exception { if (filter == null || capabilities.isEmpty()) return Collections.emptyList(); List<Capability> capabilities = new ArrayList<Capability>(); Filter f = new Filter(filter); for (Capability c : getCapabilities()) { if (ns != null && !ns.equals(c.getNamespace())) continue; Map<String,Object> attributes = c.getAttributes(); if (attributes != null) { if (f.matchMap(attributes)) capabilities.add(c); } } return capabilities; } public Map<Capability,Capability> from(Resource bundle) throws Exception { Map<Capability,Capability> mapping = new HashMap<Capability,Capability>(); addRequirements(bundle.getRequirements(null)); for (Capability c : bundle.getCapabilities(null)) { CapReqBuilder clone = CapReqBuilder.clone(c); Capability addedCapability = addCapability0(clone); mapping.put(c, addedCapability); } return mapping; } public Reporter getReporter() { return reporter; } public void addContentCapability(URI uri, String sha256, long length, String mime) throws Exception { assert uri != null; assert sha256 != null && sha256.length() == 64; assert length >= 0; CapabilityBuilder c = new CapabilityBuilder(ContentNamespace.CONTENT_NAMESPACE); c.addAttribute(ContentNamespace.CONTENT_NAMESPACE, sha256); c.addAttribute(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, uri.toString()); c.addAttribute(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE, length); c.addAttribute(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE, mime != null ? mime : BUNDLE_MIME_TYPE); addCapability(c); } public boolean addFile(File file, URI uri) throws Exception { if (uri == null) uri = file.toURI(); Domain manifest = Domain.domain(file); String mime = BUNDLE_MIME_TYPE; boolean hasIdentity = false; if (manifest != null) hasIdentity = addManifest(manifest); else mime = JAR_MIME_TYPE; String sha256 = SHA256.digest(file).asHex(); addContentCapability(uri, sha256, file.length(), mime); return hasIdentity; } }