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