11 Replies Latest reply on Mar 30, 2014 8:22 PM by ron_sigal

    RESTEASY-1008: Collision between CDI and Bean Validation

    ron_sigal

      https://issues.jboss.org/browse/RESTEASY-1008 reveals an interesting problem at the intersection of Resteasy, CDI, and Bean Validation.  The problem is that JAX-RS injection into a JAX-RS resource is postponed to the execution of JaxrsInjectionTarget, which runs when a resource method is invoked, but Bean Validation is performed before the method is invoked. As a result, validation tests are run against null fields.

       

      I think I understand the point of JaxrsInjectionTarget. The native Resteasy property injector, ResourcePropertyInjector called by way of CdiPropertyInjector in this case, works on a Weld proxy.  When I remove the restriction that prevents CdiPropertyInjector from injecting into JAX-RS resources, I see that the line

      injector.param.getField().set(target, injector.injector.inject(request, response));

      in ResourcePropertyInjector creates a new field, with the same name as the field in the resource, param in this case:

       

      proxy.png

      The param field in the resource itself remains untouched.

       

      On the other hand, JaxrsInjectionTarget has access to the actual resource.

       

      I'm trying to find a way around the problem, with a couple of possibilities coming to mind.

       

      1. Is it possible to retrieve the target object from a Weld proxy, so that ResourcePropertyInjector can inject into the actual resource?
      2. Is it possible to get access to the appropriate JaxrsInjectionTarget and run it before a resource method is invoked?

       

      If neither of these are possible, I might have to move validation down into the resource, which might be a mess.

       

      Any ideas?

       

      Thanks,

      Ron

        • 1. Re: RESTEASY-1008: Collision between CDI and Bean Validation
          jharting

          My understanding is that this only happens when you validate a JAX-RS resource that you @Inject into another resource, is that correct?

           

          Either way:

          1) There is not portable way of unwrapping the proxy. Note that client proxies work lazily which means that there may be no instance to unwrap until a method is actually invoked on the proxy.

          2) Currently I do not think there is a way of obtaining the JaxrsInjectionTarget. ResteasyCdiExtension could be modified to expose the modified InjectionTargets.

          • 2. Re: RESTEASY-1008: Collision between CDI and Bean Validation
            jharting

            Actually, looking at the reproducer this may be a BV integration issue. When is the validation performed?

            • 3. Re: RESTEASY-1008: Collision between CDI and Bean Validation
              ron_sigal

              Hi Josef,

               

              re: "My understanding is that this only happens when you validate a JAX-RS resource that you @Inject into another resource, is that correct?"

               

              In the example I've been using, the resource isn't injected anywhere.

               

              re: "There is not portable way of unwrapping the proxy. Note that client proxies work lazily which means that there may be no instance to unwrap until a method is actually invoked on the proxy."

               

              This isn't the first time I've wished that I could access the object behind a proxy.  I guess the semantics would have to require the proxy to create a backing object on demand if it doesn't exist.  Is this something Weld could support?  Has anyone else asked for it?

               

              re: "ResteasyCdiExtension could be modified to expose the modified InjectionTargets."

               

              Looking at JaxrsInjectionTarget again, I see that inject() receives the actual object from Weld, so I'm not sure that remembering a JaxrsInjectionTarget would help me if I don't have access to that object.

               

              re: "When is the validation performed?"

               

              ResourceMethodInvoker.invoke() calls

               

                 Object target = resource.createResource(request, response, resourceMethodProviderFactory);

               

              which then calls

               

                  Object obj = constructorInjector.construct(request, response);

                  propertyInjector.inject(request, response, obj);

               

              Shortly afterward, ResourceMethodInvoker.invokeOnTarget() validates the property and class constraints.  Then, MethodInjectorImpl.invoke() validates the method parameters.  If no violations are detected, the method is invoked and then the return value is validated.

              • 4. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                jharting

                Did you consider implementing validation using CDI interceptors instead?

                • 5. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                  ron_sigal

                  Hmmm, no I didn't think about using CDI interceptors.  In general, it wouldn't work because Resteasy has to be able to run in standalone mode.  But if CDI is enabled, it would be OK. I suppose that injection into the resource would be done by the time the interceptor runs. This is very promising.  Thanks, Jozef.

                  • 6. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                    abhi0123

                    I'm not trying to piggyback on this post but I've a very similar problem described here. Should I ask a question separately or can we discuss here?

                    • 7. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                      ron_sigal

                      Hi Abhijit,

                       

                      I think these are different problems, but the jury's out on whether they are related.  I'm making progress on Jozef's interceptor, and when I've got something, I'll attach some jars here so you can see if the fix to RESTEASY-1008 helps with your problem.

                       

                      -Ron

                      • 8. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                        ron_sigal

                        Hi Jozef,

                         

                        I've gotten back to this issue lately, and the interceptor idea worked out pretty well.  I've got an interceptor with a binding annotation, and a CDI extension that wraps each resource class with an AnnotatedType which adds the binding annotation.

                         

                        I'm having one problem, though.  If I include the jar with the CDI extension, interceptor, annotation, etc., in the war file, everything works.  But if I put the jar into the modules directory, the interceptor gets ignored. I can see that the CDI extension detects the interceptor and adds the binding annotation to the test resource, but the interceptor isn't getting called.  Do you know if this has something to do with the jars in the modules directory being processed differently, or am I just doing something wrong?

                         

                        Thanks,

                        Ron

                        • 9. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                          ron_sigal

                          Hi Jozef,

                           

                          I've switched from CDI interceptors to old school interceptors bound by the @Interceptors annotation, and now everything is working in WildFly 8 when I put the interceptor in the modules directory.

                           

                          But I've got two problems when I try to do the same thing in AS 7.

                           

                          1. I want to inject the ServletContext and ServletRequest into the interceptor.  It works fine in WF 8, with Weld 2.1.2, but not in AS 7 with Weld 1.1.5.AS71, which doesn't natively support injection of the ServletContext and ServletRequest.  I've got producer methods that create the ServletContext and ServletRequest, and it works fine when I put the interceptor and other classes in the WAR, but not when I put them in the modules directory.  I get

                           

                          Caused by: org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type ServletContext with qualifiers @ResteasyValidationCDIProducerBinding

                            at injection point [BackedAnnotatedField] @Inject @ResteasyValidationCDIProducerBinding private org.jboss.resteasy.plugins.validation.cdi.ResteasyValidationCdiInterceptor.context

                            at org.jboss.resteasy.plugins.validation.cdi.ResteasyValidationCdiInterceptor.context(ResteasyValidationCdiInterceptor.java:0)

                          WELD-001475: The following beans match by type, but none have matching qualifiers:

                            - WELD|AbstractSyntheticBean|org.jboss.resteasy.resteasy-validation-cdi:main.additionalClasses|ServletContext

                           

                          2. The other problem seems to be related to my wrapping AnnotatedType:

                           

                          public class ResteasyValidationCdiAnnotatedType<TYPE> implements AnnotatedType<TYPE>

                          {

                            

                             public abstract static class InterceptorQualifer extends AnnotationLiteral<Interceptors> implements Interceptors

                             {

                             }

                           

                             public static final Annotation interceptors = new InterceptorQualifer()

                             {

                                public Class[] value()

                                {

                                   return new Class[] {ResteasyValidationCdiInterceptor.class};

                                }

                             };

                           

                             private AnnotatedType<TYPE> delegate;

                             private Set<Annotation> annotations = new HashSet<Annotation>();

                            

                             public ResteasyValidationCdiAnnotatedType(AnnotatedType<TYPE> delegate)

                             {

                                this.delegate = delegate;

                                this.annotations.addAll(delegate.getAnnotations());

                                this.annotations.add(interceptors);

                             }

                             ...

                           

                          Again, it works fine in WF 8, but in AS 7 I get

                           

                          2:11:57,999 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-8) MSC00001: Failed to start service jboss.deployment.unit."RESTEASY-1008.war".WeldService: org.jboss.msc.service.StartException in service jboss.deployment.unit."RESTEASY-1008.war".WeldService: org.jboss.weld.exceptions.DeploymentException: WELD-000826 Cannot access values() on annotation

                              at org.jboss.as.weld.services.WeldService.start(WeldService.java:83)

                              at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1811) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]

                              at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1746) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]

                              at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) [rt.jar:1.6.0_41]

                              at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) [rt.jar:1.6.0_41]

                              at java.lang.Thread.run(Thread.java:662) [rt.jar:1.6.0_41]

                          Caused by: org.jboss.weld.exceptions.DeploymentException: WELD-000826 Cannot access values() on annotation

                              at org.jboss.weld.util.reflection.SecureReflections.extractValues(SecureReflections.java:431)

                              at org.jboss.weld.bean.AbstractClassBean.initInterceptionModelForType(AbstractClassBean.java:263)

                              at org.jboss.weld.bean.ManagedBean.initializeAfterBeanDiscovery(ManagedBean.java:343)

                              at org.jboss.weld.bootstrap.BeanDeployment.doAfterBeanDiscovery(BeanDeployment.java:216)

                              at org.jboss.weld.bootstrap.BeanDeployment.afterBeanDiscovery(BeanDeployment.java:208)

                              at org.jboss.weld.bootstrap.WeldBootstrap.deployBeans(WeldBootstrap.java:352)

                              at org.jboss.as.weld.WeldContainer.start(WeldContainer.java:82)

                              at org.jboss.as.weld.services.WeldService.start(WeldService.java:76)

                              ... 5 more

                          Caused by: java.lang.RuntimeException: java.lang.NullPointerException

                              at org.jboss.weld.util.reflection.SecureReflectionAccess.runAsInvocation(SecureReflectionAccess.java:154)

                              at org.jboss.weld.util.reflection.SecureReflections.invoke(SecureReflections.java:309)

                              at org.jboss.weld.util.reflection.SecureReflections.extractValues(SecureReflections.java:428)

                              ... 12 more

                          Caused by: java.lang.NullPointerException

                              at org.jboss.weld.util.reflection.SecureReflections$15.work(SecureReflections.java:316)

                              at org.jboss.weld.util.reflection.SecureReflectionAccess.run(SecureReflectionAccess.java:52)

                              at org.jboss.weld.util.reflection.SecureReflectionAccess.runAsInvocation(SecureReflectionAccess.java:137)

                              ... 14 more

                           

                          The message isn't very explicit, but I've only got one AnnotationLiteral in the module.

                           

                          Any ideas?

                           

                          Thanks,

                          Ron

                          • 10. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                            ron_sigal

                            Ok, I see where the second problem is coming from.  org.jboss.weld.bean.AbstractClassBean.initInterceptionModelForType() includes the code:

                             

                                // initialize EJB3 interceptors
                                Class<?>[] classDeclaredInterceptors = null;
                                if (getWeldAnnotated().isAnnotationPresent(InterceptionUtils.getInterceptorsAnnotationClass())) {
                                    Annotation interceptorsAnnotation = getType().getAnnotation(InterceptionUtils.getInterceptorsAnnotationClass());
                                    classDeclaredInterceptors = SecureReflections.extractValues(interceptorsAnnotation);
                                }

                             

                            getWeldAnnotated() gets an instance of WeldClassImpl, whose annotationMap includes the @Interceptors annotation I added in the wrapping AnnotatedType. But getType() gets the actual resource class, which doesn't have the @Interceptors annotations.  So interceptorsAnnotation is null, leading to a NPE.

                             

                            I don't know what to do about this one.

                            • 11. Re: RESTEASY-1008: Collision between CDI and Bean Validation
                              ron_sigal

                              I've reorganized the solution, putting validation in org.jboss.resteasy.cdi.JaxrsInjectionTarget in the resteasy-cdi module, which avoids some problems with using interceptors when the JAX-RS resource is a session bean.  All of my tests are passing now.

                               

                              Jozef, thank you for your help.