10 Replies Latest reply on Dec 12, 2008 6:56 AM by Matt Drees

    Seam won't set POJO values for abstract base classes

    Brian Cowdery Newbie

      We're currently writing a java frontend for our reporting system using Seam, and to accommodate the differentiating parameter types that each report expects, we built an abstract super class and left it up to the implementation classes to specify the type of the expected values. This works great as it allows us to specify custom validation etc, but Seam is having issues setting values on anything OTHER than a String type value.



      Below is a simplified example of our source code, I've removed all the cruft for you lazy readers


      Report contains many report parameters, using a polymorphic association


      @Entity
      @Table(name = "ENTERPRISE_REPORTS")
      public class EnterpriseReport implements Serializable {
      
          @OneToMany(cascade = CascadeType.ALL, mappedBy = "report", fetch = FetchType.EAGER)    
          private List<EnterpriseReportParam> parameters;
      
          // getters & setters ...
      }
      




      Base report param class, contains common data such as name, description etc.


      @Entity
      @Table(name = "ENTERPRISE_REPORT_PARAMS")
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
      @DiscriminatorColumn(name = "REPORT_PARAM_TYPE", discriminatorType = DiscriminatorType.STRING)
      public abstract class EnterpriseReportParam<T> implements Serializable {
      
          @Transient
          public abstract T getValue();
          public abstract void setValue(T value);   
      
          @ManyToOne
          @JoinColumns({
              @JoinColumn(name = "REPORT_ID", referencedColumnName = "REPORT_ID", nullable = false, updatable = false)
          })
          private EnterpriseReport report;
      }
      



      And the concrete implementation


      @Entity
      @DiscriminatorValue("Period")
      public class PeriodEnterpriseReportParam extends EnterpriseReportParam<Integer> {
      
          @Column(name = "PERIOD_VALUE", length = 6)    
          private Integer value;
      
          public Integer getValue() {
              return value;
          }
      
          public void setValue(Integer value) {
              this.value = value;
          }
      }
      



      Our input.xhtml page iterates through each EnterpriseReportParam, printing out an input field for each one. We bind the h:inputText value to the parameters.getValue() method (which will be typed for the Concrete implementation).


      <ui:repeat value="#{report.parameters}" var="param" >
          <h:inputText value="#{param.value}"/>
      </ui:repeat>
      



      But when we type in the value and submit the form, an ambiguous error is displayed - yet nothing shows in the logs.


      /reporting/parametersInput.xhtml @20,106 value="#{param.value}": Error writing 'value' on type com.companyname.reporting.entities.parameters.PeriodEnterpriseReportParam



      We've tried defining converters by name, and using @Converter(forClass = Integer.class), but nothing seems to help. Anyone have any ideas?


        • 1. Re: Seam won't set POJO values for abstract base classes
          Brian Cowdery Newbie

          Sorry, forgot to mention that we're using Seam 2.1.0 GA

          • 3. Re: Seam won't set POJO values for abstract base classes
            Brian Cowdery Newbie

            Sorry, but there's no stack trace to be had.


            The only error to be seen is the FacesMessage set and displayed on the page (shown in the above post).


            I even tried bumping the logging level to DEBUG for both org.jboss.seam and org.ajax4jsf to no avail. Thats part of what makes this so difficult to figure out.


            On a side node, i recently tried upgrading to Seam 2.1.1.CR2 and got the same issue.


            Also, if i change the type to String (extends EnterpriseReportParam<String>) everything works fine. But the issue at hand (and the whole reason we're using subclassed parameters) is that we need to be able to control the data types (dates must be treated as dates, etc) but maybe it gives someone an idea as to where the issue is?


            Thanks,
            -Brian


            • 4. Re: Seam won't set POJO values for abstract base classes
              Matt Drees Master

              For what it's worth, I think this isn't as much an issue with Seam as it is with either Hibernate or Facelets.  (Though, maybe it's Jboss EL).


              Here's my guess as to what's going on:


              I see that you've set the report-to-param relationship to eager, but I wonder if for some reason you're still getting proxies.  If you are, then it's possible facelets/jboss-el sees getValue()/setValue() as properties of type Object, and so JSF doesn't to convert the string value from the form into an Integer.  The proxy might accept a String, but when passing it on to the backing PeriodEnterpriseReportParam, it throws an exception.


              One possible workaround is to create a custom JSF converter that, depending on what type of EnterpriseReportParam is currently being rendered, converts to the appropriate type.

              • 5. Re: Seam won't set POJO values for abstract base classes
                Brian Cowdery Newbie

                I doubt its Hibernate, as at this point in the code were dealing with unpersisted entities that are generated via another mechanism. I verified this by commenting out all the JPA/Hibernate annotations and got the same results as before (sorry, it may have been prudent of me to omit the annotations in the first place).


                I did try creating a simple converter, which DOES WORK when referenced by it's component name - however it only applies to a single EnterpriseReportParam type, and note that a converter where @Converter(forClass = Integer.class) did not work...


                @Name("integerConverter")
                @org.jboss.seam.annotations.faces.Converter
                @BypassInterceptors
                public class UnsignedIntegerConverter implements Converter {
                    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String string) {
                        return (string != null ? Integer.parseInt(string) : null);
                    }
                
                    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object object) {
                        return (object != null ? object.toString() : "");
                    }
                }
                
                



                and using it with my h:inputText tag in the input.xhtml file


                <ui:repeat value="#{report.parameters}" var="param" >
                    <h:inputText value="#{param.value}"
                                 converter="#{integerConverter}"/>
                </ui:repeat>
                



                But now the question is how do i dynamically specify a converter based on the type of EnterpriseReportParam? And in any case, shouldn't Seam be trying to detect the target class from the binding EL and using it's own built in converters?


                • 6. Re: Seam won't set POJO values for abstract base classes
                  Stuart Douglas Master

                  When you are testing the int parameters do you only have int parameters in the list, or is there string ones as well? If there are other types can you try it with only integers in the list?



                  I do something similar in my reports implementation however, I have a 'renderer' property that is overriden by the subclasses, the type of input that is rendered is then controlled using the rendered attribute, e.g.:



                  <ui:repeat value="#{report.parameters}" var="param" >
                      <h:inputText value="#{param.value}" rendered="#{param.renderer == 'string'} />
                      <rich:calendar value="#{param.value}" rendered="#{param.renderer == 'date'} />
                  </ui:repeat>



                  The advantage of this is that you are not limited to text boxes.

                  • 7. Re: Seam won't set POJO values for abstract base classes
                    Matt Drees Master

                    Brian Cowdery wrote on Dec 11, 2008 00:00:


                    I doubt its Hibernate, as at this point in the code were dealing with unpersisted entities that are generated via another mechanism. I verified this by commenting out all the JPA/Hibernate annotations and got the same results as before (sorry, it may have been prudent of me to omit the annotations in the first place).


                    Ah, ok.  So yeah, not hibernate.


                    I imagine the problem still has to do with the EL expression not knowing that it's an Integer property (thus preventing for-class converters), though from what I can see, it should.  If you care to dig deeper, you can get the sourcecode for jboss-el and do some debugging.  I'd be interested to know the results.  :-)




                    <ui:repeat value="#{report.parameters}" var="param" >
                        <h:inputText value="#{param.value}"
                                     converter="#{integerConverter}"/>
                    </ui:repeat>
                    



                    But now the question is how do i dynamically specify a converter based on the type of EnterpriseReportParam?


                    You could create some kind of converter factory. Something that would let you do


                        <h:inputText value="#{param.value}"
                                     converter="#{enterpriseReportParamConverters.converterFor(param)}"/>
                    


                    Not as elegant as getting the built-in converters to work, but it'd work.




                    And in any case, shouldn't Seam be trying to detect the target class from the binding EL and using it's own built in converters?

                    (just a minor correction; it's JSF that tries to detect the target class and use an appropriate converter.  Not Seam.)

                    • 8. Re: Seam won't set POJO values for abstract base classes
                      Brian Cowdery Newbie

                      Stuart Douglas wrote on Dec 11, 2008 03:23:

                      I do something similar in my reports implementation however, I have a 'renderer' property that is overriden by the subclasses, the type of input that is rendered is then controlled using the rendered attribute, e.g.:


                      Actually we do that as well, i have an HTMLElementType enum which similarly defines the type of input that is rendered. In some cases its tied to a data type - so a HTMLElementType.DATE is always used with a DateEnterpriseReportParam. We're still in the early stages and that idea will probably get some tuning, but the capability is there. I stripped it out of the above examples for simplicity.


                      As for Matt's suggestion of creating a converter factory, I'll give it a shot and let you all know how it goes.


                      I was also thinking about a creating a generic converter that could do some discovery based on the UIComponent's value expression (see Generic Converter for Domain Model in JSF) and use the appropriate converter. This may not work, as if it is a JSF or EL bug then its very likely that the binding will only resolve to Object instead of something useful.


                      If that turns out to be the case, I'll try and dig into the JbossEL source code if i get some time later :P


                      Thanks

                      • 9. Re: Seam won't set POJO values for abstract base classes
                        Brian Cowdery Newbie

                        I've created a Converter factory that will pull the declared field type and attempt to instantiate a Converter based on the retrieved class. The only hiccup here is that it appears you cannot define <h:inputText converter="#{null}"/> (or anything that evaluates to null) so the factory must at least return a generic Object <-> String converter.


                        The factory is pretty much doing what you would expect JSF (thanks for the correction) to do while looking up an appropriate converter. It determines the target class, and creates a new Converter instance defined for that type. Since Seam will setup a JSF converter for any @Converter(forClass = Foo.class), all thats required to wire up a converter to an EnterpriseReportParam.value is to annotate a Converter with the value class in question.


                        I've tested this out and it actually seems to work with the default javax.faces.convert.* Converters provided by JSF, so now I only need to worry about the edge cases...


                        Factory


                        @Name("reportParamConverterFactory")
                        public class EnterpriseReportParamConverterFactory {
                            public static Converter getConverter(EnterpriseReportParam param) throws Exception {
                                Class targetClass = param.getClass().getDeclaredField("value").getType();
                        
                                FacesContext context = FacesContext.getCurrentInstance();
                                Converter converter = context.getApplication().createConverter(targetClass);
                        
                                return (converter != null ? converter : new DefaultStringConverter());
                            }
                        }



                        Default converter


                        @Name("stringConverter")
                        @BypassInterceptors
                        @org.jboss.seam.annotations.faces.Converter(forClass = String.class)
                        public class DefaultStringConverter implements Converter {
                            public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String string) {
                                return string;
                            }
                        
                            public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object object) {
                                return (object == null ? null : object.toString());
                            }
                        
                        }



                        input.xhtml


                        <h:inputText value="#{param.value}"
                                     converter="#{reportParamConverterFactory.getConverter(param)}"/>




                        While this is a little ugly, and using reflection to lookup the declared EnterpriseReportParam.value field type can be slow - I don't have to worry about wiring up a converter for every param, I can make use of the pre-defined Converters and when I finally do nail down the root cause, I should just be able to remove the factory.


                        Thanks all.

                        • 10. Re: Seam won't set POJO values for abstract base classes
                          Matt Drees Master

                          Cool; looks like a good interim solution.