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