2
votes

I am trying to programmatically lookup and inject a CDI managed bean where the qualifier contains the name of a class (not the class I want to inject), however the problem I've got is that the code I'm using to lookup the correct bean always returns with null.

The beans I want to inject are annotated with a custom annotation called @CQRSCommandHandler which contains the name of a class being used as a qualifier and the beans also implement an interface called CommandHandler. The classes I'm using qualifier implement the interface Command.

Based on my somewhat limited knowledge of CDI, I believe that in order to programmatically lookup the correct bean which has been qualified with the @CQRSCommandHandler annotation, I need to extend AnnotationLiteral and I can then use Instance to select the bean.

The code for the @CQRSCommandHandler annotation is as follows:

@Qualifier
@Documented
@Retention(value= RetentionPolicy.RUNTIME)
public @interface CQRSCommandHandler {
    Class<? extends Command> command();
}

The code for extending AnnotationLiteral is as follows:

public class CQRSCommandHandlerQualifier extends AnnotationLiteral<CQRSCommandHandler> implements CQRSCommandHandler {

    private static final long serialVersionUID = 1L;
    private final Class<? extends Command> command;

    public CQRSCommandHandlerQualifier(Class<? extends Command> command) {
        this.command = command;
    }

    @Override
    public Class<? extends Command> command() {
        return command;
    }

}

The code I'm using to lookup the correct bean using CDI is as follows:

@Inject
@Any
private Instance<CommandHandler> commandHandlerInstance;

private CommandHandler findCommandHandlerFor(Command command) {

    CommandHandler commandHandler = commandHandlerInstance.select(new CQRSCommandHandlerQualifier(command.getClass())).get();  //This always returns null
    return commandHandler;
}

Despite many hours of google searching I can't work out why commandHandlerInstance.select(new CQRSCommandHandlerQualifier(command.getClass())).get(); does not return an instance of a bean which has been annotated with @CQRSCommandHandler (command = MyCommand.class) where the bean implements the CommandHandler interface and MyCommand.class implements the interface Command.

Is this the correct way to programmatically lookup and inject a CDI managed bean where the qualifier contains the name of a class? If so, where am I going wrong with the above code? If not, what is the best way to achieve the same end result?


Update

The following code is an example implementation of a bean that I'm trying to lookup:

@CQRSCommandHandler(command = CreateToDoItemCommand.class)
public class CreateToDoItemCommandHandler implements CommandHandler {

    @Override
    public <R> Object handle(Command command) {
        System.out.println("This is the CreateToDoItemCommandHandler");
        return null;
    }
}

The following code is the interface for CommandHandler:

public interface CommandHandler {

    public <R> Object handle(Command command);

}

The following code is an example of the class I'm using as a parameter in the qualifier:

public class CreateToDoItemCommand implements Command {

    private String todoId;
    private String description;

    public CreateToDoItemCommand(String todoId, String description) {
        this.todoId = todoId;
        this.description = description;
    }

    public String getTodoId() {
        return todoId;
    }

    public String getDescription() {
        return description;
    }

}

I've stepped through the code in Eclipse and it seems that the Instance object of commandHandlerInstance is null.


Update 2

As suggested by @redge I've separate each step of the instantiation onto a separate line as follows:

private CommandHandler findCommandHandlerFor(Command command) {
    CQRSCommandHandlerQualifier qualifier = new CQRSCommandHandlerQualifier(command.getClass());
    Instance<CommandHandler> instance = commandHandlerInstance.select(qualifier);
    CommandHandler commandHandler = instance.get();
    return commandHandler;
}

The issue seems to be with this line of code Instance<CommandHandler> instance = commandHandlerInstance.select(qualifier); where NullPointerException is thrown presumably because the Instance object commandHandlerInstance is null

I'm running this code on GlashFish 4 which ships with Weld 2.0.0 SP1, but I've also just tried running the same code on GlashFish 4.1 and have installed Weld version 2.2.10.SP1 which is the latest from Maven Central but the same issue occurs.

1
There are a few things I think you need to add to this question. First, can you provide an example implementation that fails? Second, is the call to get() what returns null, or is the Instance object null? The pattern you have here is a very standard pattern and does work well. Do you have a beans.xml?John Ament
I've updated the question to provide an example implementation that fails. The Instance object is null, so I guess that may be the root cause of the issue. I forgot to include it in the updated question but I do have a beans.xml file in the META-INF directory.Paul H
You have probably got something wrong with your CDI container. Which container are you using? If you have your container configured properly you could have your CommandHandler class missing from the class path or have failed to add a beans.xml file to every jar file that contains injected bean classes. Separate each step of the instantiation onto a separate line to make sure which object is null.redge
I've updated the question to separate each step of the instantiation onto a separate line. Also I've checked and all the jars which contain the beans I'm trying to inject have a beans.xml file.Paul H
I have copied your code into a web project and deployed it on both JBOSS 7.1 (JEE6) and Glassfish4.1 (JEE7). I introduced a @Singleton @Startup bean that called findCommandHandlerFor(command) from a @PostConstruct method and it works fine. I therefore suggest it must be something wrong with either your container or the packaging of the application. Do you have CDI working at all in your container? one thing to note is that for a web archive the beans.xml should be in WEB-INF not META_INF.redge

1 Answers

1
votes

You have GlassFish 4.1. I doubt you have a beans.xml file, which if you do should be marked as bean-discovery-mode="all" based on your current setup. If you don't, or you use bean-discovery-mode="annotated" then you'll need to add a bean defining annotation to each of your commands, e.g. @ApplicationScoped for each command so that they can be resolved.