From a6ffb9903eafbf3568b26ef466035b187d3d624e Mon Sep 17 00:00:00 2001 From: Marcel Offermans Date: Wed, 19 May 2010 11:56:44 +0000 Subject: [PATCH] Completed support for using dynamic proxies as generic aspects. Added a test to validate this behaviour. git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@946137 13f79535-47bb-0310-9956-ffa450edef68 --- .../felix/dm/DependencyActivatorBase.java | 3 + .../apache/felix/dm/DependencyManager.java | 11 +- .../org/apache/felix/dm/impl/AspectImpl.java | 40 +++- .../apache/felix/dm/impl/InvocationUtil.java | 5 +- .../org/apache/felix/dm/impl/ServiceImpl.java | 6 +- .../felix/dm/test/DynamicProxyAspectTest.java | 191 ++++++++++++++++++ 6 files changed, 243 insertions(+), 13 deletions(-) create mode 100644 dependencymanager/test/src/test/java/org/apache/felix/dm/test/DynamicProxyAspectTest.java diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyActivatorBase.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyActivatorBase.java index 736972c4ccf..2685e87b9f2 100644 --- a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyActivatorBase.java +++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyActivatorBase.java @@ -163,6 +163,9 @@ public Service createAspectService(Class serviceInterface, String serviceFilter, public Service createAspectService(Class serviceInterface, String serviceFilter, int ranking, Object factory, String factoryCreateMethod, Dictionary properties) { return m_manager.createAspectService(serviceInterface, serviceFilter, ranking, factory, factoryCreateMethod, properties); } + public Service createAspectService(Class serviceInterface, String serviceFilter, int ranking, Object factory, String factoryCreateMethod, String attributeName, Dictionary properties) { + return m_manager.createAspectService(serviceInterface, serviceFilter, ranking, factory, factoryCreateMethod, attributeName, properties); + } public Service createAdapterService(Class serviceInterface, String serviceFilter, String adapterInterface, Object adapterImplementation, Dictionary adapterProperties) { return m_manager.createAdapterService(serviceInterface, serviceFilter, adapterInterface, adapterImplementation, adapterProperties); diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java index 36f86b89b0a..c704526fde4 100644 --- a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java +++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java @@ -33,9 +33,9 @@ import org.apache.felix.dm.impl.AdapterImpl; import org.apache.felix.dm.impl.AspectImpl; import org.apache.felix.dm.impl.BundleAdapterImpl; +import org.apache.felix.dm.impl.FactoryConfigurationAdapterImpl; import org.apache.felix.dm.impl.FactoryConfigurationAdapterMetaTypeImpl; import org.apache.felix.dm.impl.Logger; -import org.apache.felix.dm.impl.FactoryConfigurationAdapterImpl; import org.apache.felix.dm.impl.ResourceAdapterImpl; import org.apache.felix.dm.impl.ServiceImpl; import org.apache.felix.dm.impl.dependencies.BundleDependencyImpl; @@ -201,6 +201,15 @@ public Service createAspectService(Class serviceInterface, String serviceFilter, .setCallbacks("added", "removed") ); } + public Service createAspectService(Class serviceInterface, String serviceFilter, int ranking, Object factory, String factoryCreateMethod, String attributeName, Dictionary aspectProperties) { + return createService() + .setImplementation(new AspectImpl(serviceInterface, serviceFilter, ranking, factory, factoryCreateMethod, attributeName, aspectProperties)) + .add(createServiceDependency() + .setService(serviceInterface, createAspectFilter(serviceFilter)) + .setAutoConfig(false) + .setCallbacks("added", "removed") + ); + } private String createAspectFilter(String filter) { // we only want to match services which are not themselves aspects if (filter == null || filter.length() == 0) { diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java index 230886dd577..72c143602ce 100644 --- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java +++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java @@ -37,6 +37,7 @@ public class AspectImpl extends AbstractDecorator { private final Object m_factory; private final String m_factoryCreateMethod; private final int m_ranking; + private final String m_attributeName; public AspectImpl(Class serviceInterface, String serviceFilter, int ranking, Object aspectImplementation, Dictionary properties) { m_serviceInterface = serviceInterface; @@ -46,6 +47,7 @@ public AspectImpl(Class serviceInterface, String serviceFilter, int ranking, Obj m_factory = null; m_factoryCreateMethod = null; m_ranking = ranking; + m_attributeName = null; } public AspectImpl(Class serviceInterface, String serviceFilter, int ranking, Object factory, String factoryCreateMethod, Dictionary properties) { @@ -56,6 +58,18 @@ public AspectImpl(Class serviceInterface, String serviceFilter, int ranking, Obj m_aspectProperties = properties; m_aspectImplementation = null; m_ranking = ranking; + m_attributeName = null; + } + + public AspectImpl(Class serviceInterface, String serviceFilter, int ranking, Object factory, String factoryCreateMethod, String attributeName, Dictionary properties) { + m_serviceInterface = serviceInterface; + m_serviceFilter = serviceFilter; + m_factory = factory; + m_factoryCreateMethod = factoryCreateMethod; + m_attributeName = attributeName; + m_aspectProperties = properties; + m_aspectImplementation = null; + m_ranking = ranking; } public Service createService(Object[] properties) { @@ -80,13 +94,25 @@ public Service createService(Object[] properties) { List dependencies = m_service.getDependencies(); dependencies.remove(0); if (m_aspectImplementation == null) { - return m_manager.createService() - .setInterface(m_serviceInterface.getName(), props) - .setFactory(m_factory, m_factoryCreateMethod) - .add(dependencies) - .add(m_manager.createServiceDependency() - .setService(m_serviceInterface, createAspectFilter(m_serviceFilter)) - .setRequired(true)); + if (m_attributeName == null) { + return m_manager.createService() + .setInterface(m_serviceInterface.getName(), props) + .setFactory(m_factory, m_factoryCreateMethod) + .add(dependencies) + .add(m_manager.createServiceDependency() + .setService(m_serviceInterface, createAspectFilter(m_serviceFilter)) + .setRequired(true)); + } + else { + return m_manager.createService() + .setInterface(m_serviceInterface.getName(), props) + .setFactory(m_factory, m_factoryCreateMethod) + .add(dependencies) + .add(m_manager.createServiceDependency() + .setService(m_serviceInterface, createAspectFilter(m_serviceFilter)) + .setAutoConfig(m_attributeName) + .setRequired(true)); + } } else { return m_manager.createService() diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/InvocationUtil.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/InvocationUtil.java index c2b62081275..b2e70c28e85 100644 --- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/InvocationUtil.java +++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/InvocationUtil.java @@ -20,7 +20,7 @@ public static void invokeCallbackMethod(Object instance, String methodName, Clas throw new NoSuchMethodException(methodName); } - public static void invokeMethod(Object object, Class clazz, String name, Class[][] signatures, Object[][] parameters, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public static Object invokeMethod(Object object, Class clazz, String name, Class[][] signatures, Object[][] parameters, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException { if (object == null) { throw new IllegalArgumentException("Instance cannot be null"); } @@ -34,8 +34,7 @@ public static void invokeMethod(Object object, Class clazz, String name, Class[] m = clazz.getDeclaredMethod(name, signature); if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) { m.setAccessible(true); - m.invoke(object, parameters[i]); - return; + return m.invoke(object, parameters[i]); } } catch (NoSuchMethodException e) { diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/ServiceImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/ServiceImpl.java index 9a0d33e4686..36381c4f277 100644 --- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/ServiceImpl.java +++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/ServiceImpl.java @@ -684,8 +684,10 @@ public void initService() { } else { try { - Method m = factory.getClass().getDeclaredMethod(m_instanceFactoryCreateMethod, null); - m_serviceInstance = m.invoke(factory, null); +// Method m = factory.getClass().getDeclaredMethod(m_instanceFactoryCreateMethod, null); +// m_serviceInstance = m.invoke(factory, null); +// + m_serviceInstance = InvocationUtil.invokeMethod(factory, factory.getClass(), m_instanceFactoryCreateMethod, new Class[][] {{}}, new Object[][] {{}}, false); } catch (Exception e) { m_logger.log(Logger.LOG_ERROR, "Could not create service instance using factory " + factory + " method " + m_instanceFactoryCreateMethod + ".", e); diff --git a/dependencymanager/test/src/test/java/org/apache/felix/dm/test/DynamicProxyAspectTest.java b/dependencymanager/test/src/test/java/org/apache/felix/dm/test/DynamicProxyAspectTest.java new file mode 100644 index 00000000000..67282396b7d --- /dev/null +++ b/dependencymanager/test/src/test/java/org/apache/felix/dm/test/DynamicProxyAspectTest.java @@ -0,0 +1,191 @@ +/* + * 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.test; + +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.CoreOptions.options; +import static org.ops4j.pax.exam.CoreOptions.provision; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import junit.framework.Assert; + +import org.apache.felix.dm.DependencyManager; +import org.apache.felix.dm.service.Service; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.Configuration; +import org.ops4j.pax.exam.junit.JUnit4TestRunner; +import org.osgi.framework.BundleContext; + +@RunWith(JUnit4TestRunner.class) +public class DynamicProxyAspectTest extends Base { + @Configuration + public static Option[] configuration() { + return options( + provision( + mavenBundle().groupId("org.osgi").artifactId("org.osgi.compendium").version("4.1.0"), + mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.dependencymanager").versionAsInProject() + ) + ); + } + + @Test + public void testImplementGenericAspectWithDynamicProxy(BundleContext context) { + DependencyManager m = new DependencyManager(context); + // helper class that ensures certain steps get executed in sequence + Ensure e = new Ensure(); + + // create two service providers, each providing a different service interface + Service sp1 = m.createService().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null); + Service sp2 = m.createService().setImplementation(new ServiceProvider2(e)).setInterface(ServiceInterface2.class.getName(), null); + + // create a dynamic proxy based aspect and hook it up to both services + Service a1 = m.createAspectService(ServiceInterface.class, null, 10, new Factory(e, ServiceInterface.class, "ServiceInterfaceProxy"), "create", "m_service", null); + Service a2 = m.createAspectService(ServiceInterface2.class, null, 10, new Factory(e, ServiceInterface2.class, "ServiceInterfaceProxy2"), "create", "m_service", null); + + // create a client that invokes a method on boths services, validate that it goes + // through the proxy twice + Service sc = m.createService() + .setImplementation(new ServiceConsumer(e)) + .add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true)) + .add(m.createServiceDependency().setService(ServiceInterface2.class).setRequired(true)) + ; + + // register both producers, validate that both services are started + m.add(sp1); + e.waitForStep(1, 2000); + m.add(sp2); + e.waitForStep(2, 2000); + + // add both aspects, and validate that both instances have been created + m.add(a1); + m.add(a2); + e.waitForStep(4, 4000); + + // add the client, which will automatically invoke both services + m.add(sc); + + // wait until both services have been invoked + e.waitForStep(6, 4000); + + // make sure the proxy has been called twice + Assert.assertEquals("Proxy should have been invoked this many times.", 2, DynamicProxyHandler.getCounter()); + } + + static interface ServiceInterface { + public void invoke(Runnable run); + } + + static interface ServiceInterface2 { + public void invoke(Runnable run); + } + + static class ServiceProvider implements ServiceInterface { + private final Ensure m_ensure; + public ServiceProvider(Ensure e) { + m_ensure = e; + } + public void start() { + m_ensure.step(1); + } + public void invoke(Runnable run) { + run.run(); + } + } + + static class ServiceProvider2 implements ServiceInterface2 { + private final Ensure m_ensure; + public ServiceProvider2(Ensure ensure) { + m_ensure = ensure; + } + public void start() { + m_ensure.step(2); + } + public void invoke(Runnable run) { + run.run(); + } + } + + static class ServiceConsumer implements Runnable { + private volatile ServiceInterface m_service; + private volatile ServiceInterface2 m_service2; + private final Ensure m_ensure; + + public ServiceConsumer(Ensure e) { + m_ensure = e; + } + + public void init() { + Thread t = new Thread(this); + t.start(); + } + + public void run() { + m_service.invoke(Ensure.createRunnableStep(m_ensure, 5)); + m_service2.invoke(Ensure.createRunnableStep(m_ensure, 6)); + } + } + + static class DynamicProxyHandler implements InvocationHandler { + public volatile Object m_service; // ISSUE, we cannot inject into "Object" at the moment + private final String m_label; + private static volatile int m_counter = 0; + + public DynamicProxyHandler(String label) { + m_label = label; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (m_service == null) { + Assert.fail("No service was injected into dynamic proxy handler " + m_label); + } + Method m = m_service.getClass().getMethod(method.getName(), method.getParameterTypes()); + if (m == null) { + Assert.fail("No method " + method.getName() + " was found in instance " + m_service + " in dynamic proxy handler " + m_label); + } + m_counter++; + return m.invoke(m_service, args); + } + + public static int getCounter() { + return m_counter; + } + } + + static class Factory { + private final String m_label; + private Class m_class; + private final Ensure m_ensure; + + public Factory(Ensure ensure, Class clazz, String label) { + m_ensure = ensure; + m_class = clazz; + m_label = label; + } + + public Object create() { + m_ensure.step(); + return Proxy.newProxyInstance(m_class.getClassLoader(), new Class[] { m_class }, new DynamicProxyHandler(m_label)); + } + } +}