package aQute.remote.plugin;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.Manifest;
import aQute.bnd.build.Container;
import aQute.bnd.build.Project;
import aQute.bnd.build.ProjectLauncher;
import aQute.bnd.build.RunSession;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.FileResource;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.URLResource;
import aQute.bnd.version.Version;
import aQute.lib.converter.Converter;
import aQute.lib.strings.Strings;
import aQute.remote.embedded.activator.EmbeddedActivator;
import aQute.remote.util.JMXBundleDeployer;
/**
* This is the plugin. It is found by bnd on the -runpath when it needs to
* launch. The plugin is reponsible for launching the specification. In this
* case, we will inspect -runremote. The -runremote can contain a number of
* remote framework specifications.
*/
public class RemoteProjectLauncherPlugin extends ProjectLauncher {
private static Converter converter = new Converter();
static {
converter.setFatalIsException(false);
}
private Parameters runremote;
private List<RunSessionImpl> sessions = new ArrayList<RunSessionImpl>();
private boolean prepared;
/**
* The well defined launcher
*
* @param project the project or Run
*/
public RemoteProjectLauncherPlugin(Project project) throws Exception {
super(project);
runremote = new Parameters(getProject().getProperty(Constants.RUNREMOTE), getProject());
}
/**
* We do not have a main for a remote framework
*/
@Override
public String getMainTypeName() {
return "";
}
/**
* Called when a change in the IDE is detected. We will then upate from the
* project and then update the remote framework.
*/
@Override
public void update() throws Exception {
updateFromProject();
Parameters runremote = new Parameters(getProject().getProperty(Constants.RUNREMOTE), getProject());
for (RunSessionImpl session : sessions)
try {
Attrs attrs = runremote.get(session.getName());
RunRemoteDTO dto = Converter.cnv(RunRemoteDTO.class, attrs);
session.update(dto);
} catch (Exception e) {
getProject().exception(e, "Failed to update session %s", session.getName());
}
}
/**
* We parse the -runremote and create sessions for each one of them
*/
@Override
public void prepare() throws Exception {
if (prepared)
return;
prepared = true;
updateFromProject();
Map<String,Object> properties = new HashMap<String,Object>(getRunProperties());
calculatedProperties(properties);
Collection<String> embeddedActivators = getActivators();
if (embeddedActivators != null && !embeddedActivators.isEmpty()) {
properties.put("biz.aQute.remote.embedded", Strings.join(embeddedActivators));
}
for (Entry<String,Attrs> entry : runremote.entrySet()) {
RunRemoteDTO dto = converter.convert(RunRemoteDTO.class, entry.getValue());
dto.name = entry.getKey();
Map<String,Object> sessionProperties = new HashMap<String,Object>(properties);
sessionProperties.putAll(entry.getValue());
sessionProperties.put("session.name", dto.name);
if (dto.jmx != null) {
tryJMXDeploy(dto.jmx, "biz.aQute.remote.agent");
}
RunSessionImpl session = new RunSessionImpl(this, dto, properties);
sessions.add(session);
}
}
/**
* provide backward compatibility with the older API when IDE did not have
* multiple sessions. This should be straightforward to do since this method
* should not return until the process has exited. So we should be able to
* just launch all the sessions in their own threads and then sync.
*/
@Override
public int launch() throws Exception {
prepare();
final int[] results = new int[sessions.size()];
final Thread[] sessionThreads = new Thread[sessions.size()];
for (int i = 0; i < sessions.size(); i++) {
final int j = i;
final RunSessionImpl session = sessions.get(j);
sessionThreads[j] = new Thread("session launch " + j) {
@Override
public void run() {
try {
results[j] = session.launch();
} catch (Exception e) {
//
}
}
};
sessionThreads[j].start();
}
for (Thread sessionThread : sessionThreads) {
sessionThread.join();
}
for (int result : results) {
if (result > 0) {
return result;
}
}
return 0;
}
/**
* Make sure all sessions are closed
*/
public void close() {
for (RunSessionImpl session : sessions)
try {
session.close();
} catch (Exception e) {
// ignore
}
}
/**
* Kill!
*/
public void cancel() throws Exception {
for (RunSessionImpl session : sessions)
try {
session.cancel();
} catch (Exception e) {
// ignore
}
}
/**
* Send any given text to the remote framework and treat it as input
*/
@Override
public void write(String text) throws Exception {
throw new UnsupportedOperationException("This launcher only understands run sessions");
}
/**
* Get the sessions
*/
@Override
public List< ? extends RunSession> getRunSessions() throws Exception {
prepare();
return sessions;
}
@SuppressWarnings("deprecation")
private void tryJMXDeploy(String jmx, String bsn) {
JMXBundleDeployer jmxBundleDeployer = null;
int port = -1;
try {
port = Integer.parseInt(jmx);
} catch (Exception e) {
// not an integer
}
try {
if (port > -1) {
jmxBundleDeployer = new JMXBundleDeployer(port);
} else {
jmxBundleDeployer = new JMXBundleDeployer();
}
} catch (Exception e) {
// ignore if we can't create bundle deployer (no remote osgi.core
// jmx avail)
}
if (jmxBundleDeployer != null) {
for (String path : this.getRunpath()) {
File file = new File(path);
try (Jar jar = new Jar(file)) {
if (bsn.equals(jar.getBsn())) {
long bundleId = jmxBundleDeployer.deploy(bsn, file);
trace("agent installed with bundleId=%s", bundleId);
break;
}
} catch (Exception e) {
//
}
}
}
}
/**
* Created a JAR that is a bundle and that contains its dependencies
*/
@Override
public Jar executable() throws Exception {
Collection<String> bsns = getProject().getBsns();
if (bsns.size() != 1)
throw new IllegalArgumentException("Can only handle a single bsn for a run configuration " + bsns);
String bsn = bsns.iterator().next();
Jar jar = new Jar(bsn);
String path = "aQute/remote/embedded/activator/EmbeddedActivator.class";
URLResource resource = new URLResource(getClass().getClassLoader().getResource(path));
jar.putResource("aQute/remote/embedded/activator/EmbeddedActivator.class", resource);
Collection<Container> rb = getProject().getRunbundles();
rb = Container.flatten(rb);
Attrs attrs = new Attrs();
for (Container c : rb) {
if (c.getError() != null) {
getProject().error("invalid runbundle %s", c);
} else {
File f = c.getFile();
String tbsn = c.getBundleSymbolicName();
String version = c.getVersion();
if (version == null || !Version.isVersion(version))
getProject().warning("The version of embedded bundle %s does not have a proper version", c);
jar.putResource("jar/" + c.getBundleSymbolicName() + ".jar", new FileResource(f));
attrs.put(tbsn, version);
}
}
Analyzer a = new Analyzer(getProject());
a.setJar(jar);
a.setBundleActivator(EmbeddedActivator.class.getName());
a.setProperty("Bnd-Embedded", attrs.toString().replace(';', ','));
Manifest manifest = a.calcManifest();
jar.setManifest(manifest);
getProject().getInfo(a);
return jar;
}
}