You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@felix.apache.org by "Pierre De Rop (JIRA)" <ji...@apache.org> on 2015/12/18 08:35:46 UTC

[jira] [Comment Edited] (FELIX-4689) Create a more fluent syntax for the dependency manager builder

    [ https://issues.apache.org/jira/browse/FELIX-4689?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15055210#comment-15055210 ] 

Pierre De Rop edited comment on FELIX-4689 at 12/18/15 7:35 AM:
----------------------------------------------------------------

Committed some updates:

- Renamed in my sandbox the prototype to dependencymanager-lambda
- Removed the dependency on the typetools library: the generic lambda parameters are now introspected using the SerializableLambda class available from the jdk.
- Refactored some classes, and also the bnd files in the tests.
- Introduced a new required dependency that allows to wait for a CompletableFuture 
- Added examples using RxJava

h4. new "CompletableFuture" dependency:

CompletableFuture java8 class provides functional operations and promotes an asynchronous event-driven model. But when you go for asynchronous model, then in some situation you have to take care. For instance assuming you want to initialize a component from the init method using some asynchronous stuff, then the init method may return before the async operations have completed; and the component.start() callback may then be called too early. And what about if you want to add dependencies from the init() method using the result of an asynchronous task ? In this case you would have to block on the CompletableFuture but this is something you don't want to do, because in a reactive world, it is a bad idea to block on the current thread.

So, to avoid blocking or some boilerplate code, a new (required) CompletableFuture dependency has been added (in the dm-lambda library),  and you can use it to make sure that the component remains in the init state until a given "CompletableFuture" completes. The new dependency is implemented as all other dependencies, and it extends the org.apache.felix.dm.context.AbstractDependency class.

There is sample which gives an example usage of this new dependency, see 
http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager-lambda/org.apache.felix.dependencymanager.lambda.samples/src/org/apache/felix/dependencymanager/lambda/samples/future/README

Let's describe the sample: we have a "SiteInfoImpl" component that is using a CompletableFuture from its init method in order to asynchronously download and parse an URL from the web. When the page content is downloaded, then  the hrefs (links) contained in the URL page are parsed. The component state remains in the init state until the page is downloaded.

Here is the activator:

{code}
public class Activator extends DependencyActivatorBase {
    @Override
    public void init() throws Exception {    	
    	// Define the SiteInfo component that provides some informations about the Felix web site.
        component(comp -> comp
        	.provides(SiteInfo.class)
        	.factory(() -> new SiteInfoImpl("http://felix.apache.org/"))
                .withService(LogService.class, srv -> srv.onAdd(SiteInfoImpl::bind)));
        
        // Define a component that depends on the SiteInfo service
        component(comp -> comp
                .impl(DisplaySite.class)
                .withService(LogService.class).withService(SiteInfo.class));
    }
}
{code}

Nothing special so far.

Now let's take a look at the SiteInfoImpl.init() method:
{code}
void init(Component c) {
	// Asynchronously download and parse the links from the web page.
	// We use a completable future that is then added as a dependency to our component, which
	// will be started once the future has completed.		
       CompletableFuture<List<String>> links = CompletableFuture.supplyAsync(() -> downloadSite(m_url)).thenApply(this::getSiteLinks);
		
       // Add a CompletableFuture dependency to make sure our component remains in the init state until the async donwload has 
       // completed.										
       component(c, builder -> builder.withFuture(links, b -> b.thenAccept(this::setLinks)));
}
{code}

So, the "builder.withFuture(links, b -> b.thenAccept(this::setLinks))" code simply defines a "completable future" dependency that will be available once the "links" future has completed, and the method reference passed to the b.thenAccept method will be invoked exactly if you would have invoked the same method on the CompletableFuture "links" object.

h4. about the RxJava samples

Two new samples have been added about the usage of RxJava, which like the CompletableFuture allows to react on events, but using non blocking push paradigm.The intent is to experiment the usage of RxJava and see if the new dm library has enough hooks to support Rx based libraries.

h5. first experiment: use RxJava to build DM components:

sample code: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager-lambda/org.apache.felix.dependencymanager.lambda.samples/src/org/apache/felix/dependencymanager/lambda/samples/rx/observable/

So, in this sample, A DM component is built from code (see ObservableComponent.java) using RxJava style.
That is: using some Rx "Observables" and a chain of map() calls to build the DM component.

So, basically, The sample first transforms a DM Component into an "Observable".
Turning any object to an Rx Observable is easy, using the Observable<T>.create(Publisher publisher) method.
For example:

{code}
Observable<Component> createObservableComponent(String componentParameters) {
	return Observable.create(publisher -> {		
                publisher.onSubscribe(EmptySubscription.INSTANCE);
		component(m_dm, builder -> builder
			.factory(() -> new SomeDMComponent(componentParameters))
			.onStart(publisher::onNext)); // will trigger next action in the map() chain only AFTER component is started.
	});
}
{code}

So, the component can then be created using a flow of map() transformations calls like in the following example:

{code}
	query("rxjava")                                            // returns Observable<List<String>> (list of URLs).
		.flatMap(urls -> Observable.fromStream(urls.stream())) // Loop on each URL returned by the query
		.flatMap(url -> getTitle(url))                         // get the title of the current URL page content
		.filter(title -> title != null)                        // Ignore page without title
		.flatMap(title -> createObservableComponent(title))    // and create a component for the current title			
		.subscribe(component -> System.out.println("Component has started: " + component));        
{code}

The query(String search) returns an Observable<List<String>> representing the URLS in the web that match the given text search. The subscriber will be called only after the Component has been started, because the createObservableComponent method only calls the "publisher::onNext" method once the component is fully active (see "onStart(publisher::onNext))" call in the createObservableComponent function.

h5. second experiment: mix new CompletableFuture dependency with  RxJava Observable from component init method:

Sample code: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager-lambda/org.apache.felix.dependencymanager.lambda.samples/src/org/apache/felix/dependencymanager/lambda/samples/rx/completable

So, in this sample, we have a use case where RxJava is used to download and parse an URL from the web, in order to count the number of hrefs (links) contained in the URL page, and we do this from a component.init() method. Now, the problem is that the page is downloaded asynchronously using RxJava, and when the component init() method returns,  any services implemented by the component are registered. Moreover, the component start() method is called while the download is still pending.

So, ultimately, we would need some kind of required dependency on something in order to make sure that the component remains in the init state, until the page has been downloaded and parsed. Obsously, we can reuse the new CompletableFuture dependency that was presented before. The only thing to do is to wrap an Observable into a CompletableFuture.

This can be simply done using the following helper class:

{code}
class ObservableCompletableFuture<T> extends CompletableFuture<T> {
    private final Disposable m_subscription;

    public ObservableCompletableFuture(Observable<T> observable) {
        m_subscription = observable
        		.subscribeOn(Schedulers.io())
        		.subscribe(this::complete, this::completeExceptionally);        		
    }
        
    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        m_subscription.dispose();
        return super.cancel(mayInterruptIfRunning);
    }
}
{code}

This simple class takes in the constructor an Observable<T>, and subscribes to it. So, when the Observable completes, the "this::complete" calls the CompletableFuture.complete() method in order to propagate the completion of the Observable to the CompletableFuture.

Now let's go with a concrete example: Using this class above allows to build nice DM components without boilerplate code, like this:

>From the Activator, you can for example define a service providing component like this:

Activator.java:
{code}
        // Define the FelixSiteInfo component that provides some informations about the Felix web site.
        component(comp -> comp
        	.provides(SiteInfo.class)
        	.factory(() -> new SiteInfoImpl("http://felix.apache.org/")
                .withService(LogService.class, srv -> srv.onAdd(SiteInfoImpl::bind)));
{code}

 And from the SiteInfoImpl.init() method:
 
{code} 
void init(Component c) {
	// Asynchronously calculate the number of links from the Felix web site.
	// We use Rx to define an asynchrous Observable task, and we wrap it to a CompletableFuture
	// that is used as a Dependency Manager "Future" dependency, in order to block the activation of
	// our component until the felix site has been downloaded and parsed.
		
	Observable<List<String>> links = downloadSite(m_url).map(this::getSiteLinks);								
        component(c, comp -> comp.withFuture(toFuture(links), futuredep -> futuredep.thenAccept(this::setLinks)));        		
}
 
 // Called when our future has completed.
void setLinks(List<String> links) {
	m_felixLinks = links;
}
	
// once our future has completed, our component is started.
void start() {
	m_log.log(LogService.LOG_INFO, 
	"Service starting: number of links found from site=" + m_felixLinks.size());
}

public static <T> CompletableFuture<T> toFuture(Observable<T> observable) {
    	return new ObservableCompletableFuture<T>(observable);
}
{code}
	
The important part to understand is the following:

{code}
component(c, comp -> comp.withFuture(toFuture(links), futuredep -> futuredep.thenAccept(this::setLinks)));        		
{code}

here, we wrap the links (which is an Observable<List<String>> into the ObservableCompletableFuture helper that was presented previously. And when the CompletableFuture will complete , it will call the "thenAccept(this::setLinks)" method in order to inject the result into the component before the component.start() method, exactly in the same manner when a required dependency is injected before start().


was (Author: pderop):
Committed some updates:

- Renamed in my sandbox the prototype to dependencymanager-lambda
- Removed the dependency on the typetools library: the generic lambda parameters are now introspected using the SerializableLambda class available from the jdk.
- Refactored some classes, and also the bnd files in the tests.
- Introduced a new required dependency that allows to wait for a CompletableFuture 
- Added examples using RxJava

h4. new "CompletableFuture" dependency:

CompletableFuture java8 class provides functional operations and promotes an asynchronous event-driven model. But when you go for asynchronous model, then in some situation you have to take care. For instance assuming you want to initialize a component from the init method using some asynchronous stuff, then the init method may return before the async operations have completed; and the component.start() callback may then be called too early. And what about if you want to add dependencies from the init() method  and you want to wait for the async operations before adding the dependency ? In this case you would have to block on the CompletableFuture but this is something you don't want to do.

So, to avoid blocking or some boilerplate code, a new (required) CompletableFuture dependency has been added (in the dm-lambda library),  and you can use it to make sure that the component remains in the init state until a given "CompletableFuture" completes. The new dependency is implemented as all other dependencies, and it extends the org.apache.felix.dm.context.AbstractDependency class.

There is sample which gives an example usage of this new dependency, see 
http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager-lambda/org.apache.felix.dependencymanager.lambda.samples/src/org/apache/felix/dependencymanager/lambda/samples/future/README

Let's describe the sample: we have a "SiteInfoImpl" component that is using a CompletableFuture from its init method in order to asynchronously download and parse an URL from the web. When the page content is downloaded, then  the hrefs (links) contained in the URL page are parsed. The component state remains in the init state until the page is downloaded.

Here is the activator:

{code}
public class Activator extends DependencyActivatorBase {
    @Override
    public void init() throws Exception {    	
    	// Define the SiteInfo component that provides some informations about the Felix web site.
        component(comp -> comp
        	.provides(SiteInfo.class)
        	.factory(() -> new SiteInfoImpl("http://felix.apache.org/"))
                .withService(LogService.class, srv -> srv.onAdd(SiteInfoImpl::bind)));
        
        // Define a component that depends on the SiteInfo service
        component(comp -> comp
                .impl(DisplaySite.class)
                .withService(LogService.class).withService(SiteInfo.class));
    }
}
{code}

Nothing special so far.

Now let's take a look at the SiteInfoImpl.init() method:
{code}
void init(Component c) {
	// Asynchronously download and parse the links from the web page.
	// We use a completable future that is then added as a dependency to our component, which
	// will be started once the future has completed.		
       CompletableFuture<List<String>> links = CompletableFuture.supplyAsync(() -> downloadSite(m_url)).thenApply(this::getSiteLinks);
		
       // Add a CompletableFuture dependency to make sure our component remains in the init state until the async donwload has 
       // completed.										
       component(c, builder -> builder.withFuture(links, b -> b.thenAccept(this::setLinks)));
}
{code}

So, the "builder.withFuture(links, b -> b.thenAccept(this::setLinks))" code simply defines a "completable future" dependency that will be available once the "links" future has completed, and the method reference passed to the b.thenAccept method will be invoked exactly if you would have invoked the same method on the CompletableFuture "links" object.

h4. about the RxJava samples

Two new samples have been added about the usage of RxJava, which like the CompletableFuture allows to react on events, but using non blocking push paradigm.The intent is to experiment the usage of RxJava and see if the new dm library has enough hooks to support Rx based libraries.

h5. first experiment: use RxJava to build DM components:

sample code: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager-lambda/org.apache.felix.dependencymanager.lambda.samples/src/org/apache/felix/dependencymanager/lambda/samples/rx/observable/

So, in this sample, A DM component is built from code (see ObservableComponent.java) using RxJava style.
That is: using some Rx "Observables" and a chain of map() calls to build the DM component.

So, basically, The sample first transforms a DM Component into an "Observable".
Turning any object to an Rx Observable is easy, using the Observable<T>.create(Publisher publisher) method.
For example:

{code}
Observable<Component> createObservableComponent(String componentParameters) {
	return Observable.create(publisher -> {		
                publisher.onSubscribe(EmptySubscription.INSTANCE);
		component(m_dm, builder -> builder
			.factory(() -> new SomeDMComponent(componentParameters))
			.onStart(publisher::onNext)); // will trigger next action in the map() chain only AFTER component is started.
	});
}
{code}

So, the component can then be created using a flow of map() transformations calls like in the following example:

{code}
	query("rxjava")                                            // returns Observable<List<String>> (list of URLs).
		.flatMap(urls -> Observable.fromStream(urls.stream())) // Loop on each URL returned by the query
		.flatMap(url -> getTitle(url))                         // get the title of the current URL page content
		.filter(title -> title != null)                        // Ignore page without title
		.flatMap(title -> createObservableComponent(title))    // and create a component for the current title			
		.subscribe(component -> System.out.println("Component has started: " + component));        
{code}

The query(String search) returns an Observable<List<String>> representing the URLS in the web that match the given text search. The subscriber will be called only after the Component has been started, because the createObservableComponent method only calls the "publisher::onNext" method once the component is fully active (see "onStart(publisher::onNext))" call in the createObservableComponent function.

h5. second experiment: mix new CompletableFuture dependency with  RxJava Observable from component init method:

Sample code: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager-lambda/org.apache.felix.dependencymanager.lambda.samples/src/org/apache/felix/dependencymanager/lambda/samples/rx/completable

So, in this sample, we have a use case where RxJava is used to download and parse an URL from the web, in order to count the number of hrefs (links) contained in the URL page, and we do this from a component.init() method. Now, the problem is that the page is downloaded asynchronously using RxJava, and when the component init() method returns,  any services implemented by the component are registered. Moreover, the component start() method is called while the download is still pending.

So, ultimately, we would need some kind of required dependency on something in order to make sure that the component remains in the init state, until the page has been downloaded and parsed. Obsously, we can reuse the new CompletableFuture dependency that was presented before. The only thing to do is to wrap an Observable into a CompletableFuture.

This can be done using the following helper class:

{code}
class ObservableCompletableFuture<T> extends CompletableFuture<T> {
    private final Disposable m_subscription;

    public ObservableCompletableFuture(Observable<T> observable) {
        m_subscription = observable
        		.subscribeOn(Schedulers.io())
        		.subscribe(this::complete, this::completeExceptionally);        		
    }
        
    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        m_subscription.dispose();
        return super.cancel(mayInterruptIfRunning);
    }
}
{code}

This simple class takes in the constructor an Observable<T>, and subscribes to it. So, when the Observable completes, the "this::complete" calls the CompletableFuture.complete() method in order to propagate the completion of the Observable to the CompletableFuture.

Now let's go with a concrete example: Using this class above allows to build nice DM components without boilerplate code, like this:

>From the Activator, you can for example define a service providing component like this:

Activator.java:
{code}
        // Define the FelixSiteInfo component that provides some informations about the Felix web site.
        component(comp -> comp
        	.provides(SiteInfo.class)
        	.factory(() -> new SiteInfoImpl("http://felix.apache.org/")
                .withService(LogService.class, srv -> srv.onAdd(SiteInfoImpl::bind)));
{code}

 And from the SiteInfoImpl.init() method:
 
 FelixSiteImpl.java:
{code} 
void init(Component c) {
	// Asynchronously calculate the number of links from the Felix web site.
	// We use Rx to define an asynchrous Observable task, and we wrap it to a CompletableFuture
	// that is used as a Dependency Manager "Future" dependency, in order to block the activation of
	// our component until the felix site has been downloaded and parsed.
		
	Observable<List<String>> links = downloadSite(m_url).map(this::getSiteLinks);								
        component(c, comp -> comp.withFuture(toFuture(links), futuredep -> futuredep.thenAccept(this::setLinks)));        		
}
 
 // Called when our future has completed.
void setLinks(List<String> links) {
	m_felixLinks = links;
}
	
// once our future has completed, our component is started.
void start() {
	m_log.log(LogService.LOG_INFO, 
	"Service starting: number of links found from site=" + m_felixLinks.size());
}

public static <T> CompletableFuture<T> toFuture(Observable<T> observable) {
    	return new ObservableCompletableFuture<T>(observable);
}
{code}
	
The important part to understand is the following:

{code}
component(c, comp -> comp.withFuture(toFuture(links), futuredep -> futuredep.thenAccept(this::setLinks)));        		
{code}

here, we wrap the links (which is an Observable<List<String>> into the ObservableCompletableFuture helper that was presented previously. And when the CompletableFuture will complete , it will call the "thenAccept(this::setLinks)" method in order to inject the result into the component before the component.start() method, exactly in the same manner when a required dependency is injected before start().

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