package aQute.bnd.repository.maven.provider; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.osgi.resource.Capability; import org.osgi.resource.Requirement; import org.osgi.resource.Resource; import org.osgi.util.function.Function; import org.osgi.util.promise.Failure; import org.osgi.util.promise.Promise; import org.osgi.util.promise.Promises; import aQute.bnd.header.Attrs; import aQute.bnd.osgi.Domain; import aQute.bnd.osgi.resource.ResourceBuilder; import aQute.bnd.osgi.resource.ResourceUtils; import aQute.bnd.service.repository.Phase; import aQute.bnd.service.repository.SearchableRepository.ResourceDescriptor; import aQute.bnd.version.Version; import aQute.lib.collections.MultiMap; import aQute.lib.io.IO; import aQute.lib.json.JSONCodec; import aQute.lib.strings.Strings; import aQute.libg.cryptography.SHA1; import aQute.libg.cryptography.SHA256; import aQute.maven.api.Archive; import aQute.maven.api.IMavenRepo; import aQute.maven.api.Program; import aQute.service.reporter.Reporter; /** * Maintains an index of descriptors of archives. These descriptors are stored * in the maven repository */ class IndexFile { private static final JSONCodec CODEC = new JSONCodec(); public class BundleDescriptor extends ResourceDescriptor { public long lastModified; public Archive archive; public boolean merged; String error; Promise<File> promise; Resource resource; public synchronized Resource getResource() { if (resource == null) { try { File f = promise.getValue(); ResourceBuilder rb = new ResourceBuilder(); rb.addFile(f, f.toURI()); resource = rb.build(); } catch (Exception e) { reporter.exception(e, "Failed to get file for %s", archive); resource = ResourceUtils.DUMMY_RESOURCE; } } return resource; } } private final ConcurrentMap<Archive,Promise<File>> promises = new ConcurrentHashMap<>(); final ConcurrentMap<Archive,BundleDescriptor> descriptors = new ConcurrentHashMap<>(); final File indexFile; final File cacheDir; private final IMavenRepo repo; private final Reporter reporter; private long lastModified; private AtomicBoolean refresh = new AtomicBoolean(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); IndexFile(Reporter reporter, File file, IMavenRepo repo) throws Exception { this.reporter = reporter; this.indexFile = file; this.repo = repo; this.cacheDir = new File(indexFile.getParentFile(), indexFile.getName() + ".info"); } void open() throws Exception { loadIndexFile(); sync(); } private void sync() throws Exception { List<Promise<Void>> sync = new ArrayList<>(promises.size()); for (Iterator<Entry<Archive,Promise<File>>> i = promises.entrySet().iterator(); i.hasNext();) { Entry<Archive,Promise<File>> entry = i.next(); final Archive archive = entry.getKey(); Promise<File> promise = entry.getValue(); i.remove(); sync.add(promise.<Void> then(null, new Failure() { @Override public void fail(Promise< ? > resolved) throws Exception { reporter.exception(resolved.getFailure(), "Failed to sync %s", archive); } })); } Promises.all(sync).getFailure(); // block until all promises resolved } BundleDescriptor add(Archive archive) throws Exception { BundleDescriptor old = descriptors.putIfAbsent(archive, createInitialDescriptor(archive)); BundleDescriptor descriptor = descriptors.get(archive); updateDescriptor(descriptor, repo.get(archive).getValue()); if (old == null || !Arrays.equals(descriptor.id, old.id)) { saveIndexFile(); return descriptor; } return null; } BundleDescriptor remove(Archive archive) throws Exception { BundleDescriptor descriptor = descriptors.remove(archive); if (descriptor != null) { saveIndexFile(); } return descriptor; } public void remove(String bsn) throws Exception { for (Iterator<BundleDescriptor> bd = descriptors.values().iterator(); bd.hasNext();) { if (isBsn(bsn, bd.next())) bd.remove(); } saveIndexFile(); } Collection<String> list() { Set<String> result = new HashSet<>(); for (BundleDescriptor descriptor : descriptors.values()) { result.add(descriptor.bsn); } return result; } Collection<Version> list(String bsn) { Set<Version> result = new HashSet<>(); for (BundleDescriptor descriptor : descriptors.values()) { if (isBsn(bsn, descriptor)) { result.add(descriptor.version); } } return result; } boolean isBsn(String bsn, BundleDescriptor descriptor) { return bsn.equals(descriptor.bsn) || bsn.equals(descriptor.archive.getWithoutVersion()); } public synchronized BundleDescriptor getDescriptor(String bsn, Version version) throws Exception { sync(); for (BundleDescriptor descriptor : descriptors.values()) { if (isBsn(bsn, descriptor) && version.equals(descriptor.version)) { return descriptor; } } return null; } int getErrors(String name) { int errors = 0; for (BundleDescriptor bd : descriptors.values()) if ((name == null || name.equals(bd.bsn)) && bd.error != null) errors++; return errors; } Set<Program> getProgramsForBsn(String name) { Set<Program> set = new HashSet<>(); for (BundleDescriptor bd : descriptors.values()) if (name == null || name.equals(bd.bsn)) { set.add(bd.archive.revision.program); } return set; } long last = 0L; boolean refresh() throws Exception { refresh.getAndSet(false); if (indexFile.lastModified() != lastModified && last + 10000 < System.currentTimeMillis()) { loadIndexFile(); last = System.currentTimeMillis(); return true; } else { boolean refreshed = false; for (BundleDescriptor bd : descriptors.values()) { if ((bd.promise != null) && bd.promise.isDone() && (bd.promise.getFailure() == null)) { File f = bd.promise.getValue(); if ((f != null) && f.isFile() && (f.lastModified() != bd.lastModified)) { updateDescriptor(bd, f); refreshed = true; } } } if (refreshed) return true; } return false; } private void loadIndexFile() throws Exception { lastModified = indexFile.lastModified(); Set<Archive> toBeDeleted = new HashSet<>(descriptors.keySet()); if (indexFile.isFile()) { lock.readLock().lock(); try (BufferedReader rdr = IO.reader(indexFile)) { String line; while ((line = rdr.readLine()) != null) { line = Strings.trim(line); if (line.startsWith("#") || line.isEmpty()) continue; Archive a = Archive.valueOf(line); if (a == null) { reporter.error("MavenBndRepository: invalid entry %s in file %s", line, indexFile); } else { toBeDeleted.remove(a); loadDescriptorAsync(a); } } } finally { lock.readLock().unlock(); } this.descriptors.keySet().removeAll(toBeDeleted); this.promises.keySet().removeAll(toBeDeleted); } } void updateDescriptor(final Archive archive, File file) { BundleDescriptor descriptor = descriptors.get(archive); updateDescriptor(descriptor, file); } void updateDescriptor(BundleDescriptor descriptor, File file) { try { if (file == null || !file.isFile()) { File descriptorFile = getDescriptorFile(descriptor.archive); IO.delete(descriptorFile); reporter.error("Could not find file %s", descriptor.archive); descriptor.error = "File not found"; } else { if (descriptor.lastModified != file.lastModified()) { Domain m = Domain.domain(file); if (m == null) m = Domain.domain(Collections.<String, String> emptyMap()); Entry<String,Attrs> bsn = m.getBundleSymbolicName(); descriptor.bsn = bsn != null ? bsn.getKey() : null; descriptor.version = m.getBundleVersion() == null ? null : Version.parseVersion(m.getBundleVersion()); if (descriptor.bsn == null) { descriptor.bsn = descriptor.archive.getWithoutVersion(); descriptor.version = descriptor.archive.revision.version.getOSGiVersion(); } else if (descriptor.version == null) descriptor.version = Version.LOWEST; descriptor.description = m.getBundleDescription(); descriptor.id = SHA1.digest(file).digest(); descriptor.included = false; descriptor.lastModified = file.lastModified(); descriptor.sha256 = SHA256.digest(file).digest(); saveDescriptor(descriptor); refresh.set(true); } if (descriptor.promise == null && file != null) descriptor.promise = Promises.resolved(file); } } catch (Exception e) { e.printStackTrace(); descriptor.error = e.toString(); refresh.set(true); } } private void saveDescriptor(BundleDescriptor descriptor) throws IOException, Exception { File df = getDescriptorFile(descriptor.archive); IO.mkdirs(df.getParentFile()); CODEC.enc().to(df).put(descriptor); } private void loadDescriptorAsync(final Archive archive) throws Exception { if (updateLocal(archive)) return; updateAsync(archive); } private boolean updateLocal(final Archive archive) { File archiveFile = repo.toLocalFile(archive); if (archiveFile.isFile()) { File descriptorFile = getDescriptorFile(archive); if (descriptorFile.isFile()) { try { BundleDescriptor descriptor = CODEC.dec().from(descriptorFile).get(BundleDescriptor.class); descriptor.promise = Promises.resolved(archiveFile); descriptor.resource = null; descriptors.put(archive, descriptor); return true; } catch (Exception e) { // ignore } } } return false; } Promise<File> updateAsync(Archive archive) throws Exception { Promise<File> promise = repo.get(archive); return updateAsync(archive, promise); } private Promise<File> updateAsync(Archive archive, Promise<File> promise) throws Exception { BundleDescriptor descriptor = createInitialDescriptor(archive); promise = updateAsync(descriptor, promise); descriptors.put(archive, descriptor); return promise; } Promise<File> updateAsync(final BundleDescriptor descriptor, Promise<File> promise) throws Exception { descriptor.promise = promise.map(new Function<File,File>() { @Override public File apply(File file) { updateDescriptor(descriptor, file); return file; } }); descriptor.resource = null; promises.put(descriptor.archive, descriptor.promise); return descriptor.promise; } File getDescriptorFile(Archive archive) { File dir = new File(repo.toLocalFile(archive).getParentFile(), ".bnd"); return new File(dir, archive.getName()); } private BundleDescriptor createInitialDescriptor(Archive archive) throws Exception { BundleDescriptor descriptor = new BundleDescriptor(); descriptor.archive = archive; descriptor.phase = archive.isSnapshot() ? Phase.STAGING : Phase.MASTER; descriptor.url = repo.toRemoteURI(archive); descriptor.bsn = archive.getWithoutVersion(); descriptor.version = archive.revision.version.getOSGiVersion(); return descriptor; } private void saveIndexFile() throws Exception { lock.writeLock().lock(); try { File tmp = File.createTempFile("index", null); try (PrintWriter pw = new PrintWriter(tmp)) { List<Archive> archives = new ArrayList<>(this.descriptors.keySet()); Collections.sort(archives); for (Archive archive : archives) { pw.println(archive); } } Files.move(tmp.toPath(), indexFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } finally { lock.writeLock().unlock(); } lastModified = indexFile.lastModified(); } public void save() throws Exception { saveIndexFile(); } public Collection<Archive> getArchives() { return descriptors.keySet(); } @SuppressWarnings({ "unchecked", "rawtypes" }) public Map<Requirement,Collection<Capability>> findProviders(Collection< ? extends Requirement> requirements) { MultiMap<Requirement,Capability> providers = new MultiMap<>(); for (BundleDescriptor bd : descriptors.values()) { Resource r = bd.getResource(); if (r != null) { for (Requirement req : requirements) { for (Capability cap : r.getCapabilities(req.getNamespace())) { if (ResourceUtils.matches(req, cap)) providers.add(req, cap); } } } } return (Map) providers; } }