eclipse / lsp4jakarta

Language Server for Jakarta EE
Eclipse Public License 2.0
33 stars 51 forks source link

Depedency Injection: Diagnostic for Ambigious Dependencies with `@Inject` #163

Open KidoVin01 opened 2 years ago

KidoVin01 commented 2 years ago

What is ambiguous dependency?

An ambiguous dependency exists at an injection point when multiple beans are eligible for injection to the injection point.

Dependency injection and lookup

When resolving a bean at an injection point, the container considers bean type, qualifiers and selected alternatives

Which Java classes are managed beans?

A Java class is a managed bean if it meets all of the following conditions:

  • is not an inner class.
  • It is a non-abstract class, or is annotated @Decorator.
  • It does not implement jakarta.enterprise.inject.spi.Extension.
  • It is not annotated @Vetoed or in a package annotated @Vetoed.
  • It has an appropriate constructor - either:
  • the class has a constructor with no parameters, or
  • the class declares a constructor annotated @Inject. All Java classes that meet these conditions are managed beans and thus no special declaration is required to define a managed bean.

Beans and Attributes

A bean comprises the following attributes:

  • A (nonempty) set of bean types
  • A (nonempty) set of qualifiers
  • A scope
  • Optionally, a bean name
  • A set of interceptor bindings
  • A bean implementation

Furthermore, a bean may or may not be an alternative.

Error Identification:

  1. Consider a field, method or constructor annotated with @Inject and a set of default or user-specified bean attributes
  2. Check if the dependency class consists of multiple sub-classes that are of valid managed beans.
  3. Check if the dependency class (or its sub-classes) has EXACTLY ONE implementation that corresponds the same set of bean attributes.

Note: if no classes match the set of bean attributes, it is an unsatisfied error, refer to #164

Potential Diagnostics:

Example error for @Alternative:

[INFO] [ERROR   ] CWWKZ0004E: An exception occurred while starting the application demo-servlet. The exception message was: com.ibm.ws.container.service.state.StateChangeException: org.jboss.weld.exceptions.DeploymentException: WELD-001409: Ambiguous dependencies for type GreetingItf with qualifiers @Default
[INFO]   at injection point [BackedAnnotatedField] @Inject private io.openliberty.sample.jakarta.di.ambiguous.alt.GreetingServlet.greeting
[INFO]   at io.openliberty.sample.jakarta.di.ambiguous.alt.GreetingServlet.greeting(GreetingServlet.java:0)
[INFO]   Possible dependencies: 
[INFO]   - Managed Bean [class io.openliberty.sample.jakarta.di.ambiguous.alt.GreetingA] with qualifiers [@Any @Default],
[INFO]   - Managed Bean [class io.openliberty.sample.jakarta.di.ambiguous.alt.GreetingB] with qualifiers [@Any @Default]


Note: The diagnostics and error identification depends on #159 for reading class information across files.

Related to #153

kathrynkodama commented 2 years ago

Can you link/quote where in the spec this is mentioned and provide an example of how this might look?

kathrynkodama commented 2 years ago

@AlvinTan2000 Does this section of the CDI spec cover the ambiguous injection you are referring to?

https://jakarta.ee/specifications/cdi/3.0/jakarta-cdi-spec-3.0.html#unsatisfied_and_ambig_dependencies

If so, we need to come up with some concrete ways of checking whether it is an ambiguous injection

KidoVin01 commented 2 years ago

Provided some examples code demonstrating the errors.

kathrynkodama commented 2 years ago

Provided some examples code demonstrating the errors.

What conditions would we check for when delivering the diagnostic?

KidoVin01 commented 2 years ago

We would check for the whether all the applicable sub-classes share the conflicting annotations, which would depend on #159

kathrynkodama commented 2 years ago

How to check for ambiguous injection (expanding on the error identification section above):

  1. @Inject on a field, method or constructor
@Inject 
private GreetingItf greeting;
  1. Check if the injected field is an interface, for methods and constructors check that the corresponding parameter is an interface
public interface GreetingItf {
    public String greet(String greeting);
}
  1. Search the java project for classes that implement the corresponding interface. If there is more than one class that implements the interface then we need to if every implementation has the same bean attributes. If they do, then deliver a diagnostic warning that the injection is ambiguous.

Detecting if every implementation has the same bean attributes gets a bit tricky, the logic is if every implementation has:

To start, we can cover the base case and iteratively build up support. The base case would be:

}


```java
@ApplicationScoped
public class GreetingB implements GreetingItf{

    public String greet(String name) {
        return "GreetingB, " + name;
    }

}

We can break this issue down into subsequent tasks and iteratively build:

KidoVin01 commented 2 years ago

Detecting if every implementation has the same bean attributes gets a bit tricky, the logic is if every implementation has:

  • the same scope defined, (possible scopes are: @RequestScoped, @SessionScoped, @ApplicationScoped, @Dependent, @ConversationScoped) AND IF @Named is present: the same name defined (value set by: @Named ) AND IF a custom @Qualifier annotation is present: the same qualifier defined (would involve checking for custom qualifiers) AND IF@Alternative is present: all are marked @Alternative or ![all but one] are marked @Alternative AND IF [add priority logic here] AND IF [add type logic here] @AlvinTan2000 I am leaving these logic sections empty as I am not sure the rules associated. Please fill in with an additional comment.

AND IF @Priority(value) is present: at least two shares the same highest value

Don't think it is possible to purposely create an ambiguous error with just a single @Type annotation.