package aQute.bnd.main; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import org.osgi.framework.namespace.NativeNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.framework.wiring.dto.BundleRevisionDTO; import org.osgi.resource.dto.CapabilityDTO; import org.osgi.service.repository.ContentNamespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import aQute.bnd.header.Attrs; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Constants; import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.Verifier; import aQute.bnd.osgi.resource.CapabilityBuilder; import aQute.bnd.version.Version; import aQute.lib.converter.Converter; import aQute.lib.converter.TypeReference; import aQute.lib.getopt.Arguments; import aQute.lib.getopt.Description; import aQute.lib.getopt.Options; import aQute.remote.api.Agent; import aQute.remote.api.Event; import aQute.remote.api.Supervisor; import aQute.remote.util.AgentSupervisor; class RemoteCommand extends Processor { private final static Logger logger = LoggerFactory.getLogger(RemoteCommand.class); private static TypeReference<List<Version>> tref = new TypeReference<List<Version>>() {}; private Yaml y = new Yaml(); private bnd bnd; private LauncherSupervisor launcher = new LauncherSupervisor(); private Agent agent; private int port; private String host; private static Set<String> IGNORED_NAMESPACES = new HashSet<>(); static { IGNORED_NAMESPACES.add(PackageNamespace.PACKAGE_NAMESPACE); // handled // specially IGNORED_NAMESPACES.add(ContentNamespace.CONTENT_NAMESPACE); } /** * This is the supervisor on the bnd launcher side. It provides the SHA * repository for the agent and handles the redirection. It also handles the * events. */ class LauncherSupervisor extends AgentSupervisor<Supervisor,Agent> implements Supervisor { @Override public boolean stdout(String out) throws Exception { System.out.print(out); return true; } @Override public boolean stderr(String out) throws Exception { System.err.print(out); return true; } public void connect(String host, int port) throws Exception { super.connect(Agent.class, this, host, port); } @Override public void event(Event e) throws Exception { System.out.println(e); } } @Description("Communicates with the remote agent") interface RemoteOptions extends Options { @Description("Specify the host to commicate with, default is 'localhost'") String host(String deflt); @Description("Specify the port to commicate with, default is " + Agent.DEFAULT_PORT) int port(int deflt); } RemoteCommand(bnd bnd, RemoteOptions options) throws Exception { super(bnd); this.bnd = bnd; use(bnd); launcher = new LauncherSupervisor(); launcher.connect(host = options.host("localhost"), port = options.port(Agent.DEFAULT_PORT)); agent = launcher.getAgent(); } public void close() throws IOException { launcher.close(); } @Description("Get the framework info") @Arguments(arg = {}) interface FrameworkOptions extends Options {} public void _framework(FrameworkOptions opts) throws Exception { dump(agent.getFramework()); } @Description("Get the bundle revisions") @Arguments(arg = { "bundleid..." }) interface RevisonOptions extends Options {} public void _revisions(RevisonOptions opts) throws Exception { long[] ids = Converter.cnv(long[].class, opts._arguments()); dump(agent.getBundleRevisons(ids)); } @Description("Ping the remote framework") @Arguments(arg = {}) interface PingOptions extends Options {} public void _ping(PingOptions opts) throws Exception { long start = System.currentTimeMillis(); if (agent.ping()) bnd.out.println("Ok " + (System.currentTimeMillis() - start) + "ms"); else bnd.out.println("Could not reach " + host + ":" + port); } /** * Create a distro from a remote agent */ @Description("Create a distro jar from a remote agent") @Arguments(arg = { "bsn", "[version]" }) interface DistroOptions extends Options { String vendor(); String description(); String copyright(); String license(); String extra(); String output(String deflt); } public void _distro(DistroOptions opts) throws Exception { List<String> args = opts._arguments(); String bsn; String version; bsn = args.remove(0); if (!Verifier.isBsn(bsn)) { error("Not a bundle symbolic name %s", bsn); } if (args.isEmpty()) version = "0"; else { version = args.remove(0); if (!Version.isVersion(version)) { error("Invalid version %s", version); } } File output = getFile(opts.output("distro.jar")); if (output.getParentFile() == null || !output.getParentFile().isDirectory()) { error("Cannot write to %s because parent not a directory", output); } if (output.isFile() && !output.canWrite()) { error("Cannot write to %s", output); } logger.debug("Starting distro {};{}", bsn, version); List<BundleRevisionDTO> bundleRevisons = agent.getBundleRevisons(); logger.debug("Found {} bundle revisions", bundleRevisons.size()); Parameters packages = new Parameters(); List<Parameters> provided = new ArrayList<>(); for (BundleRevisionDTO brd : bundleRevisons) { for (CapabilityDTO c : brd.capabilities) { CapabilityBuilder cb = new CapabilityBuilder(c.namespace); // // We need to fixup versions :-( // Versions are encoded as strings in DTOs // and that means we need to treat the version key // special // for (Entry<String,Object> e : c.attributes.entrySet()) { String key = e.getKey(); Object value = e.getValue(); if (key.equals("version")) { if (value instanceof Collection || value.getClass().isArray()) value = Converter.cnv(tref, value); else value = new Version((String) value); } cb.addAttribute(key, value); } cb.addDirectives(c.directives); Attrs attrs = cb.toAttrs(); if (cb.isPackage()) { attrs.remove(Constants.BUNDLE_SYMBOLIC_NAME_ATTRIBUTE); attrs.remove(Constants.BUNDLE_VERSION_ATTRIBUTE); String pname = attrs.remove(PackageNamespace.PACKAGE_NAMESPACE); if (pname == null) { warning("Invalid package capability found %s", c); } else packages.put(pname, attrs); logger.debug("P: {};{}", pname, attrs); } else if (NativeNamespace.NATIVE_NAMESPACE.equals(c.namespace)) { Attrs newAttrs = new Attrs(); for (Entry<String,String> entry : attrs.entrySet()) { if (entry.getKey().startsWith(NativeNamespace.NATIVE_NAMESPACE)) { newAttrs.put(entry.getKey(), entry.getValue()); } } Parameters p = new Parameters(); p.put(c.namespace, newAttrs); provided.add(p); } else if (!IGNORED_NAMESPACES.contains(c.namespace)) { logger.debug("C {};{}", c.namespace, attrs); Parameters p = new Parameters(); p.put(c.namespace, attrs); provided.add(p); } } } if (isOk()) { Manifest m = new Manifest(); Attributes main = m.getMainAttributes(); main.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); main.putValue(Constants.BUNDLE_SYMBOLICNAME, bsn); main.putValue(Constants.BUNDLE_VERSION, version); main.putValue(Constants.EXPORT_PACKAGE, packages.toString()); // Make distro unresolvable Parameters unresolveable = new Parameters( "osgi.unresolvable; filter:='(&(must.not.resolve=*)(!(must.not.resolve=*)))'"); main.putValue(Constants.REQUIRE_CAPABILITY, unresolveable.toString()); provided.add(new Parameters("osgi.unresolvable")); StringBuilder sb = new StringBuilder(); for (Parameters parameter : provided) { sb.append(parameter.toString()); sb.append(","); } String capabilities = sb.toString().substring(0, sb.length() - 1); main.putValue(Constants.PROVIDE_CAPABILITY, capabilities); if (opts.description() != null) main.putValue(Constants.BUNDLE_DESCRIPTION, opts.description()); if (opts.license() != null) main.putValue(Constants.BUNDLE_LICENSE, opts.license()); if (opts.copyright() != null) main.putValue(Constants.BUNDLE_COPYRIGHT, opts.copyright()); if (opts.vendor() != null) main.putValue(Constants.BUNDLE_VENDOR, opts.vendor()); Jar jar = new Jar("distro"); jar.setManifest(m); Verifier v = new Verifier(jar); v.setProperty(Constants.FIXUPMESSAGES, "osgi.* namespace must not be specified with generic capabilities"); v.verify(); v.getErrors(); if (isFailOk() || v.isOk()) { jar.updateModified(System.currentTimeMillis(), "Writing distro jar"); jar.write(output); } else getInfo(v); } } private void dump(Object o) { y.dump(o, new OutputStreamWriter(bnd.out)); } }