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

kwin pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-osgi-mock.git


The following commit(s) were added to refs/heads/master by this push:
     new d2c969d  SLING-10919 automatically generate service properties 
"component.name" and "component.id" (#12)
d2c969d is described below

commit d2c969d094fb8d090619a6e28cdf2678db276b35
Author: Konrad Windszus <[email protected]>
AuthorDate: Fri Nov 19 21:00:37 2021 +0100

    SLING-10919 automatically generate service properties "component.name" and 
"component.id" (#12)
    
    distinguish between service and DS component properties
    component properties starting with "." are private and must not be exposed 
as service properties
---
 .../testing/mock/osgi/DictionaryCollector.java     | 71 ++++++++++++++++++++++
 .../sling/testing/mock/osgi/MapMergeUtil.java      | 27 +++++---
 .../apache/sling/testing/mock/osgi/MockOsgi.java   | 14 +++--
 .../mock/osgi/context/OsgiContextImplTest.java     | 28 +++++++--
 .../osgi/testsvc/osgiserviceutil/Service3.java     |  2 +-
 5 files changed, 125 insertions(+), 17 deletions(-)

diff --git 
a/core/src/main/java/org/apache/sling/testing/mock/osgi/DictionaryCollector.java
 
b/core/src/main/java/org/apache/sling/testing/mock/osgi/DictionaryCollector.java
new file mode 100644
index 0000000..f4c05f9
--- /dev/null
+++ 
b/core/src/main/java/org/apache/sling/testing/mock/osgi/DictionaryCollector.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+package org.apache.sling.testing.mock.osgi;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+public class DictionaryCollector<K, V> implements Collector<Entry<K,V>, 
Hashtable<K,V>, Dictionary<K,V>> {
+
+    private final Function<? super Entry<K,V>, ? extends K> keyMapper;
+    private final Function<? super Entry<K,V>, ? extends V> valueMapper;
+    
+    public DictionaryCollector(Function<? super Entry<K,V>, ? extends K> 
keyMapper, Function<? super Entry<K,V>, ? extends V> valueMapper) {
+        super();
+        this.keyMapper = keyMapper;
+        this.valueMapper = valueMapper;
+    }
+
+    @Override
+    public Supplier<Hashtable<K,V>> supplier() {
+        return Hashtable::new;
+    }
+
+    @Override
+    public BiConsumer<Hashtable<K,V>, Entry<K,V>> accumulator() {
+        return (hashTable, entry) -> { if (valueMapper.apply(entry) != null) { 
hashTable.put(keyMapper.apply(entry), valueMapper.apply(entry)); } };
+    }
+
+    @Override
+    public BinaryOperator<Hashtable<K,V>> combiner() {
+        return (dictionary1, dictionary2) -> {
+            dictionary1.putAll(dictionary2);
+            return dictionary1;
+        };
+    }
+
+    @Override
+    public Function<Hashtable<K,V>, Dictionary<K,V>> finisher() {
+        return table -> table;
+    }
+
+    @Override
+    public Set<Characteristics> characteristics() {
+        return Collections.singleton(Characteristics.UNORDERED);
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/sling/testing/mock/osgi/MapMergeUtil.java 
b/core/src/main/java/org/apache/sling/testing/mock/osgi/MapMergeUtil.java
index b8bd56c..082be40 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/osgi/MapMergeUtil.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/osgi/MapMergeUtil.java
@@ -25,29 +25,35 @@ import java.io.IOException;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.OsgiMetadata;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.ComponentConstants;
 
 /**
  * Map util merge methods.
  */
 final class MapMergeUtil {
 
+    static final AtomicLong COMPONENT_ID_COUNTER = new AtomicLong();
+
     private MapMergeUtil() {
         // static methods only
     }
 
     /**
-     * Merge service properties from three sources (with this precedence):
-     * 1. Properties defined in calling unit test code
-     * 2. Properties from ConfigurationAdmin
-     * 3. Properties from OSGi SCR metadata
+     * Merge DS component properties from the following sources (with this 
precedence):
+     * 1. Automatically generated DS service properties
+     * 2. Properties defined in calling unit test code
+     * 3. Properties from ConfigurationAdmin
+     * 4. Properties from the OSGi DS Component Description (in the context of 
Apache Felix referred to as SCR metadata)
      * @param targetClass Target service class
      * @param configAdmin Configuration admin or null if none is registered
      * @param properties Properties from unit test code or null if none where 
passed
      * @return Merged properties
+     * @see <a 
href="http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-component.properties";>Component
 Properties</a>
      */
     static Dictionary<String, Object> propertiesMergeWithOsgiMetadata(Class<?> 
targetClass,
             ConfigurationAdmin configAdmin,
@@ -56,14 +62,16 @@ final class MapMergeUtil {
     }
 
     /**
-     * Merge service properties from three sources (with this precedence):
-     * 1. Properties defined in calling unit test code
-     * 2. Properties from ConfigurationAdmin
-     * 3. Properties from OSGi SCR metadata
+     * Merge DS component properties relevant for DS components from the 
following sources (with this precedence):
+     * 1. Automatically generated DS service properties
+     * 2. Properties defined in calling unit test code
+     * 3. Properties from ConfigurationAdmin
+     * 4. Properties from the OSGi DS Component Description (in the context of 
Apache Felix referred to as SCR metadata)
      * @param targetClass Target service class
      * @param configAdmin Configuration admin or null if none is registered
      * @param properties Properties from unit test code or null if none where 
passed
      * @return Merged properties
+     * @see <a 
href="http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-component.properties";>Component
 Properties</a>
      */
     static Map<String, Object> propertiesMergeWithOsgiMetadata(Class<?> 
targetClass,
             ConfigurationAdmin configAdmin,
@@ -98,6 +106,9 @@ final class MapMergeUtil {
             mergedProperties.putAll(properties);
         }
 
+        // add non overwritable auto-generated properties
+        mergedProperties.put(ComponentConstants.COMPONENT_NAME, 
targetClass.getName());
+        mergedProperties.put(ComponentConstants.COMPONENT_ID, 
COMPONENT_ID_COUNTER.getAndIncrement());
         return mergedProperties;
     }
 
diff --git 
a/core/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java 
b/core/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
index 089b9cd..39f1852 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
@@ -25,6 +25,8 @@ import static 
org.apache.sling.testing.mock.osgi.MapUtil.toMap;
 import java.io.IOException;
 import java.util.Dictionary;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Stream;
 
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.OsgiMetadata;
 import org.jetbrains.annotations.NotNull;
@@ -220,7 +222,7 @@ public final class MockOsgi {
      * @param <T> DS Component type
      * @param component a DS component instance
      * @param bundleContext Bundle context from which services are fetched to 
inject and which is used for registering new services
-     * @param properties Service properties (optional)
+     * @param properties component properties (optional)
      */
     public static final @NotNull <T> void 
registerInjectActivateService(@NotNull final T component, @NotNull 
BundleContext bundleContext, @Nullable final Map<String, Object> properties) {
         Map<String, Object> mergedProperties = 
propertiesMergeWithOsgiMetadata(component.getClass(), 
getConfigAdmin(bundleContext), properties);
@@ -229,7 +231,9 @@ public final class MockOsgi {
         OsgiServiceUtil.activateDeactivate(component, 
(MockComponentContext)componentContext, true);
         OsgiMetadata metadata = 
OsgiMetadataUtil.getMetadata(component.getClass());
         if (!metadata.getServiceInterfaces().isEmpty()) {
-            
bundleContext.registerService(metadata.getServiceInterfaces().toArray(new 
String[0]), component, toDictionary(mergedProperties));
+            // convert component properties to service properties 
(http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-service.properties)
+            Dictionary<String, Object> serviceProperties = 
mergedProperties.entrySet().stream().filter(e -> e.getKey() != null && 
!e.getKey().startsWith(".")).collect(new DictionaryCollector<String, 
Object>(Entry::getKey, Entry::getValue));
+            
bundleContext.registerService(metadata.getServiceInterfaces().toArray(new 
String[0]), component, serviceProperties);
         }
     }
 
@@ -238,7 +242,7 @@ public final class MockOsgi {
      * @param <T> DS Component type
      * @param component a DS component instance
      * @param bundleContext Bundle context from which services are fetched to 
inject and which is used for registering new services.
-     * @param properties Service properties (optional)
+     * @param properties component properties (optional)
      */
     public static final @NotNull <T> void 
registerInjectActivateService(@NotNull final T component, @NotNull 
BundleContext bundleContext, @NotNull final Object @NotNull ... properties) {
         registerInjectActivateService(component, bundleContext, 
MapUtil.toMap(properties));
@@ -269,7 +273,9 @@ public final class MockOsgi {
         T component = OsgiServiceUtil.activateInjectServices(dsComponentClass, 
(MockComponentContext)componentContext);
         OsgiMetadata metadata = OsgiMetadataUtil.getMetadata(dsComponentClass);
         if (!metadata.getServiceInterfaces().isEmpty()) {
-            bundleContext.registerService( 
metadata.getServiceInterfaces().toArray(new String[0]), component,  
toDictionary(mergedProperties));
+            // convert component properties to service properties 
(http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-service.properties)
+            Dictionary<String, Object> serviceProperties = 
mergedProperties.entrySet().stream().filter(e -> e.getKey() != null && 
!e.getKey().startsWith(".")).collect(new DictionaryCollector<String, 
Object>(Entry::getKey, Entry::getValue));
+            
bundleContext.registerService(metadata.getServiceInterfaces().toArray(new 
String[0]), component, serviceProperties);
         }
         return component;
     }
diff --git 
a/core/src/test/java/org/apache/sling/testing/mock/osgi/context/OsgiContextImplTest.java
 
b/core/src/test/java/org/apache/sling/testing/mock/osgi/context/OsgiContextImplTest.java
index 2e3a467..b8ad838 100644
--- 
a/core/src/test/java/org/apache/sling/testing/mock/osgi/context/OsgiContextImplTest.java
+++ 
b/core/src/test/java/org/apache/sling/testing/mock/osgi/context/OsgiContextImplTest.java
@@ -20,9 +20,12 @@ package org.apache.sling.testing.mock.osgi.context;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -38,6 +41,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
 import org.osgi.util.tracker.ServiceTracker;
 
@@ -122,7 +126,9 @@ public class OsgiContextImplTest {
         context.registerService(ServiceInterface2.class, 
mock(ServiceInterface2.class));
         Service3 service = context.registerInjectActivateService(new 
Service3());
         assertNotNull(service.getReference1());
-        assertEquals(1, service.getReferences2().size());
+        assertEquals(2, service.getReferences2().size());
+        assertEquals(Service3.class.getName(), 
service.getConfig().get("component.name"));
+        assertNotNull(service.getConfig().get("component.id"));
     }
 
     @Test
@@ -131,7 +137,9 @@ public class OsgiContextImplTest {
         context.registerService(ServiceInterface2.class, 
mock(ServiceInterface2.class));
         Service3 service = 
context.registerInjectActivateService(Service3.class);
         assertNotNull(service.getReference1());
-        assertEquals(1, service.getReferences2().size());
+        assertEquals(2, service.getReferences2().size());
+        assertEquals(Service3.class.getName(), 
service.getConfig().get("component.name"));
+        assertNotNull(service.getConfig().get("component.id"));
     }
 
     @Test
@@ -143,11 +151,21 @@ public class OsgiContextImplTest {
     }
 
     @Test
-    public void testRegisterInjectActivateWithProperties_Class() {
+    public void testRegisterInjectActivateWithProperties_Class() throws 
InvalidSyntaxException {
         context.registerService(ServiceInterface1.class, 
mock(ServiceInterface1.class));
         context.registerService(ServiceInterface2.class, 
mock(ServiceInterface2.class));
-        Service3 service = 
context.registerInjectActivateService(Service3.class, "prop1", "value3");
+        Service3 service = 
context.registerInjectActivateService(Service3.class, "prop1", "value3", 
".privateProp", "privateValue");
         assertEquals("value3", service.getConfig().get("prop1"));
+        // private key visible in component properties
+        assertTrue(service.getConfig().containsKey(".privateProp"));
+        assertEquals(Service3.class.getName(), 
service.getConfig().get("component.name"));
+        assertNotNull(service.getConfig().get("component.id"));
+        Collection<ServiceReference<ServiceInterface2>> serviceReferences = 
context.bundleContext().getServiceReferences(ServiceInterface2.class, 
"(component.name="+Service3.class.getName()+")");
+        assertEquals("Expected only one service reference matching filter", 1, 
serviceReferences.size());
+        ServiceReference<ServiceInterface2> serviceReference = 
serviceReferences.iterator().next();
+        assertEquals("value3", serviceReference.getProperty("prop1"));
+        // private key not visible in service properties
+        assertNull(serviceReference.getProperty(".privateProp"));
     }
 
     @Test
@@ -173,6 +191,8 @@ public class OsgiContextImplTest {
         props.put(null, null);
         Service3 service = context.registerInjectActivateService(new 
Service3(), props);
         assertEquals("value3", service.getConfig().get("prop1"));
+        assertEquals(Service3.class.getName(), 
service.getConfig().get("component.name"));
+        assertNotNull(service.getConfig().get("component.id"));
     }
 
     @Test(expected=RuntimeException.class)
diff --git 
a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/Service3.java
 
b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/Service3.java
index 8b2c6e7..6f0bd12 100644
--- 
a/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/Service3.java
+++ 
b/test-services/src/main/java/org/apache/sling/testing/mock/osgi/testsvc/osgiserviceutil/Service3.java
@@ -41,7 +41,7 @@ import 
org.osgi.service.component.annotations.ReferencePolicyOption;
 @Component(reference = { @Reference(name = "reference2", service = 
ServiceInterface2.class, cardinality = ReferenceCardinality.AT_LEAST_ONE,
         policy = ReferencePolicy.DYNAMIC, policyOption = 
ReferencePolicyOption.GREEDY,
         bind="bindReference2", unbind="unbindReference2") })
-public class Service3 {
+public class Service3 implements ServiceInterface2 {
 
     @Reference(bind="bindReference1", unbind="unbindReference1", policy = 
ReferencePolicy.DYNAMIC)
     private volatile ServiceInterface1 reference1;

Reply via email to