[
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)