package aQute.bnd.repository.osgi;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.Deferred;
import org.osgi.util.promise.Failure;
import org.osgi.util.promise.Promise;
import org.osgi.util.promise.Promises;
import org.osgi.util.promise.Success;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.http.HttpClient;
import aQute.bnd.http.HttpRequest;
import aQute.bnd.osgi.repository.BridgeRepository;
import aQute.bnd.osgi.repository.ResourcesRepository;
import aQute.bnd.osgi.repository.XMLResourceParser;
import aQute.bnd.osgi.resource.ResourceUtils;
import aQute.bnd.osgi.resource.ResourceUtils.ContentCapability;
import aQute.bnd.service.url.TaggedData;
import aQute.bnd.version.Version;
import aQute.lib.exceptions.Exceptions;
import aQute.lib.io.IO;
class OSGiIndex {
private final static Logger logger = LoggerFactory.getLogger(OSGiIndex.class);
private final Promise<BridgeRepository> repository;
private final HttpClient client;
private final long staleTime;
private final File cache;
private final String name;
private final Collection<URI> uris;
OSGiIndex(String name, HttpClient client, File cache, Collection<URI> uris, int staleTime, boolean refresh)
throws Exception {
this.name = name;
this.uris = uris;
this.client = client;
this.cache = checkCache(cache);
this.staleTime = staleTime * 1000L;
this.repository = readIndexes(refresh);
}
private Promise<BridgeRepository> readIndexes(boolean refresh) throws Exception {
List<Promise<List<Resource>>> promises = new ArrayList<>(getURIs().size());
for (URI uri : getURIs()) {
promises.add(download(uri, refresh));
}
Promise<List<List<Resource>>> all = Promises.all(promises);
return all.map(new Function<List<List<Resource>>,BridgeRepository>() {
@Override
public BridgeRepository apply(List<List<Resource>> resources) {
try {
ResourcesRepository rr = new ResourcesRepository();
for (List<Resource> p : resources) {
rr.addAll(p);
}
return new BridgeRepository(rr);
} catch (Exception e) {
throw Exceptions.duck(e);
}
}
});
}
private static File checkCache(File cache) throws Exception {
IO.mkdirs(cache);
if (!cache.isDirectory())
throw new IllegalArgumentException("Cannot create directory for " + cache);
return cache;
}
private Promise<List<Resource>> download(final URI uri, boolean refresh) throws Exception {
HttpRequest<File> req = client.build().useCache(refresh ? -1 : staleTime);
return req.async(uri).map(new Function<File,List<Resource>>() {
@Override
public List<Resource> apply(File file) {
try {
if (file == null) {
logger.debug("{}: No file downloaded for {}", name, uri);
return Collections.emptyList();
}
try (XMLResourceParser xmlp = new XMLResourceParser(IO.stream(file), name, uri)) {
return xmlp.parse();
}
} catch (Exception e) {
throw Exceptions.duck(e);
}
}
});
}
Promise<File> get(String bsn, Version version, File file) throws Exception {
Resource resource = getBridge().get(bsn, version);
if (resource == null)
return null;
ContentCapability content = ResourceUtils.getContentCapability(resource);
if (content == null) {
logger.warn("{}: No content capability for {}", name, resource);
return null;
}
URI url = content.url();
if (url == null) {
logger.warn("{}: No content capability for {}", name, resource);
return null;
}
return client.build().useCache(file, staleTime).async(url);
}
BridgeRepository getBridge() throws Exception {
return repository.getValue();
}
File getCache() {
return cache;
}
/**
* Check any of the URL indexes are stale.
*
* @return
* @throws Exception
*/
boolean isStale() throws Exception {
final Deferred<List<Void>> freshness = new Deferred<>();
List<Promise<Void>> promises = new ArrayList<>(getURIs().size());
for (final URI uri : getURIs()) {
if (freshness.getPromise().isDone()) {
break; // early exit if staleness already detected
}
try {
Promise<TaggedData> async = client.build().useCache().asTag().async(uri);
promises.add(async.then(new Success<TaggedData,Void>() {
@Override
public Promise<Void> call(Promise<TaggedData> resolved) throws Exception {
switch (resolved.getValue().getState()) {
case OTHER :
// in the offline case
// ignore might be best here
logger.debug("Could not verify {}", uri);
break;
case UNMODIFIED :
break;
case NOT_FOUND :
case UPDATED :
default :
logger.debug("Found {} to be stale", uri);
freshness.fail(new Exception("stale"));
}
return null;
}
}, new Failure() {
@Override
public void fail(Promise< ? > resolved) throws Exception {
logger.debug("Could not verify {}: {}", uri, resolved.getFailure());
freshness.fail(resolved.getFailure());
}
}));
} catch (Exception e) {
logger.debug("Checking stale status: {}: {}", uri, e);
}
}
// Resolve when all uris checked
Promise<List<Void>> all = Promises.all(promises);
freshness.resolveWith(all);
// Block until freshness is resolved
return freshness.getPromise().getFailure() != null;
}
Collection<URI> getURIs() {
return uris;
}
Map<Requirement,Collection<Capability>> findProviders(Collection< ? extends Requirement> requirements)
throws Exception {
return getBridge().getRepository().findProviders(requirements);
}
}