Package aQute.library.bnd

Source Code of aQute.library.bnd.JpmRepository$Options

package aQute.library.bnd;

import java.awt.*;
import java.awt.datatransfer.*;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.List;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.regex.*;

import org.osgi.resource.Capability;
import org.osgi.service.indexer.*;
import org.osgi.service.indexer.impl.*;

import aQute.bnd.header.*;
import aQute.bnd.osgi.*;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.service.*;
import aQute.bnd.service.repository.*;
import aQute.bnd.version.*;
import aQute.lib.collections.*;
import aQute.lib.converter.*;
import aQute.lib.hex.*;
import aQute.lib.io.*;
import aQute.lib.justif.*;
import aQute.lib.settings.*;
import aQute.libg.glob.*;
import aQute.libg.reporter.*;
import aQute.library.capreq.*;
import aQute.library.remote.*;
import aQute.service.library.*;
import aQute.service.library.Library.Deposit;
import aQute.service.library.Library.Program;
import aQute.service.library.Library.Requirement;
import aQute.service.library.Library.Revision;
import aQute.service.library.Library.RevisionRef;
import aQute.service.library.SharedTypes.Contributor;
import aQute.service.library.SharedTypes.Developer;
import aQute.service.library.SharedTypes.License;
import aQute.service.library.Library.Requirement;
import aQute.service.reporter.*;
import aQute.struct.struct.Error;

/**
* A bnd repository based on the jpm4j server.
*/
public class JpmRepository implements Plugin, RepositoryPlugin, Closeable, Refreshable, Actionable, RegistryPlugin,
    org.osgi.service.repository.Repository, SearchableRepository {
  private static final String            SUPERIOR_1          = " \u00B9";
  private final String              DOWN_ARROW          = " \u21E9";
  protected final DownloadListener[]        EMPTY_LISTENER        = new DownloadListener[0];
  private Pattern                  SHA              = Pattern.compile(
                                            "([A-F0-9][a-fA-F0-9]){20,20}",
                                            Pattern.CASE_INSENSITIVE);
  private Pattern                  COORDINATE_P        = Pattern
                                            .compile("([-\\w_.\\d]+):([-\\w_.\\d]+)[:@]([-\\w_.\\d]+)");
  private XRemoteLibrary              library;
  private final Justif              j              = new Justif(80, new int[] {
      20, 28, 36, 44
                                        });
  private final Settings              settings          = new Settings();
  private boolean                  canwrite;
  private final MultiMap<File,DownloadListener>  queues            = new MultiMap<File,RepositoryPlugin.DownloadListener>();

  private final Pattern              JPM_REVISION_URL_PATTERN  = Pattern
                                            .compile("https?://.+#!?/p/([^/]+)/([^/]+)/([^/]+)?/([^/]+)+");
  private Options                  options;
  private Reporter                reporter          = new ReporterAdapter(System.out);

  /**
   * Maintains the index of what we've downloaded so far.
   */
  private File                  indexFile;
  private File                  obrFile;
  private Index                  index;
  private boolean                  offline;
  private Registry                registry;
  private StoredRevisionCache            cache;
  private Set<File>                notfound          = new HashSet<File>();
  private Set<String>                notfoundref          = new HashSet<String>();
  private final Semaphore              limitDownloads        = new Semaphore(12);
  private Engine                  engine;

  interface Options {
    /**
     * The URL to the remote repository. Default is jpm4j.org/rest
     *
     * @return
     */
    URI url();

    /**
     * The domain of a depository,optional.
     *
     * @return
     */
    String domain();

    /**
     * The id of the depository
     *
     * @return
     */
    String depository();

    /**
     * The email address of the user
     *
     * @return
     */
    String email();

    /**
     * Where the index file is stored. The default should reside in the
     * workspace and be part of the scm
     *
     * @return
     */
    String index();

    /**
     * The cache location, default is ~/.bnd/cache. This file is relative
     * from the users home directory if not absolute.
     *
     * @return
     */
    String location();

    /**
     * The
     */

    /**
     * Set the settings
     */
    String settings();
  }

  /**
   * Get a revision.
   */
  @Override
  public File get(String bsn, Version version, Map<String,String> attrs, final DownloadListener... listeners)
      throws Exception {

    init();
    // Check if we're supposed to have this
    RevisionRef resource = index.getRevisionRef(bsn, version);
    if (resource == null) {
      if (index.isLearning())
        // Not in the index. See if we can retrieve it. We add it
        // when the bsn was not marked remote so we can
        // let the repo 'discover' the bsns so that an existing
        // workspace can learn the configuration.
        return getRemote(bsn, version, attrs, true, listeners);
      else
        return null;
    } else
      return getLocal(resource, attrs, listeners);
  }

  /**
   * The index indicates we're allowed to have this one. So check if we have
   * it cached or if we need to download it.
   */
  private File getLocal(RevisionRef resource, Map<String,String> attrs, DownloadListener... downloadListeners)
      throws Exception {
    File file = cache.getPath(resource.bsn, Index.toVersion(resource), resource.revision);

    scheduleDownload(file, resource.url, resource.revision, resource.size, downloadListeners);
    return file;
  }

  /**
   * The index does not know the artifact but since bndtools immediately
   * fetches something when clicked upon we need to fetch it in the cache.
   * However, since it will not be added automatically to the index it will
   * not be listed and cannot be used as a dep. Needs to be added for that.
   */
  private File getRemote(String bsn, Version version, Map<String,String> attrs, boolean addIt,
      DownloadListener... downloadListeners) throws Exception {
    if (!isConnected()) {
      reporter.warning("system is not connected, can't retrieve %s", bsn + "-" + version);
      return null;
    }
    // Search on jpm
    RevisionRef ref = getRevisionRef(bsn, version);
    if (ref == null)
      return null;

    if (addIt) {
      index.addRevision(ref);
      index.save();
    }
    File file = cache.getPath(bsn, version, ref.revision);
    scheduleDownload(file, ref.url, ref.revision, ref.size, downloadListeners);
    return file;
  }

  /**
   * Schedule a download, handling the listeners
   */
  private void scheduleDownload(final File file, final URI url, final byte[] sha, final long size,
      DownloadListener... listeners) throws Exception {

    synchronized (notfound) {
      if (notfound.contains(file)) {
        failure(listeners, file, "Not found");
        return;
      }
    }

    if (file.isFile()) {
      if (file.length() == size) {
        // Already exists, done
        success(listeners, file);
        reporter.trace("was in cache");
        return;
      }
      reporter.error("found file but of different length %s, will refetch", file);
    } else {
      reporter.trace("not in cache %s", file + " " + queues);
    }

    // Check if we need synchronous
    if (listeners.length == 0) {
      reporter.trace("in cache, no listeners");
      cache.download(file, url, sha);
      return;
    }

    //
    // With download listeners we need to be careful to queue them
    // appropriately. Don't want to download n times because
    // requests arrive during downloads.
    //

    synchronized (queues) {
      List<DownloadListener> list = queues.get(file);
      boolean first = list == null || list.isEmpty();
      for (DownloadListener l : listeners) {
        queues.add(file, l);
      }

      if (!first) {
        // return, file is being downloaded by another and that
        // other will signal the download listener.
        reporter.trace("someone else is downloading our file " + queues.get(file));
        return;
      }
    }
    try {
      reporter.trace("starting thread for " + file);

      // Limit the total downloads going on at the same time
      limitDownloads.acquire();

      Thread t = new Thread("Downloading " + file) {
        public void run() {
          try {
            reporter.trace("downloading in background " + file);
            cache.download(file, url, sha);
            success(queues.get(file).toArray(EMPTY_LISTENER), file);
          }
          catch (FileNotFoundException e) {
            synchronized (notfound) {
              reporter.error("Not found %s", e, file);
              notfound.add(file);
            }
            synchronized (queues) {
              failure(queues.get(file).toArray(EMPTY_LISTENER), file, e.toString());
            }
          }
          catch (Throwable e) {
            reporter.error("failed to download %s: %s", e, file);
            synchronized (queues) {
              failure(queues.get(file).toArray(EMPTY_LISTENER), file, e.toString());
            }
          }
          finally {
            synchronized (queues) {
              queues.remove(file);
            }
            reporter.trace("downloaded " + file);

            // Allow other downloads to start
            limitDownloads.release();
          }
        }
      };
      t.start();
    }
    catch (Exception e) {
      // Is very unlikely to happen but we must ensure the
      // listeners are called and we're at the head of the queue
      reporter.error("Starting a download for %s failed %s", file, e);
      synchronized (queues) {
        failure(queues.get(file).toArray(EMPTY_LISTENER), file, e.toString());
        queues.remove(file);
      }
    }
  }

  /**
   * API method
   */

  @Override
  public boolean canWrite() {
    return canwrite;
  }

  /**
   * Put an artifact in the repo
   */
  @Override
  public PutResult put(InputStream in, PutOptions options) throws Exception {
    try {
      Deposit depo = new Library.Deposit();
      depo.depository = this.options.depository();
      depo.organization = this.options.domain();
      depo.email = settings.getEmail();
      depo.type = options.type;

      List<Error> s = depo.validate();
      if (!s.isEmpty())
        throw new IllegalArgumentException("Invalid request: " + s.toString());

      File f = File.createTempFile("depo-", ".jar");
      try {
        MessageDigest md = MessageDigest.getInstance("SHA1");
        DigestInputStream din = new DigestInputStream(in, md);
        IO.copy(din, f);
        byte[] sha = md.digest();
        if (options.digest != null && !Arrays.equals(sha, depo.sha))
          throw new IllegalArgumentException("Indicated SHA does not match input stream");

        depo.sha = sha;
        FileInputStream fin = new FileInputStream(f);
        try {
          Revision r = null; // = library.deposit(depo, fin);
          if (r == null)
            throw new IllegalArgumentException("No result returned");

          if (!Arrays.equals(r._id, sha))
            throw new IllegalArgumentException("Mismatch in shas " + Hex.toHexString(r._id) + "!="
                + Hex.toHexString(options.digest));

          PutResult putr = new PutResult();
          putr.artifact = r.url;
          putr.digest = r._id;
          return putr;
        }
        finally {
          fin.close();
        }
      }
      finally {
        f.delete();
      }
    }
    finally {
      in.close();
    }
  }

  /**
   * If we have no search or an empty search we list our index. Otherwise we
   * query remotely.
   */
  @Override
  public List<String> list(String query) throws Exception {
    init();
    if (query == null || query.trim().isEmpty())
      query = "*";
    Glob glob = null;
    try {
      glob = new Glob(query);
    }
    catch (Exception e) {
      glob = new Glob("*");
    }

    Set<String> bsns = new HashSet<String>();
    for (String bsn : index.getBsns()) {
      if (glob.matcher(bsn).matches())
        bsns.add(bsn);
    }
    List<String> result = new ArrayList<String>(bsns);
    Collections.sort(result);
    return result;
  }

  /**
   * List the versions belonging to a bsn
   */
  @Override
  public SortedSet<Version> versions(String bsn) throws Exception {
    init();
    SortedSet<Version> versions = index.getVersions(bsn);
    if (!versions.isEmpty() || !index.isLearning()) {
      return versions;
    }

    // We try to learn what is needed so a new project
    // does not have to add them all by hand.

    Program program = library.getProgram(Library.OSGI_GROUP, bsn);
    if (program == null)
      return versions;

    RevisionRef ref = program.revisions.get(0);

    index.addRevision(ref);
    index.save();
    return index.getVersions(bsn);
  }

  /*
   * Convert a baseline/qualifier to a version
   */
  private Version toVersion(String baseline, String qualifier) {
    if (qualifier == null || qualifier.isEmpty())
      return new Version(baseline);
    else
      return new Version(baseline + "." + qualifier);
  }

  /*
   * Return if bsn is a SHA
   */
  private boolean isSha(String bsn) {
    return SHA.matcher(bsn).matches();
  }

  @Override
  public String getName() {
    return "jpm4j";
  }

  @Override
  public void setProperties(Map<String,String> map) {
    try {
      options = Converter.cnv(Options.class, map);
      setOptions(options);
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public void setOptions(Options options) {
    try {

      canwrite = options.domain() != null && options.depository() != null;

      setLibrary(options.url(), options.location());
      setLocation(options.location());

      String email = options.email();
      if (email == null)
        email = settings.getEmail();

      if (options.domain() != null) {
        if (email != null)
          library.credentials(email, InetAddress.getLocalHost().getHostName(), settings.getPublicKey(),
              settings.getPrivateKey());
      }

      if (options.index() == null)
        throw new IllegalArgumentException("Index file not set");

      setIndex(options.index());

    }
    catch (Exception e) {
      if (reporter != null)
        reporter.exception(e, "Creating options");
      throw new RuntimeException(e);
    }
  }

  public void setIndex(String indexFile) {
    this.indexFile = new File(indexFile);
    this.obrFile = new File(this.indexFile.getAbsolutePath() + ".cache");
    if (!this.indexFile.isAbsolute())
      throw new IllegalArgumentException("Index file must be absolute");
  }

  public void setLocation(String location) {
    if (location != null) {
      File cacheDir = IO.getFile(IO.home, location);
      cacheDir.mkdirs();
      if (!cacheDir.isDirectory())
        throw new IllegalArgumentException("Not able to create cache directory " + cacheDir);
      cache = new StoredRevisionCache(cacheDir, settings);
      File pc = new File(cacheDir, "jpm-library");
      pc.mkdirs();
    }
  }

  public void setLibrary(URI url, String location) {
    if (url != null)
      library = new XRemoteLibrary(url.toString());
    else
      library = new XRemoteLibrary(null);

    if (location == null)
      return;

    File cache = IO.getFile(IO.home, location);
    cache.mkdirs();
    if (!cache.exists()) {
      reporter.error("Got cache dir but cannot create it: %s", null, cache);
      return;
    }

    library.setCache(cache);
  }

  @Override
  public void setReporter(Reporter processor) {
    reporter = processor;
    if (index != null)
      index.setReporter(reporter);
  }

  @Override
  public boolean refresh() throws Exception {
    index = new Index(indexFile);
    library.refresh();
    cache.refresh();
    notfound.clear();
    notfoundref.clear();
    engine = null;
    return true;
  }

  /**
   * Return the actions for this repository
   */
  @Override
  public Map<String,Runnable> actions(Object... target) throws Exception {
    init();
    if (target == null)
      return null;

    if (target.length == 0)
      return getRepositoryActions();

    final String bsn = (String) target[0];
    Program careful = null;

    try {
      careful = library.getProgram(Library.OSGI_GROUP, bsn);
    }
    catch (Exception e) {
      reporter.error("Offline? %s", e);
    }

    final Program p = careful;
    if (target.length == 1)
      return getProgramActions(bsn, p);

    if (target.length >= 2) {
      final Version version = (Version) target[1];

      return getRevisionActions(p, bsn, version);
    }
    return null;
  }

  /**
   * @param p
   * @param bsn
   * @param version
   * @return
   * @throws Exception
   */
  private Map<String,Runnable> getRevisionActions(final Program p, final String bsn, final Version version)
      throws Exception {
    final Library.RevisionRef resource = index.getRevisionRef(bsn, version);
    Map<String,Runnable> map = new LinkedHashMap<String,Runnable>();
    map.put("Inspect Revision", new Runnable() {
      public void run() {
        open("https://www.jpm4j.org#!/p/sha/" + Hex.toHexString(resource.revision) + "//0.0.0");
      }
    });
    RevisionRef update = findUpdate(p, version);
    if (update != null) {
      map.put("Update to " + update.version, new Runnable() {
        public void run() {
          try {
            update(p, bsn, version);
          }
          catch (Exception e) {
            e.printStackTrace();
          }
        }

      });
    }
    map.put("Delete", new Runnable() {
      public void run() {
        try {
          delete(bsn, version, true);
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }

    });
    map.put("Copy Metadata", new Runnable() {
      public void run() {
        try {
          wrap(bsn, version);
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }

    });
    return map;
  }

  /**
   * @param bsn
   * @param p
   * @return
   * @throws Exception
   */
  private Map<String,Runnable> getProgramActions(final String bsn, final Program p) throws Exception {
    Map<String,Runnable> map = new LinkedHashMap<String,Runnable>();

    if (p != null) {
      Set<Version> update = new TreeSet<Version>();
      for (Version v : index.getVersions(bsn)) {
        RevisionRef ref = findUpdate(p, v);
        if (ref != null) {
          update.add(toVersion(ref.baseline, ref.qualifier));
        }
      }

      map.put("Inspect Program", new Runnable() {
        public void run() {
          open("https://www.jpm4j.org#/p/osgi/" + bsn);
        }
      });

      RevisionRef ref = p.revisions.get(0);
      Version latest = toVersion(ref.baseline, ref.qualifier);
      for (Version v : index.getVersions(bsn)) {
        if (v.equals(latest)) {
          latest = null;
          break;
        }
      }
      final Version l = latest;
      String title = "Get Latest";
      if (latest == null)
        title = "-" + title;
      else
        title += " " + l;

      map.put(title, new Runnable() {
        public void run() {
          try {
            add(bsn, l);
          }
          catch (Exception e) {
            throw new RuntimeException(e);
          }
        }
      });

      String label = "-Update";
      if (!update.isEmpty())
        label = "Update to " + Processor.join(update);

      map.put(label, new Runnable() {

        @Override
        public void run() {
          try {
            update(bsn);
          }
          catch (Exception e) {
            throw new RuntimeException(e);
          }
        }

      });
      map.put("Delete", new Runnable() {
        public void run() {
          try {
            delete(bsn);
          }
          catch (Exception e) {
            throw new RuntimeException(e);
          }
        }
      });
    } else {
      map.put("-Update (offline)", null);
    }
    return map;
  }

  /**
   * @return
   */
  private Map<String,Runnable> getRepositoryActions() {
    Map<String,Runnable> map = new LinkedHashMap<String,Runnable>();
    map.put("Delete Cache", new Runnable() {

      @Override
      public void run() {
        try {
          cache.deleteAll();
        }
        catch (Exception e) {
          reporter.error("Deleting cache %s", e);
        }
      }
    });
    map.put("Refresh", new Runnable() {

      @Override
      public void run() {
        try {
          refresh();
        }
        catch (Exception e) {
          reporter.error("Refreshing %s", e);
        }
      }
    });
    map.put("Update All", new Runnable() {

      @Override
      public void run() {
        try {
          updateAll();
        }
        catch (Exception e) {
          reporter.error("Update all %s", e);
        }
      }
    });

    String title = "Learning{Unknown resources are an error, select to learn}";
    if (index.isLearning()) {
      title = "!Learning{Will attempt to fetch unknown resources, select to make this an error}";
    }
    map.put(title, new Runnable() {

      @Override
      public void run() {
        try {
          index.setLearning(!index.isLearning());
          index.save();
        }
        catch (Exception e) {
          reporter.error("Learning %s", e);
        }
      }
    });
    title = "Recurse{Do not fetch dependencies automatically}";
    if (index.isRecurse()) {
      title = "!Recurse{Fetch dependencies automatically}";
    }
    map.put(title, new Runnable() {

      @Override
      public void run() {
        try {
          index.setRecurse(!index.isRecurse());
          index.save();
        }
        catch (Exception e) {
          reporter.error("Learning %s", e);
        }
      }
    });
    return map;
  }

  @Override
  public String tooltip(Object... target) throws Exception {
    init();
    if (target == null || target.length == 0)
      return repositoryTooltip();

    if (target.length == 1)
      return programTooltip((String) target[0]);

    if (target.length == 2)
      return revisionTooltip((String) target[0], (Version) target[1]);

    return "Hmm, have no idea on what object you want a tooltip ...";
  }

  private String repositoryTooltip() throws Exception {
    Formatter f = new Formatter();
    try {
      f.format("%s, a JPM4J repository!\n", getName());
      f.format("Repository     %s\n", options.url() == null ? "default" : options.url());

      if (options.depository() != null)
        f.format("Depository     %s\n", options.depository());

      String email = options.email();
      if (email == null)
        email = settings.getEmail();

      if (email != null)
        f.format("Email          %s\n", email);

      f.format("Cache location %s\n", options.location());
      f.format("Index file     %s\n", options.index());
      f.format("Number of bsns %s\n", index.getBsns().size());
      f.format("Number of revs %s\n", index.getRevisionRefs().size());

      return f.toString();
    }
    finally {
      f.close();
    }
  }

  private String programTooltip(String bsn) throws Exception {
    Program p = library.getProgram(Library.OSGI_GROUP, bsn);
    if (p != null) {
      Formatter sb = new Formatter();
      try {
        if (p.wiki != null && p.wiki.text != null)
          sb.format("%s\n", p.wiki.text.replaceAll("#\\s?", ""));
        else if (p.last.description != null)
          sb.format("%s\n", p.last.description);
        else
          sb.format("No description\n");
        j.wrap((StringBuilder) sb.out());
        return sb.toString();
      }
      finally {
        sb.close();
      }
    }
    return null;
  }

  private String revisionTooltip(String bsn, Version version) throws Exception {
    RevisionRef r = getRevisionRef(bsn, version);

    if (r == null)
      return null;

    Formatter sb = new Formatter();
    try {
      sb.format("[%s:%s", r.groupId, r.artifactId);

      if (r.classifier != null) {
        sb.format(":%s", r.classifier);
      }
      sb.format("@%s]\n\n", r.version);

      if (r.releaseSummary != null)
        sb.format("%s\n\n", r.releaseSummary);

      if (r.description != null)
        sb.format("%s\n\n", r.description.replaceAll("#\\s*", ""));
      sb.format("Size %s\nSHA-1 %s\nAge %s\nURL %s\n", size(r.size, 0), Hex.toHexString(r.revision),
          age(r.created), r.url);

      File f = cache.getPath(bsn, version, r.revision);
      if (f.isFile() && f.length() == r.size)
        sb.format("Cached %s\n", f);
      else
        sb.format("Not downloaded\n");

      Program p = library.getProgram(Library.OSGI_GROUP, bsn);
      if (p != null) {
        RevisionRef update = findUpdate(p, version);
        if (update != null) {
          sb.format(DOWN_ARROW + " This version should be updated to " + update.version);
        }
      }
      j.wrap((StringBuilder) sb.out());
      return sb.toString();
    }
    finally {
      sb.close();
    }
  }

  private List<RevisionRef> getRevisionRefs(String bsn) throws Exception {
    String classifier = null;
    String parts[] = bsn.split("__");
    if (parts.length == 3) {
      bsn = parts[0] + "__" + parts[1];
      classifier = parts[2];
    }

    Program program = library.getProgram(Library.OSGI_GROUP, bsn);
    if (program != null) {
      List<RevisionRef> refs = new ArrayList<Library.RevisionRef>();
      for (RevisionRef r : program.revisions) {
        if (eq(classifier, r.classifier))
          refs.add(r);
      }
      return refs;
    }
    return Collections.emptyList();
  }

  /**
   * Find a revisionref for a bsn/version
   *
   * @param bsn
   * @param version
   * @return
   * @throws Exception
   */
  private RevisionRef getRevisionRef(String bsn, Version version) throws Exception {
    // Handle when we have a sha reference

    String id = bsn + "-" + version;
    if (notfoundref.contains(id))
      return null;

    if (isSha(bsn) && version.equals(Version.LOWEST)) {
      Revision r = library.getRevision(Library.SHA_GROUP + ":" + bsn + "@0.0.0");
      if (r == null)
        return null;

      return new RevisionRef(r);
    }
    reporter.trace("Looking for %s-%s", bsn, version);
    for (RevisionRef r : getRevisionRefs(bsn)) {
      Version v = toVersion(r.baseline, r.qualifier);
      if (v.equals(version))
        return r;
    }
    notfoundref.add(id);
    return null;
  }

  private boolean eq(String a, String b) {
    if (a == null)
      a = "";
    if (b == null)
      b = "";
    return a.equals(b);
  }

  private String age(long created) {
    if (created == 0)
      return "unknown";

    long diff = (System.currentTimeMillis() - created) / (1000 * 60 * 60);
    if (diff < 48)
      return diff + " hours";

    diff /= 24;
    if (diff < 14)
      return diff + " days";

    diff /= 7;
    if (diff < 8)
      return diff + " weeks";

    diff /= 4;
    if (diff < 24)
      return diff + " months";

    diff /= 12;
    return diff + " years";
  }

  String[]  sizes  = {
      "bytes", "Kb", "Mb", "Gb", "Tb", "Pb", "Showing off?"
            };

  private String size(long size, int power) {
    if (power >= sizes.length)
      return size + " Pb";

    if (size < 1000)
      return size + sizes[power];
    return size(size / 1000, power + 1);
  }

  public void setLibrary(XRemoteLibrary library) {
    this.library = library;
  }

  /**
   * Create a bnd recipe for this element from the library data that wraps the
   * JAR with some proper metadata. This is used to easily wrap jars from JPM
   * into real bundles.
   *
   * @param bsn
   * @param version
   * @throws Exception
   */
  private void wrap(String bsn, Version version) throws Exception {
    Revision r = library.getRevision(Library.OSGI_GROUP + ":" + bsn + "@" + version.toString());
    if (r == null)
      return;

    Jar jar = new Jar(get(bsn, version, null));
    Formatter f = new Formatter();
    Analyzer an = new Analyzer();
    try {
      an.setJar(jar);
      an.analyze();

      f.format("# Generated bnd wrapper for           %s:%s@%s\n", r.groupId, r.artifactId, r.version);
      f.format("# Recipe by                           ${global;name}\n\n");

      if (an.getBundleSymbolicName() != null) {
        f.format("#\n# ALREADY AN OSGI BUNDLE!!!\n#\n");
      }

      Manifest m = jar.getManifest();
      if (m != null) {
        String del = "# From original manifest\n";
        boolean yes = false;
        for (Entry<Object,Object> e : m.getMainAttributes().entrySet()) {
          yes = true;
          String key = e.getKey().toString();
          if ("Manifest-Version".equalsIgnoreCase(key) || "Bundle-ManifestVersion".equalsIgnoreCase(key)
              || key.startsWith("Bnd") || key.startsWith("Created"))
            continue;

          f.format("%s%-40s: %s", del, key, e.getValue());

          del = "\n";
        }
        if (yes)
          f.format("\n\n# End of original manifest");
        f.format("\n\n");
      }

      if (r.description != null)
        f.format("Bundle-Description:                   %s %s\\\n ${disclaimer}\n", r.title == null ? ""
            : r.title, r.description);
      else
        f.format("Bundle-Description: ##########################################################");

      String v = r.baseline;
      if (r.qualifier != null)
        v += "." + r.qualifier;

      f.format("Bundle-Version:                       %s\n", v);

      if (r.icon != null)
        f.format("Bundle-Icon:                          %s\n", r.icon);

      f.format("\n#\n# Coordinates\n#\n");
      f.format("JPM-From:                             sha:%s@0.0.0;coordinates=%s;bsn=%s\n",
          Hex.toHexString(r._id), getCoordinates(r), r.bsn);
      f.format("JPM-URL:                              https://jpm4j.org/#/p/%s/%s/%s/%s\n", r.groupId,
          r.artifactId, r.classifier == null ? "" : r.classifier, r.version);
      f.format("Include-Resource:                     @${repo;%s;0.0.0}\n", Hex.toHexString(r._id));

      if (r.docUrl != null)
        f.format("Bundle-DocURL:                        %s\n", r.docUrl);

      if (r.organization != null && r.organization.name != null) {
        f.format("Bundle-Vendor:                        %s\n", r.organization.name);
      }

      if (r.organization != null && r.organization.url != null) {
        f.format("Bundle-ContactAddress:                        %s\n", r.organization.url);
      }

      String del = "Bundle-License:                   ";
      for (License license : r.licenses) {
        f.format("%s %s", del, license.name);
        if (license.url != null)
          f.format(";url='%s'", license.url);
        if (license.comments != null)
          f.format(";description='%s'", license.comments);

        del = ", \\\n   ";
      }
      f.format("\n\n");

      del = "Bundle-Developers: \\\n   ";
      for (Developer dev : r.developers) {
        f.format("%s '%s'", del, dev.name);
        if (dev.email != null)
          f.format(";email='%s'", dev.email);

        if (dev.roles != null) {
          ExtList<String> el = new ExtList<String>(dev.roles);
          if (!el.isEmpty())
            f.format(";roles='%s'", el.join());
        }
        del = ", \\\n   ";
      }
      f.format("\n\n");

      del = "Bundle-Contributors: \\\n   ";
      for (Contributor dev : r.contributors) {
        f.format("%s %s", del, dev.name);
        if (dev.email != null)
          f.format(";email='%s'", dev.email);

        if (dev.roles != null) {
          ExtList<String> el = new ExtList<String>(dev.roles);
          if (!el.isEmpty())
            f.format(";roles='%s'", el.join());
        }
        del = ", \\\n   ";
      }
      f.format("\n\n");

      f.format("Export-Package: ");

      del = "";
      for (Entry<PackageRef,Attrs> pr : an.getContained().entrySet()) {
        f.format("%s\\\n  %s", del, pr.getKey().getFQN());
        if (pr.getValue().containsKey("version")) {
          f.format(";version=%s", pr.getValue().get("version"));
        } else {
          f.format(";version=100.0.0;provide:=true");
        }
        del = ",";
      }
      f.format("\n\n");
      f.format("#\n# Remove after inspection:\n\nImport-Package: ");

      del = "";
      for (Entry<PackageRef,Attrs> pr : an.getReferred().entrySet()) {
        if (pr.getKey().isJava() || pr.getKey().isMetaData())
          continue;

        f.format("%s\\\n  %s", del, pr.getKey().getFQN());
        if (pr.getValue().containsKey("version")) {
          f.format(";version='%s'", pr.getValue().get("version"));
        }
        del = ",";
      }
      f.format("\n\n");

      f.format("-buildpath:");
      del = " \\\n    ";

      for (Requirement req : r.requirements) {
        String name = req.name == null ? (String) req.ps.get("name:") : req.name;
        if ("x-maven".equals(req.ns) && name != null) {
          Matcher matcher = COORDINATE_P.matcher(name);
          if (matcher.matches()) {
            String g = matcher.group(1);
            String a = matcher.group(2);
            String vv = matcher.group(3);

            Revision dep = library.getRevision(g + ":" + a + "@" + vv);
            if (dep == null) {
              f.format("%s%s.%s;version=%s", del, g, a, vv);
            } else {
              f.format("%s%s;version=%s", del, r.bsn, vv);
            }
            del = ", \\\n";
          } else {
            f.format("\n#### %s\n", req.name);
          }
        }
      }
      f.format("\n\n");
    }
    catch (Exception e) {
      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw);
      e.printStackTrace(pw);
      pw.close();
      f.format("Sorry, should not have happend :-(\n\n%s\n", sw);
    }
    finally {
      jar.close();
      f.close();
      an.close();
    }

    clipboard(f.toString());
  }

  private Object getCoordinates(Revision r) {
    return r.groupId + ":" + r.artifactId + (r.classifier == null ? "" : ":" + r.classifier) + "@" + r.version;
  }

  private void clipboard(final String string) {
    Clipboard clipboard = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
    clipboard.setContents(new Transferable() {

      @Override
      public boolean isDataFlavorSupported(DataFlavor flavor) {
        return false;
      }

      @Override
      public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] {
          DataFlavor.stringFlavor
        };
      }

      @Override
      public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        return string;
      }
    }, new ClipboardOwner() {

      @Override
      public void lostOwnership(Clipboard clipboard, Transferable contents) {
        // what me worry?
      }
    });
  }

  /**
   * Update all bsns
   *
   * @throws Exception
   */

  void updateAll() throws Exception {
    for (String bsn : index.getBsns()) {
      update(bsn);
    }
  }

  /**
   * Update all baselines for a bsn
   *
   * @param bsn
   * @throws Exception
   */
  void update(String bsn) throws Exception {
    Program p = library.getProgram(Library.OSGI_GROUP, bsn);
    for (Version v : index.getVersions(bsn))
      update(p, bsn, v);
  }

  /**
   * Update a revision of there is a higher with the same baseline but higher
   * qualifier.
   *
   * @param p
   * @param v
   * @throws Exception
   */
  void update(Program p, String bsn, Version v) throws Exception {

    RevisionRef ref = findUpdate(p, v);
    if (ref == null)
      return;

    index.addRevision(ref);
    index.delete(bsn, v);
    index.save(indexFile);
  }

  /**
   * Find a RevisionRef from the Program. We are looking for a version with
   * the same baseline but a higher qualifier.
   *
   * @param p
   * @param currentVersion
   * @return
   * @throws Exception
   */

  private RevisionRef findUpdate(Program p, Version currentVersion) throws Exception {
    String baseline = currentVersion.getWithoutQualifier().toString();

    Version candidate = currentVersion;
    RevisionRef candidateRef = null;

    for (RevisionRef r : p.revisions) {
      if (r.baseline.equals(baseline)) {
        if (r.phase == Library.Phase.MASTER) {
          if (currentVersion.equals(toVersion(r.baseline, r.qualifier)))
            return null;
          return r;
        }

        Version refVersion = toVersion(r.baseline, r.qualifier);
        if (refVersion.compareTo(candidate) > 0) {
          candidate = refVersion;
          candidateRef = r;
        }
      }
    }
    return candidateRef;
  }

  public void setIndex(File index) {
    indexFile = index;
    obrFile = IO.getFile(index.getParentFile(), "cache/" + index.getName() + ".xml");
  }

  private void success(DownloadListener[] downloadListeners, File f) {
    for (DownloadListener l : downloadListeners) {
      try {
        l.success(f);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private void failure(DownloadListener[] listeners, File f, String reason) {
    for (DownloadListener l : listeners) {
      try {
        l.failure(f, reason);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  @SuppressWarnings({
      "rawtypes", "unchecked"
  })
  public Map<org.osgi.resource.Requirement,Collection<org.osgi.resource.Capability>> findProviders(
      Collection< ? extends org.osgi.resource.Requirement> requirements) {

    MultiMap map = new MultiMap<Requirement,List<Capability>>();
    Set<org.osgi.resource.Requirement> notFound = new HashSet<org.osgi.resource.Requirement>();

    try {
      Engine engine = getEngine();
      for (org.osgi.resource.Requirement req : requirements) {
        List<Capability> providers = engine.findProviders(req);
        if (providers.isEmpty()) {
          if (Engine.isMandatory(req))
            notFound.add(req);
        } else
          map.put(req, providers);
      }
      for (org.osgi.resource.Requirement req : notFound) {
        System.out.println("Not found " + req);
      }
      return map;
    }
    catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
  }

  @Override
  public String title(Object... target) throws Exception {
    init();
    if (target == null || target.length == 0)
      return getName();

    if (target.length == 1 && target[0] instanceof String) {
      String bsn = (String) target[0];
      String title = bsn;
      Program p = library.getProgram(Library.OSGI_GROUP, bsn);
      if (p != null) {
        for (Version version : versions(bsn)) {
          RevisionRef findUpdate = findUpdate(p, version);
          if (findUpdate != null)
            title += SUPERIOR_1; // superscript 1
        }
      }
      return title;
    }

    if (target.length == 2 && target[0] instanceof String && target[1] instanceof Version) {
      String bsn = (String) target[0];
      Version version = (Version) target[1];

      Library.RevisionRef resource = index.getRevisionRef(bsn, version);
      if (resource == null)
        return "[deleted " + version + "]";

      String title = getPhase(resource.phase.toString()) + " " + version.toString();

      File path = cache.getPath(bsn, version, resource.revision);
      if (path.isFile() && path.length() == resource.size) {
        title += DOWN_ARROW;
      }
      Program p = library.getProgram(Library.OSGI_GROUP, bsn);
      if (p != null) {
        RevisionRef findUpdate = findUpdate(p, version);
        if (findUpdate != null)
          title += SUPERIOR_1; // superscript 1
      }
      return title;
    }

    return null;
  }

  // Temp until we fixed bnd in bndtools
  enum Phase {
    STAGING(false, false, false, "[s]"), LOCKED(true, false, false, "[l]"), MASTER(true, true, true, "[m]"), RETIRED(
        true, false, true, "[r]"), WITHDRAWN(true, false, true, "[x]"), UNKNOWN(true, false, false, "[?]");

    boolean      locked;
    boolean      listable;
    boolean      permanent;
    final String  symbol;

    private Phase(boolean locked, boolean listable, boolean permanent, String symbol) {
      this.locked = locked;
      this.listable = listable;
      this.permanent = permanent;
      this.symbol = symbol;
    }

    public boolean isLocked() {
      return locked;
    }

    public boolean isListable() {
      return listable;
    }

    public boolean isPermanent() {
      return permanent;
    }

    public String getSymbol() {
      return symbol;
    }
  }

  private String getPhase(String phase) {
    try {
      return Phase.valueOf(phase).getSymbol();
    }
    catch (Exception e) {
      return "?";
    }
  }

  @Override
  public File getRoot() {
    return cache.getRoot();
  }

  @Override
  public void close() throws IOException {}

  @Override
  public String getLocation() {
    return options.location();
  }

  protected void fireBundleAdded(File file) throws IOException {
    if (registry == null)
      return;
    List<RepositoryListenerPlugin> listeners = registry.getPlugins(RepositoryListenerPlugin.class);
    if (listeners.isEmpty())
      return;

    Jar jar = new Jar(file);
    try {
      for (RepositoryListenerPlugin listener : listeners) {
        try {
          listener.bundleAdded(this, jar, file);
        }
        catch (Exception e) {
          reporter.error("Repository listener threw an unexpected exception: %s", e, e);
        }
        finally {}
      }
    }
    finally {
      jar.close();
    }
  }

  @Override
  public void setRegistry(Registry registry) {
    this.registry = registry;
  }

  private void init() {
    if (index == null) {
      reporter.trace("init " + indexFile);
      index = new Index(indexFile);
      index.setReporter(reporter);
    }
  }

  public void add(String bsn, Version version) throws Exception {
    reporter.trace("Add %s %s", bsn, version);
    RevisionRef ref = getRevisionRef(bsn, version);
    add(ref);
  }

  void add(RevisionRef ref) throws Exception {
    index.addRevision(ref);

    if (index.isRecurse()) {
      Iterable<RevisionRef> refs = library.getClosure(ref.revision, false);
      for (RevisionRef r : refs) {
        index.addRevision(r);
      }
    }
    index.save(indexFile);
  }

  public void delete(String bsn, Version version, boolean immediate) throws Exception {
    reporter.trace("Delete %s %s", bsn, version);

    Library.RevisionRef resource = index.getRevisionRef(bsn, version);
    if (resource != null) {
      boolean removed = index.delete(bsn, version);
      reporter.trace("Was present " + removed);
      index.save();
    } else
      reporter.trace("No such resource");
  }

  public void delete(String bsn) throws Exception {
    reporter.trace("Delete %s", bsn);
    Set<Version> set = new HashSet<Version>(index.getVersions(bsn));
    reporter.trace("Versions %s", set);
    for (Version version : set) {
      delete(bsn, version, true);
    }
  }

  public boolean dropTarget(URI uri) throws Exception {
    Matcher m = JPM_REVISION_URL_PATTERN.matcher(uri.toString());
    if (!m.matches())
      return false;

    Revision revision = library.getRevision(m.group(1), m.group(2), m.group(3), m.group(4));
    if (revision == null)
      return false;

    Library.RevisionRef resource = index.getRevisionRef(revision._id);
    if (resource != null)
      return true;

    RevisionRef ref = new RevisionRef(revision);
    add(ref);
    return true;
  }

  /*
   * A utility to open a URL on different OS's browsers
   * @param url the url to open
   * @throws IOException
   */
  void open(String url) {
    try {
      try {
        Desktop desktop = Desktop.getDesktop();
        desktop.browse(new URI(url));
        return;
      }
      catch (Throwable e) {

      }
      String os = System.getProperty("os.name").toLowerCase();
      Runtime rt = Runtime.getRuntime();

      if (os.indexOf("mac") >= 0 || os.indexOf("darwin") >= 0) {
        rt.exec("open " + url);
      } else if (os.indexOf("win") >= 0) {
        // this doesn't support showing urls in the form of
        // "page.html#nameLink"
        rt.exec("rundll32 url.dll,FileProtocolHandler " + url);

      } else if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0) {

        // Do a best guess on unix until we get a platform independent
        // way
        // Build a list of browsers to try, in this order.
        String[] browsers = {
            "epiphany", "firefox", "mozilla", "konqueror", "netscape", "opera", "links", "lynx"
        };

        // Build a command string which looks like
        // "browser1 "url" || browser2 "url" ||..."
        StringBuffer cmd = new StringBuffer();
        for (int i = 0; i < browsers.length; i++)
          cmd.append((i == 0 ? "" : " || ") + browsers[i] + " \"" + url + "\" ");

        rt.exec(new String[] {
            "sh", "-c", cmd.toString()
        });

      } else
        System.out.println("Open " + url);
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Get the resources of the current repository.
   *
   * @return
   * @throws Exception
   */
  public Engine getEngine() throws Exception {
    if (!indexFile.isFile() || obrFile.lastModified() < indexFile.lastModified()) {
      RepoIndex indexer = new RepoIndex();
      indexer.addAnalyzer(new KnownBundleAnalyzer(), null);

      if (registry != null) {
        List<ResourceAnalyzer> analyzers = registry.getPlugins(ResourceAnalyzer.class);
        for (ResourceAnalyzer analyzer : analyzers) {
          // TODO: where to get the filter property??
          indexer.addAnalyzer(analyzer, null);
        }
      }

      Map<String,String> config = new HashMap<String,String>();
      config.put(ResourceIndexer.REPOSITORY_NAME, indexFile.getName());
      config.put(ResourceIndexer.ROOT_URL, "/");
      config.put(ResourceIndexer.PRETTY, Boolean.FALSE.toString());

      final Set<File> set = new HashSet<File>(index.getRevisionRefs().size());
      final Semaphore downloads = new Semaphore(0);
      final List<String> errors = new ArrayList<String>();

      int count = 0;

      for (RevisionRef ref : index.getRevisionRefs()) {
        count++;
        get(ref.bsn, Index.toVersion(ref), null, new DownloadListener() {

          @Override
          public void success(File file) throws Exception {
            set.add(file);
            downloads.release();
          }

          @Override
          public void failure(File file, String reason) throws Exception {
            errors.add(file + ": " + reason);
            downloads.release();
          }

          @Override
          public boolean progress(File file, int percentage) throws Exception {
            return true;
          }
        });
      }
      downloads.acquire(count);

      if (errors.size() > 0)
        // TODO
        System.err.println("DOWNLOAD ERRORS!! " + errors);

      FileOutputStream fout = new FileOutputStream(obrFile);
      try {
        indexer.index(set, fout, config);
      }
      finally {
        fout.close();
      }
    }
    // obrFile is up to data
    if (engine == null) {
      engine = new Engine();
      engine.parse(obrFile);
    }
    return engine;
  }

  /**
   * Answer the resource descriptors from a URL
   */
  @Override
  public Set<ResourceDescriptor> getResources(URI url, boolean includeDependencies) throws Exception {
    Matcher m = JPM_REVISION_URL_PATTERN.matcher(url.toString());
    if (!m.matches())
      return null;

    Set<ResourceDescriptor> resources = new HashSet<ResourceDescriptor>();
    Revision revision = library.getRevision(m.group(1), m.group(2), m.group(3), m.group(4));
    if (revision != null) {
      ResourceDescriptor rd = createResourceDescriptor(new RevisionRef(revision));
      resources.add(rd);
      if (includeDependencies) {
        for (RevisionRef dependency : library.getClosure(revision._id, false)) {
          resources.add(createResourceDescriptor(dependency));
        }
      }
    }

    return resources;
  }

  private ResourceDescriptor createResourceDescriptor(RevisionRef ref) throws Exception {
    ResourceDescriptorImpl rd = new ResourceDescriptorImpl(ref);
    rd.bsn = ref.bsn;
    rd.version = toVersion(ref.baseline, ref.qualifier);
    rd.description = ref.description;
    rd.id = ref.revision;
    rd.included = getIndex().getRevisionRef(rd.id) != null;
    rd.phase = toPhase(ref.phase);
    return rd;
  }

  private Index getIndex() {
    init();
    return index;
  }

  private aQute.bnd.service.repository.Phase toPhase(aQute.service.library.Library.Phase phase) {
    switch (phase) {
      case STAGING :
        return aQute.bnd.service.repository.Phase.STAGING;
      case LOCKED :
        return aQute.bnd.service.repository.Phase.LOCKED;
      case MASTER :
        return aQute.bnd.service.repository.Phase.MASTER;
      case RETIRED :
        return aQute.bnd.service.repository.Phase.RETIRED;
      case WITHDRAWN :
        return aQute.bnd.service.repository.Phase.WITHDRAWN;
      default :
        return null;
    }
  }

  @Override
  public Set<ResourceDescriptor> query(String query) throws Exception {
    Set<ResourceDescriptor> resources = new HashSet<ResourceDescriptor>();
    RevisionRef master = null;
    RevisionRef staging = null;

    for (Program p : library.findProgram().query(query)) {
      for (RevisionRef ref : p.revisions) {
        if (master == null && ref.phase == Library.Phase.MASTER) {
          master = ref;
        } else if (staging != null && ref.phase == Library.Phase.STAGING) {
          staging = ref;
        }
      }
      if (master != null)
        resources.add(createResourceDescriptor(master));
      if (staging != null)
        resources.add(createResourceDescriptor(staging));
    }
    return resources;
  }

  @Override
  public boolean addResource(ResourceDescriptor resource) throws Exception {
    if (resource instanceof ResourceDescriptorImpl) {
      RevisionRef ref = ((ResourceDescriptorImpl) resource).revision;
      if (index.addRevision(ref)) {
        index.save();
        return true;
      }
    }
    return false;
  }

  @Override
  public Set<ResourceDescriptor> findResources(org.osgi.resource.Requirement requirement, boolean includeDependencies)
      throws Exception {
    throw new UnsupportedOperationException("Not yet");
  }

  /**
   * Check if there is at least one network interface up and running so we
   * have internet access.
   */
  private boolean isConnected() throws SocketException {
    if (offline)
      return false;

    try {
      for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements();) {
        NetworkInterface interf = e.nextElement();
        if (!interf.isLoopback() && interf.isUp())
          return true;
      }
    }
    catch (SocketException e) {
      // ignore, we assume we're offline
    }
    return false;
  }
}
TOP

Related Classes of aQute.library.bnd.JpmRepository$Options

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.