package aQute.bnd.main;
import java.io.*;
import java.net.*;
import java.util.*;
import aQute.bnd.build.*;
import aQute.bnd.differ.*;
import aQute.bnd.header.*;
import aQute.bnd.maven.support.*;
import aQute.bnd.osgi.*;
import aQute.bnd.osgi.Verifier;
import aQute.bnd.service.*;
import aQute.bnd.service.RepositoryPlugin.PutResult;
import aQute.bnd.service.diff.*;
import aQute.bnd.service.repository.*;
import aQute.bnd.service.repository.SearchableRepository.ResourceDescriptor;
import aQute.bnd.version.*;
import aQute.lib.collections.*;
import aQute.lib.deployer.*;
import aQute.lib.getopt.*;
import aQute.lib.io.*;
import aQute.lib.json.*;
import aQute.libg.cryptography.*;
import aQute.libg.glob.*;
public class RepoCommand {
final static JSONCodec codec = new JSONCodec();
@Description("Access to the repositories. Provides a number of sub commands to manipulate the repository "
+ "(see repo help) that provide access to the installed repos for the current project.")
@Arguments(arg = {
"sub-cmd", "..."
})
interface repoOptions extends Options {
@Description("Add a File Repository")
Collection<String> filerepo();
@Description("Include the maven repository")
boolean maven();
@Description("Specify a project")
@OptionArgument("<path>")
String project();
@Description("Include the cache repository")
boolean cache();
@Description("Override the name of the release repository (-releaserepo)")
Glob release();
}
final bnd bnd;
final repoOptions opts;
final RepositoryPlugin writable;
final List<RepositoryPlugin> repos = new ArrayList<RepositoryPlugin>();
/**
* Called from the command line
*
* @param bnd
* @param opts
* @throws Exception
*/
public RepoCommand(bnd bnd, repoOptions opts) throws Exception {
this.opts = opts;
this.bnd = bnd;
// We can include the maven repository
if (opts.maven()) {
bnd.trace("maven");
MavenRemoteRepository maven = new MavenRemoteRepository();
maven.setProperties(new Attrs());
maven.setReporter(bnd);
repos.add(maven);
}
// Repos given by the --repo option
if (opts.filerepo() != null) {
for (String r : opts.filerepo()) {
bnd.trace("file repo " + r);
FileRepo repo = new FileRepo();
repo.setReporter(bnd);
File location = bnd.getFile(r);
repo.setLocation(location.getAbsolutePath());
repos.add(repo);
}
}
// If no repos are set
if (repos.isEmpty()) {
bnd.trace("getting project repos");
Project p = bnd.getProject(opts.project());
if (p != null) {
repos.addAll(p.getWorkspace().getRepositories());
} else {
Workspace w = bnd.getWorkspace((File) null);
if (w != null) {
System.out.println("Ws " + w.getBase());
repos.addAll(w.getRepositories());
}
}
}
bnd.trace("repos " + repos);
// Clean up and find first writable
RepositoryPlugin w = null;
for (Iterator<RepositoryPlugin> rp = repos.iterator(); rp.hasNext();) {
RepositoryPlugin rpp = rp.next();
// Check for the cache
if (!opts.cache() && rpp.getName().equals("cache")) {
rp.remove();
}
if (w == null && rpp.canWrite()) {
if (opts.release() == null || opts.release().matcher(rpp.getName()).matches())
w = rpp;
}
}
this.writable = w;
bnd.trace("writable " + w);
List<String> args = opts._();
if (args.size() == 0) {
// Default command
_repos(null);
} else {
// Other commands
String cmd = args.remove(0);
String help = opts._command().execute(this, cmd, args);
if (help != null) {
bnd.out.print(help);
}
}
}
/**
* List the repos
*/
@Arguments(arg = {})
@Description("List the current repositories")
interface reposOptions extends Options {}
@Description("List the current repositories")
public void _repos(@SuppressWarnings("unused") reposOptions opts) {
int n = 1;
for (RepositoryPlugin repo : repos) {
String location = "";
try {
location = repo.getLocation();
}
catch (Throwable e) {
// Ignore
}
bnd.out.printf("%03d: %-20s %4s %-20s %s%n", n++, repo.getName(), repo.canWrite() ? "r/w" : "r/o",
Descriptors.getShortName(repo.getClass().getName()), location);
}
}
/**
* List the content of the repos
*/
@Description("List all artifacts from the current repositories with their versions")
@Arguments(arg = {})
interface listOptions extends Options {
@Description("Do not list the versions, just the bsns")
boolean noversions();
@Description("Optional search term for the list of bsns (given to the repo)")
String query();
@Description("A glob expression on the source repo, default is all repos")
Instruction from();
}
@Description("List all artifacts from the current repositories with their versions")
public void _list(listOptions opts) throws Exception {
bnd.trace("list");
Set<String> bsns = new HashSet<String>();
Instruction from = opts.from();
if (from == null)
from = new Instruction("*");
for (RepositoryPlugin repo : repos) {
if (from.matches(repo.getName()))
bsns.addAll(repo.list(opts.query()));
}
bnd.trace("list " + bsns);
for (String bsn : new SortedList<String>(bsns)) {
if (!opts.noversions()) {
Set<Version> versions = new TreeSet<Version>();
for (RepositoryPlugin repo : repos) {
bnd.trace("get " + bsn + " from " + repo);
if (from.matches(repo.getName())) {
SortedSet<Version> result = repo.versions(bsn);
if (result != null)
versions.addAll(result);
}
}
bnd.out.printf("%-40s %s%n", bsn, versions);
} else {
bnd.out.printf("%s%n", bsn);
}
}
}
/**
* get a file from the repo
*
* @param opts
*/
@Description("Get an artifact from a repository.")
@Arguments(arg = {
"bsn", "[range]"
})
interface getOptions extends Options {
@Description("Where to store the artifact")
String output();
@Description("")
boolean lowest();
Instruction from();
}
@Description("Get an artifact from a repository.")
public void _get(getOptions opts) throws Exception {
Instruction from = opts.from();
if (from == null)
from = new Instruction("*");
List<String> args = opts._();
if (args.isEmpty()) {
bnd.error("Get needs at least a bsn");
return;
}
String bsn = args.remove(0);
String range = null;
if (!args.isEmpty()) {
range = args.remove(0);
if (!args.isEmpty()) {
bnd.error("Extra args %s", args);
}
}
VersionRange r = new VersionRange(range == null ? "0" : range);
Map<Version,RepositoryPlugin> index = new HashMap<Version,RepositoryPlugin>();
for (RepositoryPlugin repo : repos) {
if (from.matches(repo.getName())) {
SortedSet<Version> versions = repo.versions(bsn);
if (versions != null)
for (Version v : versions) {
if (r.includes(v))
index.put(v, repo);
}
}
}
SortedList<Version> l = new SortedList<Version>(index.keySet());
if (l.isEmpty()) {
bnd.out.printf("No versions found for %s%n", bsn);
return;
}
Version v;
if (opts.lowest())
v = l.first();
else
v = l.last();
RepositoryPlugin repo = index.get(v);
File file = repo.get(bsn, v, null);
File dir = bnd.getBase();
String name = file.getName();
if (opts.output() != null) {
File f = bnd.getFile(opts.output());
if (f.isDirectory())
dir = f;
else {
dir = f.getParentFile();
name = f.getName();
}
}
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("Could not create directory " + dir);
}
IO.copy(file, new File(dir, name));
}
/**
* put
*/
@Description("Put an artifact into the repository after it has been verified.")
@Arguments(arg = {
"<jar>..."
})
interface putOptions extends Options {
@Description("Put in repository even if verification fails (actually, no verification is done).")
boolean force();
}
@Description("Put an artifact into the repository after it has been verified.")
public void _put(putOptions opts) throws Exception {
if (writable == null) {
bnd.error("No writable repository in %s", repos);
return;
}
List<String> args = opts._();
if (args.isEmpty()) {
bnd.out.println("Writable repo is " + writable.getName() + " (" + writable.getLocation() + ")");
return;
}
nextArgument: while (args.size() > 0) {
boolean delete = false;
String source = args.remove(0);
File file = bnd.getFile(source);
if (!file.isFile()) {
file = File.createTempFile("jar", ".jar");
delete = true;
try {
IO.copy(new URL(source).openStream(), file);
}
catch (Exception e) {
bnd.error("No such file %s", source);
continue nextArgument;
}
}
bnd.trace("put %s", file);
Jar jar = new Jar(file);
try {
String bsn = jar.getBsn();
if (bsn == null) {
bnd.error("File %s is not a bundle (it has no bsn) ", file);
return;
}
bnd.trace("bsn %s version %s", bsn, jar.getVersion());
if (!opts.force()) {
Verifier v = new Verifier(jar);
v.setTrace(true);
v.setExceptions(true);
v.verify();
bnd.getInfo(v);
}
if (bnd.isOk()) {
PutResult r = writable.put(new BufferedInputStream(new FileInputStream(file)),
new RepositoryPlugin.PutOptions());
bnd.trace("put %s in %s (%s) into %s", source, writable.getName(), writable.getLocation(),
r.artifact);
}
}
finally {
jar.close();
}
if (delete)
file.delete();
}
}
@Arguments(arg = {
"newer repo", "[older repo]"
})
@Description("Show the diff tree of a single repo or compare 2 repos. A diff tree is a "
+ "detailed tree of all aspects of a bundle, including its packages, types, methods, "
+ "fields, and modifiers.")
interface diffOptions extends Options {
@Description("Serialize to JSON")
boolean json();
@Description("Show full diff tree (also wen entries are equal)")
boolean full();
@Description("Formatted like diff")
boolean diff();
@Description("Both add and removes")
boolean all();
@Description("Just removes (no additions)")
boolean remove();
@Description("Just additions (no removes)")
boolean added();
}
@Description("Diff jars (or show tree)")
public void _diff(diffOptions options) throws UnsupportedEncodingException, IOException, Exception {
List<String> _ = options._();
String newer = _.remove(0);
String older = _.size() > 0 ? _.remove(0) : null;
RepositoryPlugin rnewer = findRepo(newer);
RepositoryPlugin rolder = older == null ? null : findRepo(older);
if (rnewer == null) {
bnd.messages.NoSuchRepository_(newer);
return;
}
if (older != null && rolder == null) {
bnd.messages.NoSuchRepository_(newer);
return;
}
PrintWriter pw = new PrintWriter(new OutputStreamWriter(bnd.out, "UTF-8"));
Tree tNewer = RepositoryElement.getTree(rnewer);
if (rolder == null) {
if (options.json())
codec.enc().to(new OutputStreamWriter(bnd.out, "UTF-8")).put(tNewer.serialize()).flush();
else
DiffCommand.show(pw, tNewer, 0);
} else {
Tree tOlder = RepositoryElement.getTree(rolder);
Diff diff = new DiffImpl(tNewer, tOlder);
MultiMap<String,String> map = new MultiMap<String,String>();
for (Diff bsn : diff.getChildren()) {
for (Diff version : bsn.getChildren()) {
if (version.getDelta() == Delta.UNCHANGED)
continue;
if (options.remove() == false && options.added() == false
|| (options.remove() //
&& version.getDelta() == Delta.REMOVED)
|| (options.added() && version.getDelta() == Delta.ADDED)) {
map.add(bsn.getName(), version.getName());
}
}
}
if (options.json())
codec.enc().to(new OutputStreamWriter(bnd.out, "UTF-8")).put(map).flush();
else if (!options.diff())
bnd.printMultiMap(map);
else
DiffCommand.show(pw, diff, 0, !options.full());
}
pw.flush();
}
private RepositoryPlugin findRepo(String name) {
for (RepositoryPlugin repo : repos) {
if (repo.getName().equals(name))
return repo;
}
return null;
}
@Description("Refresh refreshable repositories")
@Arguments(arg = {})
interface RefreshOptions extends Options {
}
@Description("Refresh refreshable repositories")
public void _refresh(RefreshOptions opts) throws Exception {
for (Object o : repos) {
if (o instanceof Refreshable) {
bnd.trace("refresh %s", o);
((Refreshable) o).refresh();
}
}
}
@Description("Displays a sorted set of versions for a given bsn that can be found in the current repositories.")
@Arguments(arg = "bsn")
interface VersionsOptions extends Options {
}
@Description("Displays a list of versions for a given bsn that can be found in the current repositories.")
public void _versions(VersionsOptions opts) throws Exception {
TreeSet<Version> versions = new TreeSet<Version>();
String bsn = opts._().remove(0);
for (RepositoryPlugin repo : repos) {
versions.addAll(repo.versions(bsn));
}
bnd.out.println(versions);
}
/**
* Copy
*/
@Arguments(arg = {
"source", "dest"
})
interface CopyOptions extends Options {
@Description("Do not really copy but trace the steps")
boolean dry();
}
public void _copy(CopyOptions options) throws Exception {
List<String> args = options._();
Workspace ws = Workspace.findWorkspace(bnd.getBase());
if (ws == null) {
bnd.error("Cannot find a workspace from " + bnd.getBase());
return;
}
RepositoryPlugin source = ws.getRepository(args.remove(0));
RepositoryPlugin dest = ws.getRepository(args.remove(0));
if (source == null)
bnd.error("No such source repository: %s", source);
if (dest == null)
bnd.error("No such destination repository: %s", dest);
else if (!dest.canWrite())
bnd.error("Destination repository cannot write: %s", dest);
if (!bnd.isOk() || source == null || dest == null) {
return;
}
@SuppressWarnings("unused")
class Spec {
DownloadBlocker src;
DownloadBlocker dst;
String bsn;
Version version;
public byte[] digest;
}
List<Spec> sources = new ArrayList<Spec>();
//
// Get the repo contents, using background downloads
//
for (String bsn : source.list(null)) {
for (Version version : source.versions(bsn)) {
Spec spec = new Spec();
spec.bsn = bsn;
spec.version = version;
spec.src = new DownloadBlocker(bnd);
File src = source.get(bsn, version, null, spec.src);
if (src == null) {
bnd.error("No such entry: %s-%s", bsn, version);
} else {
spec.dst = findMatchingVersion(dest, bsn, version);
sources.add(spec);
}
}
}
//
// Verify they all exist and are valid to download
//
for (Spec spec : sources) {
String reason = spec.src.getReason();
if (reason != null) {
bnd.error("Failed to find %s because: %s", spec.src.getFile(), reason);
}
File src = spec.src.getFile();
if (!src.isFile()) {
bnd.error("Not a valid file %s", spec.src.getFile());
}
spec.digest = SHA1.digest(src).digest();
}
//
// See if we can prune the list by diffing
//
ResourceRepository resources = null;
if (dest instanceof ResourceRepository)
resources = (ResourceRepository) dest;
nextFile: for (Iterator<Spec> i = sources.iterator(); i.hasNext();) {
Spec spec = i.next();
if (resources != null) {
ResourceDescriptor rd = resources.getResourceDescriptor(spec.digest);
if (rd != null)
// Already exists
continue nextFile;
}
// TODO Diff
}
if (!bnd.isOk())
return;
for (Spec spec : sources) {
File src = spec.src.getFile();
if (!options.dry()) {
FileInputStream fin = new FileInputStream(src);
try {
PutResult put = dest.put(fin, null);
if (put.digest != null) {
if (!Arrays.equals(spec.digest, put.digest)) {
bnd.error("Digest error in upload %s", src);
}
}
}
catch (Exception e) {
bnd.error("Exception %s in upload %s", e, src);
}
finally {
fin.close();
}
}
}
}
private DownloadBlocker findMatchingVersion(RepositoryPlugin dest, String bsn, Version version) throws Exception {
Version floor = version.getWithoutQualifier();
Version ceiling = new Version(floor.getMajor()+1, 0,0);
VersionRange range = new VersionRange(true,floor, ceiling, false);
SortedSet<Version> versions = dest.versions(bsn);
if ( versions == null || versions.isEmpty())
return null;
for ( Version v : range.filter(versions)) {
// First one is highest
// TODO Diff
}
return null;
}
}