2

I'm trying to use a POJO as CDI producer for injecting the right EJB but I get org.jboss.weld.exceptions.UnsatisfiedResolutionException: WELD-001308.

This is my producer POJO

public class STGatewayUtilProducer {

    @Produces
    @Chosen
    public ISTGatewayUtil getISTGatewayUtil(Instance<STGatewayWSUtil> ws, Instance<STGatewayMQTTUtil> mqtt, ConfigurationManager cm) {
        switch(cm.getGatewayProtocol()) {
            case ConfigurationManager.GATEWAY_PROTOCOL_TYPE_MQTT:
                return mqtt.get();
            default:
                return ws.get();
        }
    }

}

This is the qualifier definition:

@Qualifier
@Target({ TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Chosen {}

And those are the EJB declarations:

@Stateless
public class STGatewayMQTTUtil implements Serializable, ISTGatewayUtil {
    ...
}

@Stateless
public class STGatewayWSUtil implements Serializable, ISTGatewayUtil {
    ...
}

Finally, this is the way I'm injecting the EJB:

@Inject
@Chosen
private Instance<ISTGatewayUtil> gtwUtil;

I'm facing the problem both with JBoss AS 7 and WildFly 10.

Edit

I found the core of my problem! I declared a common abstract parent class which implements the ejb interface and let my session beans extend it: using this structure the beans can't be resolved.

Instead, if i move the implements clause on session beans the problem disappear: may someone explain me what's wrong with my class hierarchy?

Marco Stramezzi
  • 2,143
  • 4
  • 17
  • 37

2 Answers2

2

Quoting the specification

3.2.2. Bean types of a session bean

The unrestricted set of bean types for a session bean contains all local interfaces of the bean and their superinterfaces. If the session bean has a no-interface view, the unrestricted set of bean types contains the bean class and all superclasses. In addition, java.lang.Object is a bean type of every session bean.

Remote interfaces are not included in the set of bean types.

So here as both of your session bean have a local interface, they don't have their classes in their set of bean types. Thus it is normal that you can't resolve them with their class.

You need to add extra info to your session beans definition to be able to distinguish them or declare them as No-Interface view EJB with @LocalBean annotation.
Here's a new version of your code declaring your EJB as NIV

@Stateless
@LocalBean 
public class STGatewayMQTTUtil implements Serializable, ISTGatewayUtil {
    ...
}

@Stateless
@LocalBean
public class STGatewayWSUtil implements Serializable, ISTGatewayUtil {
    ...
}

Your producer don't need to inject 2 Instances<T>. You can either inject both beans and return the chosen one.

public class STGatewayUtilProducer {

    @Produces
    @Chosen
    public ISTGatewayUtil getISTGatewayUtil(STGatewayWSUtil ws, STGatewayMQTTUtil mqtt, ConfigurationManager cm) {
        switch(cm.getGatewayProtocol()) {
            case ConfigurationManager.GATEWAY_PROTOCOL_TYPE_MQTT:
                return mqtt;
            default:
                return ws;
        }
    }

}

or use Instance<T> like this

public class STGatewayUtilProducer {

    @Produces
    @Chosen
    public ISTGatewayUtil getISTGatewayUtil(Instance<ISTGatewayUtil> gw, ConfigurationManager cm) {
        switch(cm.getGatewayProtocol()) {
            case ConfigurationManager.GATEWAY_PROTOCOL_TYPE_MQTT:
                return gw.select(STGatewayMQTTUtil.class).get();
            default:
                return gw.select(STGatewayWSUtil.class).get();
        }
    }

}

Be careful when you use a bean instance to produce a new bean, it should have the @Dependent scope (to avoid superposing 2 lifecycle on your produced bean) or be injected with @New keyword. Here your session beans are in @Dependent scope since they don't specify any scope.

Antoine Sabot-Durand
  • 4,875
  • 17
  • 33
  • Thank you very much Antoine! I understood that I have to read more about EJB view because it is not really clear to me... I tried to use local interface EJBs instead of NIV removing `@LocalBean` from the two classes and placing `@Local` on the interface `ISTGatewayUtil` but it throws the same exception: could you please explain me why? Also, I did not understand your last warning about superposing 2 lifecycles): may you explain it further or link me some other reads? – Marco Stramezzi Feb 18 '16 at 16:30
  • If you remove `@LocalBean` from your session bean you go back to previous use case. Adding `@Local` to the interface only makes it explicit. I'm not an EJB expert (only CDI spec lead) , you could check this blog post to have more info on EJB views: http://piotrnowicki.com/2013/03/defining-ejb-3-1-views-local-remote-no-interface/ – Antoine Sabot-Durand Feb 19 '16 at 09:50
  • If you declare a CDI bean in `@RequestScoped` a given instance of it will only live the time of the http request. This "life and death" (lifecycle) is automatically managed by the CDI container. If you inject a `@RequestScoped` bean in the producer of an `@ApplicationScoped` bean (via parameter like in your example), and that you use all or part of this bean to create you `@ApplicationScoped` bean you'll obtain strange result since both beans are in different scope. `@New` was introduce for that, but since CDI 1.1 it's deprecated and we prefer to use @Dependent bean for this kind of use case – Antoine Sabot-Durand Feb 19 '16 at 10:09
  • I understood the problem of mixing lifecycle. Instead, I have not yet clear how I am supposed to use CDI producers with session beans exposing only a local interface. Or producers can be used only declaring (also) `@LocalBean`? – Marco Stramezzi Feb 19 '16 at 12:54
  • It's a not a problem of producer it's a problem of bean resolution. When an EJB is used with CDI, unless it is an no interface view (NIV) EJB(defined with `@LocalBean`), its class is not taken into account to resolve an injection point. Here, your 2 EJB weren't (NIV) EJB and implemented the same interface `ISTGatewayUtil`, so there wasn't any mean to distinguish them. Distinction could have been made by introducing 2 interfaces or using 2 different qualifiers on your EJBs. – Antoine Sabot-Durand Feb 19 '16 at 13:22
0

Your case scenario here, applies quite well to CDI qualifiers, and you can still maintain the ejb session beans if you require transaction management (if you dont require any transaction logic, then i would do away with the ejbs in the first place).

That said, i would design your scenario thus:

@Qualifier
@Retention(RUNTIME)
@Target(FIELD, METHOD, PARAMETER, TYPE)
public @interface ISTGateway {

   ISTGatewayType value()

   enum ISTGatewayType {
       MQT,
       WS
   }
}

The usage wil look like this: (NOTE the ejbs have been annotated with @Dependent to enable CDI container detect them automatically )

@Stateless
@Dependent 
@ISTGateway(MQT)
public class STGatewayMQTTUtil implements Serializable, ISTGatewayUtil {
    ...
}

@Stateless
@Dependent
@ISTGateway(WS)
public class STGatewayWSUtil implements Serializable, ISTGatewayUtil {
    ...
}

Your producer should look like this: (the good thing about the producer here is that you never need to update it, if you ever add a new ISTGatewayUtil)

@ApplicationScoped
public class STGatewayUtilProducer {
    @Any
    @Inject
    private Instance<ISTGatewayUtil> istGatewayUtils;

    @Inject
    private ConfigurationManager configurationManager;

    @Chosen
    @Produces
    public ISTGatewayUtil getISTGatewayUtil() {
        final ISTGateway istGateway = new ISTGatewayImpl(cm.getGatewayProtocol());
        return istGatewayUtils.select(istGateway).get();
    }

    private static final class ISTGatewayImpl extends AnnotationLiteral<ISTGateway> implements ISTGateway {

       private final ISTGatewayType istGatewayType;

       private ISTGatewayImpl( final ISTGatewayType istGatewayType) {
          this.istGatewayType = istGatewayType;
       }

       public ISTGatewayType value() {
           return istGatewayType;
       }
    }  
}
maress
  • 3,533
  • 1
  • 19
  • 37