javajakarta-eecdijsr299

Fire event with subclass runtime type


I want to fire an event in CDI whose type I can only determine on runtime. For instance, let's say there's some interface A with implementing classes AA and AB. I have two observers:

public void observeAA(@Observes AA aa) {
}

public void observeAA(@Observes AB ab) {
}

Then some event producer:

@Inject @Any
private Event<A> event;

public A getPayload();

public void fire() {
    this.event.fire(getPayload());
}

This doesn't work because A is neither a subtype of AA or AB (it's the other way around). I've noticed there's a select method that takes a subtype:

public <U extends T> Event<U> select(Class<U> subtype, Annotation... qualifiers);

However, it requires a correctly parameterized Class object, which (correct if I'm wrong), I can't build at runtime.

Is there any solution or will I have to use qualifiers (possibly an annotation with a Class<?> method)?


Solution

  • I ended up using a qualifier with a Class<?> member.

    @Qualifier
    @Target({TYPE, METHOD, PARAMETER, FIELD})
    @Retention(RUNTIME)
    public @interface EventType {
        Class<?> value();
    }
    
    
    public class Dispatcher {
    
        @Inject @Any
        private Event<A> event;
    
        public void fireEvent(A a) {
                this.event.select(
                        getTypeAnnotation(
                        a.getClass())).fire(a);
        }
    
        public static EventType getTypeAnnotation(
                final Class<?> type) {
            return (EventType) Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class<?>[]{EventType.class},
                    new InvocationHandler() {
    
                @Override
                public Object invoke(Object proxy, Method method,
                        Object[] args) throws Throwable {
                    if (method.equals(
                            EventType.class.getMethod("value"))) {
                        return type;
                    } else if (method.equals(Annotation.class.getMethod(
                            "annotationType"))) {
                        return EventType.class;
                    } else if (method.getName().equals("hashCode")) {
                        return 127 * "value".hashCode() ^ type.hashCode();
                    } else if (method.getName().equals("equals")) {
                        return (args[0] instanceof EventType &&
                                ((EventType)args[0]).value()
                                .equals(type));
                    }
                    return null;
                }
            });
        }
    }
    
    public class X {
        public void observeA(
                @Observes @EventType(AA.class) A a) {
        ...
    

    EDIT

    This is a simpler way of instantiating the annotation:

    public abstract static class ConfigTypeAnnotation
            extends AnnotationLiteral<ConfigType>
            implements ConfigType { }
    
    public static ConfigType getConfigTypeAnnotation(final Class<?> type) {
        return new ConfigTypeAnnotation() {
            @Override
            public Class<?> value() {
                return type;
            }
        };
    }