package org.osgi.service.indexer.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import org.osgi.service.indexer.Builder;
import org.osgi.service.indexer.Capability;
import org.osgi.service.indexer.Namespaces;
import org.osgi.service.indexer.Requirement;
import org.osgi.service.indexer.Resource;
import org.osgi.service.indexer.ResourceAnalyzer;
import org.osgi.service.indexer.impl.types.SymbolicName;
import org.osgi.service.indexer.impl.types.VersionKey;
import org.osgi.service.indexer.impl.types.VersionRange;
import org.osgi.service.indexer.impl.util.Hex;
import org.osgi.service.indexer.impl.util.OSGiHeader;
import org.osgi.service.indexer.impl.util.Yield;
import org.osgi.service.log.LogService;
class BundleAnalyzer implements ResourceAnalyzer {
private static final String SHA_256 = "SHA-256";
// Duplicate these constants here to avoid a compile-time dependency on OSGi R4.3
private static final String PROVIDE_CAPABILITY = "Provide-Capability";
private static final String REQUIRE_CAPABILITY = "Require-Capability";
// The mime-type of an OSGi bundle
private static final String MIME_TYPE_OSGI_BUNDLE = "application/vnd.osgi.bundle";
private final ThreadLocal<GeneratorState> state = new ThreadLocal<GeneratorState>();
private final LogService log;
public BundleAnalyzer(LogService log) {
this.log = log;
}
public void analyzeResource(Resource resource, List<Capability> capabilities, List<Requirement> requirements) throws Exception {
doIdentity(resource, capabilities);
doContent(resource, capabilities);
doBundleAndHost(resource, capabilities);
doExports(resource, capabilities);
doImports(resource, requirements);
doRequireBundles(resource, requirements);
doFragment(resource, requirements);
doExportService(resource, capabilities);
doImportService(resource, requirements);
doBREE(resource, requirements);
doCapabilities(resource, capabilities);
doRequirements(resource, requirements);
}
private void doIdentity(Resource resource, List<? super Capability> caps) throws Exception {
Manifest manifest = resource.getManifest();
if (manifest == null)
throw new IllegalArgumentException("Missing bundle manifest.");
Attributes attribs = manifest.getMainAttributes();
String fragmentHost = attribs.getValue(Constants.FRAGMENT_HOST);
String identity = (fragmentHost == null) ? Namespaces.RESOURCE_TYPE_BUNDLE : Namespaces.RESOURCE_TYPE_FRAGMENT;
SymbolicName bsn = Util.getSymbolicName(resource);
boolean singleton = Boolean.TRUE.toString().equalsIgnoreCase(bsn.getAttributes().get(Constants.SINGLETON_DIRECTIVE + ":"));
Version version = Util.getVersion(resource);
Builder builder = new Builder()
.setNamespace(Namespaces.NS_IDENTITY)
.addAttribute(Namespaces.NS_IDENTITY, bsn.getName())
.addAttribute(Namespaces.ATTR_IDENTITY_TYPE, identity)
.addAttribute(Namespaces.ATTR_VERSION, version);
if (singleton)
builder.addDirective(Namespaces.DIRECTIVE_SINGLETON, Boolean.TRUE.toString());
caps.add(builder.buildCapability());
}
void setStateLocal(GeneratorState state) {
this.state.set(state);
}
private GeneratorState getStateLocal() {
return state.get();
}
private void doContent(Resource resource, List<? super Capability> capabilities) throws Exception {
Builder builder = new Builder()
.setNamespace(Namespaces.NS_CONTENT);
String sha = calculateSHA(resource);
builder.addAttribute(Namespaces.NS_CONTENT, sha);
String location = calculateLocation(resource);
builder.addAttribute(Namespaces.ATTR_CONTENT_URL, location);
long size = resource.getSize();
if (size > 0L) builder.addAttribute(Namespaces.ATTR_CONTENT_SIZE, size);
builder.addAttribute(Namespaces.ATTR_CONTENT_MIME, MIME_TYPE_OSGI_BUNDLE);
capabilities.add(builder.buildCapability());
}
private String calculateSHA(Resource resource) throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(SHA_256);
byte[] buf = new byte[1024];
InputStream stream = null;
try {
stream = resource.getStream();
while (true) {
int bytesRead = stream.read(buf, 0, 1024);
if (bytesRead < 0)
break;
digest.update(buf, 0, bytesRead);
}
} finally {
if (stream != null) stream.close();
}
return Hex.toHexString(digest.digest());
}
private String calculateLocation(Resource resource) throws IOException {
String location = resource.getLocation();
File path = new File(location);
String fileName = path.getName();
String dir = path.getAbsoluteFile().getParentFile().toURI().toURL().toString();
String result = location;
GeneratorState state = getStateLocal();
if (state != null) {
String rootUrl = state.getRootUrl().toString();
if (!rootUrl.endsWith("/"))
rootUrl += "/";
if (rootUrl != null) {
if (dir.startsWith(rootUrl))
dir = dir.substring(rootUrl.length());
else
throw new IllegalArgumentException("Cannot index files above the root URL.");
}
String urlTemplate = state.getUrlTemplate();
if (urlTemplate != null) {
result = urlTemplate.replaceAll("%s", Util.getSymbolicName(resource).getName());
result = result.replaceAll("%f", fileName);
result = result.replaceAll("%p", dir);
result = result.replaceAll("%v", "" + Util.getVersion(resource));
} else {
result = dir + fileName;
}
}
return result;
}
private static String translate(String value, Properties localStrings) {
if (value == null)
return null;
if (!value.startsWith("%"))
return value;
value = value.substring(1);
return localStrings.getProperty(value, value);
}
private static Properties loadLocalStrings(Resource resource) throws IOException {
Properties props = new Properties();
Attributes attribs = resource.getManifest().getMainAttributes();
String path = attribs.getValue(Constants.BUNDLE_LOCALIZATION);
if (path == null)
path = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
path += ".properties";
Resource propsResource = resource.getChild(path);
if (propsResource != null) {
try {
props.load(propsResource.getStream());
} finally {
propsResource.close();
}
}
return props;
}
private void doBundleAndHost(Resource resource, List<? super Capability> caps) throws Exception {
Builder bundleBuilder = new Builder().setNamespace(Namespaces.NS_WIRING_BUNDLE);
Builder hostBuilder = new Builder().setNamespace(Namespaces.NS_WIRING_HOST);
boolean allowFragments = true;
Attributes attribs = resource.getManifest().getMainAttributes();
if (attribs.getValue(Constants.FRAGMENT_HOST) != null)
return;
SymbolicName bsn = Util.getSymbolicName(resource);
Version version = Util.getVersion(resource);
bundleBuilder.addAttribute(Namespaces.NS_WIRING_BUNDLE, bsn.getName())
.addAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE, version);
hostBuilder.addAttribute(Namespaces.NS_WIRING_HOST, bsn.getName())
.addAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE, version);
for (Entry<String, String> attribEntry : bsn.getAttributes().entrySet()) {
String key = attribEntry.getKey();
if (key.endsWith(":")) {
String directiveName = key.substring(0, key.length() - 1);
if (Constants.FRAGMENT_ATTACHMENT_DIRECTIVE.equalsIgnoreCase(directiveName)) {
if (Constants.FRAGMENT_ATTACHMENT_NEVER.equalsIgnoreCase(attribEntry.getValue()))
allowFragments = false;
} else if (!Constants.SINGLETON_DIRECTIVE.equalsIgnoreCase(directiveName)) {
bundleBuilder.addDirective(directiveName, attribEntry.getValue());
}
} else {
bundleBuilder.addAttribute(key, attribEntry.getValue());
}
}
caps.add(bundleBuilder.buildCapability());
if (allowFragments)
caps.add(hostBuilder.buildCapability());
}
private void doExports(Resource resource, List<? super Capability> caps) throws Exception {
Manifest manifest = resource.getManifest();
String exportsStr = manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
Map<String, Map<String, String>> exports = OSGiHeader.parseHeader(exportsStr);
for (Entry<String, Map<String, String>> entry : exports.entrySet()) {
Builder builder = new Builder().setNamespace(Namespaces.NS_WIRING_PACKAGE);
String pkgName = OSGiHeader.removeDuplicateMarker(entry.getKey());
builder.addAttribute(Namespaces.NS_WIRING_PACKAGE, pkgName);
String versionStr = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
Version version = (versionStr != null) ? new Version(versionStr) : new Version(0, 0, 0);
builder.addAttribute(Namespaces.ATTR_VERSION, version);
for (Entry<String, String> attribEntry : entry.getValue().entrySet()) {
String key = attribEntry.getKey();
if (!"specification-version".equalsIgnoreCase(key) && !Constants.VERSION_ATTRIBUTE.equalsIgnoreCase(key)) {
if (key.endsWith(":"))
builder.addDirective(key.substring(0, key.length() - 1), attribEntry.getValue());
else
builder.addAttribute(key, attribEntry.getValue());
}
}
SymbolicName bsn = Util.getSymbolicName(resource);
builder.addAttribute(Namespaces.ATTR_BUNDLE_SYMBOLIC_NAME, bsn.getName());
Version bundleVersion = Util.getVersion(resource);
builder.addAttribute(Namespaces.ATTR_BUNDLE_VERSION, bundleVersion);
caps.add(builder.buildCapability());
}
}
private void doImports(Resource resource, List<? super Requirement> reqs) throws Exception {
Manifest manifest = resource.getManifest();
String importsStr = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
Map<String, Map<String, String>> imports = OSGiHeader.parseHeader(importsStr);
for (Entry<String, Map<String, String>> entry: imports.entrySet()) {
StringBuilder filter = new StringBuilder();
String pkgName = OSGiHeader.removeDuplicateMarker(entry.getKey());
filter.append("(osgi.wiring.package=").append(pkgName).append(")");
String versionStr = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
if (versionStr != null) {
VersionRange version = new VersionRange(versionStr);
filter.insert(0, "(&");
Util.addVersionFilter(filter, version, VersionKey.PackageVersion);
filter.append(")");
}
Builder builder = new Builder()
.setNamespace(Namespaces.NS_WIRING_PACKAGE)
.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString());
for (Entry<String, String> attribEntry : entry.getValue().entrySet()) {
String key = attribEntry.getKey();
if (!Constants.VERSION_ATTRIBUTE.equalsIgnoreCase(key) && !"specification-version".equals(key)) {
if (key.endsWith(":")) {
String directive = key.substring(0, key.length() - 1);
builder.addDirective(directive, attribEntry.getValue());
} else {
builder.addAttribute(key, attribEntry.getValue());
}
}
}
reqs.add(builder.buildRequirement());
}
}
private void doRequireBundles(Resource resource, List<? super Requirement> reqs) throws Exception {
Manifest manifest = resource.getManifest();
String requiresStr = manifest.getMainAttributes().getValue(Constants.REQUIRE_BUNDLE);
if (requiresStr == null)
return;
Map<String, Map<String, String>> requires = OSGiHeader.parseHeader(requiresStr);
for (Entry<String, Map<String, String>> entry : requires.entrySet()) {
StringBuilder filter = new StringBuilder();
String bsn = OSGiHeader.removeDuplicateMarker(entry.getKey());
filter.append("(osgi.wiring.bundle=").append(bsn).append(")");
String versionStr = entry.getValue().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
if (versionStr != null) {
VersionRange version = new VersionRange(versionStr);
filter.insert(0, "(&");
Util.addVersionFilter(filter, version, VersionKey.BundleVersion);
filter.append(")");
}
Builder builder = new Builder()
.setNamespace(Namespaces.NS_WIRING_BUNDLE)
.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString());
reqs.add(builder.buildRequirement());
}
}
private void doFragment(Resource resource, List<? super Requirement> reqs) throws Exception {
Manifest manifest = resource.getManifest();
String fragmentHost = manifest.getMainAttributes().getValue(Constants.FRAGMENT_HOST);
if (fragmentHost != null) {
StringBuilder filter = new StringBuilder();
Map<String, Map<String, String>> fragmentList = OSGiHeader.parseHeader(fragmentHost);
if (fragmentList.size() != 1)
throw new IllegalArgumentException("Invalid Fragment-Host header: cannot contain multiple entries");
Entry<String, Map<String, String>> entry = fragmentList.entrySet().iterator().next();
String bsn = entry.getKey();
filter.append("(&(osgi.wiring.host=").append(bsn).append(")");
String versionStr = entry.getValue().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
VersionRange version = versionStr != null ? new VersionRange(versionStr) : new VersionRange(Version.emptyVersion.toString());
Util.addVersionFilter(filter, version, VersionKey.BundleVersion);
filter.append(")");
Builder builder = new Builder()
.setNamespace(Namespaces.NS_WIRING_HOST)
.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString());
reqs.add(builder.buildRequirement());
}
}
private void doExportService(Resource resource, List<? super Capability> caps) throws Exception {
@SuppressWarnings("deprecation")
String exportsStr = resource.getManifest().getMainAttributes().getValue(Constants.EXPORT_SERVICE);
Map<String, Map<String, String>> exports = OSGiHeader.parseHeader(exportsStr);
for (Entry<String, Map<String, String>> export : exports.entrySet()) {
String service = OSGiHeader.removeDuplicateMarker(export.getKey());
Builder builder = new Builder()
.setNamespace(Namespaces.NS_WIRING_SERVICE)
.addAttribute(Namespaces.NS_WIRING_SERVICE, service);
caps.add(builder.buildCapability());
}
}
private void doImportService(Resource resource, List<? super Requirement> reqs) throws Exception {
@SuppressWarnings("deprecation")
String importsStr = resource.getManifest().getMainAttributes().getValue(Constants.IMPORT_SERVICE);
Map<String, Map<String, String>> imports = OSGiHeader.parseHeader(importsStr);
for (Entry<String, Map<String, String>> imp : imports.entrySet()) {
String service = OSGiHeader.removeDuplicateMarker(imp.getKey());
StringBuilder filter = new StringBuilder();
filter.append('(').append(Namespaces.NS_WIRING_SERVICE).append('=').append(service).append(')');
Builder builder = new Builder()
.setNamespace(Namespaces.NS_WIRING_SERVICE)
.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString())
.addDirective(Namespaces.DIRECTIVE_EFFECTIVE, Namespaces.EFFECTIVE_ACTIVE);
reqs.add(builder.buildRequirement());
}
}
private void doBREE(Resource resource, List<? super Requirement> reqs) throws Exception {
String breeStr = resource.getManifest().getMainAttributes().getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
Map<String, Map<String, String>> brees = OSGiHeader.parseHeader(breeStr);
final String filter;
if (!brees.isEmpty()) {
if (brees.size() == 1) {
String bree = brees.keySet().iterator().next();
filter = String.format("(%s=%s)", Namespaces.NS_EE, bree);
} else {
StringBuilder builder = new StringBuilder().append("(|");
for (String bree : brees.keySet()) {
bree = OSGiHeader.removeDuplicateMarker(bree);
builder.append(String.format("(%s=%s)", Namespaces.NS_EE, bree));
}
builder.append(')');
filter = builder.toString();
}
Requirement requirement = new Builder()
.setNamespace(Namespaces.NS_EE)
.addDirective(Namespaces.DIRECTIVE_FILTER, filter)
.buildRequirement();
reqs.add(requirement);
}
}
private void doCapabilities(Resource resource, final List<? super Capability> caps) throws Exception {
String capsStr = resource.getManifest().getMainAttributes().getValue(PROVIDE_CAPABILITY);
buildFromHeader(capsStr, new Yield<Builder>() {
public void yield(Builder builder) {
caps.add(builder.buildCapability());
}
});
}
private void doRequirements(Resource resource, final List<? super Requirement> reqs) throws IOException {
String reqsStr = resource.getManifest().getMainAttributes().getValue(REQUIRE_CAPABILITY);
buildFromHeader(reqsStr, new Yield<Builder>() {
public void yield(Builder builder) {
reqs.add(builder.buildRequirement());
}
});
}
private static void buildFromHeader(String headerStr, Yield<Builder> output) {
if (headerStr == null) return;
Map<String, Map<String, String>> header = OSGiHeader.parseHeader(headerStr);
for (Entry<String, Map<String, String>> entry : header.entrySet()) {
String namespace = OSGiHeader.removeDuplicateMarker(entry.getKey());
Builder builder = new Builder().setNamespace(namespace);
Map<String, String> attribs = entry.getValue();
Util.copyAttribsToBuilder(builder, attribs);
output.yield(builder);
}
}
}