This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.testing.osgi-mock-2.2.2 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-osgi-mock.git
commit b031eb11a78b92fdb914f11fe5c6656c078fa11a Author: Stefan Seifert <[email protected]> AuthorDate: Mon Dec 12 15:29:38 2016 +0000 SLING-6372 OSGi Mocks - Correctly handle static, greedy references git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/osgi-mock@1773806 13f79535-47bb-0310-9956-ffa450edef68 --- .../sling/testing/mock/osgi/MockBundleContext.java | 77 ++++++++- .../sling/testing/mock/osgi/OsgiServiceUtil.java | 31 +++- ...ockBundleContextStaticGreedyReferencesTest.java | 186 +++++++++++++++++++++ .../testing/mock/osgi/OsgiServiceUtilTest.java | 102 +++++++++++ ...sling.testing.mock.osgi.OsgiServiceUtilTest.xml | 8 + 5 files changed, 398 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java index c57dcc6..7c40afa 100644 --- a/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java +++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java @@ -114,8 +114,8 @@ class MockBundleContext implements BundleContext { public ServiceRegistration registerService(final String[] clazzes, final Object service, final Dictionary properties) { Dictionary<String, Object> mergedPropertes = MapMergeUtil.propertiesMergeWithOsgiMetadata(service, configAdmin, properties); MockServiceRegistration registration = new MockServiceRegistration(this.bundle, clazzes, service, mergedPropertes, this); - handleRefsUpdateOnRegister(registration); this.registeredServices.add(registration); + handleRefsUpdateOnRegister(registration); notifyServiceListeners(ServiceEvent.REGISTERED, registration.getReference()); return registration; } @@ -126,8 +126,10 @@ class MockBundleContext implements BundleContext { * @param registration */ private void handleRefsUpdateOnRegister(MockServiceRegistration registration) { - List<ReferenceInfo> affectedReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration); - for (ReferenceInfo referenceInfo : affectedReferences) { + + // handle DYNAMIC references to this registration + List<ReferenceInfo> affectedDynamicReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration); + for (ReferenceInfo referenceInfo : affectedDynamicReferences) { Reference reference = referenceInfo.getReference(); if (reference.matchesTargetFilter(registration.getReference())) { switch (reference.getCardinality()) { @@ -145,6 +147,24 @@ class MockBundleContext implements BundleContext { } } } + + // handle STATIC+GREEDY references to this registration + List<ReferenceInfo> affectedStaticGreedyReferences = OsgiServiceUtil.getMatchingStaticGreedyReferences(registeredServices, registration); + for (ReferenceInfo referenceInfo : affectedStaticGreedyReferences) { + Reference reference = referenceInfo.getReference(); + switch (reference.getCardinality()) { + case MANDATORY_UNARY: + throw new ReferenceViolationException("Mandatory unary reference of type " + reference.getInterfaceType() + " already fulfilled " + + "for service " + reference.getServiceClass().getName() + ", registration of new service with this interface failed."); + case MANDATORY_MULTIPLE: + case OPTIONAL_MULTIPLE: + case OPTIONAL_UNARY: + restartService(referenceInfo.getServiceRegistration()); + break; + default: + throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality()); + } + } } void unregisterService(MockServiceRegistration registration) { @@ -152,6 +172,32 @@ class MockBundleContext implements BundleContext { handleRefsUpdateOnUnregister(registration); notifyServiceListeners(ServiceEvent.UNREGISTERING, registration.getReference()); } + + @SuppressWarnings("unchecked") + void restartService(MockServiceRegistration registration) { + // get current service properties + Class<?> serviceClass = registration.getService().getClass(); + Map<String,Object> properties = MapUtil.toMap(registration.getProperties()); + + // deactivate & unregister service + MockOsgi.deactivate(registration.getService(), this); + unregisterService(registration); + + // newly create and register service + Object newService; + try { + newService = serviceClass.newInstance(); + } + catch (InstantiationException e) { + throw new RuntimeException("Unable to instantiate service: " + serviceClass); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to access service class: " + serviceClass); + } + MockOsgi.injectServices(newService, this); + MockOsgi.activate(newService, this, properties); + registerService(serviceClass.getName(), newService, MapUtil.toDictionary(properties)); + } /** * Check for already registered services that may be affected by the service unregistration - either @@ -159,8 +205,10 @@ class MockBundleContext implements BundleContext { * @param registration */ private void handleRefsUpdateOnUnregister(MockServiceRegistration registration) { - List<ReferenceInfo> affectedReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration); - for (ReferenceInfo referenceInfo : affectedReferences) { + + // handle DYNAMIC references to this registration + List<ReferenceInfo> affectedDynamicReferences = OsgiServiceUtil.getMatchingDynamicReferences(registeredServices, registration); + for (ReferenceInfo referenceInfo : affectedDynamicReferences) { Reference reference = referenceInfo.getReference(); if (reference.matchesTargetFilter(registration.getReference())) { switch (reference.getCardinality()) { @@ -180,6 +228,25 @@ class MockBundleContext implements BundleContext { } } } + + // handle STATIC+GREEDY references to this registration + List<ReferenceInfo> affectedStaticGreedyReferences = OsgiServiceUtil.getMatchingStaticGreedyReferences(registeredServices, registration); + for (ReferenceInfo referenceInfo : affectedStaticGreedyReferences) { + Reference reference = referenceInfo.getReference(); + switch (reference.getCardinality()) { + case MANDATORY_UNARY: + throw new ReferenceViolationException("Reference of type " + reference.getInterfaceType() + " " + + "for service " + reference.getServiceClass().getName() + " is mandatory unary, " + + "unregistration of service with this interface failed."); + case MANDATORY_MULTIPLE: + case OPTIONAL_MULTIPLE: + case OPTIONAL_UNARY: + restartService(referenceInfo.getServiceRegistration()); + break; + default: + throw new RuntimeException("Unepxected cardinality: " + reference.getCardinality()); + } + } } @SuppressWarnings("unchecked") diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java index 9a4dafb..f95e72b 100644 --- a/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java +++ b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java @@ -35,6 +35,7 @@ import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.FieldCollectionType; import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.OsgiMetadata; import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference; import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferencePolicy; +import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferencePolicyOption; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; @@ -614,7 +615,8 @@ final class OsgiServiceUtil { } /** - * Collects all references of any registered service that match with any of the exported interfaces of the given service registration. + * Collects all references of any registered service that match with any of the exported interfaces of the given service registration + * and are defined as DYNAMIC. * @param registeredServices Registered Services * @param registration Service registration * @return List of references @@ -639,6 +641,33 @@ final class OsgiServiceUtil { return references; } + /** + * Collects all references of any registered service that match with any of the exported interfaces of the given service registration + * and are defined as STATIC + GREEDY. + * @param registeredServices Registered Services + * @param registration Service registration + * @return List of references + */ + public static List<ReferenceInfo> getMatchingStaticGreedyReferences(SortedSet<MockServiceRegistration> registeredServices, + MockServiceRegistration<?> registration) { + List<ReferenceInfo> references = new ArrayList<ReferenceInfo>(); + for (MockServiceRegistration existingRegistration : registeredServices) { + OsgiMetadata metadata = OsgiMetadataUtil.getMetadata(existingRegistration.getService().getClass()); + if (metadata != null) { + for (Reference reference : metadata.getReferences()) { + if (reference.getPolicy() == ReferencePolicy.STATIC && reference.getPolicyOption() == ReferencePolicyOption.GREEDY) { + for (String serviceInterface : registration.getClasses()) { + if (StringUtils.equals(serviceInterface, reference.getInterfaceType())) { + references.add(new ReferenceInfo(existingRegistration, reference)); + } + } + } + } + } + } + return references; + } + static class ServiceInfo { private final Object serviceInstance; diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextStaticGreedyReferencesTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextStaticGreedyReferencesTest.java new file mode 100644 index 0000000..f707222 --- /dev/null +++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextStaticGreedyReferencesTest.java @@ -0,0 +1,186 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.Service3StaticGreedy; +import org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.ServiceInterface1; +import org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.ServiceInterface1Optional; +import org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.ServiceInterface2; +import org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.ServiceInterface3; +import org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.ServiceSuperInterface3; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + +import com.google.common.collect.ImmutableSet; + +@RunWith(MockitoJUnitRunner.class) +public class MockBundleContextStaticGreedyReferencesTest { + + private BundleContext bundleContext; + private ServiceRegistration reg1a; + private ServiceRegistration reg2a; + + @Mock + private ServiceInterface1 dependency1a; + @Mock + private ServiceInterface1 dependency1b; + @Mock + private ServiceInterface1Optional dependency1aOptional; + @Mock + private ServiceInterface1Optional dependency1bOptional; + @Mock + private ServiceInterface2 dependency2a; + @Mock + private ServiceInterface2 dependency2b; + @Mock + private ServiceSuperInterface3 dependency3a; + @Mock + private ServiceSuperInterface3 dependency3b; + + @Before + public void setUp() { + bundleContext = MockOsgi.newBundleContext(); + + // setup service instance with only minimum mandatory references + reg1a = bundleContext.registerService(ServiceInterface1.class.getName(), dependency1a, null); + reg2a = bundleContext.registerService(ServiceInterface2.class.getName(), dependency2a, null); + + Service3StaticGreedy service = new Service3StaticGreedy(); + MockOsgi.injectServices(service, bundleContext); + MockOsgi.activate(service, bundleContext); + bundleContext.registerService(Service3StaticGreedy.class.getName(), service, null); + + assertDependency1(dependency1a); + assertDependency1Optional(null); + assertDependencies2(dependency2a); + assertDependencies3(); + } + + @Test + public void testAddRemoveOptionalUnaryService() { + ServiceRegistration reg1aOptional = bundleContext.registerService(ServiceInterface1Optional.class.getName(), dependency1aOptional, null); + assertDependency1Optional(dependency1aOptional); + + reg1aOptional.unregister(); + assertDependency1Optional(null); + } + + public void testAddOptionalUnaryService_TooMany() { + bundleContext.registerService(ServiceInterface1Optional.class.getName(), dependency1aOptional, null); + assertDependency1Optional(dependency1aOptional); + + // in real OSGi this should fail - but this is not covered by the current implementation. so test the real implementation here. + bundleContext.registerService(ServiceInterface1Optional.class.getName(), dependency1bOptional, null); + assertDependency1Optional(dependency1bOptional); + } + + @Test(expected = ReferenceViolationException.class) + public void testAddMandatoryUnaryService_TooMany() { + bundleContext.registerService(ServiceInterface1.class.getName(), dependency1b, null); + } + + @Test(expected = ReferenceViolationException.class) + public void testRemoveMandatoryUnaryService_TooMany() { + reg1a.unregister(); + } + + @Test + public void testAddRemoveOptionalMultipleService() { + ServiceRegistration reg3a = bundleContext.registerService(ServiceInterface3.class.getName(), dependency3a, null); + assertDependencies3(dependency3a); + + ServiceRegistration reg3b = bundleContext.registerService(ServiceInterface3.class.getName(), dependency3b, null); + assertDependencies3(dependency3a, dependency3b); + + reg3a.unregister(); + assertDependencies3(dependency3b); + + reg3b.unregister(); + assertDependencies3(); + } + + @Test + public void testAddRemoveMandatoryMultipleService() { + ServiceRegistration reg2b = bundleContext.registerService(ServiceInterface2.class.getName(), dependency2b, null); + assertDependencies2(dependency2a, dependency2b); + + reg2b.unregister(); + assertDependencies2(dependency2a); + } + + @Test(expected = ReferenceViolationException.class) + public void testAddRemoveMandatoryMultipleService_FailReg2aUnregister() { + ServiceRegistration reg2b = bundleContext.registerService(ServiceInterface2.class.getName(), dependency2b, null); + assertDependencies2(dependency2a, dependency2b); + + reg2b.unregister(); + assertDependencies2(dependency2a); + + // this should fail + reg2a.unregister(); + } + + private void assertDependency1(ServiceInterface1 instance) { + Service3StaticGreedy service =getService(); + if (instance == null) { + assertNull(service.getReference1()); + } + else { + assertSame(instance, service.getReference1()); + } + } + + private void assertDependency1Optional(ServiceInterface1Optional instance) { + Service3StaticGreedy service =getService(); + if (instance == null) { + assertNull(service.getReference1Optional()); + } + else { + assertSame(instance, service.getReference1Optional()); + } + } + + private void assertDependencies2(ServiceInterface2... instances) { + Service3StaticGreedy service =getService(); + assertEquals(ImmutableSet.<ServiceInterface2>copyOf(instances), + ImmutableSet.<ServiceInterface2>copyOf(service.getReferences2())); + } + + private void assertDependencies3(ServiceSuperInterface3... instances) { + Service3StaticGreedy service =getService(); + assertEquals(ImmutableSet.<ServiceSuperInterface3>copyOf(instances), + ImmutableSet.<ServiceSuperInterface3>copyOf(service.getReferences3())); + } + + private Service3StaticGreedy getService() { + ServiceReference<?> serviceRef = bundleContext.getServiceReference(Service3StaticGreedy.class.getName()); + return (Service3StaticGreedy)bundleContext.getService(serviceRef); + } + +} diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java index 26e6bba..ee1bf5b 100644 --- a/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java +++ b/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java @@ -400,6 +400,108 @@ public class OsgiServiceUtilTest { } @Component + @References({ @Reference(name = "reference2", referenceInterface = ServiceInterface2.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE) }) + public static class Service3StaticGreedy { + + @Reference + private ServiceInterface1 reference1; + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY) + private ServiceInterface1Optional reference1Optional; + + private List<ServiceReference> references2 = new ArrayList<ServiceReference>(); + + @Reference(name = "reference3", referenceInterface = ServiceInterface3.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE) + private List<ServiceSuperInterface3> references3 = new ArrayList<ServiceSuperInterface3>(); + private List<Map<String, Object>> reference3Configs = new ArrayList<Map<String, Object>>(); + + private ComponentContext componentContext; + private Map<String, Object> config; + + @Activate + private void activate(ComponentContext ctx) { + this.componentContext = ctx; + this.config = MapUtil.toMap(ctx.getProperties()); + } + + @Deactivate + private void deactivate(ComponentContext ctx) { + this.componentContext = null; + } + + @Modified + private void modified(Map<String,Object> newConfig) { + this.config = newConfig; + } + + public ServiceInterface1 getReference1() { + return this.reference1; + } + + public ServiceInterface1Optional getReference1Optional() { + return this.reference1Optional; + } + + public List<ServiceInterface2> getReferences2() { + List<ServiceInterface2> services = new ArrayList<ServiceInterface2>(); + for (ServiceReference<?> serviceReference : references2) { + services.add((ServiceInterface2)componentContext.getBundleContext().getService(serviceReference)); + } + return services; + } + + public List<ServiceSuperInterface3> getReferences3() { + return this.references3; + } + + public List<Map<String, Object>> getReference3Configs() { + return this.reference3Configs; + } + + public ComponentContext getComponentContext() { + return this.componentContext; + } + + public Map<String, Object> getConfig() { + return config; + } + + protected void bindReference1Optional(ServiceInterface1Optional service) { + reference1Optional = service; + } + + protected void unbindReference1Optional(ServiceInterface1Optional service) { + reference1Optional = null; + } + + protected void bindReference1(ServiceInterface1 service) { + reference1 = service; + } + + protected void unbindReference1(ServiceInterface1 service) { + reference1 = null; + } + + protected void bindReference2(ServiceReference serviceReference) { + references2.add(serviceReference); + } + + protected void unbindReference2(ServiceReference serviceReference) { + references2.remove(serviceReference); + } + + protected void bindReference3(ServiceSuperInterface3 service, Map<String, Object> serviceConfig) { + references3.add(service); + reference3Configs.add(serviceConfig); + } + + protected void unbindReference3(ServiceSuperInterface3 service, Map<String, Object> serviceConfig) { + references3.remove(service); + reference3Configs.remove(serviceConfig); + } + + } + + @Component @Reference(referenceInterface = ServiceInterface1.class, name = "customName", bind = "customBind", unbind = "customUnbind") public static class Service4 { diff --git a/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml index d8d0095..0229572 100644 --- a/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml +++ b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml @@ -52,6 +52,14 @@ <reference name="reference3" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3" field-collection-type="service"/> <reference name="references3Filtered" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3Filtered" field-collection-type="service" target="(prop1=abc)"/> </scr:component> + <scr:component name="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedy" activate="activate" deactivate="deactivate" modified="modified"> + <implementation class="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedy"/> + <property name="service.pid" value="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedy"/> + <reference name="reference1" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface1" cardinality="1..1" policy="static" policy-option="greedy" bind="bindReference1" unbind="unbindReference1"/> + <reference name="reference1Optional" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface1Optional" cardinality="0..1" policy="dynamic" policy-option="greedy" bind="bindReference1Optional" unbind="unbindReference1Optional"/> + <reference name="reference2" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface2" cardinality="1..n" policy="static" policy-option="greedy" bind="bindReference2" unbind="unbindReference2"/> + <reference name="reference3" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="static" policy-option="greedy" bind="bindReference3" unbind="unbindReference3"/> + </scr:component> <scr:component name="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service4_other_name"> <implementation class="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service4"/> <property name="service.pid" value="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service4"/> -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
