[ https://issues.apache.org/jira/browse/FELIX-4689?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15003155#comment-15003155 ]
Pierre De Rop edited comment on FELIX-4689 at 11/12/15 11:16 PM: ----------------------------------------------------------------- So, I have committed in my sandbox an experimental (it is just an attempt) prototype for a java8 builder on top of DependencyManager API. All is committed in http://svn.apache.org/repos/asf/felix/sandbox/pderop/dependencymanager.builder.java/ The work is not finished, I will get back to it later, after FELIX-4955 is done. Basically, this prototype is an attempt to implement your original idea, but in a separate module and also using some java8-isms (lambda expressions, type-safe method references, and other java8-isms). This builder is implemented outside the code of DependencyManager, in the form of a separate module (I called it org.apache.felix.dependencymanager.java). In this mail, you will find the following parts: - general comments on the prototype - technical solutions used to manage method reference for DM callbacks - prototype presentation - use cases ranging from simple scenarios to more advances use cases (adapters, aspects) General comments on the prototype: ============================ Before describing the prototype, I would like to say that on the one hand, I'm satisfied by this prototype because you can define nice activators using compact and (relatively) type-safe lambda expression, method refs, new java8 stuff, etc ... like in the following example: {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { // Create a SpellChecker component with a LogService dependency component(comp -> comp .factory(() -> SpellCheckerImpl::new) .provides(SpellChecker.class) .properties(COMMAND_SCOPE, "dictionary", COMMAND_FUNCTION, new String[] {"spellcheck"}) .withService(LogService.class, srv -> srv.onAdd(SpellCheckerImpl:bind))); } } {code} However, on the other hand, if you have time to look into the implementation, I'm afraid that you will find it probably complex, and not easy to read. This is because dealing with method references was actually challenging, and it was not easy to define all possible component and dependency callbacks using functional interfaces. The implementation was also hard to do, because the prototype is extensively based on generics for the support of automatic type inference. Maybe I excessively used generics. Initially I chose to use generics for the sake of strictness, but admittedly, coding with generics sometimes leads to java code which is difficult to read, but I think that this is the cost to pay for a type-safe builder API. However, I may rework later the code in order to possibly remove some generics where it is actually not really necessary. I also faced an issue when trying to retrieve the actual type of generic parameters. Indeed, Java does not provide an API that allows to get actual type of generic parameters declared on classes, interfaces, or lambda expressions. So, to preserve my soul, I used a lightweight and simple tool called "typetools" (https://github.com/jhalterman/typetools, a nice tool made by Jonathan Halterman, with Apache 2 License) which allows to easily get actual types of generic parameters (by looking up in java byte code). Finally, the prototype requires latest Eclipse Mars, because Luna suffers from many java8 bugs, especially regarding method references. Technical solutions used to manage method reference callbacks: ================================================= Before describing the new API, I'm now giving some simple use cases which will help to understand the implementation of the prototype regarding method references management: use case 1: Define a dependency callback on an already instantiated object, with a reference to a bind method on that object: {code} class MyServiceImpl { void bind(LogService log) {} } MyServiceImpl impl = new MyServicImpl(); {code} So, to define a method reference on the bind method, this is simply done using a java.util.function.Consumer<LogService> method reference: {code} Consumer<LogService> callback = impl::bind; {code} and later, when we want to inject the service, we simply call the consumer like this: {code} callback.accept(logService); {code} so far so good. Now something a bit more tricky: use case 2: Define a method reference on a class method without the instance. This is a more complex use case. Here we want to define a method reference on a class method, but we don"t have yet the instance. We'll instantiate the implementation class later, once all dependencies are available, and at this point, we'll then inject the dependencies using the instance we have just created. So, to define such a method reference on an object instance that we don't have yet, we need to use something like a BiConsumer functional interface. Example: {code} class MyComponent { void bind(LogService log) {} } {code} Now, to define a method reference on the bind method (but without a MyComponent instance), we can do this: {code} BiConsumer<MyComponent, LogService> callback = MyComponent::bind; {code} This declaration is similar to: {code} BiConsumer<Test, LogService> callback = (myComponent, param) -> myComponent.bind(param); {code} So, when we'll instantiate later MyComponent class, we'll then simply call the BiConsumer.accept method with the MyComponent instance, as well as the logService that we want to inject to the myComponent.bind method: {code} MyComponent comp = new MyComponent(); ... // now inject the logService in the comp.bind method: callback.accept(comp, logService); {code} One last remark about the implementation; it concerns the ConfigurationDependencyBuilder: When you specify a method reference on a component instance "updated" callback, a proxy callback object is used by the ConfigurationDependencyBuilder; and when the proxy object is called in its "updated" callback, then the proxy calls "component.getInstances()" method in order to call the method reference on the component instances. But with the current DM API, we have a problem here, because when you add a callback instance on a configuration dependency, then the confDependency.needsInstance() returns false, and the component instances won't be available at the time the proxy.updated method is invoked. The component instances is not instantiated when using instance callbacks, mainly for the support of factories that needs to get configuration before being able to instantiate the component implementation from the factory.create method (see the compositefactory in the sample code, in order to fully understand all this). So, in order to work around, I used a simple solution: I exposed the "instantiateComponent" method in the ComponentContext interface, in order to let the ConfigurationBuilder force the instantiation of the component instances before invoking the component.getInstances() method. Prototype presentation ====================== Mainly, the builder design pattern is reused, and is inspired from your initial work (using a builder API in order to create real DependencyManager objects). The new things concerns java8. Essentially, you can now define method references for dependency injections, as well as lambdas when initializing components and dependencies. The prototype contains three bundles: org.apache.felix.dependencymanager.builder.java: this bundle contains the builder API + the implementation. org.apache.felix.dependencymanager.builder.java.itest: integration tests. (to be finished) org.apache.felix.dependencymanager.builder.java.samples: some of the original samples available from DependencyManager, but adapted to the new builder API When using the builder API, you have to write an activator, as before, but this time you extend the "org.apache.felix.dm.builder.java.DependencyActivatorBase" class. This class provides an init() method that you have to extend, and also some functions that allows to create components, adapters, as well as some static functions that allow to create components outside of the Activator. The components are auto-added to the DependencyManager object that is created in the DependencyActivatorBase class, however you can create components and use the "autoAdd(false)" method in the ComponentBuilder. This will make sure the component is not automatically added to the dm object (sometimes, this is useful). let's take a look at a simple example: here we have one component that depends on a log service injected by reflection (autoconfig): {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { component(comp -> comp .impl(ServiceImpl.class) .withService(LogService.class)); } } {code} The ServiceImpl class will be injected with the LogService on any field having a LogService type. Notice that the component function takes as parameter a lambda expression. The corresponding functional interface is a Consumer<ComponentBuilder> function that accepts a ComponentBuilder (comp -> comp). Now, using that builder, you can call the builder methods (comp.impl(..).withService(..) If you want to configure a service dependency with more informations (like callbacks), you can also use another version of the "withService" method which also takes a lambda that accepts a ServiceDependencyBuilder object: {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { component(comp -> comp .impl(ServiceImpl.class) .withService(LogService.class, srv -> srv.filter("(foo=bar)").onAdd(this::bind).required(false))); } } {code} The withService you see above takes the service dependency type (LogService.class), as well as a lambda that is a Consumer<ServiceDependencyBuilder>. And the lambda can then use the "srv" builder and call proper ServiceDependencyBuilder methods: {code} withService(LogService.class, srv -> srv.filter("(foo=bar)").onAdd(this::bind).required(false) {code} Notice that, unlike in the original DependencyManager API, dependencies are required by default. Currently, the prototype supports the following components: Component Aspects Adapters Factory Configuration Adapters and the following dependencies are supported: ServiceDependency ConfigurationDependency So, the following things still need to be done: - add support for ResourceDependency - add support for BundleDependency - add support for ResourceAdapter - add support for BundleAdapter All the remaining things to be done are described in the TODO file Use cases ranging from simple scenarios to more advances use cases (adapters, aspects) ====================================================================================== 1) simple component with a service consumer and a service provider: In the samples, you will find a simple example with a ServiceConsumer, a ServiceProvider, and Configurator service: org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/hello: {code} public class Activator extends DependencyActivatorBase { void bind(ServiceProvider provider) { System.out.println("Activator.bind(" + provider + ")"); } @Override public void init() throws Exception { component(comp -> comp .provides(ServiceProvider.class) .onStart(ServiceProviderImpl::activate) .properties("foo", "bar", "gabu", "zo") // foo=bar, gabu=zo .impl(ServiceProviderImpl.class) .withService(LogService.class, srv -> srv.onAdd(ServiceProviderImpl::bind))); component(comp -> comp .impl(ServiceConsumer.class) .withService(LogService.class) .withService(ServiceProvider.class, srv -> srv.filter("(foo=bar)").onAdd(this::bind)) .withConfiguration(conf -> conf.pid(ServiceConsumer.class).onUpdate(ServiceConsumer::updated))); component(comp -> comp.impl(Configurator.class).withService(ConfigurationAdmin.class)); } } {code} The first component "ServiceProvider" has a special start method (activate), so we are using the onStart(ServiceProviderImpl::activate) method. The properties can now be provided using the one liner properties(...) method, which takes an even number of parameters, each pair of parameters consisting of a key-value params: properties("foo", "bar", "gabu", "zo") // foo=bar, gabu=zo the provider depends on a LogService (required), which will be injected in the ServiceProviderImpl::bind method. The second component is the ServiceConsumer that depends on the ServiceProvider, and also on a configuration. The last component is a Configurator component that populates the configuration into ConfigAdmin for the ServiceConsumer component. 2) A component which is created using a factory object. (org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/factory/) {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { component(comp -> comp .provides(Provider.class) .factory(ProviderFactory::new, ProviderFactory::create) .withService(LogService.class, srv -> srv.required().onAdd(ProviderImpl::set)) .onStart(ProviderImpl::start)); } } {code} Here, the Provider class is instantiated using the ProviderFactory that is instantiated using "ProviderFactory::new" constructor reference, and the ProviderFactory::create method. 3) Object composition: The example from org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/compositefactory/ contains a ProviderImpl component that is instantiated from a CompositionManager object, and the configuration is injected in the CompositionManager. {code} /** * Defines a factory that also returns a composition. * The LogService in only injected to the ProviderImpl and the ProviderParticipant1. */ public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { CompositionManager compositionMngr = new CompositionManager(); component(comp -> comp .factory(compositionMngr::create, compositionMngr::getComposition) .withService(LogService.class, srv -> srv.onAdd(ProviderImpl::bind).onAdd(ProviderParticipant1::bind)) .withConfiguration(conf -> conf.pid(CompositionManager.class).onUpdate(compositionMngr::updated))); component(comp -> comp .impl(Configurator.class).withService(ConfigurationAdmin.class)); } } {code} 4) Adapter example org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/device/ This is an example showing a Dependency Manager "Adapter" in action. Two kinds of services are registered in the registry: some Device, and some DeviceParameter services. For each Device (having a given id), there is also a corresponding "DeviceParameter" service, having the same id. Then a "DeviceAccessImpl" adapter service is defined: it is used to "adapt" the "Device" service to a "DeviceAccess" service, which provides the union of each pair of Device/DeviceParameter having the same device.id . The adapter also dynamically propagate the service properties of the adapted Device service. Here is the activator {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { createDeviceAndParameter(1); createDeviceAndParameter(2); // Adapts a Device service to a DeviceAccess service adapter(Device.class, comp -> comp.provides(DeviceAccess.class).impl(DeviceAccessImpl.class)); component(comp -> comp .impl(DeviceAccessConsumer.class) .withService(LogService.class) .withService(DeviceAccess.class, srv -> srv.onAdd(DeviceAccessConsumer::add))); } private void createDeviceAndParameter(int id) { component(comp -> comp .provides(Device.class).properties("device.id", id) .factory(() -> new DeviceImpl(id))); // lazily create DeviceImpl component(comp -> comp .provides(DeviceParameter.class).properties("device.id", id) .factory(() -> new DeviceParameterImpl(id))); // lazily create DeviceParameterImpl } } {code} This example is interesting because it uses an adapter and also a factory that takes a lazy Supplier lambda that is used when instantiating components. also, the example shows how to add dynamic dependencies from component's init method. For example, when the DeviceAccessImpl component is initialized, it is passed the (real) DependencyManager Component that is then modified in order to add a dynamic dependency: {code} public class DeviceAccessImpl implements DeviceAccess { volatile Device device; // injected volatile DeviceParameter deviceParameter; // injected void init(Component c) { // Dynamically add an extra dependency on a DeviceParameter (using the builder API). // Notice that we also add a "device.access.id" service property dynamically. component(c, builder -> builder .properties("device.access.id", device.getDeviceId()) .withService(DeviceParameter.class, srv -> srv.filter("(device.id=" + device.getDeviceId() + ")"))); } } {code} was (Author: pderop): So, I have committed in my sandbox an experimental (it is just an attempt) prototype for a java8 builder on top of DependencyManager API. All is committed in http://svn.apache.org/repos/asf/felix/sandbox/pderop/dependencymanager.builder.java/ The work is not finished, I will get back to it later, after FELIX-4955 is done. Basically, this prototype is an attempt to implement your original idea, but in a separate module and also using some java8-isms (lambda expressions, type-safe method references, and other java8-isms). This builder is implemented outside the code of DependencyManager, in the form of a separate module (I called it org.apache.felix.dependencymanager.java). In this mail, you will find the following parts: - general comments on the prototype - technical solutions used to manage method reference for DM callbacks - prototype presentation - use cases ranging from simple scenarios to more advances use cases (adapters, aspects) General comments on the prototype: ============================ Before describing the prototype, I would like to say that on the one hand, I'm satisfied by this prototype because you can define nice activators using compact and (relatively) type-safe lambda expression, method refs, new java8 stuff, etc ... like in the following example: {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { // Create a SpellChecker component with a LogService dependency component(comp -> comp .factory(() -> SpellCheckerImpl::new) .provides(SpellChecker.class) .properties(COMMAND_SCOPE, "dictionary", COMMAND_FUNCTION, new String[] {"spellcheck"}) .withService(LogService.class, srv -> srv.onAdd(SpellCheckerImpl:bind))); } } {code} However, on the other hand, if you have time to look into the implementation, I'm afraid that you will find it probably complex, and not easy to read. This is because dealing with method references was actually challenging, and it was not easy to define all possible component and dependency callbacks using functional interfaces. The implementation was also hard to do, because the prototype is extensively based on generics for the support of automatic type inference. Maybe I excessively used generics. Initially I chose to use generics for the sake of strictness, but admittedly, coding with generics sometimes leads to java code which is difficult to read, but I think that this is the cost to pay for a type-safe builder API. However, I may rework later the code in order to possibly remove some generics where it is actually not really necessary. I also faced an issue when trying to retrieve the actual type of generic parameters. Indeed, Java does not provide an API that allows to get actual type of generic parameters declared on classes, interfaces, or lambda expressions. So, to preserve my soul, I used a lightweight and simple tool called "typetools" (https://github.com/jhalterman/typetools, a nice tool made by Jonathan Halterman, with Apache 2 License) which allows to easily get actual types of generic parameters (by looking up in java byte code). Finally, the prototype requires latest Eclipse Mars, because Luna suffers from many java8 bugs, especially regarding method references. Technical solutions used to manage method reference callbacks: ================================================= Before describing the new API, I'm now giving some simple use cases which will help to understand the implementation of the prototype regarding method references management: use case 1: Define a dependency callback on an already instantiated object, with a reference to a bind method on that object: {code} class MyServiceImpl { void bind(LogService log) {} } MyServiceImpl impl = new MyServicImpl(); {code} So, to define a method reference on the bind method, this is simply done using a java.util.function.Consumer<LogService> method reference: {code} Consumer<LogService> callback = impl::bind; {code} and later, when we want to inject the service, we simply call the consumer like this: {code} callback.accept(logService); {code} so far so good. Now something a bit more tricky: use case 2: Define a method reference on a class method without the instance. This is a more complex use case. Here we want to define a method reference on a class method, but we don"t have yet the instance. We'll instantiate the implementation class later, once all dependencies are available, and at this point, we'll then inject the dependencies using the instance we have just created. So, to define such a method reference on an object instance that we don't have yet, we need to use something like a BiConsumer functional interface. Example: {code} class MyComponent { void bind(LogService log) {} } {code} Now, to define a method reference on the bind method (but without a MyComponent instance), we can do this: {code} BiConsumer<MyComponent, LogService> callback = MyComponent::bind; {code} This declaration is similar to: {code} BiConsumer<Test, LogService> callback = (myComponent, param) -> myComponent.bind(param); {code} So, when we'll instantiate later MyComponent class, we'll then simply call the BiConsumer.accept method with the MyComponent instance, as well as the logService that we want to inject to the myComponent.bind method: {code} MyComponent comp = new MyComponent(); ... // now inject the logService in the comp.bind method: callback.accept(comp, logService); {code} One last remark about the implementation; it concerns the ConfigurationDependencyBuilder: When you specify a method reference on a component instance "updated" callback, a proxy callback object is used by the ConfigurationDependencyBuilder; and when the proxy object is called in its "updated" callback, then the proxy calls "component.getInstances()" method in order to call the method reference on the component instances. But with the current DM API, we have a problem here, because when you add a callback instance on a configuration dependency, then the confDependency.needsInstance() returns false, and the component instances won't be available at the time the proxy.updated method is invoked. The component instances is not instantiated when using instance callbacks, mainly for the support of factories that needs to get configuration before being able to instantiate the component implementation from the factory.create method (see the compositefactory in the sample code, in order to fully understand all this). So, in order to work around, I used a simple solution: I exposed the "instantiateComponent" method in the ComponentContext interface, in order to let the ConfigurationBuilder force the instantiation of the component instances before invoking the component.getInstances() method. Prototype presentation ====================== Mainly, the builder design pattern is reused, and is inspired from your initial work (using a builder API in order to create real DependencyManager objects). The new things concerns java8. Essentially, you can now define method references for dependency injections, as well as lambdas when initializing components and dependencies. The prototype contains three bundles: org.apache.felix.dependencymanager.builder.java: this bundle contains the builder API + the implementation. org.apache.felix.dependencymanager.builder.java.itest: integration tests. (to be finished) org.apache.felix.dependencymanager.builder.java.samples: some of the original samples available from DependencyManager, but adapted to the new builder API When using the builder API, you have to write an activator, as before, but this time you extend the "org.apache.felix.dm.builder.java.DependencyActivatorBase" class. This class provides an init() method that you have to extend, and also some functions that allows to create components, adapters, as well as some static functions that allow to create components outside of the Activator. The components are auto-added to the DependencyManager object that is created in the DependencyActivatorBase class, however you can create components and use the "autoAdd(false)" method in the ComponentBuilder. This will make sure the component is not automatically added to the dm object (sometimes, this is useful). let's take a look at a simple example: here we have one component that depends on a log service injected by reflection (autoconfig): {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { component(comp -> comp .impl(ServiceImpl.class) .withService(LogService.class)); } } {code} The ServiceImpl class will be injected with the LogService on any field having a LogService type. Notice that the component function takes as parameter a lambda expression. The corresponding functional interface is a Consumer<ComponentBuilder> function that accepts a ComponentBuilder (comp -> comp). Now, using that builder, you can call the builder methods (comp.impl(..).withService(..) If you want to configure a service dependency with more informations (like callbacks), you can also use another version of the "withService" method which also takes a lambda that accepts a ServiceDependencyBuilder object: {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { component(comp -> comp .impl(ServiceImpl.class) .withService(LogService.class, srv -> srv.filter("(foo=bar)").onAdd(this::bind).required(false))); } } {code} The withService you see above takes the service dependency type (LogService.class), as well as a lambda that is a Consumer<ServiceDependencyBuilder>. And the lambda can then use the "srv" builder and call proper ServiceDependencyBuilder methods: {code} withService(LogService.class, srv -> srv.filter("(foo=bar)").onAdd(this::bind).required(false) {code} Notice that, unlike in the original DependencyManager API, dependencies are required by default. Currently, the prototype supports the following components: Component Aspects Adapters Factory Configuration Adapters and the following dependencies are supported: ServiceDependency ConfigurationDependency So, the following things still need to be done: - add support for ResourceDependency - add support for BundleDependency - add support for ResourceAdapter - add support for BundleAdapter All the remaining things to be done are described in the TODO file Use cases ranging from simple scenarios to more advances use cases (adapters, aspects) ====================================================================================== 1) simple component with a service consumer and a service provider: In the samples, you will find a simple example with a ServiceConsumer, a ServiceProvider, and Configurator service: org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/hello: {code} public class Activator extends DependencyActivatorBase { void bind(ServiceProvider provider) { System.out.println("Activator.bind(" + provider + ")"); } @Override public void init() throws Exception { component(comp -> comp .provides(ServiceProvider.class) .onStart(ServiceProviderImpl::activate) .properties("foo", "bar", "gabu", "zo") // foo=bar, gabu=zo .impl(ServiceProviderImpl.class) .withService(LogService.class, srv -> srv.onAdd(ServiceProviderImpl::bind))); component(comp -> comp .impl(ServiceConsumer.class) .withService(LogService.class) .withService(ServiceProvider.class, srv -> srv.filter("(foo=bar)").onAdd(this::bind)) .withConfiguration(conf -> conf.pid(ServiceConsumer.class).onUpdate(ServiceConsumer::updated))); component(comp -> comp.impl(Configurator.class).withService(ConfigurationAdmin.class)); } } {code} The first component "ServiceProvider" has a special start method (activate), so we are using the onStart(ServiceProviderImpl::activate) method. The properties can now be provided using the one liner properties(...) method, which takes an even number of parameters, each pair of parameters consisting of a key-value params: properties("foo", "bar", "gabu", "zo") // foo=bar, gabu=zo the provider depends on a LogService (required), which will be injected in the ServiceProviderImpl::bind method. The second component is the ServiceConsumer that depends on the ServiceProvider, and also on a configuration. The last component is a Configurator component that populates the configuration into ConfigAdmin for the ServiceConsumer component. 2) A component which is created using a factory object. (org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/factory/) {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { component(comp -> comp .provides(Provider.class) .factory(ProviderFactory::new, ProviderFactory::create) .withService(LogService.class, srv -> srv.required().onAdd(ProviderImpl::set)) .onStart(ProviderImpl::start)); } } {code} Here, the Provider class is instantiated using the ProviderFactory that is instantiated using "ProviderFactory::new" constructor reference, and the ProviderFactory::create method. 3) Object composition: The example from org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/compositefactory/ contains a ProviderImpl component that is instantiated from a CompositionManager object, and the configuration is injected in the CompositionManager. {code} /** * Defines a factory that also returns a composition. * The LogService in only injected to the ProviderImpl and the ProviderParticipant1. */ public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { CompositionManager compositionMngr = new CompositionManager(); component(comp -> comp .factory(compositionMngr::create, compositionMngr::getComposition) .withService(LogService.class, srv -> srv.onAdd(ProviderImpl::bind).onAdd(ProviderParticipant1::bind)) .withConfiguration(conf -> conf.pid(CompositionManager.class).onUpdate(compositionMngr::updated))); component(comp -> comp .impl(Configurator.class).withService(ConfigurationAdmin.class)); } } {code} 4) Adapter example org.apache.felix.dependencymanager.builder.java.samples/src/org/apache/felix/dependencymanager/samples/device/ This is an example showing a Dependency Manager "Adapter" in action. Two kinds of services are registered in the registry: some Device, and some DeviceParameter services. For each Device (having a given id), there is also a corresponding "DeviceParameter" service, having the same id. Then a "DeviceAccessImpl" adapter service is defined: it is used to "adapt" the "Device" service to a "DeviceAccess" service, which provides the union of each pair of Device/DeviceParameter having the same device.id . The adapter also dynamically propagate the service properties of the adapted Device service. Here is the activator {code} public class Activator extends DependencyActivatorBase { @Override public void init() throws Exception { createDeviceAndParameter(1); createDeviceAndParameter(2); // Adapts a Device service to a DeviceAccess service adapter(Device.class, comp -> comp.provides(DeviceAccess.class).impl(DeviceAccessImpl.class)); component(comp -> comp .impl(DeviceAccessConsumer.class) .withService(LogService.class) .withService(DeviceAccess.class, srv -> srv.onAdd(DeviceAccessConsumer::add))); } private void createDeviceAndParameter(int id) { component(buicomplder -> buicomplder .provides(Device.class).properties("device.id", id) .factory(() -> new DeviceImpl(id))); // lazily create DeviceImpl component(comp -> comp .provides(DeviceParameter.class).properties("device.id", id) .factory(() -> new DeviceParameterImpl(id))); // lazily create DeviceParameterImpl } } {code} This example is interesting because it uses an adapter and also a factory that takes a lazy Supplier lambda that is used when instantiating components. also, the example shows how to add dynamic dependencies from component's init method. For example, when the DeviceAccessImpl component is initialized, it is passed the (real) DependencyManager Component that is then modified in order to add a dynamic dependency: {code} public class DeviceAccessImpl implements DeviceAccess { volatile Device device; // injected volatile DeviceParameter deviceParameter; // injected void init(Component c) { // Dynamically add an extra dependency on a DeviceParameter (using the builder API). // Notice that we also add a "device.access.id" service property dynamically. component(c, builder -> builder .properties("device.access.id", device.getDeviceId()) .withService(DeviceParameter.class, srv -> srv.filter("(device.id=" + device.getDeviceId() + ")"))); } } {code} > Create a more fluent syntax for the dependency manager builder > -------------------------------------------------------------- > > Key: FELIX-4689 > URL: https://issues.apache.org/jira/browse/FELIX-4689 > Project: Felix > Issue Type: Improvement > Components: Dependency Manager > Reporter: Christian Schneider > Attachments: FELIX-4689-1.patch > > > I wonder if the DependencyManager API could be made a bit more fluent. > Technically it already uses the fluent builder pattern > but all the builder verbs still look a lot like traditional setters. > I know what I propose is mostly syntactic sugar but I think the result > looks more readable and crisp. See below for some ideas. > There is the concern about auto adding the component() to manager as it would > acrivate the not fully configured component. We could perhaps overcome this > by adding the component to a list of pending components first and then moving > them to the active components after the init method. > The camel DSL solves this similarly. > This is from samples.dependonservice: > public void init(BundleContext context, DependencyManager manager) > throws Exception { > manager.add(createComponent() > .setImplementation(DataGenerator.class) > .add(createServiceDependency() > .setService(Store.class) > .setRequired(true) > ) > .add(createServiceDependency() > .setService(LogService.class) > .setRequired(false) > ) > ); > } > Why not make it look like this: > public void init(BundleContext context, DependencyManager manager) > throws Exception { > component() > .implementation(DataGenerator.class) > .add(serviceDependency(Store.class).required()) > .add(serviceDependency(LogService.class)) > ); > ); > } > component() could create and add the component. > Or for configuration: > public void init(BundleContext context, DependencyManager manager) > throws Exception { > manager.add(createComponent() > .setImplementation(Task.class) > .add(createConfigurationDependency() > .setPid("config.pid") > // The following is optional and allows to display our > configuration from webconsole > .setHeading("Task Configuration") > .setDescription("Configuration for the Task Service") > .add(createPropertyMetaData() > .setCardinality(0) > .setType(String.class) > .setHeading("Task Interval") > .setDescription("Declare here the interval used to > trigger the Task") > .setDefaults(new String[] {"10"}) > .setId("interval")))); > } > could be: > public void init(BundleContext context, DependencyManager manager) > throws Exception { > component().implementation(Task.class) > .configuration("config.pid") > .add(meta("Task Configuration) > .description("Configuration for the Task Service") > .add(property("interval") > .cardinality(0) > .type(String.class) > .heading("Task Interval") > .description("Declare here the interval used > to trigger the Task") > .default("10")) > } -- This message was sent by Atlassian JIRA (v6.3.4#6332)