This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 3a715de8f1 FELIX-6470 : ConfigAdmin/Coordinator handling not optimal
3a715de8f1 is described below

commit 3a715de8f163cd967e412db857ac2236cd891fd2
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Tue Oct 25 16:48:14 2022 +0200

    FELIX-6470 : ConfigAdmin/Coordinator handling not optimal
---
 .../apache/felix/cm/impl/ConfigurationManager.java |  24 +--
 .../org/apache/felix/cm/impl/CoordinatorUtil.java  | 113 ++++++++++----
 .../cm/integration/CoordinatorIntegrationTest.java | 171 +++++++++++++++++++++
 3 files changed, 270 insertions(+), 38 deletions(-)

diff --git 
a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java 
b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
index 44d930a7a1..a2068de0e1 100644
--- 
a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
+++ 
b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
@@ -1057,9 +1057,9 @@ public class ConfigurationManager implements 
BundleListener
      * ManagedService is registered with multiple PIDs an instance of this
      * class is used for each registered PID.
      */
-    private class ManagedServiceUpdate implements Runnable
+    public class ManagedServiceUpdate implements Runnable
     {
-        private final String[] pids;
+        public final List<String> pids = new ArrayList<>();
 
         private final ServiceReference<ManagedService> sr;
 
@@ -1068,7 +1068,9 @@ public class ConfigurationManager implements 
BundleListener
 
         ManagedServiceUpdate( String[] pids, ServiceReference<ManagedService> 
sr, ConfigurationMap<?> configs )
         {
-            this.pids = pids;
+            for(final String pid : pids) {
+                this.pids.add(pid);
+            }
             this.sr = sr;
             this.configs = configs;
         }
@@ -1124,7 +1126,7 @@ public class ConfigurationManager implements 
BundleListener
             }
 
             Log.logger.log( LogService.LOG_DEBUG, "Updating service {0} with 
configuration {1}@{2}", new Object[]
-                    { servicePid, configPid, new Long( revision ) } );
+                    { servicePid, configPid, revision } );
 
             managedServiceTracker.provideConfiguration( sr, configPid, null, 
properties, revision, this.configs );
         }
@@ -1132,7 +1134,7 @@ public class ConfigurationManager implements 
BundleListener
         @Override
         public String toString()
         {
-            return "ManagedService Update: pid=" + Arrays.asList( pids );
+            return "ManagedService Update: pid=" + pids;
         }
     }
 
@@ -1143,7 +1145,7 @@ public class ConfigurationManager implements 
BundleListener
      * multiple PIDs an instance of this class is used for each registered
      * PID.
      */
-    private class ManagedServiceFactoryUpdate implements Runnable
+    public class ManagedServiceFactoryUpdate implements Runnable
     {
         private final String[] factoryPids;
 
@@ -1274,7 +1276,7 @@ public class ConfigurationManager implements 
BundleListener
         }
     }
 
-    private abstract class ConfigurationProvider<T> implements Runnable
+    public abstract class ConfigurationProvider<T> implements Runnable
     {
 
         protected final ConfigurationImpl config;
@@ -1368,7 +1370,7 @@ public class ConfigurationManager implements 
BundleListener
      * they are subscribed to. This may cause the configuration to be
      * supplied to multiple services.
      */
-    private class UpdateConfiguration extends ConfigurationProvider
+    public class UpdateConfiguration extends ConfigurationProvider
     {
 
         UpdateConfiguration( final ConfigurationImpl config )
@@ -1381,7 +1383,7 @@ public class ConfigurationManager implements 
BundleListener
         public void run()
         {
             Log.logger.log( LogService.LOG_DEBUG, "Updating configuration {0} 
to revision #{1}", new Object[]
-                    { config.getPid(), new Long( revision ) } );
+                    { config.getPid(), revision } );
 
             final List<ServiceReference<?>> srList = 
this.getHelper().getServices( getTargetedServicePid() );
             if ( !srList.isEmpty() )
@@ -1449,7 +1451,7 @@ public class ConfigurationManager implements 
BundleListener
      * <code>ManagedService[Factory]</code> services of a configuration
      * being deleted.
      */
-    private class DeleteConfiguration extends ConfigurationProvider
+    public class DeleteConfiguration extends ConfigurationProvider
     {
 
         private final String configLocation;
@@ -1511,7 +1513,7 @@ public class ConfigurationManager implements 
BundleListener
         }
     }
 
-    private class LocationChanged extends ConfigurationProvider
+    public class LocationChanged extends ConfigurationProvider
     {
         private final String oldLocation;
 
diff --git 
a/configadmin/src/main/java/org/apache/felix/cm/impl/CoordinatorUtil.java 
b/configadmin/src/main/java/org/apache/felix/cm/impl/CoordinatorUtil.java
index a79b6ffe4c..455ef83f7b 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/CoordinatorUtil.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/CoordinatorUtil.java
@@ -19,7 +19,10 @@
 package org.apache.felix.cm.impl;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import org.osgi.service.coordinator.Coordination;
 import org.osgi.service.coordinator.Coordinator;
@@ -28,64 +31,120 @@ import org.osgi.service.coordinator.Participant;
 /**
  * Utility class for coordinations
  */
-public class CoordinatorUtil
-{
+public class CoordinatorUtil {
+
+    public static final class Notifier implements Participant {
 
-    public static final class Notifier implements Participant
-    {
         private final List<Runnable> runnables = new ArrayList<Runnable>();
 
         private final UpdateThread thread;
 
-        public Notifier(final UpdateThread t)
-        {
+        public Notifier(final UpdateThread t) {
             this.thread = t;
         }
 
-        private void execute()
-        {
-            for(final Runnable r : runnables)
-            {
+        /**
+         * Compact the notification if there is more than one operation for a 
given pid
+         * @return The compacted list
+         */
+        private List<Runnable> compact() {
+            final List<Runnable> result = new ArrayList<>(this.runnables);
+            this.runnables.clear();
+
+            final Map<String, ConfigurationManager.ConfigurationProvider> 
lastOperations = new HashMap<>();
+            final Map<String, Boolean> firstOperationIsCreate = new 
HashMap<>();
+
+            for(int i=0;i<result.size();i++) {
+                final Runnable current = result.get(i);
+
+                if ( current instanceof 
ConfigurationManager.UpdateConfiguration ) {
+                    final ConfigurationManager.UpdateConfiguration up = 
(ConfigurationManager.UpdateConfiguration)current;
+                    final String pid = 
up.getTargetedServicePid().getServicePid();
+                    if ( firstOperationIsCreate.get(pid) == null ) {
+                        firstOperationIsCreate.put(pid, up.revision == 1);
+                    }
+                    final ConfigurationManager.ConfigurationProvider last = 
lastOperations.get(pid);
+                    lastOperations.put(pid, up);
+                    if ( last != null ) {
+                        result.remove(last);
+                        i--;
+                    }
+                } else if ( current instanceof 
ConfigurationManager.DeleteConfiguration ) {
+                    final ConfigurationManager.DeleteConfiguration up = 
(ConfigurationManager.DeleteConfiguration)current;
+                    final String pid = 
up.getTargetedServicePid().getServicePid();
+                    if ( firstOperationIsCreate.get(pid) == null ) {
+                        firstOperationIsCreate.put(pid, false);
+                    }
+                    final ConfigurationManager.ConfigurationProvider last = 
lastOperations.get(pid);
+                    lastOperations.put(pid, up);
+                    if ( last != null ) {
+                        if ( !firstOperationIsCreate.get(pid) ) {
+                            firstOperationIsCreate.remove(pid);
+                            result.remove(i);
+                            i--;
+                            lastOperations.remove(pid);
+                        }
+                        result.remove(last);
+                        i--;
+                    }
+                }
+            }
+
+            final Iterator<Runnable> iter = result.iterator();
+            while ( iter.hasNext() ) {
+                final Runnable current = iter.next();
+                if ( current instanceof 
ConfigurationManager.ManagedServiceUpdate ) {
+                    final ConfigurationManager.ManagedServiceUpdate up = 
(ConfigurationManager.ManagedServiceUpdate)current;
+                    final Iterator<String> pidIter = up.pids.iterator();
+                    while ( pidIter.hasNext() ) {
+                        final String pid = pidIter.next();
+                        final ConfigurationManager.ConfigurationProvider last 
= lastOperations.get(pid);
+                        if ( last != null ) {
+                            pidIter.remove();
+                        }
+                    }
+                    if ( up.pids.isEmpty() ) {
+                        iter.remove();
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        private void execute() {
+            for(final Runnable r : compact()) {
                 this.thread.schedule(r);
             }
-            runnables.clear();
         }
 
         @Override
-        public void ended(Coordination coordination) throws Exception
-        {
+        public void ended(Coordination coordination) throws Exception {
             execute();
         }
 
         @Override
-        public void failed(Coordination coordination) throws Exception
-        {
+        public void failed(Coordination coordination) throws Exception {
             execute();
         }
 
-        public void add(final Runnable t)
-        {
+        public void add(final Runnable t) {
             runnables.add(t);
         }
     }
 
-    public static boolean addToCoordination(final Object srv, final 
UpdateThread thread, final Runnable task)
-    {
+    public static boolean addToCoordination(final Object srv, final 
UpdateThread thread, final Runnable task) {
         final Coordinator coordinator = (Coordinator) srv;
         Coordination c = coordinator.peek();
-        if ( c != null && !c.isTerminated() )
-        {
+        if ( c != null && !c.isTerminated() ) {
             Notifier n = null;
-            for(final Participant p : c.getParticipants())
-            {
-                if ( p instanceof Notifier )
-                {
+            for(final Participant p : c.getParticipants()) {
+                if ( p instanceof Notifier ) {
                     n = (Notifier) p;
                     break;
                 }
             }
-            if ( n == null )
-            {
+            if ( n == null ) {
                 n = new Notifier(thread);
                 c.addParticipant(n);
             }
diff --git 
a/configadmin/src/test/java/org/apache/felix/cm/integration/CoordinatorIntegrationTest.java
 
b/configadmin/src/test/java/org/apache/felix/cm/integration/CoordinatorIntegrationTest.java
new file mode 100644
index 0000000000..c3422d6e52
--- /dev/null
+++ 
b/configadmin/src/test/java/org/apache/felix/cm/integration/CoordinatorIntegrationTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+/*
+ * Note that this test is copied from the OSGi TCK https://github.com/osgi/osgi
+ * with permission of the author Stefan Bischof.
+ */
+package org.apache.felix.cm.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Coordinator;
+
+@RunWith(PaxExam.class)
+public class CoordinatorIntegrationTest extends ConfigurationTestBase {
+
+       @Override
+       protected Option[] additionalConfiguration() {
+               return new Option [] {
+                       mavenBundle("org.apache.felix", 
"org.apache.felix.coordinator", "1.0.2")
+               };
+       }
+
+       <T> T getService(Class<T> cls) {
+               ServiceReference<T> sref = 
bundleContext.getServiceReference(cls);
+               if (sref == null)
+                       return null;
+
+               return bundleContext.getService(sref);
+       }
+
+       void sleep() throws InterruptedException {
+               TimeUnit.MILLISECONDS.sleep(4000);
+       }
+
+       @Test
+       public void 
test_deliver_existing_Configuration_to_later_registered_ManagedService()
+       throws Exception {
+               ConfigurationAdmin cm = getService(ConfigurationAdmin.class);
+               Coordinator c = getService(Coordinator.class);
+               Coordination coord = c.begin("cm-test1", 0);
+               final List<String> events = new ArrayList<>();
+
+               String pid = getClass().getName() + ".mstestpid2";
+
+               try {
+                       // create the configuration
+                       Dictionary<String,Object> props = new Hashtable<>();
+                       props.put("key", "value");
+
+                       Configuration conf = cm.getConfiguration(pid);
+                       conf.update(props);
+
+                       // add managed service
+                       Dictionary<String,Object> msProps = new Hashtable<>();
+                       msProps.put(Constants.SERVICE_PID, pid);
+
+                       
bundleContext.registerService(ManagedService.class.getName(),
+                                       new ManagedService() {
+
+                                               @Override
+                                               public void 
updated(Dictionary<String, ? > properties)
+                                                               throws 
ConfigurationException {
+                                                       
events.add((String)properties.get("key2"));
+                                               }
+
+                                       }, msProps);
+
+                       // update configuration
+                       props.put("key2", "value2");
+                       conf.update(props);
+
+                       assertEquals(0, events.size());
+               } finally {
+                       coord.end();
+               }
+
+               // wait and verify listener
+               sleep();
+
+               assertEquals(1, events.size());
+        // last update
+               assertEquals("value2", events.get(0));
+       }
+
+       @Test
+       public void test_create_managedservice_delete()
+       throws Exception {
+               ConfigurationAdmin cm = getService(ConfigurationAdmin.class);
+               Coordinator c = getService(Coordinator.class);
+               Coordination coord = c.begin("cm-test1", 0);
+               final List<Boolean> events = new ArrayList<>();
+
+               String pid = getClass().getName() + ".mstestpid3";
+
+               try {
+                       // create the configuration
+                       Dictionary<String,Object> props = new Hashtable<>();
+                       props.put("key", "value");
+
+                       Configuration conf = cm.getConfiguration(pid);
+                       conf.update(props);
+
+                       // add managed service
+                       Dictionary<String,Object> msProps = new Hashtable<>();
+                       msProps.put(Constants.SERVICE_PID, pid);
+
+                       
bundleContext.registerService(ManagedService.class.getName(),
+                                       new ManagedService() {
+
+                                               @Override
+                                               public void 
updated(Dictionary<String, ? > properties)
+                                                               throws 
ConfigurationException {
+                                                       events.add(properties 
!= null);
+                                               }
+
+                                       }, msProps);
+
+                       // update configuration
+                       props.put("key2", "value2");
+                       conf.update(props);
+
+                       // delete configuration
+                       conf.delete();
+
+                       assertEquals(0, events.size());
+               } finally {
+                       coord.end();
+               }
+
+               // wait and verify listener
+               sleep();
+
+               assertEquals(1, events.size());
+               assertFalse(events.get(0));// no configuration, update with null
+       }
+}
\ No newline at end of file

Reply via email to