genericsjakarta-eecdiweldproducer

Using a proxy to inject with a CDI Producer


I've tried to follow some of the suggestions I've found in previously posted questions, but I can't seem to get a complete solution. Using the following code:

@Produces
@Dependent
@RestClientResourceConnector
public <T> RestClientProxy<T> getStatusResource(InjectionPoint injectionPoint) throws NamingException, OAuthClientException {
String propertiesFile = null;
String url = null;
AuthenticationStrategy authStrategy = null;

Class<T> clazz = (Class<T>) ((ParameterizedType)injectionPoint.getType()).getActualTypeArguments()[0];

for (Annotation qualifier : injectionPoint.getQualifiers()) {
  if (qualifier instanceof RestClientResourceConnector) {
    RestClientResourceConnector connector = (RestClientResourceConnector) qualifier;
    propertiesFile = connector.value();
    url = connector.clientUrl();
    LOGGER.debug("url set to: " + url);

    authStrategy = this.createAuthStrategry(propertiesFile);
  }
}

Constructor<?> constructor;
try {
  constructor = clazz.getConstructor(String.class, AuthenticationStrategy.class);
} catch (NoSuchMethodException | SecurityException e1) {
  // TODO Auto-generated catch block
  e1.printStackTrace();
  return null;
}

try {
  RestClientProxy rcp = new RestClientProxy();
  rcp.setService(constructor.newInstance(url, authStrategy));
  return rcp;
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

return null;
}

The source for the qualifier is:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface RestClientResourceConnector
{  
  @Nonbinding String value() default "";
  @Nonbinding String clientUrl() default "";
}

With RestClientProxy defined as:

public class RestClientProxy<T> {
 private T client;

 public RestClientProxy() {
 }

 public RestClientProxy(T service) {
     this.client = service;
 }

 public void setService(T service) {
   this.client = service;
 }

 public T get() {
   return client;
 }
}

And attempting to inject with:

@Inject
  @RestClientResourceConnector(value="ferpa.properties", clientUrl="person.enpoint.url")
  RestClientProxy<PersonResourceClient> personProxy;

I get the the weld exception:

2016-09-30 14:07:08,372 WARN [org.jboss.weld.Bootstrap] (weld-worker-1) WELD-001125: Illegal bean type javax.validation.ConstraintValidator<edu.psu.injection.validator.NotNullNotEmptyCollection, java.util.Collection<?>> ignored on [EnhancedAnnotatedTypeImpl] public class edu.psu.injection.validator.NotNullNotEmptyCollectionValidator
2016-09-30 14:07:08,782 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-2) MSC000001: Failed to start service jboss.deployment.unit."account-activation-web.war".WeldStartService: org.jboss.msc.service.StartException in service jboss.deployment.unit."account-activation-web.war".WeldStartService: Failed to start service
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1904)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type RestClientProxy<AccountActivationClient> with qualifiers @RestClientResourceConnector
at injection point [BackedAnnotatedField] @Inject @RestClientResourceConnector private edu.psu.activation.services.AccountActivationTokenService.accountActivationClientProxy
at edu.psu.activation.services.AccountActivationTokenService.accountActivationClientProxy(AccountActivationTokenService.java:0)

at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:359)
at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:281)
at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:134)
at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:155)
at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:518)
at org.jboss.weld.bootstrap.ConcurrentValidator$1.doWork(ConcurrentValidator.java:68)
at org.jboss.weld.bootstrap.ConcurrentValidator$1.doWork(ConcurrentValidator.java:66)
at org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:60)
at org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:53)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
... 3 more

2016-09-30 14:07:08,787 ERROR [org.jboss.as.controller.management-operation] (management-handler-thread - 8) WFLYCTL0013: Operation ("full-replace-deployment") failed - address: ([]) - failure description: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"account-activation-web.war\".WeldStartService" => "org.jboss.msc.service.StartException in service jboss.deployment.unit.\"account-activation-web.war\".WeldStartService: Failed to start service
Caused by: org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type RestClientProxy<AccountActivationClient> with qualifiers @RestClientResourceConnector
at injection point [BackedAnnotatedField] @Inject @RestClientResourceConnector private edu.psu.activation.services.AccountActivationTokenService.accountActivationClientProxy
at edu.psu.activation.services.AccountActivationTokenService.accountActivationClientProxy(AccountActivationTokenService.java:0)
"}}

As always, any help is greatly appreciated.


Solution

  • Fortunately, the answer is pretty simple.

    To solve your problem you should exclude your annotation members (value and clientUrl) from consideration when Weld compares two annotation instances. To achieve this use the @Nonbinding annotation.

    import javax.enterprise.util.Nonbinding;
    import javax.inject.Qualifier;
    
    @Qualifier
    @Retention(RUNTIME)
    @Target({TYPE, METHOD, FIELD, PARAMETER})
    public @interface RestClientResourceConnector {
    
        @Nonbinding
        String value() default "value";
    
        @Nonbinding
        String clientUrl() default "clientUrl";
    }
    

    Detailed explanation

    If you carefully read the stack trace you can find something like:

    WELD-001475: The following beans match by type, but none have matching qualifiers:
      - Managed Bean [class RestClientProxy] with qualifiers [@Any @Default],
      - Producer Method [RestClientProxy<T>] with qualifiers [@RestClientResourceConnector @Any] declared as [[BackedAnnotatedMethod] @Produces @Dependent @RestClientResourceConnector public *your_producer_method_goes_here*...
    

    It means that Weld found a matching bean but it didn't have a needed qualifier. You may ask, "Why?". Because your injection point contains a qualifier with two parameters:

    @RestClientResourceConnector(value="ferpa.properties", clientUrl="person.enpoint.url")
    

    but your Producer method is defined just with a

    @Produces
    @Dependent
    @RestClientResourceConnector
    

    That's why you should probably tell Weld to ignore these parameters.

    Update

    As user @ussmith found out, the problem was caused by the fact, that producer method was not defined inside a CDI bean archive.

    Again I find CDI far more confusing than it should be. With explicit configuration, such problems should not happen.