/* * 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.karaf.service.guard.impl; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.security.Principal; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.Map; import javax.security.auth.Subject; import org.apache.aries.proxy.ProxyManager; import org.apache.aries.proxy.impl.AsmProxyManager; import org.apache.karaf.jaas.boot.principal.RolePrincipal; import org.apache.karaf.service.guard.impl.GuardProxyCatalog.CreateProxyRunnable; import org.apache.karaf.service.guard.impl.GuardProxyCatalog.ServiceRegistrationHolder; import org.easymock.EasyMock; import org.easymock.IAnswer; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceFactory; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; public class GuardProxyCatalogTest { // Some assertions fail when run under a code coverage tool, they are skipped when this is set to true private static final boolean runningUnderCoverage = false; // set to false before committing any changes @Test public void testGuardProxyCatalog() throws Exception { Bundle b = EasyMock.createMock(Bundle.class); EasyMock.expect(b.getBundleId()).andReturn(9823L).anyTimes(); EasyMock.replay(b); BundleContext bc = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes(); bc.addServiceListener(EasyMock.isA(ServiceListener.class)); EasyMock.expectLastCall().once(); String caFilter = "(&(objectClass=org.osgi.service.cm.ConfigurationAdmin)" + "(!(" + GuardProxyCatalog.PROXY_SERVICE_KEY + "=*)))"; EasyMock.expect(bc.createFilter(caFilter)).andReturn(FrameworkUtil.createFilter(caFilter)).anyTimes(); String pmFilter = "(&(objectClass=org.apache.aries.proxy.ProxyManager)" + "(!(" + GuardProxyCatalog.PROXY_SERVICE_KEY + "=*)))"; EasyMock.expect(bc.createFilter(pmFilter)).andReturn(FrameworkUtil.createFilter(pmFilter)).anyTimes(); EasyMock.replay(bc); GuardProxyCatalog gpc = new GuardProxyCatalog(bc); assertTrue("Service Tracker for ConfigAdmin should be opened", gpc.configAdminTracker.getTrackingCount() != -1); assertTrue("Service Tracker for ProxyManager should be opened", gpc.proxyManagerTracker.getTrackingCount() != -1); EasyMock.verify(bc); // Add some more behaviour checks to the bundle context EasyMock.reset(bc); bc.removeServiceListener(EasyMock.isA(ServiceListener.class)); EasyMock.expectLastCall().once(); EasyMock.replay(bc); gpc.close(); assertEquals("Service Tracker for ConfigAdmin should be closed", -1, gpc.configAdminTracker.getTrackingCount()); assertEquals("Service Tracker for ProxyManager should be closed", -1, gpc.proxyManagerTracker.getTrackingCount()); EasyMock.verify(bc); } @Test public void testIsProxy() throws Exception { BundleContext bc = mockBundleContext(); GuardProxyCatalog gpc = new GuardProxyCatalog(bc); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE); assertTrue(gpc.isProxy(mockServiceReference(props))); assertFalse(gpc.isProxy(mockServiceReference(new Hashtable<String, Object>()))); } @SuppressWarnings("unchecked") @Test public void testHandleProxificationForHook() throws Exception { Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, GuardProxyCatalog.SERVICE_ACL_PREFIX + "foo"); config.put(GuardProxyCatalog.SERVICE_GUARD_KEY, "(a>=5)"); BundleContext bc = mockConfigAdminBundleContext(config); GuardProxyCatalog gpc = new GuardProxyCatalog(bc); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(Constants.SERVICE_ID, 13L); props.put("a", "6"); props.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE); ServiceReference<?> sref2 = mockServiceReference(props); assertFalse("Should not hide an existing proxy for this client", gpc.handleProxificationForHook(sref2)); assertEquals("No proxy should have been created", 0, gpc.proxyMap.size()); Dictionary<String, Object> props4 = new Hashtable<String, Object>(); props4.put(Constants.SERVICE_ID, 15L); props4.put("a", "7"); ServiceReference<?> sref4 = mockServiceReference(props4); assertTrue("Should hide a service that needs to be proxied", gpc.handleProxificationForHook(sref4)); assertEquals("Should trigger proxy creation", 1, gpc.proxyMap.size()); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testHandleServiceUnregistering() throws Exception { BundleContext clientBC = openStrictMockBundleContext(mockBundle(12345L)); BundleContext client2BC = openStrictMockBundleContext(mockBundle(6L)); EasyMock.replay(clientBC); EasyMock.replay(client2BC); Hashtable<String, Object> props = new Hashtable<String, Object>(); long originalServiceID = 12345678901234L; props.put(Constants.SERVICE_ID, new Long(originalServiceID)); props.put("foo", "bar"); ServiceReference<?> originalRef = mockServiceReference(props); Hashtable<String, Object> props2 = new Hashtable<String, Object>(); long anotherServiceID = 5123456789012345L; props2.put(Constants.SERVICE_ID, anotherServiceID); ServiceReference<?> anotherRef = mockServiceReference(props2); GuardProxyCatalog gpc = new GuardProxyCatalog(mockBundleContext()); ServiceRegistration<?> proxyReg = EasyMock.createMock(ServiceRegistration.class); EasyMock.expect(proxyReg.getReference()).andReturn((ServiceReference) mockServiceReference(props)).anyTimes(); proxyReg.unregister(); EasyMock.expectLastCall().once(); EasyMock.replay(proxyReg); ServiceRegistrationHolder srh = new GuardProxyCatalog.ServiceRegistrationHolder(); srh.registration = proxyReg; ServiceRegistration<?> proxy2Reg = EasyMock.createMock(ServiceRegistration.class); EasyMock.replay(proxy2Reg); ServiceRegistrationHolder srh2 = new GuardProxyCatalog.ServiceRegistrationHolder(); srh2.registration = proxy2Reg; gpc.proxyMap.put(originalServiceID, srh); gpc.proxyMap.put(anotherServiceID, srh2); assertEquals("Precondition", 2, gpc.proxyMap.size()); gpc.createProxyQueue.put(new MockCreateProxyRunnable(originalServiceID)); gpc.createProxyQueue.put(new MockCreateProxyRunnable(777)); assertEquals("Precondition", 2, gpc.createProxyQueue.size()); gpc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, originalRef)); assertEquals("Registered events should be ignored", 2, gpc.proxyMap.size()); assertEquals("Registered events should be ignored", 2, gpc.createProxyQueue.size()); Hashtable<String, Object> proxyProps = new Hashtable<String, Object>(props); proxyProps.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE); ServiceReference<?> proxyRef = mockServiceReference(proxyProps); gpc.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, proxyRef)); assertEquals("Unregistering the proxy should be ignored by the listener", 2, gpc.proxyMap.size()); assertEquals("Unregistering the proxy should be ignored by the listener", 2, gpc.createProxyQueue.size()); gpc.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, originalRef)); assertEquals("The proxy for this service should have been removed", 1, gpc.proxyMap.size()); assertEquals(anotherRef.getProperty(Constants.SERVICE_ID), gpc.proxyMap.keySet().iterator().next()); assertEquals("The create proxy job for this service should have been removed", 1, gpc.createProxyQueue.size()); assertEquals(777, gpc.createProxyQueue.iterator().next().getOriginalServiceID()); EasyMock.verify(proxyReg); EasyMock.verify(proxy2Reg); } @Test public void testCreateProxy() throws Exception { // This method tests proxy creation for various service implementation types. testCreateProxy(TestServiceAPI.class, new TestService()); testCreateProxy(TestServiceAPI.class, new DescendantTestService()); testCreateProxy(TestServiceAPI.class, new PrivateTestService()); testCreateProxy(TestServiceAPI.class, new PrivateTestServiceNoDirectInterfaces()); testCreateProxy(TestServiceAPI.class, new FinalTestService()); testCreateProxy(TestObjectWithoutInterface.class, new TestObjectWithoutInterface()); testCreateProxy(TestServiceAPI.class, new CombinedTestService()); testCreateProxy(PrivateTestService.class, new PrivateTestService()); testCreateProxy(PrivateTestServiceNoDirectInterfaces.class, new PrivateTestServiceNoDirectInterfaces()); testCreateProxy(Object.class, new TestService()); testCreateProxy(Object.class, new DescendantTestService()); testCreateProxy(Object.class, new PrivateTestService()); testCreateProxy(Object.class, new TestObjectWithoutInterface()); testCreateProxy(Object.class, new CombinedTestService()); testCreateProxy(Object.class, new FinalTestService()); testCreateProxy(TestServiceAPI.class, new TestServiceAPI() { @Override public String doit() { return "Doing it"; } }); testCreateProxy(Object.class, new ClassWithFinalMethod()); testCreateProxy(Object.class, new ClassWithPrivateMethod()); } @Test public void testCreateProxyMultipleObjectClasses() throws Exception { testCreateProxy(new Class [] {TestServiceAPI.class, TestService.class}, new TestService()); } @SuppressWarnings("unchecked") @Test public void testAssignRoles() throws Exception { Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, "foobar"); config.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); config.put("somemethod", "a,b"); config.put("someOtherMethod(int)", "c"); config.put("someOtherMethod(int)[/12/]", "d"); config.put("someOtherMethod(int)[\"42\"]", "e"); config.put("someOtherMethod[/.*[x][y][z].*/]", "f"); config.put("someFoo*", "g"); BundleContext bc = mockConfigAdminBundleContext(config); Dictionary<String, Object> proxyProps = testCreateProxy(bc, TestServiceAPI.class, new TestService()); assertEquals(new HashSet<String>(Arrays.asList("a", "b", "c", "d", "e", "f", "g")), new HashSet<String>((Collection<String>) proxyProps.get(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY))); } @SuppressWarnings("unchecked") @Test public void testAssignRoles2() throws Exception { Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, "foobar"); config.put("service.guard", "(objectClass=" + TestServiceAPI2.class.getName() + ")"); config.put("doit", "X"); BundleContext bc = mockConfigAdminBundleContext(config); Dictionary<String, Object> proxyProps = testCreateProxy(bc, TestServiceAPI.class, new TestService()); assertNull("No security defined for this API, so no roles should be specified at all", proxyProps.get(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY)); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testAssignRoles3() throws Exception { abstract class MyAbstractClass implements TestServiceAPI, TestServiceAPI2 {}; Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, "foobar"); config.put("service.guard", "(objectClass=" + TestServiceAPI2.class.getName() + ")"); config.put("doit", "X"); BundleContext bc = mockConfigAdminBundleContext(config); Map<ServiceReference, Object> serviceMap = new HashMap<ServiceReference, Object>(); testCreateProxy(bc, new Class [] {TestServiceAPI.class, TestServiceAPI2.class}, new MyAbstractClass() { @Override public String doit() { return null; } @Override public String doit(String s) { return null; } }, serviceMap); assertEquals(1, serviceMap.size()); assertEquals(Collections.singleton("X"), serviceMap.keySet().iterator().next(). getProperty(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY)); } @SuppressWarnings("unchecked") @Test public void testAssignRoles4() throws Exception { Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, "foobar"); config.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); config.put("somemethod", "b"); config.put("someOtherMethod", "b"); config.put("somethingelse", "*"); BundleContext bc = mockConfigAdminBundleContext(config); Dictionary<String, Object> proxyProps = testCreateProxy(bc, TestServiceAPI.class, new TestService()); Collection<String> result = (Collection<String>) proxyProps.get(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY); assertEquals(1, result.size()); assertEquals("b", result.iterator().next()); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking1() throws Exception { Dictionary<String, Object> c1 = new Hashtable<String, Object>(); c1.put(Constants.SERVICE_PID, "foobar"); c1.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); c1.put("doit", "a,b"); Dictionary<String, Object> c2 = new Hashtable<String, Object>(); c2.put(Constants.SERVICE_PID, "barfoobar"); c2.put("service.guard", "(objectClass=" + TestObjectWithoutInterface.class.getName() + ")"); c2.put("compute", "c"); BundleContext bc = mockConfigAdminBundleContext(c1, c2); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI.class, TestObjectWithoutInterface.class}, new CombinedTestService()); // Run with the right credentials so we can test the expected roles Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("b")); Subject.doAs(subject, new PrivilegedAction<Object>() { @Override public Object run() { assertEquals("Doing it", ((TestServiceAPI) proxy).doit()); if (!runningUnderCoverage) { try { ((TestObjectWithoutInterface) proxy).compute(44L); fail("Should have been blocked"); } catch (SecurityException se) { // good } } return null; } }); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking2() throws Exception { Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, "barfoobar"); config.put("service.guard", "(objectClass=" + TestObjectWithoutInterface.class.getName() + ")"); config.put("compute(long)[\"42\"]", "b"); config.put("compute(long)", "c"); BundleContext bc = mockConfigAdminBundleContext(config); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI.class, TestObjectWithoutInterface.class}, new CombinedTestService()); // Run with the right credentials so we can test the expected roles Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("b")); Subject.doAs(subject, new PrivilegedAction<Object>() { @Override public Object run() { if (!runningUnderCoverage) { assertEquals(-42L, ((TestObjectWithoutInterface) proxy).compute(42L)); try { ((TestObjectWithoutInterface) proxy).compute(44L); fail("Should have been blocked"); } catch (SecurityException se) { // good } } return null; } }); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking3() throws Exception { class MyService implements TestServiceAPI, TestServiceAPI2 { public String doit(String s) { return new StringBuilder(s).reverse().toString(); } public String doit() { return "Doing it"; } }; Dictionary<String, Object> c1 = new Hashtable<String, Object>(); c1.put(Constants.SERVICE_PID, "foobar"); c1.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); c1.put("do*", "c"); Dictionary<String, Object> c2 = new Hashtable<String, Object>(); c2.put(Constants.SERVICE_PID, "foobar2"); c2.put("service.guard", "(objectClass=" + TestServiceAPI2.class.getName() + ")"); c2.put("doit(java.lang.String)[/[tT][a]+/]", "b,d # a regex rule"); c2.put("doit(java.lang.String)", "a"); BundleContext bc = mockConfigAdminBundleContext(c1, c2); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI.class, TestServiceAPI2.class}, new MyService()); // Run with the right credentials so we can test the expected roles Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("c")); Subject.doAs(subject, new PrivilegedAction<Object>() { @Override public Object run() { assertEquals("Doing it", ((TestServiceAPI) proxy).doit()); return null; } }); Subject subject2 = new Subject(); subject2.getPrincipals().add(new RolePrincipal("b")); subject2.getPrincipals().add(new RolePrincipal("f")); Subject.doAs(subject2, new PrivilegedAction<Object>() { @Override public Object run() { try { assertEquals("Doing it", ((TestServiceAPI) proxy).doit()); fail("Should have been blocked"); } catch (SecurityException se) { // good } assertEquals("aaT", ((TestServiceAPI2) proxy).doit("Taa")); try { ((TestServiceAPI2) proxy).doit("t"); fail("Should have been blocked"); } catch (SecurityException se) { // good } return null; } }); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking4() throws Exception { BundleContext bc = mockConfigAdminBundleContext(); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI.class, TestObjectWithoutInterface.class}, new CombinedTestService()); // Run with the right credentials so we can test the expected roles Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("b")); Subject.doAs(subject, new PrivilegedAction<Object>() { @Override public Object run() { assertEquals("Doing it", ((TestServiceAPI) proxy).doit()); if (!runningUnderCoverage) { assertEquals(42L, ((TestObjectWithoutInterface) proxy).compute(-42L)); assertEquals(-44L, ((TestObjectWithoutInterface) proxy).compute(44L)); } return null; } }); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking5() throws Exception { Dictionary<String, Object> c1 = new Hashtable<String, Object>(); c1.put(Constants.SERVICE_PID, "foobar"); c1.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); c1.put("doit", "a,b"); BundleContext bc = mockConfigAdminBundleContext(c1); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI2.class}, new TestServiceAPI2() { @Override public String doit(String s) { return s.toUpperCase(); } }); // Invoke the service with role 'c'. Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("c")); Subject.doAs(subject, new PrivilegedAction<Object>() { @Override public Object run() { assertEquals("The invocation under role 'c' should be ok, as there are no rules specified " + "for this service at all.", "HELLO", ((TestServiceAPI2) proxy).doit("hello")); return null; } }); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking6() throws Exception { Dictionary<String, Object> c1 = new Hashtable<String, Object>(); c1.put(Constants.SERVICE_PID, "foobar"); c1.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); c1.put("doit", "a,b"); Dictionary<String, Object> c2 = new Hashtable<String, Object>(); c2.put(Constants.SERVICE_PID, "foobar2"); c2.put("service.guard", "(objectClass=" + TestServiceAPI2.class.getName() + ")"); c2.put("bar", "c"); BundleContext bc = mockConfigAdminBundleContext(c1, c2); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI2.class}, new TestServiceAPI2() { @Override public String doit(String s) { return s.toUpperCase(); } }); // Invoke the service with role 'c'. Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("a")); subject.getPrincipals().add(new RolePrincipal("b")); subject.getPrincipals().add(new RolePrincipal("c")); Subject.doAs(subject, new PrivilegedAction<Object>() { @Override public Object run() { try { ((TestServiceAPI2) proxy).doit("hello"); fail("The invocation should not process as the 'doit' operation has no roles associated with it"); } catch (SecurityException se) { // good } return null; } }); } @SuppressWarnings("unchecked") @Test public void testInvocationBlocking7() throws Exception { Dictionary<String, Object> c1 = new Hashtable<String, Object>(); c1.put(Constants.SERVICE_PID, "foobar"); c1.put("service.guard", "(objectClass=" + TestServiceAPI3.class.getName() + ")"); c1.put("foo()", "a"); c1.put("bar", "b"); c1.put("*", "*"); BundleContext bc = mockConfigAdminBundleContext(c1); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI3.class}, new TestService3()); Subject s1 = new Subject(); Subject.doAs(s1, new PrivilegedAction<Object>() { @Override public Object run() { TestServiceAPI3 obj = (TestServiceAPI3) proxy; assertEquals("Should have allowed this invocation for any (or no) role", -7, obj.foo(7)); try { obj.foo(); fail("Should have been blocked"); } catch (SecurityException se) { // good } try { obj.bar(); fail("Should have been blocked"); } catch (SecurityException se) { // good } return null; } }); Subject s2 = new Subject(); s2.getPrincipals().add(new RolePrincipal("a")); s2.getPrincipals().add(new RolePrincipal("b")); s2.getPrincipals().add(new RolePrincipal("d")); Subject.doAs(s2, new PrivilegedAction<Object>() { @Override public Object run() { TestServiceAPI3 obj = (TestServiceAPI3) proxy; assertEquals(42, obj.foo()); assertEquals(99, obj.bar()); assertEquals(-32767, obj.foo(32767)); return null; } }); } @SuppressWarnings("unchecked") @Test public void testCustomRole() throws Exception { class MyRolePrincipal implements Principal { @Override public String getName() { return "role1"; } }; Dictionary<String, Object> c1 = new Hashtable<String, Object>(); c1.put(Constants.SERVICE_PID, "foobar"); c1.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); c1.put("doit", MyRolePrincipal.class.getName() + ":role1"); BundleContext bc = mockConfigAdminBundleContext(c1); final Object proxy = testCreateProxy(bc, new Class [] {TestServiceAPI.class}, new TestService()); Subject s1 = new Subject(); s1.getPrincipals().add(new RolePrincipal("role1")); Subject.doAs(s1, new PrivilegedAction<Object>() { @Override public Object run() { try { ((TestServiceAPI) proxy).doit(); fail("Should have prevented this invocation as the custom role is required"); } catch (SecurityException se) { // good } return null; } }); Subject s2 = new Subject(); s2.getPrincipals().add(new MyRolePrincipal()); Subject.doAs(s2, new PrivilegedAction<Object>() { @Override public Object run() { ((TestServiceAPI) proxy).doit(); // Should work, the custom role is there return null; } }); Subject s3 = new Subject(); s3.getPrincipals().add(new MyRolePrincipal()); s3.getPrincipals().add(new RolePrincipal("role1")); Subject.doAs(s3, new PrivilegedAction<Object>() { @Override public Object run() { ((TestServiceAPI) proxy).doit(); // Should work, the custom role is there return null; } }); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testProxyCreationThread() throws Exception { ProxyManager proxyManager = getProxyManager(); ConfigurationAdmin ca = EasyMock.createMock(ConfigurationAdmin.class); EasyMock.expect(ca.listConfigurations(EasyMock.anyObject(String.class))).andReturn(null).anyTimes(); EasyMock.replay(ca); ServiceReference pmSref = EasyMock.createMock(ServiceReference.class); EasyMock.replay(pmSref); ServiceReference pmSref2 = EasyMock.createMock(ServiceReference.class); EasyMock.replay(pmSref2); ServiceReference cmSref = EasyMock.createMock(ServiceReference.class); EasyMock.replay(cmSref); Bundle b = EasyMock.createMock(Bundle.class); EasyMock.expect(b.getBundleId()).andReturn(23992734L).anyTimes(); EasyMock.replay(b); BundleContext bc = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes(); EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() { @Override public Filter answer() throws Throwable { return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]); } }).anyTimes(); final ServiceListener [] pmListenerHolder = new ServiceListener [1]; String pmFilter = "(&(objectClass=" + ProxyManager.class.getName() + ")" + "(!(" + GuardProxyCatalog.PROXY_SERVICE_KEY + "=*)))"; bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.eq(pmFilter)); EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { pmListenerHolder[0] = (ServiceListener) EasyMock.getCurrentArguments()[0]; return null; } }).anyTimes(); EasyMock.expect(bc.getServiceReferences(EasyMock.anyObject(String.class), EasyMock.contains(ConfigurationAdmin.class.getName()))).andReturn(new ServiceReference[] {cmSref}).anyTimes(); EasyMock.expect(bc.getService(pmSref)).andReturn(proxyManager).anyTimes(); EasyMock.expect(bc.getService(pmSref2)).andReturn(proxyManager).anyTimes(); EasyMock.expect(bc.getService(cmSref)).andReturn(ca).anyTimes(); EasyMock.replay(bc); // This should put a ServiceListener in the pmListenerHolder, the ServiceTracker does that GuardProxyCatalog gpc = new GuardProxyCatalog(bc); // The service being proxied has these properties final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(Constants.OBJECTCLASS, new String [] {TestServiceAPI.class.getName()}); serviceProps.put(Constants.SERVICE_ID, 162L); final Map<ServiceReference<?>, Object> serviceMap = new HashMap<ServiceReference<?>, Object>(); // The mock bundle context for the bundle providing the service is set up here BundleContext providerBC = EasyMock.createMock(BundleContext.class); // These are the expected service properties of the proxy registration. Note the proxy marker... final Hashtable<String, Object> expectedProxyProps = new Hashtable<String, Object>(serviceProps); expectedProxyProps.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE); EasyMock.expect(providerBC.registerService( EasyMock.isA(String[].class), EasyMock.anyObject(), EasyMock.isA(Dictionary.class))).andAnswer(new IAnswer() { @Override public ServiceRegistration answer() throws Throwable { Dictionary<String,Object> props = (Dictionary<String, Object>) EasyMock.getCurrentArguments()[2]; ServiceRegistration reg = EasyMock.createMock(ServiceRegistration.class); ServiceReference sr = mockServiceReference(props); EasyMock.expect(reg.getReference()).andReturn(sr).anyTimes(); reg.unregister(); EasyMock.expectLastCall().once(); EasyMock.replay(reg); serviceMap.put(sr, EasyMock.getCurrentArguments()[1]); return reg; } }).once(); EasyMock.expect(providerBC.getService(EasyMock.isA(ServiceReference.class))).andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { return serviceMap.get(EasyMock.getCurrentArguments()[0]); } }).anyTimes(); EasyMock.replay(providerBC); // In some cases the proxy-creating code is looking for a classloader (e.g. when run through // a coverage tool such as EclEmma). This will satisfy that. BundleWiring bw = EasyMock.createMock(BundleWiring.class); EasyMock.expect(bw.getClassLoader()).andReturn(getClass().getClassLoader()).anyTimes(); EasyMock.replay(bw); // The mock bundle that provides the original service (and also the proxy is registered with this) Bundle providerBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(providerBundle.getBundleContext()).andReturn(providerBC).anyTimes(); EasyMock.expect(providerBundle.adapt(BundleWiring.class)).andReturn(bw).anyTimes(); EasyMock.replay(providerBundle); ServiceReference sr = mockServiceReference(providerBundle, serviceProps); assertEquals("Precondition", 0, gpc.proxyMap.size()); assertEquals("Precondition", 0, gpc.createProxyQueue.size()); // Create the proxy for the service gpc.proxyIfNotAlreadyProxied(sr); assertEquals(1, gpc.proxyMap.size()); assertEquals(1, gpc.createProxyQueue.size()); // The actual proxy creation is done asynchronously. GuardProxyCatalog.ServiceRegistrationHolder holder = gpc.proxyMap.get(162L); assertNull("The registration shouldn't have happened yet", holder.registration); assertEquals(1, gpc.createProxyQueue.size()); Thread[] tarray = new Thread[Thread.activeCount()]; Thread.enumerate(tarray); for (Thread t : tarray) { if (t != null) { assertTrue(!GuardProxyCatalog.PROXY_CREATOR_THREAD_NAME.equals(t.getName())); } } // make the proxy manager appear pmListenerHolder[0].serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, pmSref)); Thread.sleep(400); // give the system some time to send the events... Thread ourThread = null; Thread[] tarray2 = new Thread[Thread.activeCount()]; Thread.enumerate(tarray2); for (Thread t : tarray2) { if (t != null) { if (t.getName().equals(GuardProxyCatalog.PROXY_CREATOR_THREAD_NAME)) { ourThread = t; } } } assertNotNull(ourThread); assertTrue(ourThread.isDaemon()); assertTrue(ourThread.isAlive()); assertNotNull(holder.registration); assertEquals(0, gpc.createProxyQueue.size()); int numProxyThreads = 0; pmListenerHolder[0].serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, pmSref2)); Thread.sleep(300); // give the system some time to send the events... Thread[] tarray3 = new Thread[Thread.activeCount()]; Thread.enumerate(tarray3); for (Thread t : tarray3) { if (t != null) { if (t.getName().equals(GuardProxyCatalog.PROXY_CREATOR_THREAD_NAME)) { numProxyThreads++; } } } assertEquals("Maximum 1 proxy thread, even if there is more than 1 proxy service", 1, numProxyThreads); // Clean up thread pmListenerHolder[0].serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, pmSref)); Thread.sleep(300); // Give the system some time to stop the threads... Thread[] tarray4 = new Thread[Thread.activeCount()]; Thread.enumerate(tarray4); for (Thread t : tarray4) { if (t != null) { assertTrue(!GuardProxyCatalog.PROXY_CREATOR_THREAD_NAME.equals(t.getName())); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testHandleServiceModified() throws Exception { Dictionary<String, Object> config = new Hashtable<String, Object>(); config.put(Constants.SERVICE_PID, "test.1.2.3"); config.put("service.guard", "(objectClass=" + TestServiceAPI.class.getName() + ")"); config.put("doit", "role.1"); Dictionary<String, Object> config2 = new Hashtable<String, Object>(); config2.put(Constants.SERVICE_PID, "test.1.2.4"); config2.put("service.guard", "(objectClass=" + TestServiceAPI2.class.getName() + ")"); config2.put("doit", "role.2"); BundleContext bc = mockConfigAdminBundleContext(config, config2); GuardProxyCatalog gpc = new GuardProxyCatalog(bc); // The service being proxied has these properties long serviceID = 1L; final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(Constants.OBJECTCLASS, new String [] {TestServiceAPI.class.getName(), TestServiceAPI2.class.getName()}); serviceProps.put(Constants.SERVICE_ID, serviceID); serviceProps.put(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY, Arrays.asList("someone")); // will be overwritten Object myObject = new Object(); serviceProps.put("foo.bar", myObject); BundleContext providerBC = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(providerBC.registerService( EasyMock.aryEq(new String [] {TestServiceAPI.class.getName(), TestServiceAPI2.class.getName()}), EasyMock.anyObject(), EasyMock.anyObject(Dictionary.class))).andAnswer(new IAnswer() { @Override public Object answer() throws Throwable { final Dictionary props = (Dictionary) EasyMock.getCurrentArguments()[2]; assertEquals(Boolean.TRUE, props.get(GuardProxyCatalog.PROXY_SERVICE_KEY)); ServiceRegistration reg = EasyMock.createMock(ServiceRegistration.class); ServiceReference sr = mockServiceReference(props); EasyMock.expect(reg.getReference()).andReturn(sr).anyTimes(); reg.setProperties(EasyMock.isA(Dictionary.class)); EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { // Push the update into the service reference ArrayList<String> oldKeys = Collections.list(props.keys()); for (String key : oldKeys) { props.remove(key); } Dictionary<String, Object> newProps = (Dictionary<String, Object>) EasyMock.getCurrentArguments()[0]; for (String key : Collections.list(newProps.keys())) { props.put(key, newProps.get(key)); } return null; } }).once(); EasyMock.replay(reg); return reg; } }).anyTimes(); EasyMock.replay(providerBC); // In some cases the proxy-creating code is looking for a classloader (e.g. when run through // a coverage tool such as EclEmma). This will satisfy that. BundleWiring bw = EasyMock.createMock(BundleWiring.class); EasyMock.expect(bw.getClassLoader()).andReturn(getClass().getClassLoader()).anyTimes(); EasyMock.replay(bw); // The mock bundle that provides the original service (and also the proxy is registered with this) Bundle providerBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(providerBundle.getBundleContext()).andReturn(providerBC).anyTimes(); EasyMock.expect(providerBundle.adapt(BundleWiring.class)).andReturn(bw).anyTimes(); EasyMock.replay(providerBundle); ServiceReference sr = mockServiceReference(providerBundle, serviceProps); gpc.proxyIfNotAlreadyProxied(sr); GuardProxyCatalog.CreateProxyRunnable runnable = gpc.createProxyQueue.take(); runnable.run(getProxyManager()); ServiceRegistrationHolder holder = gpc.proxyMap.get(serviceID); ServiceRegistration<?> reg = holder.registration; for (String key : serviceProps.keySet()) { if (GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY.equals(key)) { assertEquals(new HashSet(Arrays.asList("role.1", "role.2")), reg.getReference().getProperty(key)); } else { assertEquals(serviceProps.get(key), reg.getReference().getProperty(key)); } } assertEquals(Boolean.TRUE, reg.getReference().getProperty(GuardProxyCatalog.PROXY_SERVICE_KEY)); // now change the original service and let the proxy react serviceProps.put("test", "property"); assertEquals("Precondition, the mocked reference should have picked up this change", "property", sr.getProperty("test")); gpc.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, sr)); assertEquals("Changing the service should not change the number of proxies", 1, gpc.proxyMap.size()); for (String key : serviceProps.keySet()) { if (GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY.equals(key)) { assertEquals(new HashSet(Arrays.asList("role.1", "role.2")), reg.getReference().getProperty(key)); } else { assertEquals(serviceProps.get(key), reg.getReference().getProperty(key)); } } assertEquals("property", reg.getReference().getProperty("test")); assertEquals(Boolean.TRUE, reg.getReference().getProperty(GuardProxyCatalog.PROXY_SERVICE_KEY)); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testHandleServiceModified2() throws Exception { BundleContext bc = mockConfigAdminBundleContext(); // no configuration used in this test... GuardProxyCatalog gpc = new GuardProxyCatalog(bc); // The service being proxied has these properties long serviceID = 1L; final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(Constants.OBJECTCLASS, new String [] {TestServiceAPI.class.getName()}); serviceProps.put(Constants.SERVICE_ID, serviceID); BundleContext providerBC = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(providerBC.registerService( EasyMock.aryEq(new String [] {TestServiceAPI.class.getName()}), EasyMock.anyObject(), EasyMock.anyObject(Dictionary.class))).andAnswer(new IAnswer() { @Override public Object answer() throws Throwable { final Dictionary props = (Dictionary) EasyMock.getCurrentArguments()[2]; assertEquals(Boolean.TRUE, props.get(GuardProxyCatalog.PROXY_SERVICE_KEY)); ServiceRegistration reg = EasyMock.createMock(ServiceRegistration.class); ServiceReference sr = mockServiceReference(props); EasyMock.expect(reg.getReference()).andReturn(sr).anyTimes(); reg.setProperties(EasyMock.isA(Dictionary.class)); EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { // Push the update into the service reference ArrayList<String> oldKeys = Collections.list(props.keys()); for (String key : oldKeys) { props.remove(key); } Dictionary<String, Object> newProps = (Dictionary<String, Object>) EasyMock.getCurrentArguments()[0]; for (String key : Collections.list(newProps.keys())) { props.put(key, newProps.get(key)); } return null; } }).once(); EasyMock.replay(reg); return reg; } }).anyTimes(); EasyMock.replay(providerBC); // In some cases the proxy-creating code is looking for a classloader (e.g. when run through // a coverage tool such as EclEmma). This will satisfy that. BundleWiring bw = EasyMock.createMock(BundleWiring.class); EasyMock.expect(bw.getClassLoader()).andReturn(getClass().getClassLoader()).anyTimes(); EasyMock.replay(bw); // The mock bundle that provides the original service (and also the proxy is registered with this) Bundle providerBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(providerBundle.getBundleContext()).andReturn(providerBC).anyTimes(); EasyMock.expect(providerBundle.adapt(BundleWiring.class)).andReturn(bw).anyTimes(); EasyMock.replay(providerBundle); ServiceReference sr = mockServiceReference(providerBundle, serviceProps); gpc.proxyIfNotAlreadyProxied(sr); GuardProxyCatalog.CreateProxyRunnable runnable = gpc.createProxyQueue.take(); runnable.run(getProxyManager()); ServiceRegistrationHolder holder = gpc.proxyMap.get(serviceID); ServiceRegistration<?> reg = holder.registration; assertFalse("No roles defined for this service using configuration, so roles property should not be set", Arrays.asList(reg.getReference().getPropertyKeys()).contains(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY)); for (String key : serviceProps.keySet()) { assertEquals(serviceProps.get(key), reg.getReference().getProperty(key)); } assertEquals(Boolean.TRUE, reg.getReference().getProperty(GuardProxyCatalog.PROXY_SERVICE_KEY)); // now change the original service and let the proxy react serviceProps.put(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY, "foobar"); assertEquals("Precondition, the mocked reference should have picked up this change", "foobar", sr.getProperty(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY)); gpc.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, sr)); assertEquals("Changing the service should not change the number of proxies", 1, gpc.proxyMap.size()); assertFalse("The roles property set on the modified service should have been removed", Arrays.asList(reg.getReference().getPropertyKeys()).contains(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY)); assertEquals(Boolean.TRUE, reg.getReference().getProperty(GuardProxyCatalog.PROXY_SERVICE_KEY)); } @Test @SuppressWarnings({ "unchecked", "rawtypes" }) public void testServiceFactoryBehaviour() throws Exception { final Map<ServiceReference, Object> serviceMap = new HashMap<ServiceReference, Object>(); TestServiceAPI testService = new TestService(); BundleContext bc = mockConfigAdminBundleContext(); GuardProxyCatalog gpc = new GuardProxyCatalog(bc); // The service being proxied has these properties long serviceID = 117L; final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(Constants.OBJECTCLASS, new String [] {TestServiceAPI.class.getName()}); serviceProps.put(Constants.SERVICE_ID, serviceID); serviceProps.put("bar", 42L); BundleContext providerBC = EasyMock.createMock(BundleContext.class); EasyMock.expect(providerBC.registerService( EasyMock.isA(String[].class), EasyMock.anyObject(), EasyMock.isA(Dictionary.class))).andAnswer(new IAnswer() { @Override public ServiceRegistration answer() throws Throwable { Dictionary<String,Object> props = (Dictionary<String, Object>) EasyMock.getCurrentArguments()[2]; ServiceRegistration reg = EasyMock.createMock(ServiceRegistration.class); ServiceReference sr = mockServiceReference(props); EasyMock.expect(reg.getReference()).andReturn(sr).anyTimes(); EasyMock.replay(reg); serviceMap.put(sr, EasyMock.getCurrentArguments()[1]); return reg; } }).once(); EasyMock.expect(providerBC.getService(EasyMock.isA(ServiceReference.class))).andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { return serviceMap.get(EasyMock.getCurrentArguments()[0]); } }).anyTimes(); EasyMock.replay(providerBC); // In some cases the proxy-creating code is looking for a classloader (e.g. when run through // a coverage tool such as EclEmma). This will satisfy that. BundleWiring bw = EasyMock.createMock(BundleWiring.class); EasyMock.expect(bw.getClassLoader()).andReturn(getClass().getClassLoader()).anyTimes(); EasyMock.replay(bw); // The mock bundle that provides the original service (and also the proxy is registered with this) Bundle providerBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(providerBundle.getBundleContext()).andReturn(providerBC).anyTimes(); EasyMock.expect(providerBundle.adapt(BundleWiring.class)).andReturn(bw).anyTimes(); EasyMock.replay(providerBundle); ServiceReference sr = mockServiceReference(providerBundle, serviceProps); // The mock bundle context for the client bundle BundleContext clientBC = EasyMock.createMock(BundleContext.class); EasyMock.expect(clientBC.getService(sr)).andReturn(testService).anyTimes(); EasyMock.replay(clientBC); // The mock bundle that consumes the service Bundle clientBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(clientBundle.getBundleContext()).andReturn(clientBC).anyTimes(); EasyMock.replay(clientBundle); gpc.proxyIfNotAlreadyProxied(sr); // The actual proxy creation is done asynchronously. GuardProxyCatalog.ServiceRegistrationHolder holder = gpc.proxyMap.get(serviceID); // Mimic the thread that works the queue to create the proxy GuardProxyCatalog.CreateProxyRunnable runnable = gpc.createProxyQueue.take(); assertEquals(117L, runnable.getOriginalServiceID()); ProxyManager pm = getProxyManager(); runnable.run(pm); // The runnable should have put the actual registration in the holder ServiceReference<?> proxySR = holder.registration.getReference(); // Check that the proxy registration was done on the original provider bundle's context EasyMock.verify(providerBC); // Test that the actual proxy invokes the original service... ServiceFactory proxyServiceSF = (ServiceFactory) serviceMap.get(proxySR); TestServiceAPI proxyService = (TestServiceAPI) proxyServiceSF.getService(clientBundle, null); assertNotSame("The proxy should not be the same object as the original service", testService, proxyService); assertEquals("Doing it", proxyService.doit()); EasyMock.reset(clientBC); EasyMock.expect(clientBC.ungetService(sr)).andReturn(true).once(); EasyMock.replay(clientBC); proxyServiceSF.ungetService(clientBundle, null, proxyService); EasyMock.verify(clientBC); } @SuppressWarnings("unchecked") public Dictionary<String, Object> testCreateProxy(Class<?> intf, Object testService) throws Exception { return testCreateProxy(mockConfigAdminBundleContext(), intf, intf, testService); } public Dictionary<String, Object> testCreateProxy(BundleContext bc, Class<?> intf, Object testService) throws Exception { return testCreateProxy(bc, intf, intf, testService); } @SuppressWarnings({ "unchecked", "rawtypes" }) public Dictionary<String, Object> testCreateProxy(BundleContext bc, Class intf, final Class proxyRegClass, Object testService) throws Exception { // Create the object that is actually being tested here GuardProxyCatalog gpc = new GuardProxyCatalog(bc); // The service being proxied has these properties long serviceID = 456L; final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(Constants.OBJECTCLASS, new String [] {intf.getName()}); serviceProps.put(Constants.SERVICE_ID, serviceID); serviceProps.put(".foo", 123L); final Map<ServiceReference<?>, Object> serviceMap = new HashMap<ServiceReference<?>, Object>(); // The mock bundle context for the bundle providing the service is set up here BundleContext providerBC = EasyMock.createMock(BundleContext.class); // These are the expected service properties of the proxy registration. Note the proxy marker... final Hashtable<String, Object> expectedProxyProps = new Hashtable<String, Object>(serviceProps); expectedProxyProps.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE); // This will check that the right proxy is being registered. EasyMock.expect(providerBC.registerService( EasyMock.isA(String[].class), EasyMock.anyObject(), EasyMock.isA(Dictionary.class))).andAnswer(new IAnswer() { @Override public ServiceRegistration answer() throws Throwable { if (!runningUnderCoverage) { // Some of these checks don't work when running under coverage assertArrayEquals(new String [] {proxyRegClass.getName()}, (String []) EasyMock.getCurrentArguments()[0]); Object svc = EasyMock.getCurrentArguments()[1]; assertTrue(svc instanceof ServiceFactory); } Dictionary<String,Object> props = (Dictionary<String, Object>) EasyMock.getCurrentArguments()[2]; for (String key : expectedProxyProps.keySet()) { assertEquals(expectedProxyProps.get(key), props.get(key)); } ServiceRegistration reg = EasyMock.createMock(ServiceRegistration.class); ServiceReference sr = mockServiceReference(props); EasyMock.expect(reg.getReference()).andReturn(sr).anyTimes(); reg.unregister(); EasyMock.expectLastCall().once(); EasyMock.replay(reg); serviceMap.put(sr, EasyMock.getCurrentArguments()[1]); return reg; } }).once(); EasyMock.expect(providerBC.getService(EasyMock.isA(ServiceReference.class))).andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { return serviceMap.get(EasyMock.getCurrentArguments()[0]); } }).anyTimes(); EasyMock.replay(providerBC); // In some cases the proxy-creating code is looking for a classloader (e.g. when run through // a coverage tool such as EclEmma). This will satisfy that. BundleWiring bw = EasyMock.createMock(BundleWiring.class); EasyMock.expect(bw.getClassLoader()).andReturn(getClass().getClassLoader()).anyTimes(); EasyMock.replay(bw); // The mock bundle that provides the original service (and also the proxy is registered with this) Bundle providerBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(providerBundle.getBundleContext()).andReturn(providerBC).anyTimes(); EasyMock.expect(providerBundle.adapt(BundleWiring.class)).andReturn(bw).anyTimes(); EasyMock.replay(providerBundle); ServiceReference sr = mockServiceReference(providerBundle, serviceProps); assertEquals("Precondition", 0, gpc.proxyMap.size()); assertEquals("Precondition", 0, gpc.createProxyQueue.size()); // Create the proxy for the service gpc.proxyIfNotAlreadyProxied(sr); assertEquals(1, gpc.proxyMap.size()); // The actual proxy creation is done asynchronously. GuardProxyCatalog.ServiceRegistrationHolder holder = gpc.proxyMap.get(serviceID); assertNull("The registration shouldn't have happened yet", holder.registration); assertEquals(1, gpc.createProxyQueue.size()); // Mimic the thread that works the queue to create the proxy GuardProxyCatalog.CreateProxyRunnable runnable = gpc.createProxyQueue.take(); ProxyManager pm = getProxyManager(); runnable.run(pm); // The runnable should have put the actual registration in the holder ServiceReference<?> proxySR = holder.registration.getReference(); for (String key : expectedProxyProps.keySet()) { assertEquals(expectedProxyProps.get(key), proxySR.getProperty(key)); } // Check that the proxy registration was done on the original provider bundle's context EasyMock.verify(providerBC); // Test that the actual proxy invokes the original service... Object proxyService = serviceMap.get(proxySR); assertNotSame("The proxy should not be the same object as the original service", testService, proxyService); // Attempt to proxy the service again, make sure that no re-proxying happens assertEquals("Precondition", 1, gpc.proxyMap.size()); assertEquals("Precondition", 0, gpc.createProxyQueue.size()); gpc.proxyIfNotAlreadyProxied(sr); assertEquals("No additional proxy should have been created", 1, gpc.proxyMap.size()); assertEquals("No additional work on the queue is expected", 0, gpc.createProxyQueue.size()); Dictionary<String, Object> proxyProps = getServiceReferenceProperties(proxySR); gpc.close(); EasyMock.verify(holder.registration); // checks that the unregister call was made return proxyProps; } @SuppressWarnings({ "unchecked", "rawtypes" }) public Object testCreateProxy(Class<?> [] objectClasses, Object testService) throws Exception { return testCreateProxy(mockConfigAdminBundleContext(), objectClasses, objectClasses, testService, new HashMap<ServiceReference, Object>()); } @SuppressWarnings("rawtypes") public Object testCreateProxy(BundleContext bc, Class<?> [] objectClasses, Object testService) throws Exception { return testCreateProxy(bc, objectClasses, objectClasses, testService, new HashMap<ServiceReference, Object>()); } @SuppressWarnings("rawtypes") public Object testCreateProxy(BundleContext bc, Class<?> [] objectClasses, Object testService, Map<ServiceReference, Object> serviceMap) throws Exception { return testCreateProxy(bc, objectClasses, objectClasses, testService, serviceMap); } @SuppressWarnings({ "unchecked", "rawtypes" }) public Object testCreateProxy(BundleContext bc, Class [] objectClasses, final Class [] proxyRegClasses, Object testService, final Map<ServiceReference, Object> serviceMap) throws Exception { // A linked hash map to keep iteration order over the keys predictable final LinkedHashMap<String, Class> objClsMap = new LinkedHashMap<String, Class>(); for (Class cls : objectClasses) { objClsMap.put(cls.getName(), cls); } // A linked hash map to keep iteration order over the keys predictable final LinkedHashMap<String, Class> proxyRegClsMap = new LinkedHashMap<String, Class>(); for (Class cls : proxyRegClasses) { proxyRegClsMap.put(cls.getName(), cls); } // Create the object that is actually being tested here GuardProxyCatalog gpc = new GuardProxyCatalog(bc); // The service being proxied has these properties long serviceID = Long.MAX_VALUE; final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); serviceProps.put(Constants.OBJECTCLASS, objClsMap.keySet().toArray(new String [] {})); serviceProps.put(Constants.SERVICE_ID, serviceID); serviceProps.put(GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY, Arrays.asList("everyone")); // will be overwritten serviceProps.put("bar", "foo"); // The mock bundle context for the bundle providing the service is set up here BundleContext providerBC = EasyMock.createMock(BundleContext.class); // These are the expected service properties of the proxy registration. Note the proxy marker... final Hashtable<String, Object> expectedProxyProps = new Hashtable<String, Object>(serviceProps); expectedProxyProps.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE); // This will check that the right proxy is being registered. EasyMock.expect(providerBC.registerService( EasyMock.isA(String[].class), EasyMock.anyObject(), EasyMock.isA(Dictionary.class))).andAnswer(new IAnswer() { @Override public ServiceRegistration answer() throws Throwable { if (!runningUnderCoverage) { // Some of these checks don't work when running under coverage assertArrayEquals(proxyRegClsMap.keySet().toArray(new String [] {}), (String []) EasyMock.getCurrentArguments()[0]); Object svc = EasyMock.getCurrentArguments()[1]; assertTrue(svc instanceof ServiceFactory); } Dictionary<String,Object> props = (Dictionary<String, Object>) EasyMock.getCurrentArguments()[2]; for (String key : expectedProxyProps.keySet()) { if (GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY.equals(key)) { assertTrue("The roles property should have been overwritten", !Arrays.asList("everyone").equals(props.get(key))); } else { assertEquals(expectedProxyProps.get(key), props.get(key)); } } ServiceRegistration reg = EasyMock.createMock(ServiceRegistration.class); ServiceReference sr = mockServiceReference(props); EasyMock.expect(reg.getReference()).andReturn(sr).anyTimes(); reg.unregister(); EasyMock.expectLastCall().once(); EasyMock.replay(reg); serviceMap.put(sr, EasyMock.getCurrentArguments()[1]); return reg; } }).once(); EasyMock.expect(providerBC.getService(EasyMock.isA(ServiceReference.class))).andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { return serviceMap.get(EasyMock.getCurrentArguments()[0]); } }).anyTimes(); EasyMock.replay(providerBC); // In some cases the proxy-creating code is looking for a classloader (e.g. when run through // a coverage tool such as EclEmma). This will satisfy that. BundleWiring bw = EasyMock.createMock(BundleWiring.class); EasyMock.expect(bw.getClassLoader()).andReturn(getClass().getClassLoader()).anyTimes(); EasyMock.replay(bw); // The mock bundle that provides the original service (and also the proxy is registered with this) Bundle providerBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(providerBundle.getBundleContext()).andReturn(providerBC).anyTimes(); EasyMock.expect(providerBundle.adapt(BundleWiring.class)).andReturn(bw).anyTimes(); EasyMock.replay(providerBundle); ServiceReference sr = mockServiceReference(providerBundle, serviceProps); // The mock bundle context for the client bundle BundleContext clientBC = EasyMock.createMock(BundleContext.class); EasyMock.expect(clientBC.getService(sr)).andReturn(testService).anyTimes(); EasyMock.replay(clientBC); // The mock bundle that consumes the service Bundle clientBundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(clientBundle.getBundleId()).andReturn(2999L).anyTimes(); EasyMock.expect(clientBundle.getBundleContext()).andReturn(clientBC).anyTimes(); EasyMock.expect(clientBundle.loadClass(EasyMock.isA(String.class))).andAnswer(new IAnswer() { @Override public Class answer() throws Throwable { return objClsMap.get(EasyMock.getCurrentArguments()[0]); } }).anyTimes(); EasyMock.replay(clientBundle); assertEquals("Precondition", 0, gpc.proxyMap.size()); assertEquals("Precondition", 0, gpc.createProxyQueue.size()); // Create the proxy for the service gpc.proxyIfNotAlreadyProxied(sr); assertEquals(1, gpc.proxyMap.size()); // The actual proxy creation is done asynchronously. GuardProxyCatalog.ServiceRegistrationHolder holder = gpc.proxyMap.get(serviceID); assertNull("The registration shouldn't have happened yet", holder.registration); assertEquals(1, gpc.createProxyQueue.size()); // Mimic the thread that works the queue to create the proxy GuardProxyCatalog.CreateProxyRunnable runnable = gpc.createProxyQueue.take(); ProxyManager pm = getProxyManager(); runnable.run(pm); // The runnable should have put the actual registration in the holder ServiceReference<?> proxySR = holder.registration.getReference(); for (String key : expectedProxyProps.keySet()) { if (GuardProxyCatalog.SERVICE_GUARD_ROLES_PROPERTY.equals(key)) { assertTrue("The roles property should have been overwritten", !Arrays.asList("everyone").equals(proxySR.getProperty(key))); } else { assertEquals(expectedProxyProps.get(key), proxySR.getProperty(key)); } } // Check that the proxy registration was done on the original provider bundle's context EasyMock.verify(providerBC); // Test that the actual proxy invokes the original service... ServiceFactory proxyServiceSF = (ServiceFactory) serviceMap.get(proxySR); Object proxyService = proxyServiceSF.getService(clientBundle, null); assertNotSame("The proxy should not be the same object as the original service", testService, proxyService); return proxyService; } private ProxyManager getProxyManager() { return new AsmProxyManager(); } private Dictionary<String, Object> getServiceReferenceProperties(ServiceReference<?> sr) { Dictionary<String, Object> dict = new Hashtable<String, Object>(); for (String key : sr.getPropertyKeys()) { dict.put(key, sr.getProperty(key)); } return dict; } private Bundle mockBundle(long id) { Bundle bundle = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(bundle.getBundleId()).andReturn(id).anyTimes(); EasyMock.replay(bundle); return bundle; } private BundleContext mockBundleContext() throws InvalidSyntaxException { return mockBundleContext(null); } private BundleContext mockBundleContext(Bundle b) throws InvalidSyntaxException { if (b == null) { b = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(b.getBundleId()).andReturn(89334L).anyTimes(); EasyMock.replay(b); } BundleContext bc = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() { @Override public Filter answer() throws Throwable { return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]); } }).anyTimes(); EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes(); EasyMock.replay(bc); return bc; } private BundleContext openStrictMockBundleContext(Bundle b) throws InvalidSyntaxException { BundleContext bc = EasyMock.createMock(BundleContext.class); EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() { @Override public Filter answer() throws Throwable { return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]); } }).anyTimes(); if (b != null) { EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes(); } return bc; } @SuppressWarnings({ "rawtypes", "unchecked" }) private BundleContext mockConfigAdminBundleContext(Dictionary<String, Object> ... configs) throws IOException, InvalidSyntaxException { Configuration [] configurations = new Configuration[configs.length]; for (int i = 0; i < configs.length; i++) { Configuration conf = EasyMock.createMock(Configuration.class); EasyMock.expect(conf.getProperties()).andReturn(configs[i]).anyTimes(); EasyMock.expect(conf.getPid()).andReturn((String) configs[i].get(Constants.SERVICE_PID)).anyTimes(); EasyMock.replay(conf); configurations[i] = conf; } if (configurations.length == 0) { configurations = null; } ConfigurationAdmin ca = EasyMock.createMock(ConfigurationAdmin.class); EasyMock.expect(ca.listConfigurations("(&(service.pid=org.apache.karaf.service.acl.*)(service.guard=*))")).andReturn(configurations).anyTimes(); EasyMock.replay(ca); final ServiceReference caSR = EasyMock.createMock(ServiceReference.class); EasyMock.replay(caSR); Bundle b = EasyMock.createMock(Bundle.class); EasyMock.expect(b.getBundleId()).andReturn(877342449L).anyTimes(); EasyMock.replay(b); BundleContext bc = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes(); EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() { @Override public Filter answer() throws Throwable { return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]); } }).anyTimes(); String cmFilter = "(&(objectClass=" + ConfigurationAdmin.class.getName() + ")" + "(!(" + GuardProxyCatalog.PROXY_SERVICE_KEY + "=*)))"; bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.eq(cmFilter)); EasyMock.expectLastCall().anyTimes(); EasyMock.expect(bc.getServiceReferences(EasyMock.anyObject(String.class), EasyMock.eq(cmFilter))). andReturn(new ServiceReference<?> [] {caSR}).anyTimes(); EasyMock.expect(bc.getService(caSR)).andReturn(ca).anyTimes(); EasyMock.replay(bc); return bc; } private ServiceReference<?> mockServiceReference(final Dictionary<String, Object> props) { return mockServiceReference(props, Object.class); } @SuppressWarnings("unchecked") private <T> ServiceReference<T> mockServiceReference(Dictionary<String, Object> props, Class<T> cls) { return (ServiceReference<T>) mockServiceReference(null, props); } private ServiceReference<?> mockServiceReference(Bundle providerBundle, final Dictionary<String, Object> serviceProps) { ServiceReference<?> sr = EasyMock.createMock(ServiceReference.class); // Make sure the properties are 'live' in that if they change the reference changes too EasyMock.expect(sr.getPropertyKeys()).andAnswer(new IAnswer<String[]>() { @Override public String[] answer() throws Throwable { return Collections.list(serviceProps.keys()).toArray(new String [] {}); } }).anyTimes(); EasyMock.expect(sr.getProperty(EasyMock.isA(String.class))).andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { return serviceProps.get(EasyMock.getCurrentArguments()[0]); } }).anyTimes(); if (providerBundle != null) { EasyMock.expect(sr.getBundle()).andReturn(providerBundle).anyTimes(); } EasyMock.replay(sr); return sr; } class MockCreateProxyRunnable implements CreateProxyRunnable { private final long orgServiceID; public MockCreateProxyRunnable(long serviceID) { orgServiceID = serviceID; } @Override public long getOriginalServiceID() { return orgServiceID; } @Override public void run(ProxyManager pm) throws Exception {} } public interface TestServiceAPI { String doit(); } public class TestService implements TestServiceAPI { @Override public String doit() { return "Doing it"; } } public interface TestServiceAPI2 { String doit(String s); } public interface TestServiceAPI3 { int foo(); int foo(int f); int bar(); } class TestService3 implements TestServiceAPI3 { public int foo() { return 42; } public int foo(int f) { return -f; } public int bar() { return 99; } } public class TestObjectWithoutInterface { public long compute(long l) { return -l; } } public class CombinedTestService extends TestObjectWithoutInterface implements TestServiceAPI { @Override public String doit() { return "Doing it"; } } private abstract class AbstractService implements TestServiceAPI { @Override public String doit() { return "Doing it"; } } public class EmptyPublicTestService extends AbstractService {} public class DescendantTestService extends EmptyPublicTestService {} private class PrivateTestService implements TestServiceAPI { @Override public String doit() { return "Doing it"; } } private class PrivateTestServiceNoDirectInterfaces extends PrivateTestService {} public final class FinalTestService extends AbstractService implements TestServiceAPI {} public class ClassWithFinalMethod { public void foo() {} public final String bar() { return "Bar"; } } public class ClassWithPrivateMethod { public void foo() {} @SuppressWarnings("unused") private void bar() {} } }