diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java index 8d46a066b6..e9a1569558 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java @@ -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; @@ -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 contentCapabilityMap = new ConcurrentHashMap<>(); private TargetRepository() { } @@ -153,7 +167,8 @@ public Map> findProviders(Collection 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()); } @@ -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 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 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 getDirectives() { + return original.getDirectives(); + } + + @Override + public Map getAttributes() { + return original.getAttributes(); + } + + @Override + public String toString() { + return original.toString(); + } + }); + } + + @Override + public List 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() { + 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; + } + + } + }