karaf-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Guillaume Nodet <gno...@apache.org>
Subject Re: [DISCUSS] Java DSL for writing commands
Date Mon, 17 Feb 2014 14:48:05 GMT
I think we could build upon those annotations and have them leveraged by
the native dependency injection framework (be it blueprint or DS).

I need to experiment a bit, but I don't see any reason why the blueprint
namespace handler would not be able to scan those commands and do create
blueprint metadata out of it.

The blueprint xml would simply look like:

    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0"
                                       scan="my.package" />

This would be equivalent to the current declaration of commands and
completers (by simply adding the @Register and @Reference annotations).

For DS, it should also be possible to have them translated into native SCR
annotations through a maven plugin with an annotation scanner which would
build the SCR descriptions.

I think the overall goals should be the following:
  * make things as easy as possible for the user
  * make things as robust as possible, i.e. keep the OSGi registry contracts
  * for the typical use cases, the commands code should be self describing,
i.e. no additional metadata needed

I'll go ahead and experiment with the above extended blueprint namespace
now.  I'll report soon.

2014-02-17 14:35 GMT+01:00 Guillaume Nodet <gnodet@apache.org>:

> I have experimented this morning with a possible implementation of what I
> outlined last week.
> We have a very simple set of 4 annotations:
>   @Service @Reference @Init @Destroy
> @Service can be added on an action (already annotated with @Command) or a
> completer implementation.
> @Reference is used on fields that contain an OSGi service that needs to be
> retrieved from the OSGi registry before the object can be used.
> @Init and @Destroy can be used to further control the lifecycle if needed.
> A bundle extender will track bundles that have a Karaf-Inject header and
> scan the classes annotated with @Service.  For non-commands, it will create
> the instances, inject and register them.  For commands, it will register an
> AbstractCommand object which will, when asked, create and inject the action.
> The annotation don't have any parameters, they're simplistic on purpose,
> as anything more complicated would mean rewriting a layer such as
> blueprint, DS, etc...  and I don't think we should go this way.
> Here's an example on how it could be used:
> https://github.com/gnodet/karaf/commit/9649a3bf8d5967d58c048b07619f1218284d040b
> 2014-02-14 20:31 GMT+01:00 Guillaume Nodet <gnodet@apache.org>:
>> 2014-02-14 18:29 GMT+01:00 Christian Schneider <chris@die-schneider.net>:
>> The DSL will make it easier to write the command. So it is quite
>>> convenient in the start.
>>> The downside I see is that it will create quite strong coupling between
>>> someone creating the service and the karaf impl. So it might be difficult
>>> when we do karaf 4 which might introduce a lot of changes in the
>>> implementation.
>> Mmh, I'm not sure how this make things more coupled really.  The
>> blueprint DSL is karaf specific anyway so that commands are now tied to the
>> blueprint DSL.
>>> I think it would be better if we find a way that allows the implementer
>>> of a command to simply implement an interface, use some annotations and
>>> export the command as a service. Basically we are almost there already. The
>>> user code already only depends on the interface Action and some annotations.
>>> I know we currently have the problem that e.g. in BlueprintCommand we
>>> create a new instance of the Action. Perhaps we could avoid that.
>> Well, it's not specific to blueprint, it's a separate decision.
>> Commands are now stateless and recreated each time.  This has a benefit
>> which is that users don't have to deal with thread safety issues.  We could
>> change that, but that would definitely be a big incompatible change.
>>> How about this:
>>> The user simply exports his Action object as a service.
>>> We pick it up in the shell bundle and simply use it in a synchronized
>>> fashion on command execution at a time. We can then prepare the command
>>> (fill in the arguments and execute it) and execute it without creating a
>>> new instance.
>> Synchronizing on commands would be a really bad idea.  Some commands do
>> run for quite a long time (log:tail, etc...).   Re-preparing the same
>> command is more difficult because you need to know the default values as
>> they have been lost.
>> Anyway, if you look at the DSL, declaring a command in the most simple
>> use case is as simple as:
>>    command(MyAction.class)
>> We could avoid that by adding annotations, but the commands usually need
>> to be injected with a service.  What could eventually be done, is to add a
>> new set of annotations for auto-registration and service injection.  If we
>> do that, we should not rely on another extender such as CDI or DS, as the
>> benefit would be lost, so it has to be pure karaf.
>> I guess this would cover 90% of the commands if we were able to inject
>> OSGI services by looking for a service matching the field type.
>> @Command(...)
>> @Register
>> public class MyCommand extends OsgiCommandSupport {
>>    @Inject
>>    private JndiService jndiService;
>>    public Object doExecute() throws Exception {
>>       ....
>>    }
>> }
>> So with 2 new annotations, it should remove most of the blueprint stuff
>> needed I think.
>> I really don't think changing the model action/command model is a good
>> idea.  I'd much rather write a new one if we want to not instantiate new
>> commands each time.
>>> Best regards
>>> Christian
>>> On 14.02.2014 17:47, Guillaume Nodet wrote:
>>>> See https://issues.apache.org/jira/browse/KARAF-2761
>>>> The idea is to simplify writing commands as much as possible.
>>>> With the recent @Completer annotation, things are already much easier to
>>>> deal with, but writing commands without blueprint is a real pain.
>>>> I've committed a simple java DSL to help around that, so you have an
>>>> example at
>>>> https://git-wip-us.apache.org/repos/asf?p=karaf.git;a=blob;
>>>> f=scr/command/src/main/java/org/apache/karaf/scr/command/
>>>> Commands.java;h=70533e5640e5ab0492b67df71a9a028a7895135e;hb=
>>>> 40140e4f0c0745a41c1a4579f529f07e5819d785#l39
>>>> So far the SCR commands, it's a clear win.
>>>> I'm also considering using it for blueprint based commands, but I
>>>> haven't
>>>> had any time to experiment yet.   One limitation of blueprint is that
>>>> there's no "abstract" definitions, so no way to make things common to
>>>> all
>>>> commands definitions in a given bundle.
>>>> Let's look at a common use case:
>>>> https://git-wip-us.apache.org/repos/asf?p=karaf.git;a=blob;
>>>> f=jndi/command/src/main/resources/OSGI-INF/blueprint/
>>>> jndi-command.xml;h=d9b9f148ca2bc3be6e6dcd574840ed19576f11da;hb=HEAD
>>>> The only real interesting things in the above xml is the class names of
>>>> commands and completers.  I think reusing the above DSL, the blueprint
>>>> would look like
>>>> <reference id="jndiService" interface="org.apache.karaf.jndi.JndiService"
>>>> />
>>>> <bean class="org.apache.karaf.jndi.command.Commands" init-method="init"
>>>> destroy-method="unregister">
>>>>    <property name="context" ref="blueprintBundleConext" />
>>>>   <property name="jndiService" ref="jndiService" />
>>>> </bean>
>>>> and then, the java code:
>>>> public class Commands extends org.apache.karaf.shell.commands.Commands
>>>> {
>>>>    private JndiService jndiService;
>>>>    public void init() {
>>>>      completer(new ContextsCompleter(jndiService));
>>>>      completer(new NamesCompleter(jndiService));
>>>>      completer(new ServiceIdsCompleter(getContext()));
>>>>      command(AliasCommand.class);
>>>>      command(BindCommand.class);
>>>>      command(ContextsCommand.class);
>>>>      command(CreateCommand.class);
>>>>      command(DeleteCommand.class);
>>>>      command(NamesCommand.class);
>>>>      command(UnbindCommand.class);
>>>>      register();
>>>>    }
>>>>    public CommandBuilder command(Class<? extends Action> actionClass)
>>>>      return super.command(actionClass)
>>>>                .properties(jndiService);
>>>>    }
>>>>    public void setJndiService(JndiService jndiService) {
>>>>      this.jndiService = jndiService;
>>>>    }
>>>> }
>>>> I haven't tried the above code yet, so there may be slight typos, but
>>>> that
>>>> gives you an idea of what it would look like.
>>>> Thoughts ?
>>>> Guillaume
>>> --
>>> Christian Schneider
>>> http://www.liquid-reality.de
>>> Open Source Architect
>>> http://www.talend.com

  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message