/* * 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.felix.dm.lambda.itest; import static org.apache.felix.dm.lambda.DependencyManagerActivator.adapter; import static org.apache.felix.dm.lambda.DependencyManagerActivator.aspect; import static org.apache.felix.dm.lambda.DependencyManagerActivator.component; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Random; import java.util.Set; import org.apache.felix.dm.Component; import org.apache.felix.dm.DependencyManager; import org.junit.Assert; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; /** * Test for aspects with service properties propagations. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ @SuppressWarnings({"rawtypes", "unchecked", "unused"}) public class AspectWithPropagationTest extends TestBase { private final static int ASPECTS = 3; private final Set<Integer> _randoms = new HashSet<Integer>(); private final Random _rnd = new Random(); private static Ensure m_invokeStep; private static Ensure m_changeStep; /** * This test does the following: * * - Create S service with property "p=s" * - Create SA (aspect of S) with property "p=aspect" * - Create Client, depending on S (actually, on SA). * - Client should see SA with properties p=aspect * - Change S service property with "p=smodified": the Client should be changed with SA(p=aspect) * - Change aspect service property with "p=aspectmodified": The client should be changed with SA(p=aspectmodified) */ public void testAspectsWithPropagationNotOverriding() { System.out.println("----------- Running testAspectsWithPropagationNotOverriding ..."); DependencyManager m = getDM(); m_invokeStep = new Ensure(); // Create our original "S" service. S s = new S() { public void invoke() { } }; Component sComp = component(m).impl(s).provides(S.class, p -> "s").build(); // Create SA (aspect of S) S sa = new S() { volatile S m_s; public void invoke() { } }; Component saComp = aspect(m, S.class).rank(1).impl(sa).properties(p -> "aspect").build(); // Create client depending on S Object client = new Object() { int m_changeCount; void add(Map props, S s) { Assert.assertEquals("aspect", props.get("p")); m_invokeStep.step(1); } void change(Map props, S s) { switch (++m_changeCount) { case 1: Assert.assertEquals("aspect", props.get("p")); m_invokeStep.step(2); break; case 2: Assert.assertEquals("aspectmodified", props.get("p")); m_invokeStep.step(3); } } }; Component clientComp = component(m).impl(client).withSvc(S.class, srv->srv.add("add").change("change")).build(); // Add components in dependency manager m.add(sComp); m.add(saComp); m.add(clientComp); // client should have been added with SA aspect m_invokeStep.waitForStep(1, 5000); // now change s "p=s" to "p=smodified": client should not see it Hashtable props = new Hashtable(); props.put("p", "smodified"); sComp.setServiceProperties(props); m_invokeStep.waitForStep(2, 5000); // now change sa aspect "p=aspect" to "p=aspectmodified": client should see it props = new Hashtable(); props.put("p", "aspectmodified"); saComp.setServiceProperties(props); m_invokeStep.waitForStep(3, 5000); // remove components m.remove(clientComp); m.remove(saComp); m.remove(sComp); } /** * This test does the following: * * - Create S service * - Create some S Aspects * - Create a Client, depending on S (actually, on the top-level S aspect) * - Client has a "change" callback in order to track S service properties modifications. * - First, invoke Client.invoke(): all S aspects, and finally original S service must be invoked orderly. * - Modify S original service properties, and check if all aspects, and the client has been orderly called in their "change" callback. * - Modify the First lowest ranked aspect (rank=1), and check if all aspects, and client have been orderly called in their "change" callback. */ public void testAspectsWithPropagation() { System.out.println("----------- Running testAspectsWithPropagation ..."); DependencyManager m = getDM(); // helper class that ensures certain steps get executed in sequence m_invokeStep = new Ensure(); // Create our original "S" service. Dictionary props = new Hashtable(); props.put("foo", "bar"); Component s = component(m).impl(new SImpl()).provides(S.class, props).build(); // Create an aspect aware client, depending on "S" service. Client clientImpl; Component client = component(m).impl((clientImpl = new Client())).withSvc(S.class, srv -> srv.add("add").change("change").remove("remove").swap("swap")).build(); // Create some "S" aspects Component[] aspects = new Component[ASPECTS]; for (int rank = 1; rank <= ASPECTS; rank ++) { aspects[rank-1] = aspect(m, S.class).rank(rank).impl(new A("A" + rank, rank)).add("add").change("change").remove("remove").swap("swap").build(); props = new Hashtable(); props.put("a" + rank, "v" + rank); aspects[rank-1].setServiceProperties(props); } // Register client m.add(client); // Randomly register aspects and original service boolean originalServiceAdded = false; for (int i = 0; i < ASPECTS; i ++) { int index = getRandomAspect(); m.add(aspects[index]); if (! originalServiceAdded && _rnd.nextBoolean()) { m.add(s); originalServiceAdded = true; } } if (! originalServiceAdded) { m.add(s); } // All set, check if client has inherited from top level aspect properties + original service properties Map check = new HashMap(); check.put("foo", "bar"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS); checkServiceProperties(check, clientImpl.getServiceProperties()); // Now invoke client, which orderly calls all aspects in the chain, and finally the original service "S". System.out.println("-------------------------- Invoking client."); clientImpl.invoke(); m_invokeStep.waitForStep(ASPECTS+1, 5000); // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, and on client. System.out.println("-------------------------- Modifying original service properties."); m_changeStep = new Ensure(); props = new Hashtable(); props.put("foo", "barModified"); s.setServiceProperties(props); // Check if aspects and client have been orderly called in their "changed" callback m_changeStep.waitForStep(ASPECTS+1, 5000); // Check if modified "foo" original service property has been propagated check = new HashMap(); check.put("foo", "barModified"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS); // we only see top-level aspect service properties checkServiceProperties(check, clientImpl.getServiceProperties()); // Now, change the top-level ranked aspect: it must propagate to all upper aspects, as well as to the client System.out.println("-------------------------- Modifying top-level aspect service properties."); m_changeStep = new Ensure(); for (int i = 1; i <= ASPECTS; i ++) { m_changeStep.step(i); // only client has to be changed. } props = new Hashtable(); props.put("a" + ASPECTS, "v" + ASPECTS + "-Modified"); aspects[ASPECTS-1].setServiceProperties(props); // That triggers change callbacks for upper aspects (with rank >= 2) m_changeStep.waitForStep(ASPECTS+1, 5000); // check if client have been changed. // Check if top level aspect service properties have been propagated up to the client. check = new HashMap(); check.put("foo", "barModified"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS + "-Modified"); checkServiceProperties(check, clientImpl.getServiceProperties()); // Clear all components. m_changeStep = null; m.clear(); } /** * This test does the following: * * - Create S service * - Create some S Aspects without any callbacks (add/change/remove/swap) * - Create a Client, depending on S (actually, on the top-level S aspect) * - Client has a "change" callack in order to track S service properties modifications. * - First, invoke Client.invoke(): all S aspects, and finally original S service must be invoked orderly. * - Modify S original service properties, and check if the client has been called in its "change" callback. */ public void testAspectsWithPropagationAndNoCallbacks() { System.out.println("----------- Running testAspectsWithPropagation ..."); DependencyManager m = getDM(); // helper class that ensures certain steps get executed in sequence m_invokeStep = new Ensure(); // Create our original "S" service. Dictionary props = new Hashtable(); props.put("foo", "bar"); Component s = component(m).impl(new SImpl()).provides(S.class, props).build(); // Create an aspect aware client, depending on "S" service. Client clientImpl; Component client = component(m).impl((clientImpl = new Client())).withSvc(S.class, srv->srv.add("add").change("change").remove("remove")).build(); // Create some "S" aspects Component[] aspects = new Component[ASPECTS]; for (int rank = 1; rank <= ASPECTS; rank ++) { aspects[rank-1] = aspect(m, S.class).rank(rank).impl(new A("A" + rank, rank)).build(); props = new Hashtable(); props.put("a" + rank, "v" + rank); aspects[rank-1].setServiceProperties(props); } // Register client m.add(client); // Randomly register aspects and original service boolean originalServiceAdded = false; for (int i = 0; i < ASPECTS; i ++) { int index = getRandomAspect(); m.add(aspects[index]); if (! originalServiceAdded && _rnd.nextBoolean()) { m.add(s); originalServiceAdded = true; } } if (! originalServiceAdded) { m.add(s); } // All set, check if client has inherited from top level aspect properties + original service properties Map check = new HashMap(); check.put("foo", "bar"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS); checkServiceProperties(check, clientImpl.getServiceProperties()); // Now invoke client, which orderly calls all aspects in the chain, and finally the original service "S". System.out.println("-------------------------- Invoking client."); clientImpl.invoke(); m_invokeStep.waitForStep(ASPECTS+1, 5000); // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, and on client. System.out.println("-------------------------- Modifying original service properties."); m_changeStep = new Ensure(); for (int i = 1; i <= ASPECTS; i ++) { m_changeStep.step(i); // skip aspects, which have no "change" callbacks. } props = new Hashtable(); props.put("foo", "barModified"); s.setServiceProperties(props); // Check if aspects and client have been orderly called in their "changed" callback m_changeStep.waitForStep(ASPECTS+1, 5000); // Check if modified "foo" original service property has been propagated check = new HashMap(); check.put("foo", "barModified"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS); // we only see top-level aspect service properties checkServiceProperties(check, clientImpl.getServiceProperties()); // Clear all components. m_changeStep = null; m.clear(); } /** * This test does the following: * * - Create S service * - Create some S Aspects * - Create S2 Adapter, which adapts S to S2 * - Create Client2, which depends on S2. Client2 listens to S2 property change events. * - Now, invoke Client2.invoke(): all S aspects, and finally original S service must be invoked orderly. * - Modify S original service properties, and check if all aspects, S2 Adapter, and Client2 have been orderly called in their "change" callback. */ public void testAdapterWithAspectsAndPropagation() { System.out.println("----------- Running testAdapterWithAspectsAndPropagation ..."); DependencyManager m = getDM(); m_invokeStep = new Ensure(); // Create our original "S" service. Dictionary props = new Hashtable(); props.put("foo", "bar"); Component s = component(m).impl(new SImpl()).provides(S.class, props).build(); // Create some "S" aspects Component[] aspects = new Component[ASPECTS]; for (int rank = 1; rank <= ASPECTS; rank ++) { aspects[rank-1] = aspect(m, S.class).rank(rank).impl(new A("A" + rank, rank)).add("add").change("change").remove("remove").swap("swap").build(); props = new Hashtable(); props.put("a" + rank, "v" + rank); aspects[rank-1].setServiceProperties(props); } // Create S2 adapter (which adapts S1 to S2 interface) Component adapter = adapter(m, S.class).add("add").change("change").remove("remove").swap("swap").provides(S2.class).impl(new S2Impl()).build(); // Create Client2, which depends on "S2" service. Client2 client2Impl; Component client2 = component(m).impl((client2Impl = new Client2())).withSvc(S2.class, srv -> srv.add("add").change("change")).build(); // Register client2 m.add(client2); // Register S2 adapter m.add(adapter); // Randomly register aspects, original service boolean originalServiceAdded = false; for (int i = 0; i < ASPECTS; i ++) { int index = getRandomAspect(); m.add(aspects[index]); if (! originalServiceAdded && _rnd.nextBoolean()) { m.add(s); originalServiceAdded = true; } } if (! originalServiceAdded) { m.add(s); } // Now invoke client2, which orderly calls all S1 aspects, then S1Impl, and finally S2 service System.out.println("-------------------------- Invoking client2."); client2Impl.invoke2(); m_invokeStep.waitForStep(ASPECTS+2, 5000); // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, S2Impl, and Client2. System.out.println("-------------------------- Modifying original service properties."); m_changeStep = new Ensure(); props = new Hashtable(); props.put("foo", "barModified"); s.setServiceProperties(props); // Check if aspects and Client2 have been orderly called in their "changed" callback m_changeStep.waitForStep(ASPECTS+2, 5000); // Check if modified "foo" original service property has been propagated to Client2 Map check = new HashMap(); check.put("foo", "barModified"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS); checkServiceProperties(check, client2Impl.getServiceProperties()); // Clear all components. m_changeStep = null; m.clear(); } /** * This test does the following: * * - Create S service * - Create some S Aspects without any callbacks (add/change/remove) * - Create S2 Adapter, which adapts S to S2 (but does not have any add/change/remove callbacks) * - Create Client2, which depends on S2. Client2 listens to S2 property change events. * - Now, invoke Client2.invoke(): all S aspects, and finally original S service must be invoked orderly. * - Modify S original service properties, and check if all aspects, S2 Adapter, and Client2 have been orderly called in their "change" callback. */ public void testAdapterWithAspectsAndPropagationNoCallbacks() { System.out.println("----------- Running testAdapterWithAspectsAndPropagationNoCallbacks ..."); DependencyManager m = getDM(); m_invokeStep = new Ensure(); // Create our original "S" service. Dictionary props = new Hashtable(); props.put("foo", "bar"); Component s = component(m).impl(new SImpl()).provides(S.class, props).build(); // Create some "S" aspects Component[] aspects = new Component[ASPECTS]; for (int rank = 1; rank <= ASPECTS; rank ++) { aspects[rank-1] = aspect(m, S.class).rank(rank).impl(new A("A" + rank, rank)).build(); props = new Hashtable(); props.put("a" + rank, "v" + rank); aspects[rank-1].setServiceProperties(props); } // Create S2 adapter (which adapts S1 to S2 interface) Component adapter = adapter(m, S.class).provides(S2.class).impl(new S2Impl()).build(); // Create Client2, which depends on "S2" service. Client2 client2Impl; Component client2 = component(m).impl((client2Impl = new Client2())).withSvc(S2.class, srv->srv.add("add").change("change")).build(); // Register client2 m.add(client2); // Register S2 adapter m.add(adapter); // Randomly register aspects, original service boolean originalServiceAdded = false; for (int i = 0; i < ASPECTS; i ++) { int index = getRandomAspect(); m.add(aspects[index]); if (! originalServiceAdded && _rnd.nextBoolean()) { m.add(s); originalServiceAdded = true; } } if (! originalServiceAdded) { m.add(s); } // Now invoke client2, which orderly calls all S1 aspects, then S1Impl, and finally S2 service System.out.println("-------------------------- Invoking client2."); client2Impl.invoke2(); m_invokeStep.waitForStep(ASPECTS+2, 5000); // Now, change original service "S" properties: this will orderly trigger "change" callbacks on aspects, S2Impl, and Client2. System.out.println("-------------------------- Modifying original service properties."); m_changeStep = new Ensure(); for (int i = 1; i <= ASPECTS+1; i ++) { m_changeStep.step(i); // skip all aspects and the adapter } props = new Hashtable(); props.put("foo", "barModified"); s.setServiceProperties(props); // Check if Client2 has been called in its "changed" callback m_changeStep.waitForStep(ASPECTS+2, 5000); // Check if modified "foo" original service property has been propagated to Client2 Map check = new HashMap(); check.put("foo", "barModified"); for (int i = 1; i < (ASPECTS - 1); i ++) { check.put("a" + i, null); // we must not inherit from lower ranks, only from the top-level aspect. } check.put("a" + ASPECTS, "v" + ASPECTS); checkServiceProperties(check, client2Impl.getServiceProperties()); // Clear all components. m_changeStep = null; m.clear(); } private void checkServiceProperties(Map<?, ?> check, Dictionary properties) { for (Object key : check.keySet()) { Object val = check.get(key); if (val == null) { Assert.assertNull(properties.get(key)); } else { Assert.assertEquals(val, properties.get(key)); } } } private int getRandomAspect() { int index = 0; do { index = _rnd.nextInt(ASPECTS); } while (_randoms.contains(new Integer(index))); _randoms.add(new Integer(index)); return index; } // S Service public static interface S { public void invoke(); } // S ServiceImpl static class SImpl implements S { public SImpl() { } public String toString() { return "S"; } public void invoke() { m_invokeStep.step(ASPECTS+1); } } // S Aspect static class A implements S { private final String m_name; private volatile ServiceRegistration m_registration; private volatile S m_next; private final int m_rank; public A(String name, int rank) { m_name = name; m_rank = rank; } public String toString() { return m_name; } public void invoke() { int rank = ServiceUtil.getRanking(m_registration.getReference()); m_invokeStep.step(ASPECTS - rank + 1); m_next.invoke(); } public void add(ServiceReference ref, S s) { System.out.println("+++ A" + m_rank + ".add:" + s + "/" + ServiceUtil.toString(ref)); m_next = s; } public void swap(ServiceReference oldSRef, S oldS, ServiceReference newSRef, S newS) { System.out.println("+++ A" + m_rank + ".swap: new=" + newS + ", props=" + ServiceUtil.toString(newSRef)); Assert.assertTrue(m_next == oldS); m_next = newS; } public void change(ServiceReference props, S s) { System.out.println("+++ A" + m_rank + ".change: s=" + s + ", props=" + ServiceUtil.toString(props)); if (m_changeStep != null) { int rank = ServiceUtil.getRanking(m_registration.getReference()); m_changeStep.step(rank); } } public void remove(ServiceReference props, S s) { System.out.println("+++ A" + m_rank + ".remove: " + s + ", props=" + ServiceUtil.toString(props)); } } // Aspect aware client, depending of "S" service aspects. static class Client { private volatile S m_s; private volatile ServiceReference m_sRef; public Client() { } public Dictionary getServiceProperties() { Dictionary props = new Hashtable(); for (String key : m_sRef.getPropertyKeys()) { props.put(key, m_sRef.getProperty(key)); } return props; } public void invoke() { m_s.invoke(); } public String toString() { return "Client"; } public void add(ServiceReference ref, S s) { System.out.println("+++ Client.add: " + s + "/" + ServiceUtil.toString(ref)); m_s = s; m_sRef = ref; } public void swap(ServiceReference oldSRef, S oldS, ServiceReference newSRef, S newS) { System.out.println("+++ Client.swap: m_s = " + m_s + ", old=" + oldS + ", oldProps=" + ServiceUtil.toString(oldSRef) + ", new=" + newS + ", props=" + ServiceUtil.toString(newSRef)); Assert.assertTrue(m_s == oldS); m_s = newS; m_sRef = newSRef; } public void change(ServiceReference properties, S s) { System.out.println("+++ Client.change: s=" + s + ", props=" + ServiceUtil.toString(properties)); if (m_changeStep != null) { m_changeStep.step(ASPECTS+1); } } public void remove(ServiceReference props, S s) { System.out.println("+++ Client.remove: " + s + ", props=" + ServiceUtil.toString(props)); } } // S2 Service public static interface S2 { public void invoke2(); } // S2 impl, which adapts S1 interface to S2 interface static class S2Impl implements S2 { private volatile S m_s; // we shall see top-level aspect on S service public void add(ServiceReference ref, S s) { System.out.println("+++ S2Impl.add: " + s + "/" + ServiceUtil.toString(ref)); m_s = s; } public void swap(ServiceReference oldSRef, S oldS, ServiceReference newSRef, S newS) { System.out.println("+++ S2Impl.swap: new=" + newS + ", props=" + ServiceUtil.toString(newSRef)); m_s = newS; } public void change(ServiceReference properties, S s) { System.out.println("+++ S2Impl.change: s=" + s + ", props=" + ServiceUtil.toString(properties)); if (m_changeStep != null) { m_changeStep.step(ASPECTS+1); } } public void remove(ServiceReference props, S s) { System.out.println("+++ S2Impl.remove: " + s + ", props=" + ServiceUtil.toString(props)); } public void invoke2() { m_s.invoke(); m_invokeStep.step(ASPECTS + 2); // All aspects, and S1Impl have been invoked } public String toString() { return "S2"; } } // Client2 depending on S2. static class Client2 { private volatile S2 m_s2; private volatile ServiceReference m_s2Ref; public Dictionary getServiceProperties() { Dictionary props = new Hashtable(); for (String key : m_s2Ref.getPropertyKeys()) { props.put(key, m_s2Ref.getProperty(key)); } return props; } public void invoke2() { m_s2.invoke2(); } public String toString() { return "Client2"; } public void add(ServiceReference ref, S2 s2) { System.out.println("+++ Client2.add: " + s2 + "/" + ServiceUtil.toString(ref)); m_s2 = s2; m_s2Ref = ref; } public void change(ServiceReference props, S2 s2) { System.out.println("+++ Client2.change: s2=" + s2 + ", props=" + ServiceUtil.toString(props)); if (m_changeStep != null) { m_changeStep.step(ASPECTS + 2); // S1Impl, all aspects, and S2 adapters have been changed before us. } } } }