package aQute.jpm.lib;
import static aQute.lib.io.IO.copy;
import static aQute.lib.io.IO.createTempFile;
import static aQute.lib.io.IO.rename;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Constants;
import aQute.bnd.version.Version;
import aQute.jpm.facade.repo.JpmRepo;
import aQute.jpm.platform.Platform;
import aQute.jsonrpc.proxy.JSONRPCProxy;
import aQute.lib.base64.Base64;
import aQute.lib.collections.ExtList;
import aQute.lib.converter.Converter;
import aQute.lib.hex.Hex;
import aQute.lib.io.IO;
import aQute.lib.json.JSONCodec;
import aQute.lib.justif.Justif;
import aQute.lib.settings.Settings;
import aQute.lib.strings.Strings;
import aQute.libg.cryptography.SHA1;
import aQute.rest.urlclient.URLClient;
import aQute.service.library.Coordinate;
import aQute.service.library.Library;
import aQute.service.library.Library.Program;
import aQute.service.library.Library.Revision;
import aQute.service.library.Library.RevisionRef;
import aQute.service.reporter.Reporter;
import aQute.struct.struct;
/**
* JPM is the Java package manager. It manages a local repository in the user
* global directory and/or a global directory. This class is the main entry
* point for the command line. This program maintains a repository, a list of
* installed commands, and a list of installed service. It provides the commands
* to changes these resources. All information is kept in a platform specific
* area. However, the layout of this area is standardized.
*
* <pre>
* platform/ check
* check for write access repo/ repository <bsn>/ bsn directory
* <bsn>-<version>.jar jar file service/ All service
* <service>/ A service data Service data (JSON) wdir/ Working dir lock
* Lock file (if running, contains port) commands/ <command> Command data
* (JSON)
* </pre>
*
* For each service, the platform must also have a user writable directory used
* for working dir, lock, and logging.
*
* <pre>
* platform var/ wdir/
* Working dir lock Lock file (exists only when running, contains UDP port)
* </pre>
*/
public class JustAnotherPackageManager {
private final static Logger logger = LoggerFactory.getLogger(JustAnotherPackageManager.class);
private static final String JPM_VMS_EXTRA = "jpm.vms.extra";
private static final String SERVICE_JAR_FILE = "service.jar";
public static final String SERVICE = "service";
public static final String COMMANDS = "commands";
public static final String LOCK = "lock";
private static final String JPM_CACHE_LOCAL = "jpm.cache.local";
private static final String JPM_CACHE_GLOBAL = "jpm.cache.global";
static final String PERMISSION_ERROR = "No write acces, might require administrator or root privileges (sudo in *nix)";
static JSONCodec codec = new JSONCodec();
static Pattern BSN_P = Pattern.compile(
"([-a-z0-9_]+(?:\\.[-a-z0-9_]+)+)(?:@([0-9]+(?:\\.[0-9]+(?:\\.[0-9]+(?:\\.[-_a-z0-9]+)?)?)?))?",
Pattern.CASE_INSENSITIVE);
static Pattern COORD_P = Pattern
.compile("([-a-z0-9_.]+):([-a-z0-9_.]+)(?::([-a-z0-9_.]+))?(?:@([-a-z0-9._]+))?", Pattern.CASE_INSENSITIVE);
static Pattern URL_P = Pattern.compile("([a-z]{3,6}:/.*)", Pattern.CASE_INSENSITIVE);
static Pattern CMD_P = Pattern.compile("([a-z_][a-z\\d_]*)", Pattern.CASE_INSENSITIVE);
static Pattern SHA_P = Pattern.compile("(?:sha:)?([a-fA-F0-9]{40,40})",
Pattern.CASE_INSENSITIVE);
static Executor executor;
final File homeDir;
final File binDir;
final File repoDir;
final File commandDir;
final File serviceDir;
final File service;
final Platform platform;
final Reporter reporter;
JpmRepo library;
final List<Service> startedByDaemon = new ArrayList<Service>();
boolean localInstall = false;
private URLClient host;
private boolean underTest = System.getProperty("jpm.intest") != null;
Settings settings;
/**
* Constructor
*
* @throws Exception
*/
public JustAnotherPackageManager(Reporter reporter, Platform platform, File homeDir, File binDir) throws Exception {
if (platform == null)
this.platform = Platform.getPlatform(reporter, null);
else
this.platform = platform;
settings = new Settings(this.platform.getConfigFile());
this.reporter = reporter;
this.homeDir = homeDir;
IO.mkdirs(homeDir);
repoDir = IO.getFile(homeDir, "repo");
IO.mkdirs(repoDir);
commandDir = new File(homeDir, COMMANDS);
serviceDir = new File(homeDir, SERVICE);
IO.mkdirs(commandDir);
IO.mkdirs(serviceDir);
service = new File(repoDir, SERVICE_JAR_FILE);
if (!service.isFile())
init();
this.binDir = binDir;
IO.mkdirs(binDir);
}
public String getArtifactIdFromCoord(String coord) {
Matcher m = COORD_P.matcher(coord);
if (m.matches()) {
return m.group(2);
} else {
return null;
}
}
public boolean hasAccess() {
assert (binDir != null);
assert (homeDir != null);
return binDir.canWrite() && homeDir.canWrite();
}
public File getHomeDir() {
return homeDir;
}
public File getRepoDir() {
return repoDir;
}
public File getBinDir() {
return binDir;
}
public List<ServiceData> getServices() throws Exception {
return getServices(serviceDir);
}
public List<ServiceData> getServices(File serviceDir) throws Exception {
List<ServiceData> result = new ArrayList<ServiceData>();
if (!serviceDir.exists()) {
return result;
}
for (File sdir : serviceDir.listFiles()) {
File dataFile = new File(sdir, "data");
ServiceData data = getData(ServiceData.class, dataFile);
result.add(data);
}
return result;
}
public List<CommandData> getCommands() throws Exception {
return getCommands(commandDir);
}
public List<CommandData> getCommands(File commandDir) throws Exception {
List<CommandData> result = new ArrayList<CommandData>();
if (!commandDir.exists()) {
return result;
}
for (File f : commandDir.listFiles()) {
CommandData data = getData(CommandData.class, f);
if (data != null)
result.add(data);
}
return result;
}
public CommandData getCommand(String name) throws Exception {
File f = new File(commandDir, name);
if (!f.isFile())
return null;
return getData(CommandData.class, f);
}
/**
* Garbage collect repository
*
* @throws Exception
*/
public void gc() throws Exception {
HashSet<byte[]> deps = new HashSet<byte[]>();
// deps.add(SERVICE_JAR_FILE);
for (File cmd : commandDir.listFiles()) {
CommandData data = getData(CommandData.class, cmd);
addDependencies(deps, data);
}
for (File service : serviceDir.listFiles()) {
File dataFile = new File(service, "data");
ServiceData data = getData(ServiceData.class, dataFile);
addDependencies(deps, data);
}
int count = 0;
for (File f : repoDir.listFiles()) {
String name = f.getName();
if (!deps.contains(name.getBytes())) {
if (!name.endsWith(".json")
|| !deps.contains(name.substring(0, name.length() - ".json".length()).getBytes())) { // Remove
// json
// files
// only
// if
// the
// bin
// is
// going
// as
// well
IO.delete(f);
count++;
} else {
}
}
}
System.out.format("Garbage collection done (%d file(s) removed)%n", count);
}
private void addDependencies(HashSet<byte[]> deps, CommandData data) {
for (byte[] dep : data.dependencies) {
deps.add(dep);
}
for (byte[] dep : data.runbundles) {
deps.add(dep);
}
}
public void deinit(Appendable out, boolean force) throws Exception {
Settings settings = new Settings(platform.getConfigFile());
if (!force) {
Justif justify = new Justif(80, 40);
StringBuilder sb = new StringBuilder();
try (Formatter f = new Formatter(sb)) {
String list = listFiles(platform.getGlobal());
if (list != null) {
f.format("In global default environment:%n");
f.format(list);
}
list = listFiles(platform.getLocal());
if (list != null) {
f.format("In local default environment:%n");
f.format(list);
}
if (settings.containsKey(JPM_CACHE_GLOBAL)) {
list = listFiles(IO.getFile(settings.get(JPM_CACHE_GLOBAL)));
if (list != null) {
f.format("In global configured environment:%n");
f.format(list);
}
}
if (settings.containsKey(JPM_CACHE_LOCAL)) {
list = listFiles(IO.getFile(settings.get(JPM_CACHE_LOCAL)));
if (list != null) {
f.format("In local configured environment:%n");
f.format(list);
}
}
list = listSupportFiles();
if (list != null) {
f.format("jpm support files:%n");
f.format(list);
}
f.format("%n%n");
f.format("All files listed above will be deleted if deinit is run with the force flag set"
+ " (\"jpm deinit -f\" or \"jpm deinit --force\"%n%n");
f.flush();
justify.wrap(sb);
out.append(sb.toString());
}
} else { // i.e. if(force)
int count = 0;
File[] caches = {
platform.getGlobal(), platform.getLocal(), null, null
};
if (settings.containsKey(JPM_CACHE_LOCAL)) {
caches[2] = IO.getFile(settings.get(JPM_CACHE_LOCAL));
}
if (settings.containsKey(JPM_CACHE_GLOBAL)) {
caches[3] = IO.getFile(settings.get(JPM_CACHE_GLOBAL));
}
ArrayList<File> toDelete = new ArrayList<File>();
for (File cache : caches) {
if (cache == null || !cache.exists()) {
continue;
}
listFiles(cache, toDelete);
if (toDelete.size() > count) {
count = toDelete.size();
if (!cache.canWrite()) {
reporter.error(PERMISSION_ERROR + " (%s)", cache);
return;
}
toDelete.add(cache);
}
}
listSupportFiles(toDelete);
for (File f : toDelete) {
if (f.exists() && !f.canWrite()) {
reporter.error(PERMISSION_ERROR + " (%s)", f);
}
}
if (reporter.getErrors().size() > 0) {
return;
}
for (File f : toDelete) {
if (f.exists()) {
IO.deleteWithException(f);
}
}
}
}
// Adapter to list without planning to delete
private String listFiles(final File cache) throws Exception {
return listFiles(cache, null);
}
private String listFiles(final File cache, List<File> toDelete) throws Exception {
boolean stopServices = false;
if (toDelete == null) {
toDelete = new ArrayList<File>();
} else {
stopServices = true;
}
int count = 0;
Formatter f = new Formatter();
f.format(" - Cache:%n * %s%n", cache.getCanonicalPath());
f.format(" - Commands:%n");
for (CommandData cdata : getCommands(new File(cache, COMMANDS))) {
f.format(" * %s \t0 handle for \"%s\"%n", cdata.bin, cdata.name);
toDelete.add(new File(cdata.bin));
count++;
}
f.format(" - Services:%n");
for (ServiceData sdata : getServices(new File(cache, SERVICE))) {
if (sdata != null) {
f.format(" * %s \t0 service directory for \"%s\"%n", sdata.sdir, sdata.name);
toDelete.add(new File(sdata.sdir));
File initd = platform.getInitd(sdata);
if (initd != null && initd.exists()) {
f.format(" * %s \t0 init.d file for \"%s\"%n", initd.getCanonicalPath(), sdata.name);
toDelete.add(initd);
}
if (stopServices) {
Service s = getService(sdata);
try {
s.stop();
} catch (Exception e) {}
}
count++;
}
}
f.format("%n");
String result = (count > 0) ? f.toString() : null;
f.close();
return result;
}
private String listSupportFiles() throws Exception { // Adapter to list
// without planning
// to delete
return listSupportFiles(null);
}
private String listSupportFiles(List<File> toDelete) throws Exception {
try (Formatter f = new Formatter()) {
if (toDelete == null) {
toDelete = new ArrayList<File>();
}
int precount = toDelete.size();
File confFile = IO.getFile(platform.getConfigFile()).getCanonicalFile();
if (confFile.exists()) {
f.format(" * %s \t0 Config file%n", confFile);
toDelete.add(confFile);
}
String result = (toDelete.size() > precount) ? f.toString() : null;
return result;
}
}
/**
* @param data
* @throws Exception
* @throws IOException
*/
public String createService(ServiceData data, boolean force) throws Exception, IOException {
if (data.main == null) {
return "No main is set";
}
File sdir = new File(serviceDir, data.name);
IO.mkdirs(sdir);
data.sdir = sdir.getAbsolutePath();
File lock = new File(data.sdir, LOCK);
data.lock = lock.getAbsolutePath();
if (data.work == null)
data.work = new File(data.sdir, "work").getAbsolutePath();
if (data.user == null)
data.user = platform.user();
if (data.user == null)
data.user = "root";
IO.mkdirs(new File(data.work));
if (data.log == null)
data.log = new File(data.sdir, "log").getAbsolutePath();
// TODO
// if (Data.validate(data) != null)
// return "Invalid service data: " + Data.validate(data);
if (service == null)
throw new RuntimeException(
"Missing biz.aQute.jpm.service in repo, should have been installed by init, try reiniting");
data.serviceLib = service.getAbsolutePath();
platform.chown(data.user, true, new File(data.sdir));
String s = platform.createService(data, null, force);
if (s == null)
storeData(new File(data.sdir, "data"), data);
return s;
}
/**
* @param data
* @throws Exception
* @throws IOException
*/
public String createCommand(CommandData data, boolean force) throws Exception, IOException {
// TODO
// if (Data.validate(data) != null)
// return "Invalid command data: " + Data.validate(data);
Map<String,String> map = null;
if (data.trace) {
map = new HashMap<String,String>();
map.put("java.security.manager", "aQute.jpm.service.TraceSecurityManager");
logger.debug("tracing");
}
String s = platform.createCommand(data, map, force, service.getAbsolutePath());
if (s == null)
storeData(new File(commandDir, data.name), data);
return s;
}
public void deleteCommand(String name) throws Exception {
CommandData cmd = getCommand(name);
if (cmd == null)
throw new IllegalArgumentException("No such command " + name);
platform.deleteCommand(cmd);
File tobedel = new File(commandDir, name);
IO.deleteWithException(tobedel);
}
public Service getService(String serviceName) throws Exception {
File base = new File(serviceDir, serviceName);
return getService(base);
}
public Service getService(ServiceData sdata) throws Exception {
return getService(new File(sdata.sdir));
}
private Service getService(File base) throws Exception {
File dataFile = new File(base, "data");
if (!dataFile.isFile())
return null;
ServiceData data = getData(ServiceData.class, dataFile);
return new Service(this, data);
}
/**
* Verify that the jar file is correct. This also verifies ok when there are
* no checksums or.
*
*/
static Pattern MANIFEST_ENTRY = Pattern.compile("(META-INF/[^/]+)|(.*/)");
public String verify(JarFile jar, String... algorithms) throws IOException {
if (algorithms == null || algorithms.length == 0)
algorithms = new String[] {
"MD5", "SHA"
};
else if (algorithms.length == 1 && algorithms[0].equals("-"))
return null;
try {
Manifest m = jar.getManifest();
if (m.getEntries().isEmpty())
return "No name sections";
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();) {
JarEntry je = e.nextElement();
if (MANIFEST_ENTRY.matcher(je.getName()).matches())
continue;
Attributes nameSection = m.getAttributes(je.getName());
if (nameSection == null)
return "No name section for " + je.getName();
for (String algorithm : algorithms) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
String expected = nameSection.getValue(algorithm + "-Digest");
if (expected != null) {
byte digest[] = Base64.decodeBase64(expected);
copy(jar.getInputStream(je), md);
if (!Arrays.equals(digest, md.digest()))
return "Invalid digest for " + je.getName() + ", " + expected + " != "
+ Base64.encodeBase64(md.digest());
} else
reporter.error("could not find digest for %s-Digest", algorithm);
} catch (NoSuchAlgorithmException nsae) {
return "Missing digest algorithm " + algorithm;
}
}
}
} catch (Exception e) {
return "Failed to verify due to exception: " + e;
}
return null;
}
/**
* @param clazz
* @param dataFile
* @throws Exception
*/
private <T> T getData(Class<T> clazz, File dataFile) throws Exception {
try {
return codec.dec().from(dataFile).get(clazz);
} catch (Exception e) {
// e.printStackTrace();
// System.out.println("Cannot read data file "+dataFile+": " +
// IO.collect(dataFile));
return null;
}
}
private void storeData(File dataFile, Object o) throws Exception {
codec.enc().to(dataFile).put(o);
}
/**
* This is called when JPM runs in the background to start jobs
*
* @throws Exception
*/
public void daemon() throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread("Daemon shutdown") {
public void run() {
for (Service service : startedByDaemon) {
try {
reporter.error("Stopping %s", service);
service.stop();
reporter.error("Stopped %s", service);
} catch (Exception e) {
// Ignore
}
}
}
});
List<ServiceData> services = getServices();
Map<String,ServiceData> map = new HashMap<String,ServiceData>();
for (ServiceData d : services) {
map.put(d.name, d);
}
List<ServiceData> start = new ArrayList<ServiceData>();
Set<ServiceData> set = new HashSet<ServiceData>();
for (ServiceData sd : services) {
checkStartup(map, start, sd, set);
}
if (start.isEmpty())
reporter.warning("No services to start");
for (ServiceData sd : start) {
try {
Service service = getService(sd.name);
logger.debug("Starting {}", service);
String result = service.start(true);
if (result != null)
reporter.error("Started error %s", result);
else
startedByDaemon.add(service);
logger.debug("Started {}", service);
} catch (Exception e) {
reporter.error("Cannot start daemon %s, due to %s", sd.name, e);
}
}
while (true) {
for (Service sd : startedByDaemon) {
try {
if (!sd.isRunning()) {
reporter.error("Starting due to failure %s", sd);
String result = sd.start();
if (result != null)
reporter.error("Started error %s", result);
}
} catch (Exception e) {
reporter.error("Cannot start daemon %s, due to %s", sd, e);
}
}
Thread.sleep(10000);
}
}
private void checkStartup(Map<String,ServiceData> map, List<ServiceData> start, ServiceData sd,
Set<ServiceData> cyclic) {
if (sd.after.isEmpty() || start.contains(sd))
return;
if (cyclic.contains(sd)) {
reporter.error("Cyclic dependency for %s", sd.name);
return;
}
cyclic.add(sd);
for (String dependsOn : sd.after) {
if (dependsOn.equals("boot"))
continue;
ServiceData deps = map.get(dependsOn);
if (deps == null) {
reporter.error("No such service %s but %s depends on it", dependsOn, sd.name);
} else {
checkStartup(map, start, deps, cyclic);
}
}
start.add(sd);
}
public void register(boolean user) throws Exception {
platform.installDaemon(user);
}
public ArtifactData putAsync(final URI uri) {
final ArtifactData data = new ArtifactData();
data.busy = true;
Runnable r = new Runnable() {
public void run() {
try {
put(uri, data);
} catch (Throwable e) {
e.printStackTrace();
data.error = e.toString();
} finally {
logger.debug("done downloading {}", uri);
data.done();
}
}
};
getExecutor().execute(r);
return data;
}
public ArtifactData put(final URI uri) throws Exception {
final ArtifactData data = new ArtifactData();
put(uri, data);
return data;
}
void put(final URI uri, ArtifactData data) throws Exception {
logger.debug("put {} {}", uri, data);
File tmp = createTempFile(repoDir, "mtp", ".whatever");
tmp.deleteOnExit();
try {
copy(uri.toURL(), tmp);
byte[] sha = SHA1.digest(tmp).digest();
logger.debug("SHA {} {}", uri, Hex.toHexString(sha));
ArtifactData existing = get(sha);
if (existing != null) {
logger.debug("existing");
xcopy(existing, data);
return;
}
File meta = new File(repoDir, Hex.toHexString(sha) + ".json");
File file = new File(repoDir, Hex.toHexString(sha));
rename(tmp, file);
logger.debug("file {}", file);
data.file = file.getAbsolutePath();
data.sha = sha;
data.busy = false;
CommandData cmddata = parseCommandData(data);
if (cmddata.bsn != null) {
data.name = cmddata.bsn + "-" + cmddata.version;
} else
data.name = Strings.display(cmddata.title, cmddata.bsn, cmddata.name, uri);
codec.enc().to(meta).put(data);
logger.debug("TD = {}", data);
} finally {
IO.delete(tmp);
logger.debug("puted {} {}", uri, data);
}
}
public ArtifactData get(byte[] sha) throws Exception {
String name = Hex.toHexString(sha);
File data = IO.getFile(repoDir, name + ".json");
logger.debug("artifact data file {}", data);
if (data.isFile()) { // Bin + metadata
ArtifactData artifact = codec.dec().from(data).get(ArtifactData.class);
artifact.file = IO.getFile(repoDir, name).getAbsolutePath();
return artifact;
}
File bin = IO.getFile(repoDir, name);
if (bin.exists()) { // Only bin
ArtifactData artifact = new ArtifactData();
artifact.file = bin.getAbsolutePath();
artifact.sha = sha;
return artifact;
}
return null;
}
public List<Revision> filter(Collection<Revision> list, EnumSet<Library.Phase> phases) {
List<Revision> filtered = new ArrayList<Library.Revision>();
for (Revision r : list)
if (phases.contains(r.phase))
filtered.add(r);
return filtered;
}
public Map<String,Revision> latest(Collection<Revision> list) {
Map<String,Revision> programs = new HashMap<String,Library.Revision>();
for (Revision r : list) {
String coordinates = r.groupId + ":" + r.artifactId;
if (r.classifier != null)
coordinates += ":" + r.classifier;
if (r.groupId.equals(Library.SHA_GROUP))
continue;
Revision current = programs.get(coordinates);
if (current == null)
programs.put(coordinates, r);
else {
// who is better?
if (compare(r, current) >= 0)
programs.put(coordinates, r);
}
}
return programs;
}
private int compare(Revision a, Revision b) {
if (Arrays.equals(a._id, b._id))
return 0;
Version va = getVersion(a);
Version vb = getVersion(b);
int n = va.compareTo(vb);
if (n != 0)
return n;
if (a.created != b.created)
return a.created > b.created ? 1 : -1;
for (int i = 0; i < a._id.length; i++)
if (a._id[i] != b._id[i])
return a._id[i] > b._id[i] ? 1 : -1;
return 0;
}
private Version getVersion(Revision a) {
if (a.qualifier != null)
return new Version(a.baseline + "." + a.qualifier);
return new Version(a.baseline);
}
public String getCoordinates(Revision r) {
StringBuilder sb = new StringBuilder(r.groupId).append(":").append(r.artifactId);
if (r.classifier != null)
sb.append(":").append(r.classifier);
sb.append("@").append(r.version);
return sb.toString();
}
public String getCoordinates(RevisionRef r) {
StringBuilder sb = new StringBuilder(r.groupId).append(":").append(r.artifactId).append(":");
if (r.classifier != null)
sb.append(r.classifier).append("@");
sb.append(r.version);
return sb.toString();
}
public ArtifactData getCandidate(String key) throws Exception {
ArtifactData data = getCandidateAsync(key);
if (data != null) {
data.sync();
}
return data;
}
public ArtifactData getCandidateAsync(String arg) throws Exception {
logger.debug("coordinate {}", arg);
if (isUrl(arg))
try {
ArtifactData data = putAsync(new URI(arg));
data.local = true;
return data;
} catch (Exception e) {
logger.debug("hmm, not a valid url {}, will try the server", arg);
return null;
}
File f = IO.getFile(arg);
if (f.isFile())
try {
ArtifactData data = putAsync(f.toURI());
data.local = true;
return data;
} catch (Exception e) {
logger.debug("hmm, not a valid file {}, will try the server", arg);
return null;
}
Coordinate c = new Coordinate(arg);
if (c.isSha()) {
ArtifactData r = get(c.getSha());
if (r != null)
return r;
}
Revision revision = library.getRevisionByCoordinate(c);
if (revision == null)
return null;
logger.debug("revision {}", Hex.toHexString(revision._id));
ArtifactData ad = get(revision._id);
if (ad != null) {
logger.debug("found in cache");
return ad;
}
URI url = revision.urls.iterator().next();
ArtifactData artifactData = putAsync(url);
artifactData.coordinate = c;
return artifactData;
}
public static Executor getExecutor() {
if (executor == null)
executor = Executors.newFixedThreadPool(4);
return executor;
}
public static void setExecutor(Executor executor) {
JustAnotherPackageManager.executor = executor;
}
public void setLibrary(URI url) throws Exception {
if (url == null)
url = new URI("http://repo.jpm4j.org/");
this.host = new URLClient(url.toString());
host.setReporter(reporter);
library = JSONRPCProxy.createRPC(JpmRepo.class, host, "jpm");
}
public void close() {
if (executor != null && executor instanceof ExecutorService)
((ExecutorService) executor).shutdown();
}
public void init() throws IOException {
URL s = getClass().getClassLoader().getResource(SERVICE_JAR_FILE);
if (s == null)
if (underTest)
return;
else
throw new Error("No " + SERVICE_JAR_FILE + " resource in jar");
IO.mkdirs(service.getParentFile());
IO.copy(s, service);
}
public Platform getPlatform() {
return platform;
}
/**
* Copy from the copy method in StructUtil. Did not want to drag that code
* in. maybe this actually should go to struct.
*
* @param from
* @param to
* @param excludes
* @throws Exception
*/
static public <T extends struct> T xcopy(struct from, T to, String... excludes) throws Exception {
Arrays.sort(excludes);
for (Field f : from.fields()) {
if (Arrays.binarySearch(excludes, f.getName()) >= 0)
continue;
Object o = f.get(from);
if (o == null)
continue;
Field tof = to.getField(f.getName());
if (tof != null)
try {
tof.set(to, Converter.cnv(tof.getGenericType(), o));
} catch (Exception e) {
System.out.println("Failed to convert " + f.getName() + " from " + from.getClass() + " to "
+ to.getClass() + " value " + o + " exception " + e);
}
}
return to;
}
public JpmRepo getLibrary() {
return library;
}
public void setLocalInstall(boolean b) {
localInstall = b;
}
public String what(String key, boolean oneliner) throws Exception {
byte[] sha;
Matcher m = SHA_P.matcher(key);
if (m.matches()) {
sha = Hex.toByteArray(key);
} else {
m = URL_P.matcher(key);
if (m.matches()) {
URL url = new URL(key);
sha = SHA1.digest(url.openStream()).digest();
} else {
File jarfile = new File(key);
if (!jarfile.exists()) {
reporter.error("File does not exist: %s", jarfile.getCanonicalPath());
}
sha = SHA1.digest(jarfile).digest();
}
}
logger.debug("sha {}", Hex.toHexString(sha));
Revision revision = library.getRevision(sha);
if (revision == null) {
return null;
}
Justif justif = new Justif(120, 20, 70, 20, 75);
DateFormat dateFormat = DateFormat.getDateInstance();
StringBuilder sb = new StringBuilder();
try (Formatter f = new Formatter(sb)) {
if (oneliner) {
f.format("%20s %s%n", Hex.toHexString(revision._id), createCoord(revision));
} else {
f.format("Artifact: %s%n", revision.artifactId);
if (revision.organization != null && revision.organization.name != null) {
f.format(" (%s)", revision.organization.name);
}
f.format("%n");
f.format("Coordinates\t0: %s%n", createCoord(revision));
f.format("Created\t0: %s%n", dateFormat.format(new Date(revision.created)));
f.format("Size\t0: %d%n", revision.size);
f.format("Sha\t0: %s%n", Hex.toHexString(revision._id));
f.format("URL\t0: %s%n", createJpmLink(revision));
f.format("%n");
f.format("%s%n", revision.description);
f.format("%n");
f.format("Dependencies\t0:%n");
boolean flag = false;
Iterable<RevisionRef> closure = library.getClosure(revision._id, true);
for (RevisionRef dep : closure) {
f.format(" - %s \t2- %s \t3- %s%n", dep.name, createCoord(dep),
dateFormat.format(new Date(dep.created)));
flag = true;
}
if (!flag) {
f.format(" None%n");
}
f.format("%n");
}
f.flush();
justif.wrap(sb);
return sb.toString();
}
}
private String createCoord(Revision rev) {
return String.format("%s:%s@%s [%s]", rev.groupId, rev.artifactId, rev.version, rev.phase);
}
private String createCoord(RevisionRef rev) {
return String.format("%s:%s@%s [%s]", rev.groupId, rev.artifactId, rev.version, rev.phase);
}
private String createJpmLink(Revision rev) {
return String.format("http://jpm4j.org/#!/p/sha/%s//%s", Hex.toHexString(rev._id), rev.baseline);
}
public class UpdateMemo {
public CommandData current; // Works for commandData and
// ServiceData, as ServiceData --|>
// CommandData
public RevisionRef best;
}
public void listUpdates(List<UpdateMemo> notFound, List<UpdateMemo> upToDate, List<UpdateMemo> toUpdate,
CommandData data, boolean staged) throws Exception {
// UpdateMemo memo = new UpdateMemo();
// memo.current = data;
//
// Matcher m = data.coordinates == null ? null :
// COORD_P.matcher(data.coordinates);
//
// if (data.version == null || m == null || !m.matches()) {
// Revision revision = library.getRevision(data.sha);
// if (revision == null) {
// notFound.add(memo);
// return;
// }
// data.version = new Version(revision.version);
// data.coordinates = getCoordinates(revision);
// if (data instanceof ServiceData) {
// storeData(IO.getFile(new File(serviceDir, data.name), "data"), data);
// } else {
// storeData(IO.getFile(commandDir, data.name), data);
// }
// }
//
// Iterable< ? extends Program> programs =
// library.getQueryPrograms(data.coordinates, 0, 0);
// int count = 0;
// RevisionRef best = null;
// for (Program p : programs) {
// // best = selectBest(p.revisions, staged, null);
// count++;
// }
// if (count != 1 || best == null) {
// notFound.add(memo);
// return;
// }
// Version bestVersion = new Version(best.version);
//
// if (data.version.compareTo(bestVersion) < 0) { // Update available
// memo.best = best;
// toUpdate.add(memo);
// } else { // up to date
// upToDate.add(memo);
// }
}
public void update(UpdateMemo memo) throws Exception {
ArtifactData target = put(memo.best.urls.iterator().next());
memo.current.version = new Version(memo.best.version);
target.sync();
memo.current.sha = target.sha;
// memo.current.dependencies = target.dependencies;
// memo.current.dependencies.add((new File(repoDir,
// Hex.toHexString(target.sha))).getCanonicalPath());
// memo.current.runbundles = target.runbundles;
// memo.current.description = target.description;
memo.current.time = target.time;
if (memo.current instanceof ServiceData) {
Service service = getService((ServiceData) memo.current);
service.remove();
createService((ServiceData) memo.current, true);
IO.delete(new File(IO.getFile(serviceDir, memo.current.name), "data"));
storeData(new File(IO.getFile(serviceDir, memo.current.name), "data"), memo.current);
} else {
platform.deleteCommand(memo.current);
createCommand(memo.current, false);
IO.delete(IO.getFile(commandDir, memo.current.name));
storeData(IO.getFile(commandDir, memo.current.name), memo.current);
}
}
private boolean isUrl(String coordinate) {
return URL_P.matcher(coordinate).matches();
}
/**
* Find programs
*
* @throws Exception
*/
public List<Program> find(String query, int skip, int limit) throws Exception {
return library.getQueryPrograms(query, skip, limit);
}
public boolean isWildcard(String coordinate) {
return coordinate != null && coordinate.endsWith("@*");
}
public CommandData parseCommandData(ArtifactData artifact) throws Exception {
File source = new File(artifact.file);
if (!source.isFile())
throw new FileNotFoundException();
CommandData data = new CommandData();
data.sha = artifact.sha;
data.jpmRepoDir = repoDir.getCanonicalPath();
try (JarFile jar = new JarFile(source)) {
logger.debug("Parsing {}", source);
Manifest m = jar.getManifest();
Attributes main = m.getMainAttributes();
data.name = data.bsn = main.getValue(Constants.BUNDLE_SYMBOLICNAME);
String version = main.getValue(Constants.BUNDLE_VERSION);
if (version == null)
data.version = Version.LOWEST;
else
data.version = new Version(version);
data.main = main.getValue("Main-Class");
data.description = main.getValue(Constants.BUNDLE_DESCRIPTION);
data.title = main.getValue("JPM-Name");
if (main.getValue("Class-Path") != null) {
File parent = source.getParentFile();
for (String entry : main.getValue("Class-Path").split("\\s+")) {
File child = new File(parent, entry);
if (!child.isFile()) {
reporter.error("Target specifies Class-Path in JAR but the indicated file %s is not found",
child);
} else {
ArtifactData x = put(child.toURI());
data.dependencies.add(x.sha);
}
}
}
logger.debug("name {} {} {}", data.name, data.main, data.title);
DependencyCollector path = new DependencyCollector(this);
path.add(artifact);
DependencyCollector bundles = new DependencyCollector(this);
if (main.getValue("JPM-Classpath") != null) {
Parameters requires = OSGiHeader.parseHeader(main.getValue("JPM-Classpath"));
for (Map.Entry<String,Attrs> e : requires.entrySet()) {
path.add(e.getKey(), e.getValue().get("name")); // coordinate
}
} else if (!artifact.local) { // No JPM-Classpath, falling back to
// server's revision
// Iterable<RevisionRef> closure =
// library.getClosure(artifact.sha,
// false);
// System.out.println("getting closure " + artifact.url + " " +
// Strings.join("\n",closure));
// if (closure != null) {
// for (RevisionRef ref : closure) {
// path.add(Hex.toHexString(ref.revision));
// }
// }
}
if (main.getValue("JPM-Runbundles") != null) {
Parameters jpmrunbundles = OSGiHeader.parseHeader(main.getValue("JPM-Runbundles"));
for (Map.Entry<String,Attrs> e : jpmrunbundles.entrySet()) {
bundles.add(e.getKey(), e.getValue().get("name"));
}
}
logger.debug("collect digests runpath");
data.dependencies.addAll(path.getDigests());
logger.debug("collect digests bundles");
data.runbundles.addAll(bundles.getDigests());
Parameters command = OSGiHeader.parseHeader(main.getValue("JPM-Command"));
if (command.size() > 1)
reporter.error("Only one command can be specified");
for (Map.Entry<String,Attrs> e : command.entrySet()) {
data.name = e.getKey();
Attrs attrs = e.getValue();
if (attrs.containsKey("jvmargs"))
data.jvmArgs = attrs.get("jvmargs");
if (attrs.containsKey("title"))
data.title = attrs.get("title");
if (data.title != null)
data.title = data.name;
}
return data;
}
}
public void setUnderTest() {
underTest = true;
}
/**
* Turn the shas into a readable form
*
* @param dependencies
* @throws Exception
*/
public List< ? > toString(List<byte[]> dependencies) throws Exception {
List<String> out = new ArrayList<String>();
for (byte[] dependency : dependencies) {
ArtifactData data = get(dependency);
if (data == null)
out.add(Hex.toHexString(dependency));
else {
out.add(Strings.display(data.name, Hex.toHexString(dependency)));
}
}
return out;
}
/**
* Get a list of candidates from a coordinate
*
* @param c
* @throws Exception
*/
public Iterable<Revision> getCandidates(Coordinate c) throws Exception {
return library.getRevisionsByCoordinate(c);
}
/**
* Post install
*/
public void doPostInstall() {
getPlatform().doPostInstall();
}
public SortedSet<JVM> getVMs() throws Exception {
TreeSet<JVM> set = new TreeSet<JVM>(JVM.comparator);
String list = settings.get(JPM_VMS_EXTRA);
if (list != null) {
ExtList<String> elist = new ExtList<String>(list.split("\\s*,\\s*"));
for (String dir : elist) {
File f = new File(dir);
JVM jvm = getPlatform().getJVM(f);
if (jvm == null) {
jvm = new JVM();
jvm.path = f.getCanonicalPath();
jvm.name = "Not a valid VM";
jvm.platformVersion = jvm.vendor = jvm.version = "";
}
set.add(jvm);
}
}
getPlatform().getVMs(set);
return set;
}
public JVM addVm(File platformRoot) throws Exception {
if (!platformRoot.isDirectory()) {
reporter.error("No such directory %s for a VM", platformRoot);
return null;
}
JVM jvm = getPlatform().getJVM(platformRoot);
if (jvm == null) {
return null;
}
String list = settings.get(JPM_VMS_EXTRA);
if (list == null)
list = platformRoot.getCanonicalPath();
else {
ExtList<String> elist = new ExtList<String>(list.split("\\s*,\\s*"));
elist.remove(platformRoot.getCanonicalPath());
elist.add(0, platformRoot.getCanonicalPath());
list = Strings.join(",", elist);
}
settings.put(JPM_VMS_EXTRA, list);
settings.save();
return jvm;
}
}