3

I'm trying to make an application extensible by using CDI, but it seems like I'm missing a piece of the puzzle.

What I want: Have a global configuration that will define which implementation of an interface to use. The implementations would have annotations like @ImplDescriptor(type="type1").

What I tried:

 @Produces
 public UEInterface createUserExit(@Any Instance<UEInterface> instance, InjectionPoint ip) {
    Annotated annotated = ip.getAnnotated();
    UESelect ueSelect = annotated.getAnnotation(UESelect.class);
    if (ueSelect != null) {
        System.out.println("type = " + ueSelect.type());
    }

    System.out.println("Inject is ambiguous? " + instance.isAmbiguous());
    if (instance.isUnsatisfied()) {
        System.out.println("Inject is unsatified!");
        return null;
    }

    // this would be ok, but causes an exception
    return instance.select(ueSelect).get();

    // or rather this:
     for (Iterator<UEInterface> it = instance.iterator(); it.hasNext();) {
         // problem: calling next() will trigger instantiation which will call this method again :(
         UEInterface candidate = it.next();
         System.out.println(candidate.getClass().getName());
     }
}

This code is close to an example I've seen: The @Produces method will be used to select and create instances and a list of candidates is injected as Instance<E>. If the method simply creates and returns an implementation, it works fine. I just don't know how to examine and select a candidate from the Instance<E>. The only way of looking the the "contents" seems to be an Iterator<E>. But as soon as I call next(), it will try to create the implementation... and unfortunately, calls my @Produces method for that, thereby creating an infinite recursion. What am I missing? How can I inspect the candidates and select one? Of course I want to instantiate only one of them...

Thanks in advance for any help and hints!

Finch
  • 76
  • 1
  • 3

2 Answers2

3

I think the issue is you are trying to select the annotation's class rather than using the annotation as a selector qualifier. Using the class directly searches for an implementation that implements that class. You need to create an AnnotationLiteral using the @ImplDescriptor class to perform a select using it as a qualifier. Create a class extending AnnotationLiteral like so.

public class ImplDescriptorLiteral extends AnnotationLiteral<ImplDescriptor> implements ImplDescriptor {
    private String type;
    public ImplDescriptorLiteral(String type) {
        this.type = type;
    }

    @Override
    public String type() {
         return type;
    }

}

then you can pass an instance of this class to the select method using the type you want.

instance.select(new ImplDescriptorLiteral("type1")).get();

Refer to the Obtaining a contextual instance by programmatic lookup documentation for more information.

Brian Blonski
  • 1,762
  • 18
  • 23
  • Use of an annotation literal is not required here. – John Ament Sep 25 '13 at 22:09
  • Sorry guys, I never meant for this to become an abandoned question... first I didn't manage to answer before my vacation, then I came back sick (down for a week), then I had serious work backlog... aaaaanyway.... thanks for the answer which put me on the right track. I'm not sure if I tried to select the annotation's class because I did get the annotation (instance), right? Using the annotation literal, I can use the simple "select" you suggested and don't need the whole @Produces system... – Finch Nov 04 '13 at 19:02
  • So I have a working solution now (thanks!), but I'm still interested in the problem with my code. How would I use the annotation at the injection point to select an implementation programatically? And/Or how can I iterate over the provided options (Instance) without causing an infinite recursion? – Finch Nov 04 '13 at 19:11
  • You are using an Instance in your producer for UEInterface. When you iterate through Instance your producer is a possible candidate which triggers your producer to be called again causing the infinite recursion. Try using the select(ueSelect) without the get() before iterating and see if that excludes your producer from your results. – Brian Blonski Nov 21 '13 at 00:15
  • InjectionPoints are for returning customized instances specific to the class they are injected in. A good example is injecting a logger that uses the classname of it's injection point in it's log statements. If you are only looking up qualifier annotations, you can probably skip InjectionPoints and Producer methods. – Brian Blonski Nov 21 '13 at 00:23
  • Be careful of iterating through all your possibilities since as you've seen it creates an instance of each on. You could try listing all the fully qualified class names you want in your global configuration. Then, at runtime iterate through them using Class.forName(name) to get the Class which you can the use in your select() to get the exact instances you want. I've used this for a modular plug-in system before and it worked fairly well. This works well if you want to use multiple instance of your interface at runtime. Otherwise just use the beans.xml alternative system. – Brian Blonski Nov 21 '13 at 00:32
1

Finch, what you have here should work. it assumes though that you have an instance of UEInterface that is annotated @UESelect, e.g.

@UESelect("one")
public class UEOne implements UEInterface {
..
}

Is this how you're expecting it to work?

John Ament
  • 11,595
  • 1
  • 36
  • 45
  • (apology for slow reply see comment below)... yes, that's how I'm expecting it to work, but it doesn't... direct selection via "select" fails and trying to iterate over the options causes an infinite recursion... – Finch Nov 04 '13 at 19:13