11 Replies Latest reply on Jan 5, 2010 8:33 PM by alberto Gori

    Can't use Object in producer methods

    alberto Gori Novice

      I have this producer method:


      @Produces @Param("") 
      public Object getParamValue(FacesContext fc, InjectionPoint ip) {
        ...
        return new Integer(...);
      }
      



      As you can see the return type is Object, but It can return an Integer or String or instances of other classes, based on some logic (I should use the injection point to coerce or convert to the right type).


      Then I have an observer method like this:


      public void action(@Observes ... myEVent,  @Param("id") Integer id) {
        ...
      }
      


      second parameter injection doesn't work because I get this exception:


      javax.enterprise.inject.UnsatisfiedResolutionException: class java.lang.Integer; binding types = [@Param]Unable to resolve any Managed Beans
           at org.jboss.weld.BeanManagerImpl.getBean(BeanManagerImpl.java:996)
           at org.jboss.weld.BeanManagerImpl.getInjectableReference(BeanManagerImpl.java:966)
           at org.jboss.weld.injection.ParameterInjectionPoint.getValueToInject(ParameterInjectionPoint.java:84)
           at org.jboss.weld.injection.MethodInjectionPoint.getParameterValues(MethodInjectionPoint.java:268)
           at org.jboss.weld.injection.MethodInjectionPoint.invokeOnInstanceWithSpecialValue(MethodInjectionPoint.java:178)
           at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:221)
           at org.jboss.weld.event.ObserverMethodImpl.notify(ObserverMethodImpl.java:196)
           at org.jboss.weld.BeanManagerImpl.notifyObservers(BeanManagerImpl.java:844)
           at org.jboss.weld.BeanManagerImpl.fireEvent(BeanManagerImpl.java:837)
           at org.jboss.weld.BeanManagerImpl.fireEvent(BeanManagerImpl.java:831)
           at agori.example.RestFacesPhaseListener.beforePhase(RestFacesPhaseListener.java:21)
           at com.sun.faces.lifecycle.Phase.handleBeforePhase(Phase.java:212)
           at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:95)
           at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:311)
              ...
      



      If I change the return type of producer method to Integer (matching the one in the observer method), everything works.
      Why I can't use Object here?



          

        • 1. Re: Can't use Object in producer methods
          Nicklas Karlsson Master

          An Object is an Object, not an Integer. The method signature is examined and not the actual returned product at runtime.


          id is non-binding, I assume?

          • 2. Re: Can't use Object in producer methods
            alberto Gori Novice

            Yes, id is not binding.
            I understand the point, but I think it would be useful to relax things (maybe using a special annotation?). I mean an Object instance could be an Integer.


            The use case is following:


            @Inject @HttpParam("id") String id;
            
            @Inject @HttpParam("id") Integer id2;
            
            @Inject @HttpParam("birthday") @DateConverter("dd/MM/yyyy") Date date;
            



            (DateConverter is not a qualifier)


            Where the producer method coerce to the rihght type examining the injection point.


            Wouldn't be nice? ;)


            • 3. Re: Can't use Object in producer methods
              alberto Gori Novice

              A further issue. I wrote three producer methods:


              @Produces @Param("") 
              public String getParamValue(FacesContext fc, InjectionPoint ip) {...}
              
              @Produces @Param("") 
              public Integer getIntegerParamValue(FacesContext fc, InjectionPoint ip) {...}
              
              @Produces @Param("") 
              public Object getWrappedParamValue(FacesContext fc, InjectionPoint ip) {...}
              
              



              And my client code is :


              public void action(@Observes ..... event, @Param("birthday") @ParamConverter("date") Object date) {
              ...
              }
              



              But I get this exception


              javax.enterprise.inject.AmbiguousResolutionException: Cannot resolve an ambiguous dependency between [org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getBigDecimalParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getIntegerParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getWrappedParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getLongParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getDoubleParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getShortParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint), org.jboss.weld.bean-web-module-ProducerMethod-agori.example.RestFacesBean.getFloatParamValue(javax.faces.context.FacesContext, javax.enterprise.inject.spi.InjectionPoint)]
              



              If Object is Object it should pick the Object producer. Why I get an ambiguous resolution?

              • 4. Re: Can't use Object in producer methods
                Gavin King Master

                Alberto Gori wrote on Dec 30, 2009 13:00:

                I understand the point, but I think it would be useful to relax things (maybe using a special annotation?). I mean an Object instance could be an Integer.

                ...

                Wouldn't be nice? ;)




                Nope, it would be completely non-typesafe.

                • 5. Re: Can't use Object in producer methods
                  Gavin King Master

                  Use:


                  @Produces @Param("") @Typed(String.class)
                  public String getParamValue(FacesContext fc, InjectionPoint ip) {...}
                  
                  @Produces @Param("") @Typed(Integer.class)
                  public Integer getIntegerParamValue(FacesContext fc, InjectionPoint ip) {...}
                  
                  @Produces @Param("") 
                  public Object getWrappedParamValue(FacesContext fc, InjectionPoint ip) {...}



                  To resolve the ambiguity.

                  • 6. Re: Can't use Object in producer methods
                    alberto Gori Novice

                    Gavin King wrote on Dec 30, 2009 16:18:



                    Alberto Gori wrote on Dec 30, 2009 13:00:

                    I understand the point, but I think it would be useful to relax things (maybe using a special annotation?). I mean an Object instance could be an Integer.

                    ...

                    Wouldn't be nice? ;)




                    Nope, it would be completely non-typesafe.


                    I agree with you in case of normal applications. But a special annotation for advanced use (for example framework based on CDI) wouldn't be so dangeroous because it's internal code.
                    If I write, as framework developer, an @HttpParam annotation and producers able to inject HTTP parameters to any type, this is not unsafe for final developers.


                    But maybe this exceed CDI purpose and overcomplicate its clean and powerful model.

                    • 7. Re: Can't use Object in producer methods
                      Gavin King Master

                      Hrrrrm, interestingly enough, with a minor spec change, there would be a way to solve this problem with just one producer method. The following producer would match injection points of any type:


                      @Produces @Param("") 
                      <T> T getParamValue(FacesContext fc, InjectionPoint ip) {
                        Class<T> type = (Class<T>) ip.getType();
                        String paramName = ip.getAnnotated().getAnnotation(Param.class).value();
                        //get the parameter value and convert it to the right type
                        ...
                      }



                      except for the fact that 2.2.1 says a type variable is not a legal bean type, and 3.3 says if a producer method return type is a type variable the container automatically detects the problem and treats it as a definition error. In fact this restriction is unnecessary, and I think I see now that it may actually be wrong.


                      Now, I still don't love this solution to your problem, since it is untypesafe: the container can't detect cases where you don't have an appropriate type converter, and so I still recommend the approach with multiple producer methods. (Or, with a portable extension that dynamically registers Bean objects based upon what type converters happen to be available.)


                      But I guess I'm unsatisfied that the spec outlaws something that is arguably useful. We could change this in the maintenance release, but I need to sleep on that. Without a better usecase, the case for the change is quite weak, but so is the case against the change.


                      • 8. Re: Can't use Object in producer methods
                        Gavin King Master

                        (We would also need to relax 5.2.2 and 3.4.)

                        • 9. Re: Can't use Object in producer methods
                          alberto Gori Novice

                          Gavin King wrote on Dec 30, 2009 16:18:



                          Alberto Gori wrote on Dec 30, 2009 13:00:

                          I understand the point, but I think it would be useful to relax things (maybe using a special annotation?). I mean an Object instance could be an Integer.

                          ...

                          Wouldn't be nice? ;)




                          Nope, it would be completely non-typesafe.


                          Finally, I think you are right. If developer needs to inject MyObject then he could just write its producer method.


                          //producer
                          @Produces @Param("") MyObject getUserById(InjectionPoint ip, ParameterHelper helper) {
                              return myObjectService.getById(helper.initialize(ip).getValue());
                          }
                          
                          
                          
                          //client 
                          public void action(@Observes ...event, @HttpParam("myId") MyObject obj) {
                          }
                          
                          



                          • 10. Re: Can't use Object in producer methods
                            Gavin King Master

                            Yes, after sleeping on it, I concluded that I would need to see a much better usecase before making this change. I'm leaving it on the wishlist, just in case a better usecase comes up, but I think your usecase is much better handled using multiple producers. I certainly don't plan to make this change in the MR.

                            • 11. Re: Can't use Object in producer methods
                              alberto Gori Novice

                              Finally I solved with a portable extension: inspecting the observer methods I detect if an injectable parameter has custom type (for example User.class).


                              If so I add a Bean with type User at AfterBeanDiscovery event, with @HttpParam qualifier, and the same algorithm used in producer method inside Bean.create method.


                              At the moment this seems to work!