2 * Copyright (C) 2007-2012 Mathieu Baudier
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.slc
.osgi
;
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
;
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);
49 private BundleContext bundleContext
;
51 private Long defaultTimeout
= 30 * 1000l;
52 private Long pollingPeriod
= 200l;
54 // Refresh sync objects
55 private final Object refreshedPackageSem
= new Object();
56 private Boolean packagesRefreshed
= false;
58 public BundlesManager() {
61 public BundlesManager(BundleContext bundleContext
) {
62 this.bundleContext
= bundleContext
;
66 * Stop the module, update it, refresh it and restart it. All synchronously.
68 public void upgradeSynchronous(OsgiBundle osgiBundle
) {
70 Bundle bundle
= findRelatedBundle(osgiBundle
);
72 long begin
= System
.currentTimeMillis();
75 stopSynchronous(bundle
);
77 long bUpdate
= System
.currentTimeMillis();
78 updateSynchronous(bundle
);
80 // Refresh in case there are fragments
81 long bRefresh
= System
.currentTimeMillis();
82 refreshSynchronous(bundle
);
84 long bStart
= System
.currentTimeMillis();
85 startSynchronous(bundle
);
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");
98 long bAppContext
= System
.currentTimeMillis();
99 String filter
= "(Bundle-SymbolicName=" + bundle
.getSymbolicName()
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
;
109 if (log
.isTraceEnabled()) {
110 log
.debug("Application context refresh performed in "
111 + (aAppContext
- bAppContext
) + "ms for bundle "
115 if (log
.isDebugEnabled())
116 log
.debug("Bundle '" + bundle
.getSymbolicName()
117 + "' upgraded and ready " + " (upgrade performed in "
118 + (end
- begin
) + "ms).");
120 if (log
.isTraceEnabled()) {
121 ApplicationContext applicationContext
= (ApplicationContext
) bundleContext
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");
130 bundleContext
.ungetService(sr
);
132 } catch (Exception e
) {
133 throw new SlcException("Cannot update bundle " + osgiBundle
, e
);
137 /** Updates bundle synchronously. */
138 protected void updateSynchronous(Bundle bundle
) throws BundleException
{
140 boolean waiting
= true;
142 long begin
= System
.currentTimeMillis();
144 int state
= bundle
.getState();
145 if (state
== Bundle
.INSTALLED
|| state
== Bundle
.ACTIVE
146 || state
== Bundle
.RESOLVED
)
150 checkTimeout(begin
, "Update of bundle " + bundle
.getSymbolicName()
151 + " timed out. Bundle state = " + bundle
.getState());
154 if (log
.isTraceEnabled())
155 log
.debug("Bundle " + bundle
.getSymbolicName() + " updated.");
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
)
165 boolean waiting
= true;
167 long begin
= System
.currentTimeMillis();
169 if (bundle
.getState() == Bundle
.ACTIVE
)
173 checkTimeout(begin
, "Start of bundle " + bundle
.getSymbolicName()
174 + " timed out. Bundle state = " + bundle
.getState());
177 if (log
.isTraceEnabled())
178 log
.debug("Bundle " + bundle
.getSymbolicName() + " started.");
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
)
188 boolean waiting
= true;
190 long begin
= System
.currentTimeMillis();
192 if (bundle
.getState() != Bundle
.ACTIVE
193 && bundle
.getState() != Bundle
.STOPPING
)
197 checkTimeout(begin
, "Stop of bundle " + bundle
.getSymbolicName()
198 + " timed out. Bundle state = " + bundle
.getState());
201 if (log
.isTraceEnabled())
202 log
.debug("Bundle " + bundle
.getSymbolicName() + " stopped.");
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
};
213 long begin
= System
.currentTimeMillis();
214 synchronized (refreshedPackageSem
) {
215 packagesRefreshed
= false;
216 packageAdmin
.refreshPackages(bundles
);
218 refreshedPackageSem
.wait(defaultTimeout
);
219 } catch (InterruptedException e
) {
222 if (!packagesRefreshed
) {
223 long now
= System
.currentTimeMillis();
224 throw new SlcException("Packages not refreshed after "
225 + (now
- begin
) + "ms");
227 packagesRefreshed
= false;
231 if (log
.isTraceEnabled())
232 log
.debug("Bundle " + bundle
.getSymbolicName() + " refreshed.");
235 public void frameworkEvent(FrameworkEvent event
) {
236 if (event
.getType() == FrameworkEvent
.PACKAGES_REFRESHED
) {
237 synchronized (refreshedPackageSem
) {
238 packagesRefreshed
= true;
239 refreshedPackageSem
.notifyAll();
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();
252 sfs
= bundleContext
.getServiceReferences(clss
, filter
);
258 checkTimeout(begin
, "Search of services " + clss
+ " with filter "
259 + filter
+ " timed out.");
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
)
273 protected void sleepWhenPolling() {
275 Thread
.sleep(pollingPeriod
);
276 } catch (InterruptedException e
) {
277 throw new SlcException("Polling interrupted");
281 /** Creates and open a new service tracker. */
282 public ServiceTracker
newTracker(Class
<?
> clss
) {
283 ServiceTracker st
= new ServiceTracker(bundleContext
, clss
.getName(),
289 @SuppressWarnings(value
= { "unchecked" })
290 public <T
> T
getSingleService(Class
<T
> clss
, String filter
,
291 Boolean synchronous
) {
293 Assert
.isTrue(OsgiFilterUtils
.isValidFilter(filter
), "valid filter");
294 ServiceReference
[] sfs
;
297 sfs
= getServiceRefSynchronous(clss
.getName(), filter
);
300 .getServiceReferences(clss
.getName(), filter
);
301 } catch (InvalidSyntaxException e
) {
302 throw new SlcException("Cannot retrieve service reference for "
306 if (sfs
== null || sfs
.length
== 0)
308 else if (sfs
.length
> 1)
309 throw new SlcException("More than one execution flow found for "
311 return (T
) bundleContext
.getService(sfs
[0]);
314 public <T
> T
getSingleServiceStrict(Class
<T
> clss
, String filter
,
315 Boolean synchronous
) {
316 T service
= getSingleService(clss
, filter
, synchronous
);
318 throw new SlcException("No execution flow found for " + filter
);
326 * @return the related bundle or null if not found
327 * @throws SlcException
328 * if osgiBundle argument is null
330 public Bundle
findRelatedBundle(OsgiBundle osgiBundle
) {
331 if (osgiBundle
== null)
332 throw new SlcException("OSGi bundle cannot be null");
334 Bundle bundle
= null;
335 if (osgiBundle
.getInternalBundleId() != null) {
336 bundle
= bundleContext
.getBundle(osgiBundle
.getInternalBundleId());
338 osgiBundle
.getName().equals(bundle
.getSymbolicName()),
339 "symbolic name consistent");
340 if (osgiBundle
.getVersion() != null)
342 osgiBundle
.getVersion().equals(
343 bundle
.getHeaders().get(
344 Constants
.BUNDLE_VERSION
)),
345 "version consistent");
346 } else if (osgiBundle
.getVersion() == null
347 || osgiBundle
.getVersion().equals("0.0.0")) {
348 bundle
= OsgiBundleUtils
.findBundleBySymbolicName(bundleContext
,
349 osgiBundle
.getName());
350 } else {// scan all bundles
351 bundles
: for (Bundle b
: bundleContext
.getBundles()) {
352 if (b
.getSymbolicName() == null) {
353 log
.warn("Bundle " + b
+ " has no symbolic name defined.");
357 if (b
.getSymbolicName().equals(osgiBundle
.getName())) {
358 if (osgiBundle
.getVersion() == null) {
363 if (b
.getHeaders().get(Constants
.BUNDLE_VERSION
)
364 .equals(osgiBundle
.getVersion())) {
366 osgiBundle
.setInternalBundleId(b
.getBundleId());
375 /** Find a single bundle based on a symbolic name pattern. */
376 public OsgiBundle
findFromPattern(String pattern
) {
377 OsgiBundle osgiBundle
= null;
378 for (Bundle b
: bundleContext
.getBundles()) {
379 if (b
.getSymbolicName().contains(pattern
)) {
380 osgiBundle
= new OsgiBundle(b
);
387 public OsgiBundle
getBundle(Long bundleId
) {
388 Bundle bundle
= bundleContext
.getBundle(bundleId
);
389 return new OsgiBundle(bundle
);
392 public void setBundleContext(BundleContext bundleContext
) {
393 this.bundleContext
= bundleContext
;
396 public void afterPropertiesSet() throws Exception
{
397 bundleContext
.addFrameworkListener(this);
400 public void destroy() throws Exception
{
401 bundleContext
.removeFrameworkListener(this);
404 public void setDefaultTimeout(Long defaultTimeout
) {
405 this.defaultTimeout
= defaultTimeout
;
409 * Use with caution since it may interfer with some cached information
412 public BundleContext
getBundleContext() {
413 return bundleContext
;
416 public void setPollingPeriod(Long pollingPeriod
) {
417 this.pollingPeriod
= pollingPeriod
;
420 public void onOsgiApplicationEvent(OsgiBundleApplicationContextEvent event
) {
421 if (event
instanceof OsgiBundleContextRefreshedEvent
) {
422 log
.debug("App context refreshed: " + event
);
423 } else if (event
instanceof OsgiBundleContextFailedEvent
) {
424 log
.debug("App context failed: " + event
);
426 if (event
instanceof OsgiBundleContextClosedEvent
) {
427 log
.debug("App context closed: " + event
);