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