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