]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java
Simplify multi-runtime
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / osgi / OsgiBoot.java
1 package org.argeo.init.osgi;
2
3 import static java.lang.System.Logger.Level.DEBUG;
4 import static java.lang.System.Logger.Level.ERROR;
5 import static java.lang.System.Logger.Level.TRACE;
6 import static java.lang.System.Logger.Level.WARNING;
7
8 import java.io.File;
9 import java.lang.System.Logger;
10 import java.lang.System.Logger.Level;
11 import java.nio.file.FileSystems;
12 import java.nio.file.Files;
13 import java.nio.file.Path;
14 import java.nio.file.PathMatcher;
15 import java.nio.file.Paths;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Optional;
23 import java.util.ServiceLoader;
24 import java.util.Set;
25 import java.util.SortedMap;
26 import java.util.StringTokenizer;
27 import java.util.TreeMap;
28
29 import org.argeo.api.a2.A2Source;
30 import org.argeo.api.a2.ProvisioningManager;
31 import org.argeo.api.init.InitConstants;
32 import org.osgi.framework.Bundle;
33 import org.osgi.framework.BundleContext;
34 import org.osgi.framework.BundleException;
35 import org.osgi.framework.FrameworkEvent;
36 import org.osgi.framework.Version;
37 import org.osgi.framework.launch.Framework;
38 import org.osgi.framework.launch.FrameworkFactory;
39 import org.osgi.framework.startlevel.BundleStartLevel;
40 import org.osgi.framework.startlevel.FrameworkStartLevel;
41 import org.osgi.framework.wiring.FrameworkWiring;
42
43 /**
44 * Basic provisioning of an OSGi runtime via file path patterns and system
45 * properties. The approach is to generate list of URLs based on various
46 * methods, configured via properties.
47 */
48 public class OsgiBoot {
49 private final static Logger logger = System.getLogger(OsgiBoot.class.getName());
50
51 @Deprecated
52 final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
53 final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
54 final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
55 final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
56
57 // booleans
58 @Deprecated
59 final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
60
61 final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
62 final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
63 final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
64
65 @Deprecated
66 public final static String DEFAULT_BASE_URL = "reference:file:";
67 final static String DEFAULT_MAX_START_LEVEL = "32";
68
69 private final BundleContext bundleContext;
70 private final String localCache;
71 private final ProvisioningManager provisioningManager;
72
73 /*
74 * INITIALISATION
75 */
76 /** Constructor */
77 public OsgiBoot(BundleContext bundleContext) {
78 this.bundleContext = bundleContext;
79 Path homePath = Paths.get(System.getProperty("user.home")).toAbsolutePath();
80 String homeUri = homePath.toUri().toString();
81 localCache = getProperty(PROP_ARGEO_OSGI_LOCAL_CACHE, homeUri + ".m2/repository/");
82
83 provisioningManager = new ProvisioningManager(bundleContext);
84 String sources = getProperty(InitConstants.PROP_ARGEO_OSGI_SOURCES);
85 if (sources == null) {
86 provisioningManager.registerDefaultSource();
87 } else {
88 // OsgiBootUtils.debug("Found sources " + sources);
89 for (String source : sources.split(",")) {
90 int qmIndex = source.lastIndexOf('?');
91 String queryPart = "";
92 if (qmIndex >= 0) {
93 queryPart = source.substring(qmIndex);
94 source = source.substring(0, qmIndex);
95 }
96 // TODO centralise in A" package?
97 if (source.trim().equals(A2Source.DEFAULT_A2_URI)) {
98 if (Files.exists(homePath))
99 provisioningManager.registerSource(
100 A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2" + queryPart);
101 provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2" + queryPart);
102 // provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/lib/a2" + queryPart);
103 provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2" + queryPart);
104 // provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/lib/a2" + queryPart);
105 } else if (source.trim().equals(A2Source.DEFAULT_A2_REFERENCE_URI)) {
106 if (Files.exists(homePath))
107 provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + "://" + homePath.toString()
108 + "/.local/share/a2" + queryPart);
109 provisioningManager
110 .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/share/a2" + queryPart);
111 // provisioningManager
112 // .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/lib/a2" + queryPart);
113 provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/share/a2" + queryPart);
114 // provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/lib/a2" + queryPart);
115 } else {
116 provisioningManager.registerSource(source + queryPart);
117 }
118 }
119 }
120 }
121
122 ProvisioningManager getProvisioningManager() {
123 return provisioningManager;
124 }
125
126 /*
127 * HIGH-LEVEL METHODS
128 */
129 /**
130 * Bootstraps the OSGi runtime using these properties, which MUST be consistent
131 * with {@link BundleContext#getProperty(String)}. If these properties are
132 * <code>null</code>, system properties are used instead.
133 */
134 public void bootstrap() {
135 try {
136 long begin = System.currentTimeMillis();
137 // Install bundles
138 install();
139 // Make sure fragments are properly considered by refreshing
140 refresh();
141 // Start bundles
142 startBundles();
143 long duration = System.currentTimeMillis() - begin;
144 logger.log(DEBUG, () -> "OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
145 + duration + "ms), " + bundleContext.getBundles().length + " bundles");
146 } catch (RuntimeException e) {
147 logger.log(ERROR, "OSGi bootstrap FAILED", e);
148 throw e;
149 }
150
151 // diagnostics
152 if (logger.isLoggable(TRACE)) {
153 OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext);
154 diagnostics.checkUnresolved();
155 Map<String, Set<String>> duplicatePackages = diagnostics.findPackagesExportedTwice();
156 if (duplicatePackages.size() > 0) {
157 logger.log(TRACE, "Packages exported twice:");
158 Iterator<String> it = duplicatePackages.keySet().iterator();
159 while (it.hasNext()) {
160 String pkgName = it.next();
161 logger.log(TRACE, pkgName);
162 Set<String> bdles = duplicatePackages.get(pkgName);
163 Iterator<String> bdlesIt = bdles.iterator();
164 while (bdlesIt.hasNext())
165 logger.log(TRACE, " " + bdlesIt.next());
166 }
167 }
168 }
169 System.out.println();
170 }
171
172 public void install() {
173 String osgiInstancePath = getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA);
174 String osgiConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
175 String osgiSharedConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
176 logger.log(DEBUG, () -> "OSGi bootstrap starting" //
177 + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
178 + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
179 + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
180 );
181
182 // legacy install bundles
183 installUrls(getBundlesUrls());
184 installUrls(getDistributionUrls());
185
186 // A2 install bundles
187 provisioningManager.install(null);
188
189 }
190
191 public void update() {
192 provisioningManager.update();
193 }
194
195 /*
196 * INSTALLATION
197 */
198 /** Install a single url. Convenience method. */
199 public Bundle installUrl(String url) {
200 List<String> urls = new ArrayList<String>();
201 urls.add(url);
202 installUrls(urls);
203 return (Bundle) getBundlesByLocation().get(url);
204 }
205
206 /** Install the bundles at this URL list. */
207 public void installUrls(List<String> urls) {
208 Map<String, Bundle> installedBundles = getBundlesByLocation();
209 for (int i = 0; i < urls.size(); i++) {
210 String url = (String) urls.get(i);
211 installUrl(url, installedBundles);
212 }
213 // refreshFramework();
214 }
215
216 /** Actually install the provided URL */
217 protected void installUrl(String url, Map<String, Bundle> installedBundles) {
218 try {
219 if (installedBundles.containsKey(url)) {
220 Bundle bundle = (Bundle) installedBundles.get(url);
221 logger.log(TRACE, () -> "Bundle " + bundle.getSymbolicName() + " already installed from " + url);
222 } else if (url.contains("/" + InitConstants.SYMBOLIC_NAME_EQUINOX + "/")
223 || url.contains("/" + InitConstants.SYMBOLIC_NAME_INIT + "/")) {
224 if (logger.isLoggable(TRACE))
225 logger.log(WARNING, "Skip " + url);
226 return;
227 } else {
228 Bundle bundle = bundleContext.installBundle(url);
229 if (url.startsWith("http"))
230 logger.log(DEBUG,
231 () -> "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
232 else
233 logger.log(TRACE,
234 () -> "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
235 assert bundle.getSymbolicName() != null;
236 // uninstall previous versions
237 bundles: for (Bundle b : bundleContext.getBundles()) {
238 if (b.getSymbolicName() == null)
239 continue bundles;
240 if (bundle.getSymbolicName().equals(b.getSymbolicName())) {
241 Version bundleV = bundle.getVersion();
242 Version bV = b.getVersion();
243 if (bV == null)
244 continue bundles;
245 if (bundleV.getMajor() == bV.getMajor() && bundleV.getMinor() == bV.getMinor()) {
246 if (bundleV.getMicro() > bV.getMicro()) {
247 // uninstall older bundles
248 b.uninstall();
249 logger.log(TRACE, () -> "Uninstalled " + b);
250 } else if (bundleV.getMicro() < bV.getMicro()) {
251 // uninstall just installed bundle if newer
252 bundle.uninstall();
253 logger.log(TRACE, () -> "Uninstalled " + bundle);
254 break bundles;
255 } else {
256 // uninstall any other with same major/minor
257 if (!bundleV.getQualifier().equals(bV.getQualifier())) {
258 b.uninstall();
259 logger.log(TRACE, () -> "Uninstalled " + b);
260 }
261 }
262 }
263 }
264 }
265 }
266 } catch (BundleException e) {
267 final String ALREADY_INSTALLED = "is already installed";
268 String message = e.getMessage();
269 if ((message.contains("Bundle \"" + InitConstants.SYMBOLIC_NAME_INIT + "\"")
270 || message.contains("Bundle \"" + InitConstants.SYMBOLIC_NAME_EQUINOX + "\""))
271 && message.contains(ALREADY_INSTALLED)) {
272 // silent, in order to avoid warnings: we know that both
273 // have already been installed...
274 } else {
275 if (message.contains(ALREADY_INSTALLED)) {
276 if (logger.isLoggable(TRACE))
277 logger.log(WARNING, "Duplicate install from " + url + ": " + message);
278 } else
279 logger.log(WARNING, "Could not install bundle from " + url + ": " + message);
280 }
281 if (logger.isLoggable(TRACE) && !message.contains(ALREADY_INSTALLED))
282 e.printStackTrace();
283 }
284 }
285
286 /*
287 * START
288 */
289
290 /**
291 * Start bundles based on these properties.
292 *
293 * @see OsgiBoot#doStartBundles(Map)
294 */
295 public void startBundles() {
296 Map<String, String> map = new TreeMap<>();
297 // first use properties
298 // if (properties != null) {
299 // for (String key : properties.keySet()) {
300 // String property = key;
301 // if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
302 // map.put(property, properties.get(property));
303 // }
304 // }
305 // }
306 // then try all start level until a maximum
307 int maxStartLevel = Integer
308 .parseInt(getProperty(InitConstants.PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL));
309 for (int i = 1; i <= maxStartLevel; i++) {
310 String key = InitConstants.PROP_ARGEO_OSGI_START + "." + i;
311 String value = getProperty(key);
312 if (value != null)
313 map.put(key, value);
314
315 }
316 // finally, override with system properties
317 // for (Object key : System.getProperties().keySet()) {
318 // if (key.toString().startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
319 // map.put(key.toString(), System.getProperty(key.toString()));
320 // }
321 // }
322 // start
323 doStartBundles(map);
324 }
325
326 // void startBundles(Properties properties) {
327 // Map<String, String> map = new TreeMap<>();
328 // // first use properties
329 // if (properties != null) {
330 // for (Object key : properties.keySet()) {
331 // String property = key.toString();
332 // if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
333 // map.put(property, properties.get(property).toString());
334 // }
335 // }
336 // }
337 // startBundles(map);
338 // }
339
340 /**
341 * Start bundle based on keys starting with
342 * {@link InitConstants#PROP_ARGEO_OSGI_START}.
343 */
344 protected void doStartBundles(Map<String, String> properties) {
345 FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
346
347 // default and active start levels from System properties
348 int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
349 int defaultStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
350 int activeStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "6"));
351 if (logger.isLoggable(TRACE)) {
352 logger.log(TRACE,
353 "OSGi default start level: "
354 + getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "<not set>") + ", using "
355 + defaultStartLevel);
356 logger.log(TRACE, "OSGi active start level: " + getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "<not set>")
357 + ", using " + activeStartLevel);
358 logger.log(TRACE, "Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: "
359 + initialStartLevel + ")");
360 }
361
362 SortedMap<Integer, List<String>> startLevels = new TreeMap<Integer, List<String>>();
363 computeStartLevels(startLevels, properties, defaultStartLevel);
364 // inverts the map for the time being, TODO optimise
365 Map<String, Integer> bundleStartLevels = new HashMap<>();
366 for (Integer level : startLevels.keySet()) {
367 for (String bsn : startLevels.get(level))
368 bundleStartLevels.put(bsn, level);
369 }
370
371 // keep only bundles with the highest version
372 Map<String, Bundle> startableBundles = new HashMap<>();
373 bundles: for (Bundle bundle : bundleContext.getBundles()) {
374 if (bundle.getVersion() == null)
375 continue bundles;
376 String bsn = bundle.getSymbolicName();
377 if (!startableBundles.containsKey(bsn)) {
378 startableBundles.put(bsn, bundle);
379 } else {
380 if (bundle.getVersion().compareTo(startableBundles.get(bsn).getVersion()) > 0) {
381 startableBundles.put(bsn, bundle);
382 }
383 }
384 }
385
386 for (Bundle bundle : startableBundles.values()) {
387 String bsn = bundle.getSymbolicName();
388 if (bundleStartLevels.containsKey(bsn)) {
389 BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class);
390 Integer level = bundleStartLevels.get(bsn);
391 if (bundleStartLevel.getStartLevel() != level || !bundleStartLevel.isPersistentlyStarted()) {
392 bundleStartLevel.setStartLevel(level);
393 try {
394 bundle.start();
395 } catch (BundleException e) {
396 logger.log(ERROR, "Cannot mark " + bsn + " as started", e);
397 }
398 logger.log(TRACE, () -> bsn + " v" + bundle.getVersion() + " starts at level " + level);
399 }
400 }
401 }
402
403 logger.log(TRACE, () -> "About to set framework start level to " + activeStartLevel + " ...");
404
405 frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> {
406 if (event.getType() == FrameworkEvent.ERROR) {
407 logger.log(ERROR, "Start sequence failed", event.getThrowable());
408 } else {
409 logger.log(TRACE, () -> "Framework started at level " + frameworkStartLevel.getStartLevel());
410 }
411 });
412
413 // // Start the framework level after level
414 // int currentStartLevel = frameworkStartLevel.getStartLevel();
415 // stages: for (int stage = currentStartLevel + 1; stage <= activeStartLevel; stage++) {
416 // if (OsgiBootUtils.isDebug())
417 // OsgiBootUtils.debug("Starting stage " + stage + "...");
418 // final int nextStage = stage;
419 // final CompletableFuture<FrameworkEvent> stageCompleted = new CompletableFuture<>();
420 // frameworkStartLevel.setStartLevel(nextStage, (FrameworkEvent event) -> {
421 // stageCompleted.complete(event);
422 // });
423 // FrameworkEvent event;
424 // try {
425 // event = stageCompleted.get();
426 // } catch (InterruptedException | ExecutionException e) {
427 // throw new IllegalStateException("Cannot continue start", e);
428 // }
429 // if (event.getThrowable() != null) {
430 // OsgiBootUtils.error("Stage " + nextStage + " failed, aborting start.", event.getThrowable());
431 // break stages;
432 // }
433 // }
434 }
435
436 private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Map<String, String> properties,
437 Integer defaultStartLevel) {
438
439 // default (and previously, only behaviour)
440 appendToStartLevels(startLevels, defaultStartLevel,
441 properties.getOrDefault(InitConstants.PROP_ARGEO_OSGI_START, ""));
442
443 // list argeo.osgi.start.* system properties
444 Iterator<String> keys = properties.keySet().iterator();
445 final String prefix = InitConstants.PROP_ARGEO_OSGI_START + ".";
446 while (keys.hasNext()) {
447 String key = keys.next();
448 if (key.startsWith(prefix)) {
449 Integer startLevel;
450 String suffix = key.substring(prefix.length());
451 String[] tokens = suffix.split("\\.");
452 if (tokens.length > 0 && !tokens[0].trim().equals(""))
453 try {
454 // first token is start level
455 startLevel = Integer.parseInt(tokens[0]);
456 } catch (NumberFormatException e) {
457 startLevel = defaultStartLevel;
458 }
459 else
460 startLevel = defaultStartLevel;
461
462 // append bundle names
463 String bundleNames = properties.get(key);
464 appendToStartLevels(startLevels, startLevel, bundleNames);
465 }
466 }
467 }
468
469 /** Append a comma-separated list of bundles to the start levels. */
470 private static void appendToStartLevels(SortedMap<Integer, List<String>> startLevels, Integer startLevel,
471 String str) {
472 if (str == null || str.trim().equals(""))
473 return;
474
475 if (!startLevels.containsKey(startLevel))
476 startLevels.put(startLevel, new ArrayList<String>());
477 String[] bundleNames = str.split(",");
478 for (int i = 0; i < bundleNames.length; i++) {
479 if (bundleNames[i] != null && !bundleNames[i].trim().equals(""))
480 (startLevels.get(startLevel)).add(bundleNames[i]);
481 }
482 }
483
484 /*
485 * BUNDLE PATTERNS INSTALLATION
486 */
487 /**
488 * Computes a list of URLs based on Ant-like include/exclude patterns defined by
489 * ${argeo.osgi.bundles} with the following format:<br>
490 * <code>/base/directory;in=*.jar;in=**;ex=org.eclipse.osgi_*;jar</code><br>
491 * WARNING: <code>/base/directory;in=*.jar,\</code> at the end of a file,
492 * without a new line causes a '.' to be appended with unexpected side effects.
493 */
494 public List<String> getBundlesUrls() {
495 String bundlePatterns = getProperty(PROP_ARGEO_OSGI_BUNDLES);
496 return getBundlesUrls(bundlePatterns);
497 }
498
499 /**
500 * Compute a list of URLs to install based on the provided patterns, with
501 * default base url
502 */
503 public List<String> getBundlesUrls(String bundlePatterns) {
504 String baseUrl = getProperty(PROP_ARGEO_OSGI_BASE_URL, DEFAULT_BASE_URL);
505 return getBundlesUrls(baseUrl, bundlePatterns);
506 }
507
508 /** Implements the path matching logic */
509 @Deprecated
510 public List<String> getBundlesUrls(String baseUrl, String bundlePatterns) {
511 List<String> urls = new ArrayList<String>();
512 if (bundlePatterns == null)
513 return urls;
514
515 // bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns);
516 logger.log(TRACE, () -> PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
517
518 StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
519 List<BundlesSet> bundlesSets = new ArrayList<BundlesSet>();
520 while (st.hasMoreTokens()) {
521 String token = st.nextToken();
522 if (new File(token).exists()) {
523 String url = locationToUrl(baseUrl, token);
524 urls.add(url);
525 } else
526 bundlesSets.add(new BundlesSet(token));
527 }
528
529 // find included
530 List<String> included = new ArrayList<String>();
531 // PathMatcher matcher = new AntPathMatcher();
532 for (int i = 0; i < bundlesSets.size(); i++) {
533 BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
534 for (int j = 0; j < bundlesSet.getIncludes().size(); j++) {
535 String pattern = (String) bundlesSet.getIncludes().get(j);
536 match(included, bundlesSet.getDir(), null, pattern);
537 }
538 }
539
540 // find excluded
541 List<String> excluded = new ArrayList<String>();
542 for (int i = 0; i < bundlesSets.size(); i++) {
543 BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
544 for (int j = 0; j < bundlesSet.getExcludes().size(); j++) {
545 String pattern = (String) bundlesSet.getExcludes().get(j);
546 match(excluded, bundlesSet.getDir(), null, pattern);
547 }
548 }
549
550 // construct list
551 for (int i = 0; i < included.size(); i++) {
552 String fullPath = (String) included.get(i);
553 if (!excluded.contains(fullPath))
554 urls.add(locationToUrl(baseUrl, fullPath));
555 }
556
557 return urls;
558 }
559
560 /*
561 * DISTRIBUTION JAR INSTALLATION
562 */
563 public List<String> getDistributionUrls() {
564 String distributionUrl = getProperty(PROP_ARGEO_OSGI_DISTRIBUTION_URL);
565 String baseUrl = getProperty(PROP_ARGEO_OSGI_BASE_URL);
566 return getDistributionUrls(distributionUrl, baseUrl);
567 }
568
569 public List<String> getDistributionUrls(String distributionUrl, String baseUrl) {
570 List<String> urls = new ArrayList<String>();
571 if (distributionUrl == null)
572 return urls;
573
574 DistributionBundle distributionBundle;
575 if (distributionUrl.startsWith("http") || distributionUrl.startsWith("file")) {
576 distributionBundle = new DistributionBundle(distributionUrl);
577 if (baseUrl != null)
578 distributionBundle.setBaseUrl(baseUrl);
579 } else {
580 // relative url
581 if (baseUrl == null) {
582 baseUrl = localCache;
583 }
584
585 if (distributionUrl.contains(":")) {
586 // TODO make it safer
587 String[] parts = distributionUrl.trim().split(":");
588 String[] categoryParts = parts[0].split("\\.");
589 String artifactId = parts[1];
590 String version = parts[2];
591 StringBuilder sb = new StringBuilder();
592 for (String categoryPart : categoryParts) {
593 sb.append(categoryPart).append('/');
594 }
595 sb.append(artifactId).append('/');
596 sb.append(version).append('/');
597 sb.append(artifactId).append('-').append(version).append(".jar");
598 distributionUrl = sb.toString();
599 }
600
601 distributionBundle = new DistributionBundle(baseUrl, distributionUrl, localCache);
602 }
603 distributionBundle.processUrl();
604 return distributionBundle.listUrls();
605 }
606
607 /*
608 * HIGH LEVEL UTILITIES
609 */
610 /** Actually performs the matching logic. */
611 protected void match(List<String> matched, String base, String currentPath, String pattern) {
612 if (currentPath == null) {
613 // Init
614 File baseDir = new File(base.replace('/', File.separatorChar));
615 File[] files = baseDir.listFiles();
616
617 if (files == null) {
618 if (logger.isLoggable(TRACE))
619 logger.log(Level.WARNING, "Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
620 + ", isDirectory=" + baseDir.isDirectory());
621 return;
622 }
623
624 for (int i = 0; i < files.length; i++)
625 match(matched, base, files[i].getName(), pattern);
626 } else {
627 PathMatcher matcher = FileSystems.getDefault().getPathMatcher(pattern);
628 String fullPath = base + '/' + currentPath;
629 if (matched.contains(fullPath))
630 return;// don't try deeper if already matched
631
632 boolean ok = matcher.matches(Paths.get(currentPath));
633 // if (debug)
634 // debug(currentPath + " " + (ok ? "" : " not ")
635 // + " matched with " + pattern);
636 if (ok) {
637 matched.add(fullPath);
638 return;
639 } else {
640 String newFullPath = relativeToFullPath(base, currentPath);
641 File newFile = new File(newFullPath);
642 File[] files = newFile.listFiles();
643 if (files != null) {
644 for (int i = 0; i < files.length; i++) {
645 String newCurrentPath = currentPath + '/' + files[i].getName();
646 if (files[i].isDirectory()) {
647 // if (matcher.matchStart(pattern, newCurrentPath)) {
648 // FIXME recurse only if start matches ?
649 match(matched, base, newCurrentPath, pattern);
650 // } else {
651 // if (OsgiBootUtils.isDebug())
652 // debug(newCurrentPath + " does not start match with " + pattern);
653 //
654 // }
655 } else {
656 boolean nonDirectoryOk = matcher.matches(Paths.get(newCurrentPath));
657 logger.log(TRACE,
658 () -> currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
659 if (nonDirectoryOk)
660 matched.add(relativeToFullPath(base, newCurrentPath));
661 }
662 }
663 }
664 }
665 }
666 }
667
668 /*
669 * LOW LEVEL UTILITIES
670 */
671 /**
672 * The bundles already installed. Key is location (String) , value is a
673 * {@link Bundle}
674 */
675 public Map<String, Bundle> getBundlesByLocation() {
676 Map<String, Bundle> installedBundles = new HashMap<String, Bundle>();
677 Bundle[] bundles = bundleContext.getBundles();
678 for (int i = 0; i < bundles.length; i++) {
679 installedBundles.put(bundles[i].getLocation(), bundles[i]);
680 }
681 return installedBundles;
682 }
683
684 /**
685 * The bundles already installed. Key is symbolic name (String) , value is a
686 * {@link Bundle}
687 */
688 public Map<String, Bundle> getBundlesBySymbolicName() {
689 Map<String, Bundle> namedBundles = new HashMap<String, Bundle>();
690 Bundle[] bundles = bundleContext.getBundles();
691 for (int i = 0; i < bundles.length; i++) {
692 namedBundles.put(bundles[i].getSymbolicName(), bundles[i]);
693 }
694 return namedBundles;
695 }
696
697 /** Creates an URL from a location */
698 protected String locationToUrl(String baseUrl, String location) {
699 return baseUrl + location;
700 }
701
702 /** Transforms a relative path in a full system path. */
703 protected String relativeToFullPath(String basePath, String relativePath) {
704 return (basePath + '/' + relativePath).replace('/', File.separatorChar);
705 }
706
707 public void refresh() {
708 Bundle systemBundle = bundleContext.getBundle(0);
709 FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
710 // TODO deal with refresh breaking native loading (e.g SWT)
711 frameworkWiring.refreshBundles(null);
712 }
713
714 /**
715 * Gets a property value
716 *
717 * @return null when defaultValue is ""
718 */
719 public String getProperty(String name, String defaultValue) {
720 String value = bundleContext.getProperty(name);
721 if (value == null)
722 return defaultValue; // may be null
723 else
724 return value;
725 }
726
727 public String getProperty(String name) {
728 return getProperty(name, null);
729 }
730
731 /*
732 * BEAN METHODS
733 */
734
735 public BundleContext getBundleContext() {
736 return bundleContext;
737 }
738
739 /*
740 * PLAIN OSGI LAUNCHER
741 */
742 /** Launch an OSGi framework. OSGi Boot initialisation is NOT performed. */
743 public static Framework defaultOsgiLaunch(Map<String, String> configuration) {
744 Optional<FrameworkFactory> frameworkFactory = ServiceLoader.load(FrameworkFactory.class).findFirst();
745 if (frameworkFactory.isEmpty())
746 throw new IllegalStateException("No framework factory found");
747 return defaultOsgiLaunch(frameworkFactory.get(), configuration);
748 }
749
750 /** Launch an OSGi framework. OSGi Boot initialisation is NOT performed. */
751 public static Framework defaultOsgiLaunch(FrameworkFactory frameworkFactory, Map<String, String> configuration) {
752 // start OSGi
753 Framework framework = frameworkFactory.newFramework(configuration);
754 try {
755 framework.start();
756 } catch (BundleException e) {
757 throw new IllegalStateException("Cannot start OSGi framework", e);
758 }
759 return framework;
760 }
761
762 /*
763 * OSGI UTILITIES
764 */
765 /** Uninstall all bundles with these symbolic names */
766 public static void uninstallBundles(BundleContext bc, String... symbolicNames) {
767 List<String> lst = Arrays.asList(symbolicNames);
768 for (Bundle b : bc.getBundles()) {
769 String sn = b.getSymbolicName();
770 if (sn == null)
771 continue;
772 if (lst.contains(sn)) {
773 try {
774 b.uninstall();
775 } catch (BundleException e) {
776 logger.log(ERROR, "Cannot uninstall " + sn, e);
777 }
778 }
779 }
780
781 }
782 }