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;