3 Replies Latest reply on Oct 28, 2005 7:47 PM by jsb

    JSF Conversion of PAR Entities problem after move to AS 4.0.

    jsb

      Greetings JBoss EJB3 community,

      Background...

      I'm getting my feet wet in EJB3 building a small app as a learning exercise. This stuff is really cool! Bravo to JBoss for pushing this stuff into the standard and getting it all working.

      The app is a simple movie catalog to help my family keep track of our DVDs. Really, just an excuse to experiment with EJB3. It is also my first JSF app. I'm developing in JBoss-IDE 1.5M3 and using the DVDStore and various Trails as guiding examples.

      The scenario...

      The model is made up of a handful of entities: Movie, Actor, Director, Genre, and User. The Movie entity has a number of fields including ManyToMany Lists of Actors and Genres and a ManyToOne Director and a ManyToOne User.

      Example from Movie entity:

      @Entity
      @Table(name = "MOVIES")
      public class Movie {
       private long id;
       private String title;
      ...other member fields...
       private List<Genre> genres;
      
       @Id(generate = GeneratorType.AUTO)
       public long getId() {
       return id;
       }
       public void setId(long id) {
       this.id = id;
       }
       public String getTitle() {
       return title;
       }
       public void setTitle(String title) {
       this.title = title;
       }
      
      ...other getters and setters...
      
       @ManyToMany(fetch = FetchType.EAGER)
       @JoinTable(joinColumns = { @JoinColumn(name = "movieid") },
       inverseJoinColumns = { @JoinColumn(name = "genreid") })
       @OrderBy("name")
       public List<Genre> getGenres() {
       return genres;
       }
       public void setGenres(List<Genre> genres) {
       this.genres = genres;
       }
      }


      A user can edit his/her own movies and is presented with a JSF constructed form to do so. The JSF backing bean gets the movie entity via a stateless session bean. The backing bean also constructs SelectItem Lists for drop-down lists and multi-select boxes populated with data it also gets via the session bean.

      Example from movieForm.jsp:
      <div><f:view>
       <h:form id="movie">
       <h:inputHidden value="#{movie.movie.id}">
       <f:convertNumber />
       </h:inputHidden>
      
       <h:panelGrid columns="2">
       <h:outputText value="Title" />
       <h:panelGroup>
       <h:inputText id="title" required="true"
       value="#{movie.movie.title}" />
       <f:verbatim>*</f:verbatim>
       <h:message for="title" styleClass="error" />
       </h:panelGroup>
      
       <h:outputText value="Genre" />
       <h:selectManyListbox value="#{movie.movie.genres}">
       <f:selectItems value="#{movie.genresList}" />
       <f:converter converterId="net.beames.movies.par.Genre" />
       </h:selectManyListbox>
      
       ...other form fields...
      
       <h:commandButton value="Save" action="#{movie.save}" id="save"
       styleClass="button" />
       <h:commandButton value="Cancel" action="cancel" immediate="true"
       id="cancel" styleClass="button" />
       </h:form>
      </f:view></div>

      Example from MovieBean:
      public class MovieBean implements Serializable {
       private static final long serialVersionUID = 1L;
       private Movie movie = new Movie();
       private String id;
       private MovieSwap movieSwap;
      
      ...other stuff...
      
       public Movie getMovie() {
       if (id != null) {
       try {
       movie = getMovieSwap().getMovie(Long.valueOf(id));
       } catch (Exception e) {
       e.printStackTrace();
       }
       }
       return movie;
       }
      
       public String save() {
       getMovieSwap().saveMovie(getMovie());
       return "success";
       }
      
      ...other stuff...
      
       public List<SelectItem> getGenresList() {
       List<SelectItem> list = new LinkedList<SelectItem>();
       List<Genre> genres = getMovieSwap().getGenres();
       for (Genre genre : genres) {
       list.add(new SelectItem(genre, genre.getName(), String.valueOf(genre.getId())));
       }
       return list;
       }
      }

      Example from MovieSwapBean:
      @Stateless
      public class MovieSwapBean implements MovieSwap {
       @PersistenceContext(unitName = "movieswap")
       EntityManager em;
      
      ...other stuff...
      
       public Movie getMovie(long id) {
       return (Movie) em.createQuery("from Movie m "
       + "left join fetch m.director "
       + "left join fetch m.actors "
       + "where m.id = :id")
       .setParameter("id", id)
       .getSingleResult();
       }
      
       public void saveMovie(Movie movie) {
       movie = em.merge(movie);
       em.flush();
       }
      
      
       @SuppressWarnings("unchecked")
       public List<Genre> getGenres() {
       return em.createQuery("from Genre g")
       .getResultList();
       }
      
       public Genre getGenre(int id) {
       return (Genre) em.createQuery("from Genre g where g.id = :id")
       .setParameter("id", id)
       .getSingleResult();
       }
      }


      A JSF converter is used to translate between the form and the model objects.

      Example from GenreConverter:
      public class GenreConverter implements Converter {
       public Object getAsObject(FacesContext ctx, UIComponent component, String value)
       throws ConverterException {
       FacesMessage error = null;
       Genre genre = new Genre();
       MovieSwap movieSwap = Utils.getMovieSwap();
       if (value == null || value.length() == 0) {
       ...
       } else {
       try {
       genre = movieSwap.getGenre(Integer.valueOf(value));
       } catch (Exception e) {
       e.printStackTrace();
       ...
       }
       }
       if (error != null) {
       throw new ConverterException(error);
       }
       return genre;
       }
      
       public String getAsString(FacesContext ctx, UIComponent component, Object obj)
       throws ConverterException {
       Genre genre = (Genre) obj;
      
       return String.valueOf(genre.getId());
       }
      }


      The problem...

      I started developing with JBoss 4.0.3RC2 (used the installer to set up a EJB3 server). And all was going except that my Enums would cause ClassCastExceptions on CGLIB property setting after an ear redeployment (not that big of a deal, restarting the app server always fixed this). I noticed that JBoss 4.0.3 final fixed EJBTHREE-236 and figured it might be good to try.

      Just moved to JBoss AS 4.0.3 + EJB3-RC3 (downloaded each separately and followed provide EJB3RC3 install.html instructions). My ear deploys cleanly (and the ClassCastException is on hot redeploy is gone). But now I'm encountering a new problem with the JSF conversion of the selectable member entities of a Movie.

      Details...

      When my movieForm save button is clicked (calling the save() method of the backing bean which in turn calls saveMovie in the session bean that uses the entity manager to merge and flush the Movie), before anything else happens I get a ClassCastException from the getAsString method of the converter:

      11:23:52,356 ERROR [[jsp]] Servlet.service() for servlet jsp threw exception
      java.lang.ClassCastException: java.lang.String
       at net.beames.movies.web.GenreConverter.getAsString(GenreConverter.java:39)


      It appears that in AS 4.0.3 + EJB3-RC3 the getAsString method of the converters gets called during the save operation and is passed in the post string value rather than the entity object. In AS 4.0.3RC2, the getAsString method is only called when building the form for display, not during post data processing of the form (where the getAsObject method of the converters are called to put things back as the model expects them).

      I know this might sound like a JSF problem, but I'm using the same JSF implementation (myfaces 1.1.0) regardless of which server I deploy too. I suspect that I am doing something wrong obvious to the experienced and experts.

      Any suggestions or ideas about what my misuse might be?

      Thanks,
      Jonn

        • 1. Re: JSF Conversion of PAR Entities problem after move to AS
          jsb

          Oops, my bad. This is definitely a JSF issue in myfaces-1.1.0:
          http://issues.apache.org/jira/browse/MYFACES-624

          I was confused because the libraries being used where provided by the app server in jbossweb-tomcat55.sar/jsf-libs not mine in WEB-INF/lib. AS 4.0.3RC2 had an older version of these jars. Reverting to the jsf-libs from 4.0.3RC2 corrected my problem in AS 4.0.3+EJB3RC3.

          Sorry for the noise,
          Jonn

          • 2. Re: JSF Conversion of PAR Entities problem after move to AS
            jsb

            The plot thickens...

            The MyFaces bug I thought was related, is not. It being fixed in source control, I decided to try out a current nightly build of MyFaces only to find that I still have the same problem. I opened a new bug:
            http://issues.apache.org/jira/browse/MYFACES-731

            The MyFaces developers weren't seeing the same problem so I whipped up a simple JSF-only app to demonstrate the problem, but could not replicate it. I dug around a little more and I believe that this is related to the dynamic changing of persistent model objects - the converters that give me trouble are dealing with lists of objects where a subset are mapped in another object (OneToMany and ManyToMany relationships).

            On form post, the converter seems to see the selected items twice: once as the request param string id it correctly converts to a model object, then again as the relational mapping id it mistakenly tries to convert to a string with the getAsString method.

            Again, my code is working as expected with the MyFaces jars that shipped with AS 4.0.3rc2, but not with MyFaces <= 1.1.0. I'm still trying to implement a simple test case for the MyFaces developers, but I'm also aware that, being new to this, user error on my part is likely.

            Anyone else having any difficulty using MyFaces 1.1.0 converters with persistent related objects?

            Much appreciation for any input that can be offered,
            Jonn

            • 3. Re: JSF Conversion of PAR Entities problem after move to AS
              jsb

              I guess I'm the only one having the problem... not a good sign for me :)

              I couldn't replicate the problem with mock objects in a standalone tomcat container, but I can consistently replicate it in JBoss 4.0.3+EJB3rc3. I've posted an example (ear plus source) to the bug:
              http://issues.apache.org/jira/browse/MYFACES-731

              Not too sure how much the myfaces team will care about a problem that only occurs inside a release candidate implementation of a changing spec, so if you care let them know.

              Thanks,
              Jonn