]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.osgiboot/src/main/java/org/argeo/slc/osgiboot/OsgiBoot.java
Make OSGiBoot wait for bundles to be in status active or resolved
[gpl/argeo-slc.git] / runtime / org.argeo.slc.osgiboot / src / main / java / org / argeo / slc / osgiboot / OsgiBoot.java
1 package org.argeo.slc.osgiboot;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.net.URL;
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.StringTokenizer;
15
16 import org.argeo.slc.osgiboot.internal.springutil.AntPathMatcher;
17 import org.argeo.slc.osgiboot.internal.springutil.PathMatcher;
18 import org.argeo.slc.osgiboot.internal.springutil.SystemPropertyUtils;
19 import org.osgi.framework.Bundle;
20 import org.osgi.framework.BundleContext;
21 import org.osgi.framework.BundleException;
22 import org.osgi.framework.Constants;
23
24 public class OsgiBoot {
25 public final static String PROP_SLC_OSGI_START = "slc.osgi.start";
26 public final static String PROP_SLC_OSGI_BUNDLES = "slc.osgi.bundles";
27 public final static String PROP_SLC_OSGI_LOCATIONS = "slc.osgi.locations";
28 public final static String PROP_SLC_OSGI_BASE_URL = "slc.osgi.baseUrl";
29 public final static String PROP_SLC_OSGI_MODULES_URL = "slc.osgi.modulesUrl";
30
31 public final static String PROP_SLC_OSGIBOOT_DEBUG = "slc.osgiboot.debug";
32 public final static String PROP_SLC_OSGIBOOT_DEFAULT_TIMEOUT = "slc.osgiboot.defaultTimeout";
33 public final static String PROP_SLC_OSGIBOOT_MODULES_URL_SEPARATOR = "slc.osgiboot.modulesUrlSeparator";
34
35 public final static String DEFAULT_BASE_URL = "reference:file:";
36 public final static String EXCLUDES_SVN_PATTERN = "**/.svn/**";
37
38 private boolean debug = Boolean.valueOf(
39 System.getProperty(PROP_SLC_OSGIBOOT_DEBUG, "false"))
40 .booleanValue();
41 /** Default is 10s (set in constructor) */
42 private long defaultTimeout;
43
44 private boolean excludeSvn = true;
45 /** Default is ',' (set in constructor) */
46 private String modulesUrlSeparator = ",";
47
48 private final BundleContext bundleContext;
49
50 public OsgiBoot(BundleContext bundleContext) {
51 this.bundleContext = bundleContext;
52 defaultTimeout = Long.parseLong(getProperty(
53 PROP_SLC_OSGIBOOT_DEFAULT_TIMEOUT, "10000"));
54 modulesUrlSeparator = getProperty(
55 PROP_SLC_OSGIBOOT_MODULES_URL_SEPARATOR, ",");
56 }
57
58 public void bootstrap() {
59 info("SLC OSGi bootstrap starting...");
60 installUrls(getBundlesUrls());
61 installUrls(getLocationsUrls());
62 installUrls(getModulesUrls());
63 startBundles();
64 info("SLC OSGi bootstrap completed");
65 }
66
67 public void installUrls(List urls) {
68 Map installedBundles = getInstalledBundles();
69 for (int i = 0; i < urls.size(); i++) {
70 String url = (String) urls.get(i);
71 try {
72 if (installedBundles.containsKey(url)) {
73 Bundle bundle = (Bundle) installedBundles.get(url);
74 // bundle.update();
75 if (debug)
76 debug("Bundle " + bundle.getSymbolicName()
77 + " already installed from " + url);
78 } else {
79 Bundle bundle = bundleContext.installBundle(url);
80 if (debug)
81 debug("Installed bundle " + bundle.getSymbolicName()
82 + " from " + url);
83 }
84 } catch (BundleException e) {
85 warn("Could not install bundle from " + url + ": "
86 + e.getMessage());
87 }
88 }
89
90 }
91
92 public void installOrUpdateUrls(Map urls) {
93 Map installedBundles = getBundles();
94
95 for (Iterator modules = urls.keySet().iterator(); modules.hasNext();) {
96 String moduleName = (String) modules.next();
97 String urlStr = (String) urls.get(moduleName);
98 if (installedBundles.containsKey(moduleName)) {
99 Bundle bundle = (Bundle) installedBundles.get(moduleName);
100 InputStream in;
101 try {
102 URL url = new URL(urlStr);
103 in = url.openStream();
104 bundle.update(in);
105 info("Updated bundle " + moduleName + " from " + urlStr);
106 } catch (Exception e) {
107 throw new RuntimeException("Cannot update " + moduleName
108 + " from " + urlStr);
109 }
110 if (in != null)
111 try {
112 in.close();
113 } catch (IOException e) {
114 e.printStackTrace();
115 }
116 } else {
117 try {
118 Bundle bundle = bundleContext.installBundle(urlStr);
119 if (debug)
120 debug("Installed bundle " + bundle.getSymbolicName()
121 + " from " + urlStr);
122 } catch (BundleException e) {
123 warn("Could not install bundle from " + urlStr + ": "
124 + e.getMessage());
125 }
126 }
127 }
128
129 }
130
131 public void startBundles() {
132 String bundlesToStart = getProperty(PROP_SLC_OSGI_START);
133 startBundles(bundlesToStart);
134 }
135
136 public void startBundles(String bundlesToStartStr) {
137 if (bundlesToStartStr == null)
138 return;
139
140 StringTokenizer st = new StringTokenizer(bundlesToStartStr, ",");
141 List bundlesToStart = new ArrayList();
142 while (st.hasMoreTokens()) {
143 String name = st.nextToken().trim();
144 bundlesToStart.add(name);
145 }
146 startBundles(bundlesToStart);
147 }
148
149 public void startBundles(List bundlesToStart) {
150 if (bundlesToStart.size() == 0)
151 return;
152
153 // used to log the bundles not found
154 List notFoundBundles = new ArrayList(bundlesToStart);
155
156 Bundle[] bundles = bundleContext.getBundles();
157 long startBegin = System.currentTimeMillis();
158 for (int i = 0; i < bundles.length; i++) {
159 Bundle bundle = bundles[i];
160 String symbolicName = bundle.getSymbolicName();
161 if (bundlesToStart.contains(symbolicName))
162 try {
163 try {
164 bundle.start();
165 } catch (Exception e) {
166 warn("Start of bundle " + symbolicName
167 + " failed because of " + e
168 + ", maybe bundle is not yet resolved,"
169 + " waiting and trying again.");
170 waitForBundleResolvedOrActive(startBegin, bundle);
171 bundle.start();
172 }
173 notFoundBundles.remove(symbolicName);
174 } catch (Exception e) {
175 warn("Bundle " + symbolicName + " cannot be started: "
176 + e.getMessage());
177 // was found even if start failed
178 notFoundBundles.remove(symbolicName);
179 }
180 }
181
182 for (int i = 0; i < notFoundBundles.size(); i++)
183 warn("Bundle " + notFoundBundles.get(i)
184 + " not started because it was not found.");
185 }
186
187 protected void waitForBundleResolvedOrActive(long startBegin, Bundle bundle)
188 throws Exception {
189 int originalState = bundle.getState();
190 if ((originalState == Bundle.RESOLVED)
191 || (originalState == Bundle.ACTIVE))
192 return;
193
194 String originalStateStr = stateAsString(originalState);
195
196 int currentState = bundle.getState();
197 while (!(currentState == Bundle.RESOLVED || currentState == Bundle.ACTIVE)) {
198 long now = System.currentTimeMillis();
199 if ((now - startBegin) > defaultTimeout)
200 throw new Exception("Bundle " + bundle.getSymbolicName()
201 + " was not RESOLVED or ACTIVE after "
202 + (now - startBegin) + "ms (originalState="
203 + originalStateStr + ", currentState="
204 + stateAsString(currentState) + ")");
205
206 try {
207 Thread.sleep(100l);
208 } catch (InterruptedException e) {
209 // silent
210 }
211 currentState = bundle.getState();
212 }
213 }
214
215 public static String stateAsString(int state) {
216 switch (state) {
217 case Bundle.UNINSTALLED:
218 return "UNINSTALLED";
219 case Bundle.INSTALLED:
220 return "INSTALLED";
221 case Bundle.RESOLVED:
222 return "RESOLVED";
223 case Bundle.STARTING:
224 return "STARTING";
225 case Bundle.ACTIVE:
226 return "ACTIVE";
227 case Bundle.STOPPING:
228 return "STOPPING";
229 default:
230 return Integer.toString(state);
231 }
232 }
233
234 /** Key is location */
235 public Map getInstalledBundles() {
236 Map installedBundles = new HashMap();
237
238 Bundle[] bundles = bundleContext.getBundles();
239 for (int i = 0; i < bundles.length; i++) {
240 installedBundles.put(bundles[i].getLocation(), bundles[i]);
241 }
242 return installedBundles;
243 }
244
245 /** Key is symbolic name */
246 public Map getBundles() {
247 Map namedBundles = new HashMap();
248 Bundle[] bundles = bundleContext.getBundles();
249 for (int i = 0; i < bundles.length; i++) {
250 namedBundles.put(bundles[i].getSymbolicName(), bundles[i]);
251 }
252 return namedBundles;
253 }
254
255 public List getLocationsUrls() {
256 String baseUrl = getProperty(PROP_SLC_OSGI_BASE_URL, DEFAULT_BASE_URL);
257 String bundleLocations = getProperty(PROP_SLC_OSGI_LOCATIONS);
258 return getLocationsUrls(baseUrl, bundleLocations);
259 }
260
261 public List getModulesUrls() {
262 List urls = new ArrayList();
263 String modulesUrlStr = getProperty(PROP_SLC_OSGI_MODULES_URL);
264 if (modulesUrlStr == null)
265 return urls;
266
267 String baseUrl = getProperty(PROP_SLC_OSGI_BASE_URL);
268
269 Map installedBundles = getBundles();
270
271 BufferedReader reader = null;
272 try {
273 URL modulesUrl = new URL(modulesUrlStr);
274 reader = new BufferedReader(new InputStreamReader(modulesUrl
275 .openStream()));
276 String line = null;
277 while ((line = reader.readLine()) != null) {
278 StringTokenizer st = new StringTokenizer(line,
279 modulesUrlSeparator);
280 String moduleName = st.nextToken();
281 String moduleVersion = st.nextToken();
282 String url = st.nextToken();
283 if (baseUrl != null)
284 url = baseUrl + url;
285
286 if (installedBundles.containsKey(moduleName)) {
287 Bundle bundle = (Bundle) installedBundles.get(moduleName);
288 String bundleVersion = bundle.getHeaders().get(
289 Constants.BUNDLE_VERSION).toString();
290 int comp = compareVersions(bundleVersion, moduleVersion);
291 if (comp > 0) {
292 warn("Installed version " + bundleVersion
293 + " of bundle " + moduleName
294 + " is newer than provided version "
295 + moduleVersion);
296 } else if (comp < 0) {
297 urls.add(url);
298 info("Updated bundle " + moduleName + " with version "
299 + moduleVersion + " (old version was "
300 + bundleVersion + ")");
301 } else {
302 // do nothing
303 }
304 } else {
305 urls.add(url);
306 }
307 }
308 } catch (Exception e1) {
309 throw new RuntimeException("Cannot read url " + modulesUrlStr, e1);
310 } finally {
311 if (reader != null)
312 try {
313 reader.close();
314 } catch (IOException e) {
315 e.printStackTrace();
316 }
317 }
318 return urls;
319 }
320
321 /**
322 * @return ==0: versions are identical, <0: tested version is newer, >0:
323 * currentVersion is newer.
324 */
325 protected int compareVersions(String currentVersion, String testedVersion) {
326 List cToks = new ArrayList();
327 StringTokenizer cSt = new StringTokenizer(currentVersion, ".");
328 while (cSt.hasMoreTokens())
329 cToks.add(cSt.nextToken());
330 List tToks = new ArrayList();
331 StringTokenizer tSt = new StringTokenizer(currentVersion, ".");
332 while (tSt.hasMoreTokens())
333 tToks.add(tSt.nextToken());
334
335 int comp = 0;
336 comp: for (int i = 0; i < cToks.size(); i++) {
337 if (tToks.size() <= i) {
338 // equals until then, tested shorter
339 comp = 1;
340 break comp;
341 }
342
343 String c = (String) cToks.get(i);
344 String t = (String) tToks.get(i);
345
346 try {
347 int cInt = Integer.parseInt(c);
348 int tInt = Integer.parseInt(t);
349 if (cInt == tInt)
350 continue comp;
351 else {
352 comp = (cInt - tInt);
353 break comp;
354 }
355 } catch (NumberFormatException e) {
356 if (c.equals(t))
357 continue comp;
358 else {
359 comp = c.compareTo(t);
360 break comp;
361 }
362 }
363 }
364
365 if (comp == 0 && tToks.size() > cToks.size()) {
366 // equals until then, current shorter
367 comp = -1;
368 }
369
370 return comp;
371 }
372
373 public List getLocationsUrls(String baseUrl, String bundleLocations) {
374 List urls = new ArrayList();
375
376 if (bundleLocations == null)
377 return urls;
378 bundleLocations = SystemPropertyUtils
379 .resolvePlaceholders(bundleLocations);
380 if (debug)
381 debug(PROP_SLC_OSGI_LOCATIONS + "=" + bundleLocations);
382
383 StringTokenizer st = new StringTokenizer(bundleLocations,
384 File.pathSeparator);
385 while (st.hasMoreTokens()) {
386 urls.add(locationToUrl(baseUrl, st.nextToken().trim()));
387 }
388 return urls;
389 }
390
391 public List getBundlesUrls() {
392 String baseUrl = getProperty(PROP_SLC_OSGI_BASE_URL, DEFAULT_BASE_URL);
393 String bundlePatterns = getProperty(PROP_SLC_OSGI_BUNDLES);
394 return getBundlesUrls(baseUrl, bundlePatterns);
395 }
396
397 public List getBundlesUrls(String baseUrl, String bundlePatterns) {
398 List urls = new ArrayList();
399
400 List bundlesSets = new ArrayList();
401 if (bundlePatterns == null)
402 return urls;
403 bundlePatterns = SystemPropertyUtils
404 .resolvePlaceholders(bundlePatterns);
405 if (debug)
406 debug(PROP_SLC_OSGI_BUNDLES + "=" + bundlePatterns
407 + " (excludeSvn=" + excludeSvn + ")");
408
409 StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
410 while (st.hasMoreTokens()) {
411 bundlesSets.add(new BundlesSet(st.nextToken()));
412 }
413
414 List included = new ArrayList();
415 PathMatcher matcher = new AntPathMatcher();
416 for (int i = 0; i < bundlesSets.size(); i++) {
417 BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
418 for (int j = 0; j < bundlesSet.getIncludes().size(); j++) {
419 String pattern = (String) bundlesSet.getIncludes().get(j);
420 match(matcher, included, bundlesSet.getDir(), null, pattern);
421 }
422 }
423
424 List excluded = new ArrayList();
425 for (int i = 0; i < bundlesSets.size(); i++) {
426 BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
427 for (int j = 0; j < bundlesSet.getExcludes().size(); j++) {
428 String pattern = (String) bundlesSet.getExcludes().get(j);
429 match(matcher, excluded, bundlesSet.getDir(), null, pattern);
430 }
431 }
432
433 for (int i = 0; i < included.size(); i++) {
434 String fullPath = (String) included.get(i);
435 if (!excluded.contains(fullPath))
436 urls.add(locationToUrl(baseUrl, fullPath));
437 }
438
439 return urls;
440 }
441
442 protected void match(PathMatcher matcher, List matched, String base,
443 String currentPath, String pattern) {
444 if (currentPath == null) {
445 // Init
446 File baseDir = new File(base.replace('/', File.separatorChar));
447 File[] files = baseDir.listFiles();
448
449 if (files == null) {
450 warn("Base dir " + baseDir + " has no children, exists="
451 + baseDir.exists() + ", isDirectory="
452 + baseDir.isDirectory());
453 return;
454 }
455
456 for (int i = 0; i < files.length; i++)
457 match(matcher, matched, base, files[i].getName(), pattern);
458 } else {
459 String fullPath = base + '/' + currentPath;
460 if (matched.contains(fullPath))
461 return;// don't try deeper if already matched
462
463 boolean ok = matcher.match(pattern, currentPath);
464 if (debug)
465 debug(currentPath + " " + (ok ? "" : " not ")
466 + " matched with " + pattern);
467 if (ok) {
468 matched.add(fullPath);
469 return;
470 } else {
471 String newFullPath = relativeToFullPath(base, currentPath);
472 File newFile = new File(newFullPath);
473 File[] files = newFile.listFiles();
474 if (files != null) {
475 for (int i = 0; i < files.length; i++) {
476 String newCurrentPath = currentPath + '/'
477 + files[i].getName();
478 if (files[i].isDirectory()) {
479 if (matcher.matchStart(pattern, newCurrentPath)) {
480 // recurse only if start matches
481 match(matcher, matched, base, newCurrentPath,
482 pattern);
483 } else {
484 if (debug)
485 debug(newCurrentPath
486 + " does not start match with "
487 + pattern);
488
489 }
490 } else {
491 boolean nonDirectoryOk = matcher.match(pattern,
492 newCurrentPath);
493 if (debug)
494 debug(currentPath + " " + (ok ? "" : " not ")
495 + " matched with " + pattern);
496 if (nonDirectoryOk)
497 matched.add(relativeToFullPath(base,
498 newCurrentPath));
499 }
500 }
501 }
502 }
503 }
504 }
505
506 protected String locationToUrl(String baseUrl, String location) {
507 int extInd = location.lastIndexOf('.');
508 String ext = null;
509 if (extInd > 0)
510 ext = location.substring(extInd);
511
512 if (baseUrl.startsWith("reference:") && ".jar".equals(ext))
513 return "file:" + location;
514 else
515 return baseUrl + location;
516 }
517
518 /** Transforms a relative path in a full system path. */
519 protected String relativeToFullPath(String basePath, String relativePath) {
520 return (basePath + '/' + relativePath).replace('/', File.separatorChar);
521 }
522
523 protected static void info(Object obj) {
524 System.out.println("#OSGiBOOT# " + obj);
525 }
526
527 protected void debug(Object obj) {
528 if (debug)
529 System.out.println("#OSGiBOOT DEBUG# " + obj);
530 }
531
532 protected void warn(Object obj) {
533 System.out.println("# WARN " + obj);
534 // Because of a weird bug under Windows when starting it in a forked VM
535 // if (System.getProperty("os.name").contains("Windows"))
536 // System.out.println("# WARN " + obj);
537 // else
538 // System.err.println("# WARN " + obj);
539 }
540
541 protected String getProperty(String name, String defaultValue) {
542 final String value;
543 if (defaultValue != null)
544 value = System.getProperty(name, defaultValue);
545 else
546 value = System.getProperty(name);
547
548 if (value == null || value.equals(""))
549 return null;
550 else
551 return value;
552 }
553
554 protected String getProperty(String name) {
555 return getProperty(name, null);
556 }
557
558 public boolean getDebug() {
559 return debug;
560 }
561
562 public void setDebug(boolean debug) {
563 this.debug = debug;
564 }
565
566 public BundleContext getBundleContext() {
567 return bundleContext;
568 }
569
570 /** Whether to exclude Subversion directories (true by default) */
571 public boolean isExcludeSvn() {
572 return excludeSvn;
573 }
574
575 public void setExcludeSvn(boolean excludeSvn) {
576 this.excludeSvn = excludeSvn;
577 }
578
579 protected class BundlesSet {
580 private String baseUrl = "reference:file";// not used yet
581 private final String dir;
582 private List includes = new ArrayList();
583 private List excludes = new ArrayList();
584
585 public BundlesSet(String def) {
586 StringTokenizer st = new StringTokenizer(def, ";");
587
588 if (!st.hasMoreTokens())
589 throw new RuntimeException("Base dir not defined.");
590 try {
591 String dirPath = st.nextToken();
592
593 if (dirPath.startsWith("file:"))
594 dirPath = dirPath.substring("file:".length());
595
596 dir = new File(dirPath.replace('/', File.separatorChar))
597 .getCanonicalPath();
598 if (debug)
599 debug("Base dir: " + dir);
600 } catch (IOException e) {
601 throw new RuntimeException("Cannot convert to absolute path", e);
602 }
603
604 while (st.hasMoreTokens()) {
605 String tk = st.nextToken();
606 StringTokenizer stEq = new StringTokenizer(tk, "=");
607 String type = stEq.nextToken();
608 String pattern = stEq.nextToken();
609 if ("in".equals(type) || "include".equals(type)) {
610 includes.add(pattern);
611 } else if ("ex".equals(type) || "exclude".equals(type)) {
612 excludes.add(pattern);
613 } else if ("baseUrl".equals(type)) {
614 baseUrl = pattern;
615 } else {
616 System.err.println("Unkown bundles pattern type " + type);
617 }
618 }
619
620 if (excludeSvn && !excludes.contains(EXCLUDES_SVN_PATTERN)) {
621 excludes.add(EXCLUDES_SVN_PATTERN);
622 }
623 }
624
625 public String getDir() {
626 return dir;
627 }
628
629 public List getIncludes() {
630 return includes;
631 }
632
633 public List getExcludes() {
634 return excludes;
635 }
636
637 public String getBaseUrl() {
638 return baseUrl;
639 }
640
641 }
642
643 public void setDefaultTimeout(long defaultTimeout) {
644 this.defaultTimeout = defaultTimeout;
645 }
646
647 public void setModulesUrlSeparator(String modulesUrlSeparator) {
648 this.modulesUrlSeparator = modulesUrlSeparator;
649 }
650
651 }