Rename OSGi Boot to Argeo Init and introduce logging framework.
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / osgi / OsgiBuilder.java
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java
new file mode 100644 (file)
index 0000000..39c42cb
--- /dev/null
@@ -0,0 +1,319 @@
+package org.argeo.init.osgi;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.ServiceTracker;
+
+/** OSGi builder, focusing on ease of use for scripting. */
+public class OsgiBuilder {
+       private final static String PROP_HTTP_PORT = "org.osgi.service.http.port";
+       private final static String PROP_HTTPS_PORT = "org.osgi.service.https.port";
+       private final static String PROP_OSGI_CLEAN = "osgi.clean";
+
+       private Map<Integer, StartLevel> startLevels = new TreeMap<>();
+       private List<String> distributionBundles = new ArrayList<>();
+
+       private Map<String, String> configuration = new HashMap<String, String>();
+       private Framework framework;
+       private String baseUrl = null;
+
+       public OsgiBuilder() {
+               // configuration.put("osgi.clean", "true");
+               configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP));
+               configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP));
+               configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
+       }
+
+       public Framework launch() {
+               // start OSGi
+               framework = OsgiBootUtils.launch(configuration);
+
+               BundleContext bc = framework.getBundleContext();
+               String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP);
+               // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
+               String osgiConf = framework.getDataFile("").getAbsolutePath();
+               if (OsgiBootUtils.isDebug())
+                       OsgiBootUtils.debug("OSGi starting - data: " + osgiData + " conf: " + osgiConf);
+
+               OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
+               if (distributionBundles.isEmpty()) {
+                       osgiBoot.getProvisioningManager().install(null);
+               } else {
+                       // install bundles
+                       for (String distributionBundle : distributionBundles) {
+                               List<String> bundleUrls = osgiBoot.getDistributionUrls(distributionBundle, baseUrl);
+                               osgiBoot.installUrls(bundleUrls);
+                       }
+               }
+               // start bundles
+               osgiBoot.startBundles(startLevelsToProperties());
+
+               // if (OsgiBootUtils.isDebug())
+               // for (Bundle bundle : bc.getBundles()) {
+               // OsgiBootUtils.debug(bundle.getLocation());
+               // }
+               return framework;
+       }
+
+       public OsgiBuilder conf(String key, String value) {
+               checkNotLaunched();
+               configuration.put(key, value);
+               return this;
+       }
+
+       public OsgiBuilder install(String uri) {
+               // TODO dynamic install
+               checkNotLaunched();
+               if (!distributionBundles.contains(uri))
+                       distributionBundles.add(uri);
+               return this;
+       }
+
+       public OsgiBuilder start(int startLevel, String bundle) {
+               // TODO dynamic start
+               checkNotLaunched();
+               StartLevel sl;
+               if (!startLevels.containsKey(startLevel))
+                       startLevels.put(startLevel, new StartLevel());
+               sl = startLevels.get(startLevel);
+               sl.add(bundle);
+               return this;
+       }
+
+       public OsgiBuilder waitForServlet(String base) {
+               service("(&(objectClass=javax.servlet.Servlet)(osgi.http.whiteboard.servlet.pattern=" + base + "))");
+               return this;
+       }
+
+       public OsgiBuilder waitForBundle(String bundles) {
+               List<String> lst = new ArrayList<>();
+               Collections.addAll(lst, bundles.split(","));
+               BundleTracker<Object> bt = new BundleTracker<Object>(getBc(), Bundle.ACTIVE, null) {
+
+                       @Override
+                       public Object addingBundle(Bundle bundle, BundleEvent event) {
+                               if (lst.contains(bundle.getSymbolicName())) {
+                                       return bundle.getSymbolicName();
+                               } else {
+                                       return null;
+                               }
+                       }
+               };
+               bt.open();
+               while (bt.getTrackingCount() != lst.size()) {
+                       try {
+                               Thread.sleep(500l);
+                       } catch (InterruptedException e) {
+                               break;
+                       }
+               }
+               bt.close();
+               return this;
+
+       }
+
+       public OsgiBuilder main(String clssUri, String[] args) {
+
+               // waitForBundle(bundleSymbolicName);
+               try {
+                       URI uri = new URI(clssUri);
+                       if (!"bundleclass".equals(uri.getScheme()))
+                               throw new IllegalArgumentException("Unsupported scheme for " + clssUri);
+                       String bundleSymbolicName = uri.getHost();
+                       String clss = uri.getPath().substring(1);
+                       Bundle bundle = null;
+                       for (Bundle b : getBc().getBundles()) {
+                               if (bundleSymbolicName.equals(b.getSymbolicName())) {
+                                       bundle = b;
+                                       break;
+                               }
+                       }
+                       if (bundle == null)
+                               throw new IllegalStateException("Bundle " + bundleSymbolicName + " not found");
+                       Class<?> c = bundle.loadClass(clss);
+                       Object[] mainArgs = { args };
+                       Method mainMethod = c.getMethod("main", String[].class);
+                       mainMethod.invoke(null, mainArgs);
+               } catch (Throwable e) {
+                       throw new RuntimeException("Cannot execute " + clssUri, e);
+               }
+               return this;
+       }
+
+       public Object service(String service) {
+               return service(service, 0);
+       }
+
+       public Object service(String service, long timeout) {
+               ServiceTracker<Object, Object> st;
+               if (service.contains("(")) {
+                       try {
+                               st = new ServiceTracker<>(getBc(), FrameworkUtil.createFilter(service), null);
+                       } catch (InvalidSyntaxException e) {
+                               throw new IllegalArgumentException("Badly formatted filter", e);
+                       }
+               } else {
+                       st = new ServiceTracker<>(getBc(), service, null);
+               }
+               st.open();
+               try {
+                       return st.waitForService(timeout);
+               } catch (InterruptedException e) {
+                       OsgiBootUtils.error("Interrupted", e);
+                       return null;
+               } finally {
+                       st.close();
+               }
+
+       }
+
+       public void shutdown() {
+               checkLaunched();
+               try {
+                       framework.stop();
+               } catch (BundleException e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+               try {
+                       framework.waitForStop(10 * 60 * 1000);
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+               System.exit(0);
+       }
+
+       public void setHttpPort(Integer port) {
+               checkNotLaunched();
+               configuration.put(PROP_HTTP_PORT, Integer.toString(port));
+       }
+
+       public void setHttpsPort(Integer port) {
+               checkNotLaunched();
+               configuration.put(PROP_HTTPS_PORT, Integer.toString(port));
+       }
+
+       public void setClean(boolean clean) {
+               checkNotLaunched();
+               configuration.put(PROP_OSGI_CLEAN, Boolean.toString(clean));
+       }
+
+       public Integer getHttpPort() {
+               if (!isLaunched()) {
+                       if (configuration.containsKey(PROP_HTTP_PORT))
+                               return Integer.parseInt(configuration.get(PROP_HTTP_PORT));
+                       else
+                               return -1;
+               } else {
+                       // TODO wait for service?
+                       ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
+                       if (sr == null)
+                               return -1;
+                       Object port = sr.getProperty("http.port");
+                       if (port == null)
+                               return -1;
+                       return Integer.parseInt(port.toString());
+               }
+       }
+
+       public Integer getHttpsPort() {
+               if (!isLaunched()) {
+                       if (configuration.containsKey(PROP_HTTPS_PORT))
+                               return Integer.parseInt(configuration.get(PROP_HTTPS_PORT));
+                       else
+                               return -1;
+               } else {
+                       // TODO wait for service?
+                       ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
+                       if (sr == null)
+                               return -1;
+                       Object port = sr.getProperty("https.port");
+                       if (port == null)
+                               return -1;
+                       return Integer.parseInt(port.toString());
+               }
+       }
+
+       public Object spring(String bundle) {
+               return service("(&(Bundle-SymbolicName=" + bundle + ")"
+                               + "(objectClass=org.springframework.context.ApplicationContext))");
+       }
+
+       //
+       // BEAN
+       //
+
+       public BundleContext getBc() {
+               checkLaunched();
+               return framework.getBundleContext();
+       }
+
+       public void setBaseUrl(String baseUrl) {
+               this.baseUrl = baseUrl;
+       }
+
+       //
+       // UTILITIES
+       //
+       private Properties startLevelsToProperties() {
+               Properties properties = new Properties();
+               for (Integer startLevel : startLevels.keySet()) {
+                       String property = OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel;
+                       StringBuilder value = new StringBuilder();
+                       for (String bundle : startLevels.get(startLevel).getBundles()) {
+                               value.append(bundle);
+                               value.append(',');
+                       }
+                       // TODO remove trailing comma
+                       properties.put(property, value.toString());
+               }
+               return properties;
+       }
+
+       private void checkLaunched() {
+               if (!isLaunched())
+                       throw new IllegalStateException("OSGi runtime is not launched");
+       }
+
+       private void checkNotLaunched() {
+               if (isLaunched())
+                       throw new IllegalStateException("OSGi runtime already launched");
+       }
+
+       private boolean isLaunched() {
+               return framework != null;
+       }
+
+       private static class StartLevel {
+               private Set<String> bundles = new HashSet<>();
+
+               public void add(String bundle) {
+                       String[] b = bundle.split(",");
+                       Collections.addAll(bundles, b);
+               }
+
+               public Set<String> getBundles() {
+                       return bundles;
+               }
+       }
+}