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.slc.osgi;
17  
18  import java.util.Collection;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.argeo.slc.SlcException;
23  import org.eclipse.gemini.blueprint.context.BundleContextAware;
24  import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEvent;
25  import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextListener;
26  import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextClosedEvent;
27  import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextFailedEvent;
28  import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextRefreshedEvent;
29  import org.eclipse.gemini.blueprint.util.OsgiBundleUtils;
30  import org.eclipse.gemini.blueprint.util.OsgiFilterUtils;
31  import org.osgi.framework.Bundle;
32  import org.osgi.framework.BundleContext;
33  import org.osgi.framework.BundleException;
34  import org.osgi.framework.Constants;
35  import org.osgi.framework.FrameworkEvent;
36  import org.osgi.framework.FrameworkListener;
37  import org.osgi.framework.InvalidSyntaxException;
38  import org.osgi.framework.ServiceReference;
39  import org.osgi.service.packageadmin.PackageAdmin;
40  import org.osgi.util.tracker.ServiceTracker;
41  import org.springframework.beans.factory.DisposableBean;
42  import org.springframework.beans.factory.InitializingBean;
43  import org.springframework.context.ApplicationContext;
44  import org.springframework.util.Assert;
45  
46  /** Wraps low-level access to a {@link BundleContext} */
47  @SuppressWarnings("deprecation")
48  public class BundlesManager implements BundleContextAware, FrameworkListener,
49  		InitializingBean, DisposableBean,
50  		OsgiBundleApplicationContextListener<OsgiBundleApplicationContextEvent> {
51  	private final static Log log = LogFactory.getLog(BundlesManager.class);
52  
53  	private BundleContext bundleContext;
54  
55  	private Long defaultTimeout = 60 * 1000l;
56  	private Long pollingPeriod = 200l;
57  
58  	// Refresh sync objects
59  	private final Object refreshedPackageSem = new Object();
60  	private Boolean packagesRefreshed = false;
61  
62  	public BundlesManager() {
63  	}
64  
65  	public BundlesManager(BundleContext bundleContext) {
66  		this.bundleContext = bundleContext;
67  	}
68  
69  	/**
70  	 * Stop the module, update it, refresh it and restart it. All synchronously.
71  	 */
72  	public void upgradeSynchronous(OsgiBundle osgiBundle) {
73  		try {
74  			Bundle bundle = findRelatedBundle(osgiBundle);
75  
76  			long begin = System.currentTimeMillis();
77  
78  			long bStop = begin;
79  			stopSynchronous(bundle);
80  
81  			long bUpdate = System.currentTimeMillis();
82  			updateSynchronous(bundle);
83  
84  			// Refresh in case there are fragments
85  			long bRefresh = System.currentTimeMillis();
86  			refreshSynchronous(bundle);
87  
88  			long bStart = System.currentTimeMillis();
89  			startSynchronous(bundle);
90  
91  			long aStart = System.currentTimeMillis();
92  			if (log.isTraceEnabled()) {
93  				log.debug("OSGi upgrade performed in " + (aStart - begin)
94  						+ "ms for bundle " + osgiBundle);
95  				log.debug(" stop \t: " + (bUpdate - bStop) + "ms");
96  				log.debug(" update\t: " + (bRefresh - bUpdate) + "ms");
97  				log.debug(" refresh\t: " + (bStart - bRefresh) + "ms");
98  				log.debug(" start\t: " + (aStart - bStart) + "ms");
99  				log.debug(" TOTAL\t: " + (aStart - begin) + "ms");
100 			}
101 
102 			long bAppContext = System.currentTimeMillis();
103 			String filter = "(Bundle-SymbolicName=" + bundle.getSymbolicName()
104 					+ ")";
105 			// Wait for application context to be ready
106 			// TODO: use service tracker
107 			Collection<ServiceReference<ApplicationContext>> srs = getServiceRefSynchronous(
108 					ApplicationContext.class, filter);
109 			ServiceReference<ApplicationContext> sr = srs.iterator().next();
110 			long aAppContext = System.currentTimeMillis();
111 			long end = aAppContext;
112 
113 			if (log.isTraceEnabled()) {
114 				log.debug("Application context refresh performed in "
115 						+ (aAppContext - bAppContext) + "ms for bundle "
116 						+ osgiBundle);
117 			}
118 
119 			if (log.isDebugEnabled())
120 				log.debug("Bundle '" + bundle.getSymbolicName()
121 						+ "' upgraded and ready " + " (upgrade performed in "
122 						+ (end - begin) + "ms).");
123 
124 			if (log.isTraceEnabled()) {
125 				ApplicationContext applicationContext = (ApplicationContext) bundleContext
126 						.getService(sr);
127 				int beanDefCount = applicationContext.getBeanDefinitionCount();
128 				log.debug(" " + beanDefCount + " beans in app context of "
129 						+ bundle.getSymbolicName()
130 						+ ", average init time per bean=" + (end - begin)
131 						/ beanDefCount + "ms");
132 			}
133 
134 			bundleContext.ungetService(sr);
135 
136 		} catch (Exception e) {
137 			throw new SlcException("Cannot update bundle " + osgiBundle, e);
138 		}
139 	}
140 
141 	/** Updates bundle synchronously. */
142 	protected void updateSynchronous(Bundle bundle) throws BundleException {
143 		bundle.update();
144 		boolean waiting = true;
145 
146 		long begin = System.currentTimeMillis();
147 		do {
148 			int state = bundle.getState();
149 			if (state == Bundle.INSTALLED || state == Bundle.ACTIVE
150 					|| state == Bundle.RESOLVED)
151 				waiting = false;
152 
153 			sleepWhenPolling();
154 			checkTimeout(begin, "Update of bundle " + bundle.getSymbolicName()
155 					+ " timed out. Bundle state = " + bundle.getState());
156 		} while (waiting);
157 
158 		if (log.isTraceEnabled())
159 			log.debug("Bundle " + bundle.getSymbolicName() + " updated.");
160 	}
161 
162 	/** Starts bundle synchronously. Does nothing if already started. */
163 	protected void startSynchronous(Bundle bundle) throws BundleException {
164 		int originalState = bundle.getState();
165 		if (originalState == Bundle.ACTIVE)
166 			return;
167 
168 		bundle.start();
169 		boolean waiting = true;
170 
171 		long begin = System.currentTimeMillis();
172 		do {
173 			if (bundle.getState() == Bundle.ACTIVE)
174 				waiting = false;
175 
176 			sleepWhenPolling();
177 			checkTimeout(begin, "Start of bundle " + bundle.getSymbolicName()
178 					+ " timed out. Bundle state = " + bundle.getState());
179 		} while (waiting);
180 
181 		if (log.isTraceEnabled())
182 			log.debug("Bundle " + bundle.getSymbolicName() + " started.");
183 	}
184 
185 	/** Stops bundle synchronously. Does nothing if already started. */
186 	protected void stopSynchronous(Bundle bundle) throws BundleException {
187 		int originalState = bundle.getState();
188 		if (originalState != Bundle.ACTIVE)
189 			return;
190 
191 		bundle.stop();
192 		boolean waiting = true;
193 
194 		long begin = System.currentTimeMillis();
195 		do {
196 			if (bundle.getState() != Bundle.ACTIVE
197 					&& bundle.getState() != Bundle.STOPPING)
198 				waiting = false;
199 
200 			sleepWhenPolling();
201 			checkTimeout(begin, "Stop of bundle " + bundle.getSymbolicName()
202 					+ " timed out. Bundle state = " + bundle.getState());
203 		} while (waiting);
204 
205 		if (log.isTraceEnabled())
206 			log.debug("Bundle " + bundle.getSymbolicName() + " stopped.");
207 	}
208 
209 	/** Refresh bundle synchronously. Does nothing if already started. */
210 	protected void refreshSynchronous(Bundle bundle) throws BundleException {
211 		ServiceReference<PackageAdmin> packageAdminRef = bundleContext
212 				.getServiceReference(PackageAdmin.class);
213 		PackageAdmin packageAdmin = (PackageAdmin) bundleContext
214 				.getService(packageAdminRef);
215 		Bundle[] bundles = { bundle };
216 
217 		long begin = System.currentTimeMillis();
218 		synchronized (refreshedPackageSem) {
219 			packagesRefreshed = false;
220 			packageAdmin.refreshPackages(bundles);
221 			try {
222 				refreshedPackageSem.wait(defaultTimeout);
223 			} catch (InterruptedException e) {
224 				// silent
225 			}
226 			if (!packagesRefreshed) {
227 				long now = System.currentTimeMillis();
228 				throw new SlcException("Packages not refreshed after "
229 						+ (now - begin) + "ms");
230 			} else {
231 				packagesRefreshed = false;
232 			}
233 		}
234 
235 		if (log.isTraceEnabled())
236 			log.debug("Bundle " + bundle.getSymbolicName() + " refreshed.");
237 	}
238 
239 	public void frameworkEvent(FrameworkEvent event) {
240 		if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
241 			synchronized (refreshedPackageSem) {
242 				packagesRefreshed = true;
243 				refreshedPackageSem.notifyAll();
244 			}
245 		}
246 	}
247 
248 	public <S> Collection<ServiceReference<S>> getServiceRefSynchronous(
249 			Class<S> clss, String filter) throws InvalidSyntaxException {
250 		if (log.isTraceEnabled())
251 			log.debug("Filter: '" + filter + "'");
252 		Collection<ServiceReference<S>> sfs = null;
253 		boolean waiting = true;
254 		long begin = System.currentTimeMillis();
255 		do {
256 			sfs = bundleContext.getServiceReferences(clss, filter);
257 
258 			if (sfs != null)
259 				waiting = false;
260 
261 			sleepWhenPolling();
262 			checkTimeout(begin, "Search of services " + clss + " with filter "
263 					+ filter + " timed out.");
264 		} while (waiting);
265 
266 		return sfs;
267 	}
268 
269 	protected void checkTimeout(long begin, String msg) {
270 		long now = System.currentTimeMillis();
271 		if (now - begin > defaultTimeout)
272 			throw new SlcException(msg + " (timeout after " + (now - begin)
273 					+ "ms)");
274 
275 	}
276 
277 	protected void sleepWhenPolling() {
278 		try {
279 			Thread.sleep(pollingPeriod);
280 		} catch (InterruptedException e) {
281 			throw new SlcException("Polling interrupted");
282 		}
283 	}
284 
285 	/** Creates and open a new service tracker. */
286 	public <S> ServiceTracker<S, S> newTracker(Class<S> clss) {
287 		ServiceTracker<S, S> st = new ServiceTracker<S, S>(bundleContext, clss,
288 				null);
289 		st.open();
290 		return st;
291 	}
292 
293 	public <T> T getSingleService(Class<T> clss, String filter,
294 			Boolean synchronous) {
295 		if (filter != null)
296 			Assert.isTrue(OsgiFilterUtils.isValidFilter(filter), "valid filter");
297 		Collection<ServiceReference<T>> sfs;
298 		try {
299 			if (synchronous)
300 				sfs = getServiceRefSynchronous(clss, filter);
301 			else
302 				sfs = bundleContext.getServiceReferences(clss, filter);
303 		} catch (InvalidSyntaxException e) {
304 			throw new SlcException("Cannot retrieve service reference for "
305 					+ filter, e);
306 		}
307 
308 		if (sfs == null || sfs.size() == 0)
309 			return null;
310 		else if (sfs.size() > 1)
311 			throw new SlcException("More than one execution flow found for "
312 					+ filter);
313 		return (T) bundleContext.getService(sfs.iterator().next());
314 	}
315 
316 	public <T> T getSingleServiceStrict(Class<T> clss, String filter,
317 			Boolean synchronous) {
318 		T service = getSingleService(clss, filter, synchronous);
319 		if (service == null)
320 			throw new SlcException("No execution flow found for " + filter);
321 		else
322 			return service;
323 	}
324 
325 	public OsgiBundle findRelatedBundle(String moduleName, String moduleVersion) {
326 		OsgiBundle osgiBundle = new OsgiBundle(moduleName, moduleVersion);
327 		if (osgiBundle.getVersion() == null) {
328 			Bundle bundle = findRelatedBundle(osgiBundle);
329 			osgiBundle = new OsgiBundle(bundle);
330 		}
331 		return osgiBundle;
332 	}
333 
334 	/**
335 	 * @param osgiBundle
336 	 *            cannot be null
337 	 * @return the related bundle or null if not found
338 	 * @throws SlcException
339 	 *             if osgiBundle argument is null
340 	 */
341 	public Bundle findRelatedBundle(OsgiBundle osgiBundle) {
342 		if (osgiBundle == null)
343 			throw new SlcException("OSGi bundle cannot be null");
344 
345 		Bundle bundle = null;
346 		if (osgiBundle.getInternalBundleId() != null) {
347 			bundle = bundleContext.getBundle(osgiBundle.getInternalBundleId());
348 			Assert.isTrue(
349 					osgiBundle.getName().equals(bundle.getSymbolicName()),
350 					"symbolic name consistent");
351 			if (osgiBundle.getVersion() != null)
352 				Assert.isTrue(
353 						osgiBundle.getVersion().equals(
354 								bundle.getHeaders().get(
355 										Constants.BUNDLE_VERSION)),
356 						"version consistent");
357 		} else if (osgiBundle.getVersion() == null
358 				|| osgiBundle.getVersion().equals("0.0.0")) {
359 			bundle = OsgiBundleUtils.findBundleBySymbolicName(bundleContext,
360 					osgiBundle.getName());
361 		} else {// scan all bundles
362 			bundles: for (Bundle b : bundleContext.getBundles()) {
363 				if (b.getSymbolicName() == null) {
364 					log.warn("Bundle " + b + " has no symbolic name defined.");
365 					continue bundles;
366 				}
367 
368 				if (b.getSymbolicName().equals(osgiBundle.getName())) {
369 					if (osgiBundle.getVersion() == null) {
370 						bundle = b;
371 						break bundles;
372 					}
373 
374 					if (b.getHeaders().get(Constants.BUNDLE_VERSION)
375 							.equals(osgiBundle.getVersion())) {
376 						bundle = b;
377 						osgiBundle.setInternalBundleId(b.getBundleId());
378 						break bundles;
379 					}
380 				}
381 			}
382 		}
383 		return bundle;
384 	}
385 
386 	/** Find a single bundle based on a symbolic name pattern. */
387 	public OsgiBundle findFromPattern(String pattern) {
388 		OsgiBundle osgiBundle = null;
389 		for (Bundle b : bundleContext.getBundles()) {
390 			if (b.getSymbolicName().contains(pattern)) {
391 				osgiBundle = new OsgiBundle(b);
392 				break;
393 			}
394 		}
395 		return osgiBundle;
396 	}
397 
398 	public OsgiBundle getBundle(Long bundleId) {
399 		Bundle bundle = bundleContext.getBundle(bundleId);
400 		return new OsgiBundle(bundle);
401 	}
402 
403 	public void setBundleContext(BundleContext bundleContext) {
404 		this.bundleContext = bundleContext;
405 	}
406 
407 	public void afterPropertiesSet() throws Exception {
408 		bundleContext.addFrameworkListener(this);
409 	}
410 
411 	public void destroy() throws Exception {
412 		bundleContext.removeFrameworkListener(this);
413 	}
414 
415 	public void setDefaultTimeout(Long defaultTimeout) {
416 		this.defaultTimeout = defaultTimeout;
417 	}
418 
419 	/**
420 	 * Use with caution since it may interfer with some cached information
421 	 * within this object
422 	 */
423 	public BundleContext getBundleContext() {
424 		return bundleContext;
425 	}
426 
427 	public void setPollingPeriod(Long pollingPeriod) {
428 		this.pollingPeriod = pollingPeriod;
429 	}
430 
431 	public void onOsgiApplicationEvent(OsgiBundleApplicationContextEvent event) {
432 		if (event instanceof OsgiBundleContextRefreshedEvent) {
433 			log.debug("App context refreshed: " + event);
434 		} else if (event instanceof OsgiBundleContextFailedEvent) {
435 			log.debug("App context failed: " + event);
436 		}
437 		if (event instanceof OsgiBundleContextClosedEvent) {
438 			log.debug("App context closed: " + event);
439 		}
440 
441 	}
442 
443 }