package aQute.remote.embedded.activator;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.Version;
import org.osgi.framework.wiring.FrameworkWiring;
/**
* This activator is placed in bundles that will load embedded bundles when
* activated. We mark installed bundles with our bsn + "@" their bsn. This makes
* it quick and easy to update the constellation.
*/
public class EmbeddedActivator implements BundleActivator {
final List<Bundle> bundles = new ArrayList<Bundle>();
final static String SYMBOLICNAME_S = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
public final static String VERSION_S = "(?:\\d{1,9})(?:\\.(?:\\d{1,9})(?:\\.(?:\\d{1,9})(?:\\.(?:[-_\\da-zA-Z]+))?)?)?";
final static Pattern BSN_VERSION_P = Pattern
.compile("\\s*(" + SYMBOLICNAME_S + ")\\s*=\\s*(" + VERSION_S + ")\\s*");
/**
* The activator start will install any bundles that are not already
* installed and update embedded bundles that are refreshed
*/
@Override
public void start(BundleContext context) throws Exception {
try {
Bundle ours = context.getBundle();
String bsn = ours.getSymbolicName();
Version version = ours.getVersion();
String embedded = ours.getHeaders().get("Bnd-Embedded");
if (embedded == null)
throw new IllegalArgumentException("Requires a Bnd-Embedded header");
Map<String,Version> index = new HashMap<String,Version>();
String[] clauses = embedded.split("\\s*,\\s*");
for (String clause : clauses) {
Matcher m = BSN_VERSION_P.matcher(clause);
if (!m.matches())
throw new IllegalArgumentException(
"Funny clause in Bnd-Embedded header " + clause + ", expecting <bsn>=<version>");
String tbsn = m.group(1);
String tversion = m.group(2);
index.put(tbsn, new Version(tversion));
}
List<Bundle> toStop = new ArrayList<Bundle>();
List<Bundle> toUpdate = new ArrayList<Bundle>();
Pattern bsn_p = Pattern.compile(Pattern.quote(bsn) + "@(.*)");
for (Bundle b : context.getBundles()) {
if (b == ours)
continue;
String location = b.getLocation();
Matcher m = bsn_p.matcher(location);
if (m.matches()) {
String tbsn = b.getSymbolicName();
Version expected = index.remove(tbsn);
if (expected == null) {
//
// No longer there
//
b.uninstall();
} else {
//
// We stop all our bundles
//
if (isGC(b)) {
//
// But the GC bundle is special. We do not
// automatically stop it and we only
// update it when we have a better version
//
if (b.getVersion().compareTo(expected) > 0)
continue;
}
toStop.add(b);
//
// And if the version differs we do
// an update
//
if (!expected.equals(b.getVersion())) {
toUpdate.add(b);
}
}
}
}
List<Bundle> toStart = new ArrayList<Bundle>();
for (Bundle b : toStop) {
b.stop();
toStart.add(b);
}
refresh(context, toStop);
for (String tbsn : index.keySet()) {
URL url = ours.getEntry("jar/" + tbsn + ".jar");
Bundle b = context.installBundle(bsn + "@" + tbsn, url.openStream());
toStart.add(b);
}
for (Bundle b : toUpdate) {
URL url = ours.getEntry("jar/" + b.getSymbolicName() + ".jar");
b.update(url.openStream());
toStart.add(b);
}
for (Bundle b : toStart) {
b.start();
}
} catch (Exception e) {
stop(context);
}
}
/*
* Refresh the stopped bundles
*/
void refresh(BundleContext context, List<Bundle> toStop) throws InterruptedException {
Bundle bundle = context.getBundle(0);
FrameworkWiring framework = bundle.adapt(FrameworkWiring.class);
final Semaphore s = new Semaphore(0);
framework.refreshBundles(toStop, new FrameworkListener() {
@Override
public void frameworkEvent(FrameworkEvent event) {
s.release();
}
});
s.tryAcquire(10, TimeUnit.SECONDS);
}
/**
* Stop any bundles that our in our domain
*/
@Override
public void stop(BundleContext context) throws Exception {
Bundle ours = context.getBundle();
String bsn = ours.getSymbolicName();
Pattern bsn_p = Pattern.compile(Pattern.quote(bsn) + "@(.*)");
List<Exception> exceptions = new ArrayList<Exception>();
for (Bundle b : context.getBundles()) {
if (b == ours || isGC(b))
continue;
String location = b.getLocation();
Matcher m = bsn_p.matcher(location);
if (m.matches())
try {
b.stop();
} catch (Exception e) {
exceptions.add(e);
}
}
if (exceptions.isEmpty())
return;
throw new Exception("Failed to stop all sub bundles " + exceptions);
}
private boolean isGC(Bundle b) {
return "biz.aQute.remote.gc".equals(b.getSymbolicName());
}
}