5 Replies Latest reply on Jan 31, 2008 8:05 AM by Thomas März

    FlushModeType.MANUAL in conversation

    Kai Muller Newbie

      Hi,

      I cannot control the synchronization from a persistent object to the database. The more I read and the more I try out the more I get confused. I hope that somebody can give me a helping hint.

      For testing I generated a simple ear project with seam-gen (Seam 2.0.0.GA) and added an entity class and a stateful session bean.

      The use case:
      A logged in user wants to change his email address (unique field in table).
      A server-side validation with a database selection is effected and fails if the new email address exists in the database.

      What happens:
      If I enter a new email and press Save, a synchronization (flush) takes place just before the selection of the existing email addresses has been started. In FlushMode.AUTO this is normal, but not in FlushMode.MANUAL. The effect is that EVERY new email is known to the system and the validation does not work as it should.


      The entity class MyUser is:

      package some.model;
      
      // imports...
      
      @Entity
      @Table
      @Name("user")
      public class MyUser implements Serializable {
      
       @Id @GeneratedValue
       private Long id = null;
      
       // username is the business key
       @Column(name="username", nullable = false, unique = true)
       private String username;
      
       @Email
       @Column(name="email", nullable = false, unique = true)
       private String email;
      
       private String firstname;
       private String lastname;
      
       public MyUser() {
       }
      
       //... getter and setter ...
      
      }
      

      The stateful session bean implementation is:
      package some.controller;
      
      // imports ...
      
      @Name( "userAction" )
      @Scope( ScopeType.CONVERSATION )
      @Stateful
      public class UserActionBean implements Serializable, UserAction {
       private static final long serialVersionUID = 1L;
      
       @PersistenceContext(type=PersistenceContextType.EXTENDED)
       private EntityManager entityManager;
      
       @In(create=true)
       private transient FacesMessages facesMessages;
      
       @In( value = "user", required = false )
       @Out( value = "user", required = false )
       private MyUser selectedUser;
      
       @In
       private Identity identity;
      
       private String backupEmail;
      
       @Begin(flushMode=FlushModeType.MANUAL, join=true)
       public String selectLoggedInUser() {
       System.out.println( "Selected logged in User: " + identity.getUsername() );
       List<MyUser> existing = (List<MyUser>) entityManager
       .createQuery("from MyUser u where u.username = :uname)")
       .setParameter("uname", identity.getUsername())
       .getResultList();
       if (existing.size() == 1) {
       selectedUser = (MyUser) existing.get(0);
       backupEmail = selectedUser.getEmail();
       System.out.println( "Redirecting to /editLoggedInUser.xhtml" );
       return "/editLoggedInUser.xhtml";
       } else {
       return "/home.xhtml";
       }
       }
      
       @End
       public String saveLoggedInUser() {
      
       // Try if email exists
       // There should NOT be a flush before the query is effected
       // But there IS a flush before the query is effected
       if (emailExists()) {
       selectedUser.setEmail(backupEmail);
       return null;
       }
      
       // Normal flush (success case)
       entityManager.flush();
      
       FacesMessages.instance().add( "#{user.username} has been saved." );
       return "/home.xhtml";
       }
      
       private boolean emailExists() {
       List<String> existing = (List<String>)
       entityManager.createQuery("from MyUser u where u.email = :email)")
       .setParameter("email", selectedUser.getEmail())
       .getResultList();
       if(existing.size() != 0) {
       facesMessages.add("#{messages['NewEmailExists']}" + selectedUser.getEmail());
       return true;
       }
       return false;
       }
      
       @Remove @Destroy
       public void destroy() {}
      }
      

      Extension of "menu.xhtml" to start the conversation:
      ...
       <rich:toolBarGroup rendered="#{identity.loggedIn}">
       <s:link action="#{userAction.selectLoggedInUser}" value="#{messages['MyUser']}"/>
       </rich:toolBarGroup>
      ...
      

      The form "editLoggedInUser.xhtml" is:
      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <ui:composition xmlns="http://www.w3.org/1999/xhtml"
       xmlns:s="http://jboss.com/products/seam/taglib"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:j4j="http://javascript4jsf.dev.java.net/"
       xmlns:rich="http://richfaces.ajax4jsf.org/rich"
       template="layout/template.xhtml">
      
      <ui:define name="body">
      
       <h:messages styleClass="message" globalOnly="true"/>
      
       <h:form id="editLoggedInUser">
      
       <rich:panel>
       <f:facet name="header">#{messages['MyUser']}</f:facet>
      
       <div class="dialog">
       <s:validateAll>
       <h:panelGrid columns="2" rowClasses="prop" columnClasses="name,value">
      
       <h:outputLabel>#{messages['Username']}</h:outputLabel>
       <h:outputText value="#{user.username}"/>
      
       <h:outputLabel for="firstname">#{messages['FirstName']}</h:outputLabel>
       <s:decorate>
       <h:inputText id="firstname" required="true"
       value="#{user.firstname}"/>
       </s:decorate>
      
       <h:outputLabel for="lastname">#{messages['LastName']}</h:outputLabel>
       <s:decorate>
       <h:inputText id="lastname" required="true"
       value="#{user.lastname}"/>
       </s:decorate>
      
       <h:outputLabel for="email">#{messages['Email']}</h:outputLabel>
       <s:decorate>
       <h:inputText id="email" required="true"
       value="#{user.email}"/>
       </s:decorate>
      
       </h:panelGrid>
       </s:validateAll>
       </div>
      
       </rich:panel>
      
       <div class="actionButtons">
       <h:commandButton id="saveLoggedInUser" value="#{messages['Save']}"
       action="#{userAction.saveLoggedInUser}"/>
       </div>
      
       </h:form>
      
       </ui:define>
      </ui:composition>
      

      The persistence.xml shows that Hibernate is the persistence provider, so MANUAL FlushMode should work:
      ...
       <persistence-unit name="aValidationTest">
       <provider>org.hibernate.ejb.HibernatePersistence</provider>
       <jta-data-source>java:/aValidationTestDatasource</jta-data-source>
       <properties>
       <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
       <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
       <property name="hibernate.show_sql" value="true"/>
       <property name="hibernate.format_sql" value="true"/>
       <property name="jboss.entity.manager.factory.jndi.name" value="java:/aValidationTestEntityManagerFactory"/>
       </properties>
       </persistence-unit>
      ...
      

      components.xml:
      ...
       <persistence:managed-persistence-context name="entityManager"
       auto-create="true"
       persistence-unit-jndi-name="java:/aValidationTestEntityManagerFactory"/>
      ...
      

      import-dev.sql:
      insert into MyUser (ID, USERNAME, FIRSTNAME, LASTNAME, EMAIL) values (1, 'admin', 'Frederic', 'Chopin', 'one@email.com');
      insert into MyUser (ID, USERNAME, FIRSTNAME, LASTNAME, EMAIL) values (2, 'user', 'Baby', 'Love', 'two@email.com');
      

      What's wrong in my thinking?

      Thank you very much for all answers.

        • 1. Re: FlushModeType.MANUAL in conversation
          Kai Muller Newbie

          The question behind my problem is:

          How do other developers validate certain user input against the database within a long-running conversation? Does nobody else have problems with transaction handling?

          • 2. Re: FlushModeType.MANUAL in conversation
            Kai Muller Newbie

            I solved it myself by manually switching the entityManager.setFlushMode just before the query invocation:

            ...
             private boolean emailExists() {
             entityManager.setFlushMode(javax.persistence.FlushModeType.COMMIT);
             List<String> existing = (List<String>)
             entityManager.createQuery("from MyUser u where u.email = :email)")
             .setParameter("email", selectedUser.getEmail())
             .getResultList();
             if(existing.size() != 0) {
             facesMessages.add("#{messages['NewEmailExists']}" + selectedUser.getEmail());
             return true;
             }
             return false;
             }
            ...
            


            Switching to COMMIT for an EntityManager disables automatic synchronization before queries.

            Please note that it is the javax.persistence.FlushModeType class, not from Seam.

            This is not the most elegant way but it fixes my problem now. I would highly appreciate the opinion of an expert...

            Is the dysfunction of org.jboss.seam.annotations.FlushModeType.MANUAL a bug in Seam?

            • 3. Re: FlushModeType.MANUAL in conversation
              Pete Muir Master

              I don't really understand what you are trying to say.

              • 4. Re: FlushModeType.MANUAL in conversation
                Juan Giovanolli Newbie

                i'm having the same problem than kai222.
                When i try to load a user, i need to validate that don't exists añother user with the same name, bue when i execute the query to validate that, before to do the SELECT, it make a flush persisting the user without validate the name.
                In pseudocode

                 persist() {
                 validate() //here i make a query to validate if exists another user with
                 // the same name, but before execution of the query, make a
                 //flush and persist the object without validate
                 if( isValid )
                 persist()
                 else
                 errorMessage()
                }
                


                thanks in advance

                • 5. Re: FlushModeType.MANUAL in conversation
                  Thomas März Newbie

                  See the posted code below to see how to validate the input.

                  /**
                   * @author Thomas Maerz (thomas.maerz@primedo.com)
                   */
                  @Name("userHome")
                  public class UserHome extends EntityHome<User> {
                  
                   private static final long serialVersionUID = 1L;
                  
                   @In
                   UserDao userDao;
                  
                   public void validateUsername(FacesContext context, UIComponent component, Object value) throws ValidatorException {
                   User user = userDao.getByUsername((String) value);
                   if (user == null || user.getId().equals(getInstance().getId())) {
                   return;
                   }
                   throw new ValidatorException(new FacesMessage(""));
                   }
                  }
                  


                  edit.html
                   <s:decorate template="/templates/edit.html">
                   <ui:define name="label">Username</ui:define>
                   <h:inputText value="#{userHome.instance.username}" validator="#{userHome.validateUsername}" required="true"/>
                   </s:decorate>