package osgi.base.debug.provider; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Formatter; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicReference; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import org.osgi.dto.DTO; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.framework.wiring.BundleCapability; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleRevisions; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.log.LogEntry; import org.osgi.service.log.LogListener; import org.osgi.service.log.LogReaderService; import org.osgi.service.log.LogService; import org.osgi.util.tracker.ServiceTracker; import osgi.enroute.debug.api.Debug; public class EnRouteCommands implements LogListener, BundleActivator { volatile boolean watchlog = Boolean.getBoolean("enRoute.watchlog"); final AtomicReference<LogReaderService> logreader = new AtomicReference<>(); private ServiceTracker<LogReaderService,LogReaderService> lrs; private BundleContext context; final List<LogEntry> log = new ArrayList<>(); /** * Show the services * * @throws InvalidSyntaxException */ public Object lss() throws InvalidSyntaxException { return context.getAllServiceReferences(null, null); } /** * Command to toggle log * * @return */ public String watchlog() { return watchlog(!watchlog); } /** * Command set log on/off * * @return */ public String watchlog(boolean on) { if (watchlog != on) { watchlog = on; } return watchlog ? "on" : "off"; } /** * Show the log (reversed so the newest entries are at the bottom) */ public Object lg(int level) { LogReaderService lrs = logreader.get(); if (lrs != null) { List<LogEntry> entries = new ArrayList<>(log.subList(Math.max(0, log.size() - 50), log.size())); Collections.reverse(entries); try (Formatter f = new Formatter()) { for (LogEntry e : entries) { if (e.getLevel() <= level) { f.format("%2$tH:%2$tM:%2$tS [%1$s] %3$04d %4$s", label(e.getLevel()), e.getTime(), e .getBundle().getBundleId(), e.getMessage()); if (e.getException() != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.getException().printStackTrace(pw); pw.flush(); f.format("%n%s%n", sw.toString()); } f.format("%n"); } } return f.toString(); } } return "No log reader service"; } private Object label(int level) { switch (level) { case LogService.LOG_DEBUG : return "D"; case LogService.LOG_INFO : return "I"; case LogService.LOG_WARNING : return "W"; case LogService.LOG_ERROR : return "E"; } return level + "?"; } public Object lg() { return lg(LogService.LOG_WARNING); } public Object lg(String level) { switch (level) { case "debug" : return lg(LogService.LOG_DEBUG); case "info" : return lg(LogService.LOG_INFO); case "warn" : return lg(LogService.LOG_WARNING); case "error" : return lg(LogService.LOG_ERROR); } return lg(LogService.LOG_DEBUG); } /** * Command to say a text */ volatile int nsays; public void say(final Object message) throws Exception { nsays++; if (nsays > 2) return; Thread t = new Thread("speak") { public void run() { try { ScriptEngine engine = new ScriptEngineManager().getEngineByName("AppleScript"); engine.eval("say \"" + message + "\""); } catch (Exception e) { // ignore } finally { nsays--; } } }; t.start(); } /** * List all the exported packages */ public static class Export extends DTO { public String name; public Version version; public Map<Double,Set<Double>> wires = new HashMap<>(); } public Object exports() { return exports(""); } public Object exports(String prefix) { Map<String,Export> index = new TreeMap<>(); boolean needRefresh = false; for (Bundle b : context.getBundles()) { int r = 0; for (BundleRevision br : b.adapt(BundleRevisions.class).getRevisions()) { BundleWiring wiring = br.getWiring(); List<BundleWire> exports = wiring.getProvidedWires(PackageNamespace.PACKAGE_NAMESPACE); List<BundleCapability> capabilities = wiring.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE); Double exportRevision = (double) br.getBundle().getBundleId(); if (r > 0) { needRefresh = true; exportRevision += r / 10D; } for (BundleCapability bc : capabilities) { String packageName = (String) bc.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); Version version = (Version) bc.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); String id = packageName + ";" + version; Export to = index.get(id); if (to == null) { to = new Export(); to.name = packageName; to.version = version; index.put(id, to); } Set<Double> list = to.wires.get(exportRevision); if (list == null) { list = new TreeSet<>(); to.wires.put(exportRevision, list); } } for (BundleWire w : exports) { String packageName = (String) w.getCapability().getAttributes() .get(PackageNamespace.PACKAGE_NAMESPACE); Version version = (Version) w.getCapability().getAttributes() .get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); String id = packageName + ";" + version; Export to = index.get(id); if (to == null) { to = new Export(); to.name = packageName; to.version = version; index.put(id, to); } BundleRevision ibr = w.getRequirer(); int ir = w.getRequirer().getBundle().adapt(BundleRevisions.class).getRevisions().indexOf(ibr); Double importRevision = (double) ibr.getBundle().getBundleId(); if (ir > 0) importRevision += ir / 10; Set<Double> list = to.wires.get(exportRevision); if (list == null) { list = new TreeSet<>(); to.wires.put(exportRevision, list); } list.add(importRevision); } r++; } } if (needRefresh) System.err.println("Refresh is needed"); try (Formatter f = new Formatter()) { String packageName = null; for (Export export : index.values()) { if (export.name.contains(prefix)) { String name = export.name.equals(packageName) ? "" : export.name; String warning = export.wires.size() > 1 ? "**" : ""; String version = export.version.getMajor() + "." + export.version.getMinor() + "." + export.version.getMicro(); Iterator<Map.Entry<Double,Set<Double>>> it = export.wires.entrySet().iterator(); Entry<Double,Set<Double>> first = it.next(); f.format("%2s %-40s %-10s %-5s -> %s%n", warning, name, version, first.getKey(), first.getValue()); for (; it.hasNext();) { Entry<Double,Set<Double>> v = it.next(); f.format("** %-8s -> %s%n", v.getKey(), v.getValue()); } } } return f.toString(); } } /** * Just show a service * * @throws InvalidSyntaxException */ public Object srv(ServiceReference< ? >... refs) throws InvalidSyntaxException { if (refs == null || refs.length == 0) return context.getServiceReferences((String) null, null); if (refs.length == 1) return refs[0]; return refs; } /** * Just check the environment for anything uncanny */ public String state() { WiringState ws = new WiringState(context); ws.verify(); return null; } /* * Check log for errors */ static long lastspoken; @Override public void logged(LogEntry entry) { if (watchlog && entry.getLevel() <= LogService.LOG_WARNING) { try { if (System.currentTimeMillis() > lastspoken + 15000 && !entry.getMessage().contains("[silent]")) { say("error in bundle " + entry.getBundle().getBundleId()); lastspoken = System.currentTimeMillis(); } System.err.println(entry.getMessage()); log.add(entry); } catch (Exception e) { e.printStackTrace(); } } } @Override public void start(BundleContext context) throws Exception { this.context = context; this.lrs = new ServiceTracker<LogReaderService,LogReaderService>(context, LogReaderService.class, null) { @Override public LogReaderService addingService(ServiceReference<LogReaderService> lrs) { LogReaderService l = super.addingService(lrs); l.addLogListener(EnRouteCommands.this); logreader.set(l); return l; } @Override public void removedService(ServiceReference<LogReaderService> l, LogReaderService ll) { logreader.compareAndSet(ll, null); } }; this.lrs.open(); Hashtable<String,Object> map = new Hashtable<>(); map.put(Debug.COMMAND_SCOPE, "enroute"); map.put(Debug.COMMAND_FUNCTION, new String[] { "say", "lg", "watchlog", "lss", "srv", "exports" }); context.registerService(Object.class, this, map); } @Override public void stop(BundleContext context) throws Exception { this.lrs.close(); } }