]> 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 List notStartedBundles = new ArrayList(bundlesToStart);
154 Bundle[] bundles = bundleContext.getBundles();
155 for (int i = 0; i < bundles.length; i++) {
156 Bundle bundle = bundles[i];
157 String symbolicName = bundle.getSymbolicName();
158 if (bundlesToStart.contains(symbolicName))
159 try {
160 try {
161 bundle.start();
162 } catch (Exception e) {
163 // start failed, maybe bundle is not yet resolved
164 waitForBundleResolvedOrActive(bundle);
165 bundle.start();
166 }
167
168 notStartedBundles.remove(symbolicName);
169 } catch (Exception e) {
170 warn("Bundle " + symbolicName + " cannot be started: "
171 + e.getMessage());
172 }
173 }
174
175 for (int i = 0; i < notStartedBundles.size(); i++)
176 warn("Bundle " + notStartedBundles.get(i)
177 + " not started because it was not found.");
178 }
179
180 protected void waitForBundleResolvedOrActive(Bundle bundle)
181 throws Exception {
182 int originalState = bundle.getState();
183 if ((originalState == Bundle.RESOLVED)
184 || (originalState == Bundle.ACTIVE))
185 return;
186
187 String originalStateStr = stateAsString(originalState);
188
189 long begin = System.currentTimeMillis();
190 while ((bundle.getState() != Bundle.RESOLVED)
191 || (bundle.getState() != Bundle.ACTIVE)) {
192 long now = System.currentTimeMillis();
193 if ((now - begin) > defaultTimeout)
194 throw new Exception("Bundle " + bundle.getSymbolicName()
195 + " was not RESOLVED or ACTIVE after " + (now - begin)
196 + "ms (originalState=" + originalStateStr
197 + ", currentState=" + stateAsString(bundle.getState())
198 + ")");
199
200 try {
201 Thread.sleep(100l);
202 } catch (InterruptedException e) {
203 // silent
204 }
205 }
206 }
207
208 public static String stateAsString(int state) {
209 switch (state) {
210 case Bundle.UNINSTALLED:
211 return "UNINSTALLED";
212 case Bundle.INSTALLED:
213 return "INSTALLED";
214 case Bundle.RESOLVED:
215 return "RESOLVED";
216 case Bundle.STARTING:
217 return "STARTING";
218 case Bundle.ACTIVE:
219 return "ACTIVE";
220 case Bundle.STOPPING:
221 return "STOPPING";
222 default:
223 return Integer.toString(state);
224 }
225 }
226
227 /** Key is location */
228 public Map getInstalledBundles() {
229 Map installedBundles = new HashMap();
230
231 Bundle[] bundles = bundleContext.getBundles();
232 for (int i = 0; i < bundles.length; i++) {
233 installedBundles.put(bundles[i].getLocation(), bundles[i]);
234 }
235 return installedBundles;
236 }
237
238 /** Key is symbolic name */
239 public Map getBundles() {
240 Map namedBundles = new HashMap();
241 Bundle[] bundles = bundleContext.getBundles();
242 for (int i = 0; i < bundles.length; i++) {
243 namedBundles.put(bundles[i].getSymbolicName(), bundles[i]);
244 }
245 return namedBundles;
246 }
247
248 public List getLocationsUrls() {
249 String baseUrl = getProperty(PROP_SLC_OSGI_BASE_URL, DEFAULT_BASE_URL);
250 String bundleLocations = getProperty(PROP_SLC_OSGI_LOCATIONS);
251 return getLocationsUrls(baseUrl, bundleLocations);
252 }
253
254 public List getModulesUrls() {
255 List urls = new ArrayList();
256 String modulesUrlStr = getProperty(PROP_SLC_OSGI_MODULES_URL);
257 if (modulesUrlStr == null)
258 return urls;
259
260 String baseUrl = getProperty(PROP_SLC_OSGI_BASE_URL);
261
262 Map installedBundles = getBundles();
263
264 BufferedReader reader = null;
265 try {
266 URL modulesUrl = new URL(modulesUrlStr);
267 reader = new BufferedReader(new InputStreamReader(modulesUrl
268 .openStream()));
269 String line = null;
270 while ((line = reader.readLine()) != null) {
271 StringTokenizer st = new StringTokenizer(line,
272 modulesUrlSeparator);
273 String moduleName = st.nextToken();
274 String moduleVersion = st.nextToken();
275 String url = st.nextToken();
276 if (baseUrl != null)
277 url = baseUrl + url;
278
279 if (installedBundles.containsKey(moduleName)) {
280 Bundle bundle = (Bundle) installedBundles.get(moduleName);
281 String bundleVersion = bundle.getHeaders().get(
282 Constants.BUNDLE_VERSION).toString();
283 int comp = compareVersions(bundleVersion, moduleVersion);
284 if (comp > 0) {
285 warn("Installed version " + bundleVersion
286 + " of bundle " + moduleName
287 + " is newer than provided version "
288 + moduleVersion);
289 } else if (comp < 0) {
290 urls.add(url);
291 info("Updated bundle " + moduleName + " with version "
292 + moduleVersion + " (old version was "
293 + bundleVersion + ")");
294 } else {
295 // do nothing
296 }
297 } else {
298 urls.add(url);
299 }
300 }
301 } catch (Exception e1) {
302 throw new RuntimeException("Cannot read url " + modulesUrlStr, e1);
303 } finally {
304 if (reader != null)
305 try {
306 reader.close();
307 } catch (IOException e) {
308 e.printStackTrace();
309 }
310 }
311 return urls;
312 }
313
314 /**
315 * @return ==0: versions are identical, <0: tested version is newer, >0:
316 * currentVersion is newer.
317 */
318 protected int compareVersions(String currentVersion, String testedVersion) {
319 List cToks = new ArrayList();
320 StringTokenizer cSt = new StringTokenizer(currentVersion, ".");
321 while (cSt.hasMoreTokens())
322 cToks.add(cSt.nextToken());
323 List tToks = new ArrayList();
324 StringTokenizer tSt = new StringTokenizer(currentVersion, ".");
325 while (tSt.hasMoreTokens())
326 tToks.add(tSt.nextToken());
327
328 int comp = 0;
329 comp: for (int i = 0; i < cToks.size(); i++) {
330 if (tToks.size() <= i) {
331 // equals until then, tested shorter
332 comp = 1;
333 break comp;
334 }
335
336 String c = (String) cToks.get(i);
337 String t = (String) tToks.get(i);
338
339 try {
340 int cInt = Integer.parseInt(c);
341 int tInt = Integer.parseInt(t);
342 if (cInt == tInt)
343 continue comp;
344 else {
345 comp = (cInt - tInt);
346 break comp;
347 }
348 } catch (NumberFormatException e) {
349 if (c.equals(t))
350 continue comp;
351 else {
352 comp = c.compareTo(t);
353 break comp;
354 }
355 }
356 }
357
358 if (comp == 0 && tToks.size() > cToks.size()) {
359 // equals until then, current shorter
360 comp = -1;
361 }
362
363 return comp;
364 }
365
366 public List getLocationsUrls(String baseUrl, String bundleLocations) {
367 List urls = new ArrayList();
368
369 if (bundleLocations == null)
370 return urls;
371 bundleLocations = SystemPropertyUtils
372 .resolvePlaceholders(bundleLocations);
373 if (debug)
374 debug(PROP_SLC_OSGI_LOCATIONS + "=" + bundleLocations);
375
376 StringTokenizer st = new StringTokenizer(bundleLocations,
377 File.pathSeparator);
378 while (st.hasMoreTokens()) {
379 urls.add(locationToUrl(baseUrl, st.nextToken().trim()));
380 }
381 return urls;
382 }
383
384 public List getBundlesUrls() {
385 String baseUrl = getProperty(PROP_SLC_OSGI_BASE_URL, DEFAULT_BASE_URL);
386 String bundlePatterns = getProperty(PROP_SLC_OSGI_BUNDLES);
387 return getBundlesUrls(baseUrl, bundlePatterns);
388 }
389
390 public List getBundlesUrls(String baseUrl, String bundlePatterns) {
391 List urls = new ArrayList();
392
393 List bundlesSets = new ArrayList();
394 if (bundlePatterns == null)
395 return urls;
396 bundlePatterns = SystemPropertyUtils
397 .resolvePlaceholders(bundlePatterns);
398 if (debug)
399 debug(PROP_SLC_OSGI_BUNDLES + "=" + bundlePatterns
400 + " (excludeSvn=" + excludeSvn + ")");
401
402 StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
403 while (st.hasMoreTokens()) {
404 bundlesSets.add(new BundlesSet(st.nextToken()));
405 }
406
407 List included = new ArrayList();
408 PathMatcher matcher = new AntPathMatcher();
409 for (int i = 0; i < bundlesSets.size(); i++) {
410 BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
411 for (int j = 0; j < bundlesSet.getIncludes().size(); j++) {
412 String pattern = (String) bundlesSet.getIncludes().get(j);
413 match(matcher, included, bundlesSet.getDir(), null, pattern);
414 }
415 }
416
417 List excluded = new ArrayList();
418 for (int i = 0; i < bundlesSets.size(); i++) {
419 BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
420 for (int j = 0; j < bundlesSet.getExcludes().size(); j++) {
421 String pattern = (String) bundlesSet.getExcludes().get(j);
422 match(matcher, excluded, bundlesSet.getDir(), null, pattern);
423 }
424 }
425
426 for (int i = 0; i < included.size(); i++) {
427 String fullPath = (String) included.get(i);
428 if (!excluded.contains(fullPath))
429 urls.add(locationToUrl(baseUrl, fullPath));
430 }
431
432 return urls;
433 }
434
435 protected void match(PathMatcher matcher, List matched, String base,
436 String currentPath, String pattern) {
437 if (currentPath == null) {
438 // Init
439 File baseDir = new File(base.replace('/', File.separatorChar));
440 File[] files = baseDir.listFiles();
441
442 if (files == null) {
443 warn("Base dir " + baseDir + " has no children, exists="
444 + baseDir.exists() + ", isDirectory="
445 + baseDir.isDirectory());
446 return;
447 }
448
449 for (int i = 0; i < files.length; i++)
450 match(matcher, matched, base, files[i].getName(), pattern);
451 } else {
452 String fullPath = base + '/' + currentPath;
453 if (matched.contains(fullPath))
454 return;// don't try deeper if already matched
455
456 boolean ok = matcher.match(pattern, currentPath);
457 if (debug)
458 debug(currentPath + " " + (ok ? "" : " not ")
459 + " matched with " + pattern);
460 if (ok) {
461 matched.add(fullPath);
462 return;
463 } else {
464 String newFullPath = relativeToFullPath(base, currentPath);
465 File newFile = new File(newFullPath);
466 File[] files = newFile.listFiles();
467 if (files != null) {
468 for (int i = 0; i < files.length; i++) {
469 String newCurrentPath = currentPath + '/'
470 + files[i].getName();
471 if (files[i].isDirectory()) {
472 if (matcher.matchStart(pattern, newCurrentPath)) {
473 // recurse only if start matches
474 match(matcher, matched, base, newCurrentPath,
475 pattern);
476 } else {
477 if (debug)
478 debug(newCurrentPath
479 + " does not start match with "
480 + pattern);
481
482 }
483 } else {
484 boolean nonDirectoryOk = matcher.match(pattern,
485 newCurrentPath);
486 if (debug)
487 debug(currentPath + " " + (ok ? "" : " not ")
488 + " matched with " + pattern);
489 if (nonDirectoryOk)
490 matched.add(relativeToFullPath(base,
491 newCurrentPath));
492 }
493 }
494 }
495 }
496 }
497 }
498
499 protected String locationToUrl(String baseUrl, String location) {
500 int extInd = location.lastIndexOf('.');
501 String ext = null;
502 if (extInd > 0)
503 ext = location.substring(extInd);
504
505 if (baseUrl.startsWith("reference:") && ".jar".equals(ext))
506 return "file:" + location;
507 else
508 return baseUrl + location;
509 }
510
511 /** Transforms a relative path in a full system path. */
512 protected String relativeToFullPath(String basePath, String relativePath) {
513 return (basePath + '/' + relativePath).replace('/', File.separatorChar);
514 }
515
516 protected static void info(Object obj) {
517 System.out.println("#OSGiBOOT# " + obj);
518 }
519
520 protected void debug(Object obj) {
521 if (debug)
522 System.out.println("#OSGiBOOT DEBUG# " + obj);
523 }
524
525 protected void warn(Object obj) {
526 System.out.println("# WARN " + obj);
527 // Because of a weird bug under Windows when starting it in a forked VM
528 // if (System.getProperty("os.name").contains("Windows"))
529 // System.out.println("# WARN " + obj);
530 // else
531 // System.err.println("# WARN " + obj);
532 }
533
534 protected String getProperty(String name, String defaultValue) {
535 final String value;
536 if (defaultValue != null)
537 value = System.getProperty(name, defaultValue);
538 else
539 value = System.getProperty(name);
540
541 if (value == null || value.equals(""))
542 return null;
543 else
544 return value;
545 }
546
547 protected String getProperty(String name) {
548 return getProperty(name, null);
549 }
550
551 public boolean getDebug() {
552 return debug;
553 }
554
555 public void setDebug(boolean debug) {
556 this.debug = debug;
557 }
558
559 public BundleContext getBundleContext() {
560 return bundleContext;
561 }
562
563 /** Whether to exclude Subversion directories (true by default) */
564 public boolean isExcludeSvn() {
565 return excludeSvn;
566 }
567
568 public void setExcludeSvn(boolean excludeSvn) {
569 this.excludeSvn = excludeSvn;
570 }
571
572 protected class BundlesSet {
573 private String baseUrl = "reference:file";// not used yet
574 private final String dir;
575 private List includes = new ArrayList();
576 private List excludes = new ArrayList();
577
578 public BundlesSet(String def) {
579 StringTokenizer st = new StringTokenizer(def, ";");
580
581 if (!st.hasMoreTokens())
582 throw new RuntimeException("Base dir not defined.");
583 try {
584 String dirPath = st.nextToken();
585
586 if (dirPath.startsWith("file:"))
587 dirPath = dirPath.substring("file:".length());
588
589 dir = new File(dirPath.replace('/', File.separatorChar))
590 .getCanonicalPath();
591 if (debug)
592 debug("Base dir: " + dir);
593 } catch (IOException e) {
594 throw new RuntimeException("Cannot convert to absolute path", e);
595 }
596
597 while (st.hasMoreTokens()) {
598 String tk = st.nextToken();
599 StringTokenizer stEq = new StringTokenizer(tk, "=");
600 String type = stEq.nextToken();
601 String pattern = stEq.nextToken();
602 if ("in".equals(type) || "include".equals(type)) {
603 includes.add(pattern);
604 } else if ("ex".equals(type) || "exclude".equals(type)) {
605 excludes.add(pattern);
606 } else if ("baseUrl".equals(type)) {
607 baseUrl = pattern;
608 } else {
609 System.err.println("Unkown bundles pattern type " + type);
610 }
611 }
612
613 if (excludeSvn && !excludes.contains(EXCLUDES_SVN_PATTERN)) {
614 excludes.add(EXCLUDES_SVN_PATTERN);
615 }
616 }
617
618 public String getDir() {
619 return dir;
620 }
621
622 public List getIncludes() {
623 return includes;
624 }
625
626 public List getExcludes() {
627 return excludes;
628 }
629
630 public String getBaseUrl() {
631 return baseUrl;
632 }
633
634 }
635
636 public void setDefaultTimeout(long defaultTimeout) {
637 this.defaultTimeout = defaultTimeout;
638 }
639
640 public void setModulesUrlSeparator(String modulesUrlSeparator) {
641 this.modulesUrlSeparator = modulesUrlSeparator;
642 }
643
644 }