View Javadoc
1   package org.argeo.osgi.boot;
2   
3   import java.lang.reflect.Method;
4   import java.net.URI;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Properties;
12  import java.util.Set;
13  import java.util.TreeMap;
14  
15  import org.eclipse.osgi.launch.EquinoxFactory;
16  import org.osgi.framework.Bundle;
17  import org.osgi.framework.BundleContext;
18  import org.osgi.framework.BundleEvent;
19  import org.osgi.framework.BundleException;
20  import org.osgi.framework.FrameworkUtil;
21  import org.osgi.framework.InvalidSyntaxException;
22  import org.osgi.framework.ServiceReference;
23  import org.osgi.framework.launch.Framework;
24  import org.osgi.framework.launch.FrameworkFactory;
25  import org.osgi.util.tracker.BundleTracker;
26  import org.osgi.util.tracker.ServiceTracker;
27  
28  /** OSGi builder, focusing on ease of use for scripting. */
29  public class OsgiBuilder {
30  	private final static String PROP_HTTP_PORT = "org.osgi.service.http.port";
31  	private final static String PROP_HTTPS_PORT = "org.osgi.service.https.port";
32  	private final static String PROP_OSGI_CLEAN = "osgi.clean";
33  
34  	private Map<Integer, StartLevel> startLevels = new TreeMap<>();
35  	private List<String> distributionBundles = new ArrayList<>();
36  
37  	private Map<String, String> configuration = new HashMap<String, String>();
38  	private Framework framework;
39  	private String baseUrl = null;
40  
41  	public OsgiBuilder() {
42  		// configuration.put("osgi.clean", "true");
43  		configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP));
44  		configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP));
45  		configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
46  	}
47  
48  	public Framework launch() {
49  		// start OSGi
50  		FrameworkFactory frameworkFactory = new EquinoxFactory();
51  		framework = frameworkFactory.newFramework(configuration);
52  		try {
53  			framework.start();
54  		} catch (BundleException e) {
55  			throw new OsgiBootException("Cannot start OSGi framework", e);
56  		}
57  
58  		BundleContext bc = framework.getBundleContext();
59  		String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP);
60  		// String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
61  		String osgiConf = framework.getDataFile("").getAbsolutePath();
62  		if (OsgiBootUtils.isDebug())
63  			OsgiBootUtils.debug("OSGi starting - data: " + osgiData + " conf: " + osgiConf);
64  
65  		OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
66  		if (distributionBundles.isEmpty()) {
67  			osgiBoot.getProvisioningManager().install(null);
68  		} else {
69  			// install bundles
70  			for (String distributionBundle : distributionBundles) {
71  				List<String> bundleUrls = osgiBoot.getDistributionUrls(distributionBundle, baseUrl);
72  				osgiBoot.installUrls(bundleUrls);
73  			}
74  		}
75  		// start bundles
76  		osgiBoot.startBundles(startLevelsToProperties());
77  
78  		// if (OsgiBootUtils.isDebug())
79  		// for (Bundle bundle : bc.getBundles()) {
80  		// OsgiBootUtils.debug(bundle.getLocation());
81  		// }
82  		return framework;
83  	}
84  
85  	public OsgiBuilder conf(String key, String value) {
86  		checkNotLaunched();
87  		configuration.put(key, value);
88  		return this;
89  	}
90  
91  	public OsgiBuilder install(String uri) {
92  		// TODO dynamic install
93  		checkNotLaunched();
94  		if (!distributionBundles.contains(uri))
95  			distributionBundles.add(uri);
96  		return this;
97  	}
98  
99  	public OsgiBuilder start(int startLevel, String bundle) {
100 		// TODO dynamic start
101 		checkNotLaunched();
102 		StartLevel sl;
103 		if (!startLevels.containsKey(startLevel))
104 			startLevels.put(startLevel, new StartLevel());
105 		sl = startLevels.get(startLevel);
106 		sl.add(bundle);
107 		return this;
108 	}
109 
110 	public OsgiBuilder waitForServlet(String base) {
111 		service("(&(objectClass=javax.servlet.Servlet)(osgi.http.whiteboard.servlet.pattern=" + base + "))");
112 		return this;
113 	}
114 
115 	public OsgiBuilder waitForBundle(String bundles) {
116 		List<String> lst = new ArrayList<>();
117 		Collections.addAll(lst, bundles.split(","));
118 		BundleTracker<Object> bt = new BundleTracker<Object>(getBc(), Bundle.ACTIVE, null) {
119 
120 			@Override
121 			public Object addingBundle(Bundle bundle, BundleEvent event) {
122 				if (lst.contains(bundle.getSymbolicName())) {
123 					return bundle.getSymbolicName();
124 				} else {
125 					return null;
126 				}
127 			}
128 		};
129 		bt.open();
130 		while (bt.getTrackingCount() != lst.size()) {
131 			try {
132 				Thread.sleep(500l);
133 			} catch (InterruptedException e) {
134 				break;
135 			}
136 		}
137 		bt.close();
138 		return this;
139 
140 	}
141 
142 	public OsgiBuilder main(String clssUri, String[] args) {
143 
144 		// waitForBundle(bundleSymbolicName);
145 		try {
146 			URI uri = new URI(clssUri);
147 			if (!"bundleclass".equals(uri.getScheme()))
148 				throw new IllegalArgumentException("Unsupported scheme for " + clssUri);
149 			String bundleSymbolicName = uri.getHost();
150 			String clss = uri.getPath().substring(1);
151 			Bundle bundle = null;
152 			for (Bundle b : getBc().getBundles()) {
153 				if (bundleSymbolicName.equals(b.getSymbolicName())) {
154 					bundle = b;
155 					break;
156 				}
157 			}
158 			if (bundle == null)
159 				throw new OsgiBootException("Bundle " + bundleSymbolicName + " not found");
160 			Class<?> c = bundle.loadClass(clss);
161 			Object[] mainArgs = { args };
162 			Method mainMethod = c.getMethod("main", String[].class);
163 			mainMethod.invoke(null, mainArgs);
164 		} catch (Throwable e) {
165 			throw new OsgiBootException("Cannot execute " + clssUri, e);
166 		}
167 		return this;
168 	}
169 
170 	public Object service(String service) {
171 		return service(service, 0);
172 	}
173 
174 	public Object service(String service, long timeout) {
175 		ServiceTracker<Object, Object> st;
176 		if (service.contains("(")) {
177 			try {
178 				st = new ServiceTracker<>(getBc(), FrameworkUtil.createFilter(service), null);
179 			} catch (InvalidSyntaxException e) {
180 				throw new IllegalArgumentException("Badly formatted filter", e);
181 			}
182 		} else {
183 			st = new ServiceTracker<>(getBc(), service, null);
184 		}
185 		st.open();
186 		try {
187 			return st.waitForService(timeout);
188 		} catch (InterruptedException e) {
189 			OsgiBootUtils.error("Interrupted", e);
190 			return null;
191 		} finally {
192 			st.close();
193 		}
194 
195 	}
196 
197 	public void shutdown() {
198 		checkLaunched();
199 		try {
200 			framework.stop();
201 		} catch (BundleException e) {
202 			e.printStackTrace();
203 			System.exit(1);
204 		}
205 		try {
206 			framework.waitForStop(10 * 60 * 1000);
207 		} catch (InterruptedException e) {
208 			e.printStackTrace();
209 			System.exit(1);
210 		}
211 		System.exit(0);
212 	}
213 
214 	public void setHttpPort(Integer port) {
215 		checkNotLaunched();
216 		configuration.put(PROP_HTTP_PORT, Integer.toString(port));
217 	}
218 
219 	public void setHttpsPort(Integer port) {
220 		checkNotLaunched();
221 		configuration.put(PROP_HTTPS_PORT, Integer.toString(port));
222 	}
223 
224 	public void setClean(boolean clean) {
225 		checkNotLaunched();
226 		configuration.put(PROP_OSGI_CLEAN, Boolean.toString(clean));
227 	}
228 
229 	public Integer getHttpPort() {
230 		if (!isLaunched()) {
231 			if (configuration.containsKey(PROP_HTTP_PORT))
232 				return Integer.parseInt(configuration.get(PROP_HTTP_PORT));
233 			else
234 				return -1;
235 		} else {
236 			// TODO wait for service?
237 			ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
238 			if (sr == null)
239 				return -1;
240 			Object port = sr.getProperty("http.port");
241 			if (port == null)
242 				return -1;
243 			return Integer.parseInt(port.toString());
244 		}
245 	}
246 
247 	public Integer getHttpsPort() {
248 		if (!isLaunched()) {
249 			if (configuration.containsKey(PROP_HTTPS_PORT))
250 				return Integer.parseInt(configuration.get(PROP_HTTPS_PORT));
251 			else
252 				return -1;
253 		} else {
254 			// TODO wait for service?
255 			ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
256 			if (sr == null)
257 				return -1;
258 			Object port = sr.getProperty("https.port");
259 			if (port == null)
260 				return -1;
261 			return Integer.parseInt(port.toString());
262 		}
263 	}
264 
265 	public Object spring(String bundle) {
266 		return service("(&(Bundle-SymbolicName=" + bundle + ")"
267 				+ "(objectClass=org.springframework.context.ApplicationContext))");
268 	}
269 
270 	//
271 	// BEAN
272 	//
273 
274 	public BundleContext getBc() {
275 		checkLaunched();
276 		return framework.getBundleContext();
277 	}
278 
279 	public void setBaseUrl(String baseUrl) {
280 		this.baseUrl = baseUrl;
281 	}
282 
283 	//
284 	// UTILITIES
285 	//
286 	private Properties startLevelsToProperties() {
287 		Properties properties = new Properties();
288 		for (Integer startLevel : startLevels.keySet()) {
289 			String property = OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel;
290 			StringBuilder value = new StringBuilder();
291 			for (String bundle : startLevels.get(startLevel).getBundles()) {
292 				value.append(bundle);
293 				value.append(',');
294 			}
295 			// TODO remove trailing comma
296 			properties.put(property, value.toString());
297 		}
298 		return properties;
299 	}
300 
301 	private void checkLaunched() {
302 		if (!isLaunched())
303 			throw new OsgiBootException("OSGi runtime is not launched");
304 	}
305 
306 	private void checkNotLaunched() {
307 		if (isLaunched())
308 			throw new OsgiBootException("OSGi runtime already launched");
309 	}
310 
311 	private boolean isLaunched() {
312 		return framework != null;
313 	}
314 
315 	private static class StartLevel {
316 		private Set<String> bundles = new HashSet<>();
317 
318 		public void add(String bundle) {
319 			String[] b = bundle.split(",");
320 			Collections.addAll(bundles, b);
321 		}
322 
323 		public Set<String> getBundles() {
324 			return bundles;
325 		}
326 	}
327 }