]> git.argeo.org Git - gpl/argeo-slc.git/blobdiff - runtime/org.argeo.slc.support.osgi/src/main/java/org/argeo/slc/osgi/BundlesManager.java
Make services lookup more robust
[gpl/argeo-slc.git] / runtime / org.argeo.slc.support.osgi / src / main / java / org / argeo / slc / osgi / BundlesManager.java
index c87f602979e6e3008662f4f9786edd39e7f0856b..7d96ae4c9e6d5487e7dddae839cc4716dae452fe 100644 (file)
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.argeo.slc.osgi;
 
 import org.apache.commons.logging.Log;
@@ -17,18 +32,28 @@ import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.osgi.context.BundleContextAware;
+import org.springframework.osgi.context.event.OsgiBundleApplicationContextEvent;
+import org.springframework.osgi.context.event.OsgiBundleApplicationContextListener;
+import org.springframework.osgi.context.event.OsgiBundleContextClosedEvent;
+import org.springframework.osgi.context.event.OsgiBundleContextFailedEvent;
+import org.springframework.osgi.context.event.OsgiBundleContextRefreshedEvent;
+import org.springframework.osgi.util.OsgiBundleUtils;
 import org.springframework.osgi.util.OsgiFilterUtils;
 import org.springframework.util.Assert;
 
 /** Wraps low-level access to a {@link BundleContext} */
 public class BundlesManager implements BundleContextAware, FrameworkListener,
-               InitializingBean, DisposableBean {
+               InitializingBean, DisposableBean, OsgiBundleApplicationContextListener {
        private final static Log log = LogFactory.getLog(BundlesManager.class);
 
        private BundleContext bundleContext;
 
-       private Long defaultTimeout = 10000l;
+       private Long defaultTimeout = 30 * 1000l;
+       private Long pollingPeriod = 200l;
+
+       // Refresh sync objects
        private final Object refreshedPackageSem = new Object();
+       private Boolean packagesRefreshed = false;
 
        /**
         * Stop the module, update it, refresh it and restart it. All synchronously.
@@ -36,21 +61,67 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
        public void upgradeSynchronous(OsgiBundle osgiBundle) {
                try {
                        Bundle bundle = findRelatedBundle(osgiBundle);
+
+                       long begin = System.currentTimeMillis();
+
+                       long bStop = begin;
                        stopSynchronous(bundle);
+
+                       long bUpdate = System.currentTimeMillis();
                        updateSynchronous(bundle);
+
                        // Refresh in case there are fragments
+                       long bRefresh = System.currentTimeMillis();
                        refreshSynchronous(bundle);
+
+                       long bStart = System.currentTimeMillis();
                        startSynchronous(bundle);
 
+                       long aStart = System.currentTimeMillis();
+                       if (log.isTraceEnabled()) {
+                               log.debug("OSGi upgrade performed in " + (aStart - begin)
+                                               + "ms for bundle " + osgiBundle);
+                               log.debug(" stop \t: " + (bUpdate - bStop) + "ms");
+                               log.debug(" update\t: " + (bRefresh - bUpdate) + "ms");
+                               log.debug(" refresh\t: " + (bStart - bRefresh) + "ms");
+                               log.debug(" start\t: " + (aStart - bStart) + "ms");
+                               log.debug(" TOTAL\t: " + (aStart - begin) + "ms");
+                       }
+
+                       long bAppContext = System.currentTimeMillis();
                        String filter = "(Bundle-SymbolicName=" + bundle.getSymbolicName()
                                        + ")";
                        // Wait for application context to be ready
                        // TODO: use service tracker
-                       getServiceRefSynchronous(ApplicationContext.class.getName(), filter);
+                       ServiceReference[] srs = getServiceRefSynchronous(
+                                       ApplicationContext.class.getName(), filter);
+                       ServiceReference sr = srs[0];
+                       long aAppContext = System.currentTimeMillis();
+                       long end = aAppContext;
+
+                       if (log.isTraceEnabled()) {
+                               log.debug("Application context refresh performed in "
+                                               + (aAppContext - bAppContext) + "ms for bundle "
+                                               + osgiBundle);
+                       }
 
                        if (log.isDebugEnabled())
-                               log.debug("Bundle " + bundle.getSymbolicName()
-                                               + " ready to be used at latest version.");
+                               log.debug("Bundle '" + bundle.getSymbolicName()
+                                               + "' upgraded and ready " + " (upgrade performed in "
+                                               + (end - begin) + "ms).");
+
+                       if (log.isTraceEnabled()) {
+                               ApplicationContext applicationContext = (ApplicationContext) bundleContext
+                                               .getService(sr);
+                               int beanDefCount = applicationContext.getBeanDefinitionCount();
+                               log.debug(" " + beanDefCount + " beans in app context of "
+                                               + bundle.getSymbolicName()
+                                               + ", average init time per bean=" + (end - begin)
+                                               / beanDefCount + "ms");
+                       }
+
+                       bundleContext.ungetService(sr);
+
                } catch (Exception e) {
                        throw new SlcException("Cannot update bundle " + osgiBundle, e);
                }
@@ -58,7 +129,6 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
 
        /** Updates bundle synchronously. */
        protected void updateSynchronous(Bundle bundle) throws BundleException {
-               // int originalState = bundle.getState();
                bundle.update();
                boolean waiting = true;
 
@@ -69,11 +139,9 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                                        || state == Bundle.RESOLVED)
                                waiting = false;
 
-                       sleep(100);
-                       if (System.currentTimeMillis() - begin > defaultTimeout)
-                               throw new SlcException("Update of bundle "
-                                               + bundle.getSymbolicName()
-                                               + " timed out. Bundle state = " + bundle.getState());
+                       sleepWhenPolling();
+                       checkTimeout(begin, "Update of bundle " + bundle.getSymbolicName()
+                                       + " timed out. Bundle state = " + bundle.getState());
                } while (waiting);
 
                if (log.isTraceEnabled())
@@ -94,11 +162,9 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                        if (bundle.getState() == Bundle.ACTIVE)
                                waiting = false;
 
-                       sleep(100);
-                       if (System.currentTimeMillis() - begin > defaultTimeout)
-                               throw new SlcException("Start of bundle "
-                                               + bundle.getSymbolicName()
-                                               + " timed out. Bundle state = " + bundle.getState());
+                       sleepWhenPolling();
+                       checkTimeout(begin, "Start of bundle " + bundle.getSymbolicName()
+                                       + " timed out. Bundle state = " + bundle.getState());
                } while (waiting);
 
                if (log.isTraceEnabled())
@@ -120,11 +186,9 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                                        && bundle.getState() != Bundle.STOPPING)
                                waiting = false;
 
-                       sleep(100);
-                       if (System.currentTimeMillis() - begin > defaultTimeout)
-                               throw new SlcException("Stop of bundle "
-                                               + bundle.getSymbolicName()
-                                               + " timed out. Bundle state = " + bundle.getState());
+                       sleepWhenPolling();
+                       checkTimeout(begin, "Stop of bundle " + bundle.getSymbolicName()
+                                       + " timed out. Bundle state = " + bundle.getState());
                } while (waiting);
 
                if (log.isTraceEnabled())
@@ -138,14 +202,23 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                PackageAdmin packageAdmin = (PackageAdmin) bundleContext
                                .getService(packageAdminRef);
                Bundle[] bundles = { bundle };
-               packageAdmin.refreshPackages(bundles);
 
+               long begin = System.currentTimeMillis();
                synchronized (refreshedPackageSem) {
+                       packagesRefreshed = false;
+                       packageAdmin.refreshPackages(bundles);
                        try {
                                refreshedPackageSem.wait(defaultTimeout);
                        } catch (InterruptedException e) {
                                // silent
                        }
+                       if (!packagesRefreshed) {
+                               long now = System.currentTimeMillis();
+                               throw new SlcException("Packages not refreshed after "
+                                               + (now - begin) + "ms");
+                       } else {
+                               packagesRefreshed = false;
+                       }
                }
 
                if (log.isTraceEnabled())
@@ -155,6 +228,7 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
        public void frameworkEvent(FrameworkEvent event) {
                if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
                        synchronized (refreshedPackageSem) {
+                               packagesRefreshed = true;
                                refreshedPackageSem.notifyAll();
                        }
                }
@@ -173,20 +247,27 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                        if (sfs != null)
                                waiting = false;
 
-                       sleep(100);
-                       if (System.currentTimeMillis() - begin > defaultTimeout)
-                               throw new SlcException("Search of services " + clss
-                                               + " with filter " + filter + " timed out.");
+                       sleepWhenPolling();
+                       checkTimeout(begin, "Search of services " + clss + " with filter "
+                                       + filter + " timed out.");
                } while (waiting);
 
                return sfs;
        }
 
-       protected void sleep(long ms) {
+       protected void checkTimeout(long begin, String msg) {
+               long now = System.currentTimeMillis();
+               if (now - begin > defaultTimeout)
+                       throw new SlcException(msg + " (timeout after " + (now - begin)
+                                       + "ms)");
+
+       }
+
+       protected void sleepWhenPolling() {
                try {
-                       Thread.sleep(ms);
+                       Thread.sleep(pollingPeriod);
                } catch (InterruptedException e) {
-                       // silent
+                       throw new SlcException("Polling interrupted");
                }
        }
 
@@ -199,11 +280,16 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
        }
 
        @SuppressWarnings(value = { "unchecked" })
-       public <T> T getSingleService(Class<T> clss, String filter) {
+       public <T> T getSingleService(Class<T> clss, String filter,
+                       Boolean synchronous) {
                Assert.isTrue(OsgiFilterUtils.isValidFilter(filter), "valid filter");
                ServiceReference[] sfs;
                try {
-                       sfs = bundleContext.getServiceReferences(clss.getName(), filter);
+                       if (synchronous)
+                               sfs = getServiceRefSynchronous(clss.getName(), filter);
+                       else
+                               sfs = bundleContext
+                                               .getServiceReferences(clss.getName(), filter);
                } catch (InvalidSyntaxException e) {
                        throw new SlcException("Cannot retrieve service reference for "
                                        + filter, e);
@@ -217,15 +303,20 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                return (T) bundleContext.getService(sfs[0]);
        }
 
-       public <T> T getSingleServiceStrict(Class<T> clss, String filter) {
-               T service = getSingleService(clss, filter);
+       public <T> T getSingleServiceStrict(Class<T> clss, String filter,
+                       Boolean synchronous) {
+               T service = getSingleService(clss, filter, synchronous);
                if (service == null)
                        throw new SlcException("No execution flow found for " + filter);
                else
                        return service;
        }
 
-       /** @return the related bundle or null if not found */
+       /**
+        * @param osgiBundle
+        *            cannot be null
+        * @return the related bundle or null if not found
+        */
        public Bundle findRelatedBundle(OsgiBundle osgiBundle) {
                Bundle bundle = null;
                if (osgiBundle.getInternalBundleId() != null) {
@@ -233,16 +324,34 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                        Assert.isTrue(
                                        osgiBundle.getName().equals(bundle.getSymbolicName()),
                                        "symbolic name consistent");
-                       Assert.isTrue(osgiBundle.getVersion().equals(
-                                       bundle.getHeaders().get(Constants.BUNDLE_VERSION)),
-                                       "version consistent");
-               } else {
-                       for (Bundle b : bundleContext.getBundles()) {
+                       if (osgiBundle.getVersion() != null)
+                               Assert.isTrue(
+                                               osgiBundle.getVersion().equals(
+                                                               bundle.getHeaders().get(
+                                                                               Constants.BUNDLE_VERSION)),
+                                               "version consistent");
+               } else if (osgiBundle.getVersion() == null
+                               || osgiBundle.getVersion().equals("0.0.0")) {
+                       bundle = OsgiBundleUtils.findBundleBySymbolicName(bundleContext,
+                                       osgiBundle.getName());
+               } else {// scan all bundles
+                       bundles: for (Bundle b : bundleContext.getBundles()) {
+                               if (b.getSymbolicName() == null) {
+                                       log.warn("Bundle " + b + " has no symbolic name defined.");
+                                       continue bundles;
+                               }
+
                                if (b.getSymbolicName().equals(osgiBundle.getName())) {
-                                       if (b.getHeaders().get(Constants.BUNDLE_VERSION).equals(
-                                                       osgiBundle.getVersion())) {
+                                       if (osgiBundle.getVersion() == null) {
+                                               bundle = b;
+                                               break bundles;
+                                       }
+
+                                       if (b.getHeaders().get(Constants.BUNDLE_VERSION)
+                                                       .equals(osgiBundle.getVersion())) {
                                                bundle = b;
                                                osgiBundle.setInternalBundleId(b.getBundleId());
+                                               break bundles;
                                        }
                                }
                        }
@@ -283,9 +392,28 @@ public class BundlesManager implements BundleContextAware, FrameworkListener,
                this.defaultTimeout = defaultTimeout;
        }
 
-       /** Temporary internal access for {@link OsgiExecutionModulesManager} */
-       BundleContext getBundleContext() {
+       /**
+        * Use with caution since it may interfer with some cached information
+        * within this object
+        */
+       public BundleContext getBundleContext() {
                return bundleContext;
        }
 
+       public void setPollingPeriod(Long pollingPeriod) {
+               this.pollingPeriod = pollingPeriod;
+       }
+
+       public void onOsgiApplicationEvent(OsgiBundleApplicationContextEvent event) {
+               if (event instanceof OsgiBundleContextRefreshedEvent) {
+                       log.debug("App context refreshed: " + event);
+               } else if (event instanceof OsgiBundleContextFailedEvent) {
+                       log.debug("App context failed: " + event);
+               }
+               if (event instanceof OsgiBundleContextClosedEvent) {
+                       log.debug("App context closed: " + event);
+               }
+
+       }
+
 }