You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gg...@apache.org on 2015/07/21 11:24:05 UTC

[07/13] camel git commit: [CAMEL-8948] Precise synchronization with BP container of the test bundle

[CAMEL-8948] Precise synchronization with BP container of the test bundle

(cherry picked from commit 692e479b940b1198df3cec386c16aa4fd071e16c)


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/d0586a23
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/d0586a23
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/d0586a23

Branch: refs/heads/camel-2.15.x
Commit: d0586a23e54af00611aba3d72f29d945ebdb9f31
Parents: ec5a7aa
Author: Grzegorz Grzybek <gr...@gmail.com>
Authored: Fri Jul 17 21:40:16 2015 +0200
Committer: Grzegorz Grzybek <gr...@gmail.com>
Committed: Tue Jul 21 09:45:02 2015 +0200

----------------------------------------------------------------------
 .../test/blueprint/CamelBlueprintHelper.java    | 52 ++++++++++-
 .../blueprint/CamelBlueprintTestSupport.java    | 91 +++++++-------------
 .../org/apache/camel/test/blueprint/Main.java   |  3 +-
 3 files changed, 82 insertions(+), 64 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/d0586a23/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintHelper.java
----------------------------------------------------------------------
diff --git a/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintHelper.java b/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintHelper.java
index 126f68a..b920f9f 100644
--- a/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintHelper.java
+++ b/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintHelper.java
@@ -35,6 +35,9 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.jar.JarInputStream;
 
 import de.kalpatec.pojosr.framework.PojoServiceRegistryFactoryImpl;
@@ -58,6 +61,9 @@ import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.blueprint.container.BlueprintEvent;
+import org.osgi.service.blueprint.container.BlueprintListener;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.util.tracker.ServiceTracker;
@@ -107,6 +113,9 @@ public final class CamelBlueprintHelper {
         String uid = "" + System.currentTimeMillis();
         String tempDir = "target/bundles/" + uid;
         System.setProperty("org.osgi.framework.storage", tempDir);
+        // explicitly set this to "false" - we will not depend on the order of starting bundles,
+        // (and running their BP containers) but we will have to do more synchornization
+        System.setProperty("org.apache.aries.blueprint.synchronous", "false");
         createDirectory(tempDir);
 
         // use another directory for the jar of the bundle as it cannot be in the same directory
@@ -169,7 +178,8 @@ public final class CamelBlueprintHelper {
     // pick up persistent file configuration
     @SuppressWarnings({"unchecked", "rawtypes"})
     public static void setPersistentFileForConfigAdmin(BundleContext bundleContext, String pid,
-                                                       String fileName, Dictionary props) throws IOException {
+                                                       String fileName, final Dictionary props,
+                                                       String symbolicName, Set<Long> bpEvents) throws IOException, InterruptedException {
         if (pid != null) {
             if (fileName == null) {
                 throw new IllegalArgumentException("The persistent file should not be null");
@@ -186,9 +196,20 @@ public final class CamelBlueprintHelper {
                 if (configAdmin != null) {
                     // ensure we update
                     // we *have to* use "null" as 2nd arg to have correct bundle location for Configuration object
-                    Configuration config = configAdmin.getConfiguration(pid, null);
+                    final Configuration config = configAdmin.getConfiguration(pid, null);
                     LOG.info("Updating ConfigAdmin {} by overriding properties {}", config, props);
-                    config.update(props);
+                    // we will have update and in consequence, BP container reload, let's wait for it to
+                    // be CREATED again
+                    CamelBlueprintHelper.waitForBlueprintContainer(bpEvents, bundleContext, symbolicName, BlueprintEvent.CREATED, new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                config.update(props);
+                            } catch (IOException e) {
+                                throw new RuntimeException(e.getMessage(), e);
+                            }
+                        }
+                    });
                 }
 
             }
@@ -226,6 +247,7 @@ public final class CamelBlueprintHelper {
             // Note that the tracker is not closed to keep the reference
             // This is buggy, as the service reference may change i think
             Object svc = tracker.waitForService(timeout);
+
             if (svc == null) {
                 Dictionary<?, ?> dic = bundleContext.getBundle().getHeaders();
                 LOG.warn("Test bundle headers: " + explode(dic));
@@ -248,6 +270,30 @@ public final class CamelBlueprintHelper {
         }
     }
 
+    /**
+     * Synchronization method to wait for particular state of BlueprintContainer under test.
+     */
+    public static void waitForBlueprintContainer(final Set<Long> eventHistory, BundleContext context, final String symbolicName, final int bpEvent, final Runnable runAndWait) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServiceRegistration<BlueprintListener> registration = context.registerService(BlueprintListener.class, new BlueprintListener() {
+            @Override
+            public void blueprintEvent(BlueprintEvent event) {
+                if (event.getType() == bpEvent && event.getBundle().getSymbolicName().equals(symbolicName)) {
+                    // we skip events that we've already seen
+                    // it works with BP container reloads if next CREATE state is at least 1ms after previous one
+                    if (eventHistory == null || eventHistory.add(event.getTimestamp())) {
+                        latch.countDown();
+                    }
+                }
+            }
+        }, null);
+        if (runAndWait != null) {
+            runAndWait.run();
+        }
+        latch.await(CamelBlueprintHelper.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
+        registration.unregister();
+    }
+
     protected static TinyBundle createTestBundle(String name, String version, String descriptors) throws FileNotFoundException, MalformedURLException {
         TinyBundle bundle = TinyBundles.newBundle();
         for (URL url : getBlueprintDescriptors(descriptors)) {

http://git-wip-us.apache.org/repos/asf/camel/blob/d0586a23/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java
----------------------------------------------------------------------
diff --git a/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java b/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java
index 71e5864..030f99e 100644
--- a/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java
+++ b/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java
@@ -17,14 +17,14 @@
 package org.apache.camel.test.blueprint;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Dictionary;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.component.properties.PropertiesComponent;
@@ -36,13 +36,9 @@ import org.junit.AfterClass;
 import org.junit.Before;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.blueprint.container.BlueprintContainer;
 import org.osgi.service.blueprint.container.BlueprintEvent;
-import org.osgi.service.blueprint.container.BlueprintListener;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ConfigurationEvent;
-import org.osgi.service.cm.ConfigurationListener;
 
 /**
  * Base class for OSGi Blueprint unit tests with Camel.
@@ -93,7 +89,7 @@ public abstract class CamelBlueprintTestSupport extends CamelTestSupport {
         }
 
         // must reuse props as we can do both load from .cfg file and override afterwards
-        Dictionary props = new Properties();
+        final Dictionary props = new Properties();
 
         // load configuration file
         String[] file = loadConfigAdminConfigurationFile();
@@ -101,79 +97,54 @@ public abstract class CamelBlueprintTestSupport extends CamelTestSupport {
             throw new IllegalArgumentException("The returned String[] from loadConfigAdminConfigurationFile must be of length 2, was " + file.length);
         }
 
+        // if blueprint XML uses <cm:property-placeholder> (any update-strategy and any default properties)
+        // - org.apache.aries.blueprint.compendium.cm.ManagedObjectManager.register() is called
+        // - ManagedServiceUpdate is scheduled in felix.cm
+        // - org.apache.felix.cm.impl.ConfigurationImpl.setDynamicBundleLocation() is called
+        // - CM_LOCATION_CHANGED event is fired
+        // - if BP was alredy created, it's <cm:property-placeholder> receives the event and
+        // - org.apache.aries.blueprint.compendium.cm.CmPropertyPlaceholder.updated() is called,
+        //   but no BP reload occurs
+        // we will however wait for BP container of the test bundle to become CREATED for the first time
+        // each configadmin update *may* lead to reload of BP container, if it uses <cm:property-placeholder>
+        // with update-strategy="reload"
+
+        // we will gather timestamps of BP events. We don't want to be fooled but repeated events related
+        // to the same state of BP container
+        Set<Long> bpEvents = new HashSet<>();
+
+        CamelBlueprintHelper.waitForBlueprintContainer(bpEvents, answer, symbolicName, BlueprintEvent.CREATED, null);
+
         if (file != null) {
             if (!new File(file[0]).exists()) {
                 throw new IllegalArgumentException("The provided file \"" + file[0] + "\" from loadConfigAdminConfigurationFile doesn't exist");
             }
-            CamelBlueprintHelper.setPersistentFileForConfigAdmin(answer, file[1], file[0], props);
+            CamelBlueprintHelper.setPersistentFileForConfigAdmin(answer, file[1], file[0], props, symbolicName, bpEvents);
         }
 
         // allow end user to override properties
         String pid = useOverridePropertiesWithConfigAdmin(props);
         if (pid != null) {
-            // we will update the configuration now. As OSGi is highly asynchronous, we need to make the tests as repeatable as possible
-            // the problem is when blueprint container defines cm:property-placeholder with update-strategy="reload"
-            // updating the configuration leads to (felix framework + aries blueprint):
-            // 1. schedule org.apache.felix.cm.impl.ConfigurationManager.UpdateConfiguration object to run in config admin thread
-            // 2. this thread calls org.apache.felix.cm.impl.ConfigurationImpl#tryBindLocation()
-            // 3. org.osgi.service.cm.ConfigurationEvent#CM_LOCATION_CHANGED is send
-            // 4. org.apache.aries.blueprint.compendium.cm.ManagedObjectManager.ConfigurationWatcher#updated() is invoked
-            // 5. new Thread().start() is called
-            // 6. org.apache.aries.blueprint.compendium.cm.ManagedObject#updated() is called
-            // 7. org.apache.aries.blueprint.compendium.cm.CmPropertyPlaceholder#updated() is called
-            // 8. new Thread().start() is called
-            // 9. org.apache.aries.blueprint.services.ExtendedBlueprintContainer#reload() is called which destroys everything in BP container
-            // 10. finally reload of BP container is scheduled (in yet another thread)
-            //
-            // if we start/use camel context between point 9 and 10 we may get many different errors described in https://issues.apache.org/jira/browse/ARIES-961
-
-            // to synchronize this (main) thread of execution with the asynchronous series of events, we can register the following listener.
-            // this way be sure that we got to point 3
-            final CountDownLatch latch = new CountDownLatch(2);
-            answer.registerService(ConfigurationListener.class, new ConfigurationListener() {
-                @Override
-                public void configurationEvent(ConfigurationEvent event) {
-                    if (event.getType() == ConfigurationEvent.CM_LOCATION_CHANGED) {
-                        latch.countDown();
-                    }
-                    // when we update the configuration, BP container will be reloaded as well
-                    // hoping that we get the event after *second* restart, let's register the listener
-                    answer.registerService(BlueprintListener.class, new BlueprintListener() {
-                        @Override
-                        public void blueprintEvent(BlueprintEvent event) {
-                            if (event.getType() == BlueprintEvent.CREATED && event.getBundle().getSymbolicName().equals(symbolicName)) {
-                                latch.countDown();
-                            }
-                        }
-                    }, null);
-                }
-            }, null);
-
+            // we will update the configuration again
             ConfigurationAdmin configAdmin = CamelBlueprintHelper.getOsgiService(answer, ConfigurationAdmin.class);
             // passing null as second argument ties the configuration to correct bundle.
             // using single-arg method causes:
             // *ERROR* Cannot use configuration xxx.properties for [org.osgi.service.cm.ManagedService, id=N, bundle=N/jar:file:xyz.jar!/]: No visibility to configuration bound to file:pojosr
-            Configuration config = configAdmin.getConfiguration(pid, null);
+            final Configuration config = configAdmin.getConfiguration(pid, null);
             if (config == null) {
                 throw new IllegalArgumentException("Cannot find configuration with pid " + pid + " in OSGi ConfigurationAdmin service.");
             }
             log.info("Updating ConfigAdmin {} by overriding properties {}", config, props);
-            config.update(props);
-
-            latch.await(CamelBlueprintHelper.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
-        } else {
-            // let's wait for BP container to start
-            final CountDownLatch latch = new CountDownLatch(1);
-            answer.registerService(BlueprintListener.class, new BlueprintListener() {
+            CamelBlueprintHelper.waitForBlueprintContainer(bpEvents, answer, symbolicName, BlueprintEvent.CREATED, new Runnable() {
                 @Override
-                public void blueprintEvent(BlueprintEvent event) {
-                    if (event.getType() == BlueprintEvent.CREATED && event.getBundle().getSymbolicName().equals(symbolicName)) {
-                        latch.countDown();
+                public void run() {
+                    try {
+                        config.update(props);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e.getMessage(), e);
                     }
                 }
-            }, null);
-
-            latch.await(CamelBlueprintHelper.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
+            });
         }
 
         return answer;

http://git-wip-us.apache.org/repos/asf/camel/blob/d0586a23/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/Main.java
----------------------------------------------------------------------
diff --git a/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/Main.java b/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/Main.java
index 3094d5e..cadc063 100644
--- a/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/Main.java
+++ b/components/camel-test-blueprint/src/main/java/org/apache/camel/test/blueprint/Main.java
@@ -100,7 +100,8 @@ public class Main extends MainSupport {
             }
             LOG.debug("Starting Blueprint XML file: " + descriptors);
             bundleContext = createBundleContext(bundleName);
-            CamelBlueprintHelper.setPersistentFileForConfigAdmin(bundleContext, configAdminPid, configAdminFileName, new Properties());
+            CamelBlueprintHelper.setPersistentFileForConfigAdmin(bundleContext, configAdminPid, configAdminFileName, new Properties(),
+                                                                 bundleName, null);
             camelContext = CamelBlueprintHelper.getOsgiService(bundleContext, CamelContext.class);
             if (camelContext == null) {
                 throw new IllegalArgumentException("Cannot find CamelContext in blueprint XML file: " + descriptors);