Skip to content

Commit

Permalink
Supply RepositoryContent / Content Capability
Browse files Browse the repository at this point in the history
Currently we return the raw BundleDescription (Resource) but the spec
requires that all Resource in a Repository needs to implement
RepositoryContent, beside that each resource should provide a content
capability.

This now wraps all BundleDescription in a class that implements Resource
and RepositoryContent and enhances the BundleDescription with a content
capability.
  • Loading branch information
laeubi committed Dec 3, 2023
1 parent dc53e89 commit 238cc7d
Showing 1 changed file with 152 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,25 @@
package org.eclipse.pde.internal.core.bnd;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
Expand All @@ -39,15 +48,20 @@
import org.eclipse.pde.internal.core.PDECore;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.repository.ContentNamespace;
import org.osgi.service.repository.RepositoryContent;

import aQute.bnd.osgi.Instruction;
import aQute.bnd.osgi.repository.BaseRepository;
import aQute.bnd.osgi.resource.CapReqBuilder;
import aQute.bnd.osgi.resource.ResourceUtils;
import aQute.bnd.service.RepositoryPlugin;

public class TargetRepository extends BaseRepository implements RepositoryPlugin {

private static final TargetRepository instance = new TargetRepository();
private static final Map<File, ContentCapabilityCache> contentCapabilityMap = new ConcurrentHashMap<>();

private TargetRepository() {
}
Expand Down Expand Up @@ -153,7 +167,8 @@ public Map<Requirement, Collection<Capability>> findProviders(Collection<? exten

public List<Capability> findProvider(Requirement requirement) {
String namespace = requirement.getNamespace();
return bundles(null).flatMap(resource -> ResourceUtils.capabilityStream(resource, namespace))
return bundles(null).map(BundleDescriptionRepositoryResource::new)
.flatMap(resource -> ResourceUtils.capabilityStream(resource, namespace))
.filter(ResourceUtils.matcher(requirement, ResourceUtils::filterPredicate))
.collect(ResourceUtils.toCapabilities());
}
Expand Down Expand Up @@ -217,4 +232,140 @@ public static TargetRepository getTargetRepository() {
return instance;
}

private static final class BundleDescriptionRepositoryResource implements RepositoryContent, Resource {

private BundleDescription bundle;

public BundleDescriptionRepositoryResource(BundleDescription bundle) {
this.bundle = bundle;
}

@Override
public List<Capability> getCapabilities(String namespace) {
String location = bundle.getLocation();
if (location != null && (namespace == null || ContentNamespace.CONTENT_NAMESPACE.equals(namespace))) {
File file = new File(location);
return Stream
.concat(bundleRequirements(namespace),
contentCapabilityMap.computeIfAbsent(file,
f -> new ContentCapabilityCache(f, BundleDescriptionRepositoryResource.this))
.capability())
.toList();
}
return bundleRequirements(namespace).toList();
}

private Stream<Capability> bundleRequirements(String namespace) {
return bundle.getCapabilities(namespace).stream().map(original -> new Capability() {

@Override
public Resource getResource() {
return BundleDescriptionRepositoryResource.this;
}

@Override
public String getNamespace() {
return original.getNamespace();
}

@Override
public Map<String, String> getDirectives() {
return original.getDirectives();
}

@Override
public Map<String, Object> getAttributes() {
return original.getAttributes();
}

@Override
public String toString() {
return original.toString();
}
});
}

@Override
public List<Requirement> getRequirements(String namespace) {
return bundle.getRequirements(namespace);
}

@Override
public InputStream getContent() {
String location = bundle.getLocation();
if (location != null) {
try {
return new FileInputStream(location);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
throw new RuntimeException(new FileNotFoundException());
}

}

private static final class ContentCapabilityCache {

private final File file;
private Capability capability;
private long lastLength;
private long lastModified;
private Resource resource;

public ContentCapabilityCache(File file, Resource resource) {
this.file = file;
this.resource = resource;
}

public synchronized Stream<Capability> capability() {
if (isOutDated()) {
CapReqBuilder content = new CapReqBuilder(resource, ContentNamespace.CONTENT_NAMESPACE);
String sha;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256"); //$NON-NLS-1$
if (file.isDirectory()) {
// directories can not really have a SHA-256 ...
digest.update(file.getAbsolutePath().getBytes());
} else {
try (DigestInputStream stream = new DigestInputStream(new FileInputStream(file), digest)) {
stream.readAllBytes();
} catch (IOException e) {
return Stream.empty();
}
}
byte[] bytes = digest.digest();
sha = HexFormat.of().formatHex(bytes);
} catch (NoSuchAlgorithmException e) {
return Stream.empty();
}
content.addAttribute(ContentNamespace.CONTENT_NAMESPACE, sha);
content.addAttribute(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE, Long.valueOf(file.length()));
content.addAttribute(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE, "application/vnd.osgi.bundle"); //$NON-NLS-1$
try {
content.addAttribute(ContentNamespace.CAPABILITY_URL_ATTRIBUTE,
file.toURI().toURL().toExternalForm());
} catch (MalformedURLException e) {
return Stream.empty();
}
capability = content.buildCapability();
}
return Stream.of(capability);
}

private boolean isOutDated() {
if (file.isFile()) {
long length = file.length();
long modified = file.lastModified();
if (length != lastLength || modified != lastModified) {
lastLength = length;
lastModified = modified;
return true;
}
}
return capability == null;
}

}

}

0 comments on commit 238cc7d

Please sign in to comment.