0

SETUP

Suppose that you have two bean archive modules A and B where A depends on B (we cannot modify B). Within B, suppose that there is a class in which an EntityManager is injected as follows:

@Dependent
class SomeClassImpl implements SomeClass {
    private EntityManager em;
    
    public SomeClassImpl() {}

    @Inject
    public SomeClassImpl(EntityManager em) {
        this.em = em;
    }
    
    ...

}

Note that SomeClass interface is never directly injected into A. That is, SomeClass is a dependency of SomeRepository which in turn is a dependency of SomeUseCase and this is what get injected in A.

Because the entity manager is not a cdi bean, we can define its producer within A. This works as expected when deploying in a WAR archive.

PROBLEM

Now, consider the following: you have three bean archive modules X, Y and Z where both X and Y depend on Z as defined above. Because X and Y define a producer for the EntityManager (different in X and Y) and these producers as visible to all modules, we must add qualifier to X and Y's producer to avoid the unsatisfied dependencies exception.

Now, the question is how do we deal with the ambiguity in Z?

If the producers were only visible to the module where defined, then this will be reduced the first scenario. But because they are not, we will get the org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type EntityManager with qualifiers

Note: although this can be solved with custom scope, I would prefer to avoid building one if possible as of now.

If it were you, how would you go about solving this problem?

Astonvish32
  • 179
  • 2
  • 11

2 Answers2

0

Since you have 2 beans of the same type (the 2 EntityManagers), you have to differentiate them using qualifiers. But how do you define which of the 2 qualified instances of the EntityManager get injected into the SomeClassImpl?

Fortunately, SomeClassImpl uses constructor injection, so you can construct it yourself. I suggest 2 producer methods (can be in the same bean), each providing a manually constructed, qualified instance of the SomeClassImpl; assuming @Q1 and @Q2 are the qualifiers AND that the 2 different EntityManagers are also qualified by @Q1, @Q2:

@Q1
@Dependent
@Produces
public SomeClassImpl(@Q1 EntityManager em) {
    return new SomeClassImpl(em);
}

@Q2
@Dependent
@Produces
public SomeClassImpl(@Q2 EntityManager em) {
    return new SomeClassImpl(em);
}

CDI will still find an unqualified instance of SomeClassImpl and will probably complain about ambiguous dependencies. If so, I would try to exclude the unqualified SomeClassImpl from bean discovery using the exclusion mechanism of beans.xml (the <scan><exclude> element).

Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • A fine suggestion, indeed. But, for this to work we must qualify the injection point of `SomeClass` with either `@Q1` or `@Q2`. This is achievable if `SomeClass` were directly injected in module X or Y but because it is not, there is no way of adding a qualifier at injection point (also assuming that the module containing `SomeClass` is not modifiable). Think it this way: `SomeClass` is a Dependency of `SomeRepository` which in turn is a dependency `SomeUseCase` which is then injected in module X or Y. This is what I mean by some `SomeClass` is never injected into X or Y directly. – Astonvish32 May 25 '23 at 06:56
  • 1
    Hmmm, I see, this is indeed a problem. Can you however extend this pattern to the classes that inject `SomeClass`? I.e. instead of producing `SomeClass`, produce the `ClassThatDependsOnSomeClass`. If the dependency chain is not too long it may work. – Nikos Paraskevopoulos May 25 '23 at 07:38
  • Yes, this is a solution although it does not feel quite clean. I will implement this for now and hope that cdi will put something in place to deal with this kind of problems (well a other than custom scope). Thank you very much for your help. – Astonvish32 May 26 '23 at 15:17
0

I would use CDI Alternatives for this.

In your problem statement you said you have two different producers that produce an EntityManager, annotate one of them with @Alternative so you have:

package X;

public class DefaultProducer {
    @javax.enterprise.inject.Produces
    @javax.enterprise.context.ApplicationScoped //Replace the scope as appropriate
    public EntityManager produceEntityManager {...}
}

package Y;

public class AlternativeProducer {
    @javax.enterprise.inject.Produces
    @javax.enterprise.inject.Alternative
    @javax.enterprise.context.ApplicationScoped //Replace the scope as appropriate
    public EntityManager produceEntityManager {...}
}

This will let you control which producer method is used in which module. If an archive contains a beans.xml with:

<beans ... >
    <alternatives>
        <class>Y.AlternativeProducer</class>
    </alternatives>
</beans>

That archive will Inject EntityManager from AlternativeProducer, otherwise it will default to DefaultProducer. Since beans.xml is per archive you'll be able to use different entity managers in different modules, and since one of them is active by default you can control which entity manager is used in B without modifying that archive.


Warning! If you do not have a beans.xml file already, then adding a beans.xml file to an archive can change the rules for bean discovery (depending on the CDI version).

CDI applications without a beans.xml file run in scanning mode annotated, so if you set that explicitly you'll avoid changes in behaviour, this works on any CDI version.

Benjamin Confino
  • 2,344
  • 3
  • 26
  • 30
  • Unfortunately, this did not work for me because each producer belongs to a different module. Reading about alternative, its seems they only apply to classes defined in the same archive. And my test shows that they are not visible for injection within dependencies of these modules. Please correct me if I am mistaken. Thanks for the suggestion! – Astonvish32 Jun 13 '23 at 18:37
  • I just tested it locally with the a test app: One ear file. Containing one war file. A lib folder containing two jar files. Both JarFiles have a producer that returns a class implementing EntityManager, WarFile has a servlet that injects an EntityManager. Without an @Alternative annotation I get an AmbigiousResolutionException, with that annotation on a Producer it works. Could you be more specific about your application's structure? The rules for how CDI treats cross archive interactions are surprisingly specific. – Benjamin Confino Jun 15 '23 at 12:46
  • Hi, apologies for the late replay. The difference between what I explained in the set up and your comment is the direction of injection. In your comment, the produces are defined within children module and the injection happens inside a parent module (the war file). In my case as explained in the setup, the producers are defined within the parent modules (two or more jars) and the injection is happening inside a child module (a jar that is a dependency of other jar, within which the producers are defined). My deployment is a single war file with multiple jars. – Astonvish32 Jun 24 '23 at 10:35