0 Replies Latest reply on Jan 19, 2009 9:35 AM by Chris Moerz

    selectMany with selectItems has no items preselected

    Chris Moerz Newbie

      I've got a very strange problem. I'm using a selectManyListBox to select items for a ManyToMany relation. What I've got working so far is: the items in the list are rendered perfectly; I can select Items and the selection is saved in the backing bean.


      However, as soon as I enter the edit screen and there are already items in the backing bean, there are no items preselected in the list box. Let me rephrase that since it might sound a little confusing: Usually I'd expect that items that are already in the variable defined by the values attribute to be attributed with a selected1. This should make them selected in the UI and reflect that they already selected.


      Let me quickly give you an overview of my code.


      My entity looks like this:


      package org.nycentral.hermes.entity;
      
      import java.io.Serializable;
      import java.util.List;
      
      import javax.persistence.Entity;
      import javax.persistence.GeneratedValue;
      import javax.persistence.Id;
      import javax.persistence.JoinColumn;
      import javax.persistence.JoinTable;
      import javax.persistence.ManyToMany;
      import javax.persistence.Table;
      import javax.persistence.Transient;
      
      import org.jboss.seam.annotations.security.management.RoleConditional;
      import org.jboss.seam.annotations.security.management.RoleGroups;
      import org.jboss.seam.annotations.security.management.RoleName;
      
      /**
       * 
       * @author Chris Moerz
       * Copyright 2008 by Christian Moerz [chris@ny-central.org]
       */
      @Entity
      @Table(name="Roles")
      public class Role implements Serializable {
           private static final long serialVersionUID = 1470773454478964267L;
           private Long          id;
           private String          name;
           private List<Role>     groups;
           private boolean          conditional;
           /**
            * Decides if this role is a system-defined role for permission assignment (e.g. Admins)
            */
           private boolean          systemRole;
           
           @Id
           @GeneratedValue
           public Long getId() {
                return id;
           }
           public void setId(Long id) {
                this.id = id;
           }
           
           @RoleName
           public String getName() {
                return name;
           }
           public void setName(String name) {
                this.name = name;
           }
           
           @RoleGroups
           @ManyToMany
           @JoinTable(                 name="RoleGroups",
                             joinColumns=@JoinColumn(name="RoleId"),
                     inverseJoinColumns=@JoinColumn(name="MemberOf")
                )
           public List<Role> getGroups() {
                return groups;
           }
           public void setGroups(List<Role> groups) {
                this.groups = groups;
           }
      
           @RoleConditional
           public boolean isConditional() {
                return conditional;
           }
           public void setConditional(boolean conditional) {
                this.conditional = conditional;
           }
      
           public boolean isSystemRole() {
                return systemRole;
           }
           public void setSystemRole(boolean systemRole) {
                this.systemRole = systemRole;
           }
           
           @Transient
           @Override
           public String toString() {
                return this.name;
           }
           
           @Transient
           @Override
           public int hashCode() {
                // FIXME non-optimal hash-code
                return (this.id+this.name).hashCode();
           }
           
           @Transient
           @Override
           public boolean equals(Object o) {
                Role     r;
                
                if ( !( o instanceof Role )) {
                     return false;
                }
                
                r = (Role) o;
                if ( r.getId() == this.getId() ) {
                     return true;
                }
                if ( r.getId() != null ) {
                     if ( this.getId() != null ) {
                          return ( r.getId().longValue() == this.getId().longValue() );
                     }
                } else {
                     if ( this.getId() == null ) {
                          if ( r.getName() != null ) {
                               if ( this.getName() != null ) {
                                    return r.getName().equals( this.getName() );
                               }
                          }
                     }
                }
                return false;
                
           }
      }
      



      We're talking about the Role.groups property, which is a ManyToMany relation onto itself. I'm not sure, but may this is causing the problems?? Anyways, I've overriden the hashCode and equals function() to no avail.


      My backing bean/component:


      package org.nycentral.hermes.session;
      
      import java.util.List;
      
      import javax.ejb.Remove;
      import javax.ejb.Stateful;
      import javax.persistence.EntityManager;
      import javax.persistence.Query;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Begin;
      import org.jboss.seam.annotations.Destroy;
      import org.jboss.seam.annotations.End;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.core.Events;
      import org.jboss.seam.log.Log;
      import org.nycentral.hermes.entity.Role;
      
      @Name("roleEdit")
      @Stateful
      @Scope(ScopeType.CONVERSATION)
      public class RoleEditAction implements RoleEdit {
           @In
           private EntityManager     entityManager;
           
           @In(required=true) @Out
           private Role               role;
           
           private List<Role>          roles;
           
           @In
           private Events               events;
           
           @Logger
           private Log                    log;
           
           @SuppressWarnings("unchecked")
           @Begin
           public void editRole( Role role ) {
                Query q = entityManager.createQuery( "from Role where id!=:roleID" );
                q.setParameter( "roleID", role.getId() );
                this.setRoles( q.getResultList() );
                
                if ( this.role != role ) {
                     this.role = role;
                }
                
                /*if ( this.role.getGroups() != null ) {
                     this.groups = new Vector<String>();
                     if ( this.role.getGroups().size() > 0 ) {
                          for( Role r : this.role.getGroups() ) {
                               this.groups.add( r.getName() );
                          }
                     }
                }*/
           }
           
           @End
           public void saveRole() {
                /*if ( this.groups != null ) {
                     List<String>     removal = new Vector<String>();
                     // first remove from all groups
                     // then put into selected groups
                     removal = IdentityManager.instance().getRoleGroups( this.getRole().getName() );
                     for ( String s : this.getGroups() ) {
                          removal.remove( s );
                     }
                     for ( String s : removal ) {
                          IdentityManager.instance().removeRoleFromGroup( this.role.getName(), s );
                     }
                     removal = IdentityManager.instance().getRoleGroups( this.getRole().getName() );
                     for ( String s : this.getGroups() ) {
                          if ( ! removal.contains( s )) {
                               IdentityManager.instance().addRoleToGroup( this.getRole().getName(), s );
                          }
                     }
                }*/
                
                entityManager.merge( role );
                events.raiseEvent( "org.nycentral.events.roleChanged" );
                if ( log.isDebugEnabled() ) {
                     log.debug( "Role data changed: {0}", role );
                }
           }
           
           @Destroy
           @Remove
           @End
           public void cancel() {
                
           }
      
           public Role getRole() {
                return role;
           }
      
           public void setRole(Role role) {
                this.role = role;
           }
      
           public List<Role> getRoles() {
                return roles;
           }
      
           public void setRoles(List<Role> roles) {
                this.roles = roles;
           }
      
      }
      



      with the interface definition


      package org.nycentral.hermes.session;
      
      import java.util.List;
      
      import javax.ejb.Remove;
      import javax.ejb.Stateful;
      import javax.persistence.EntityManager;
      import javax.persistence.Query;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Begin;
      import org.jboss.seam.annotations.Destroy;
      import org.jboss.seam.annotations.End;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.core.Events;
      import org.jboss.seam.log.Log;
      import org.nycentral.hermes.entity.Role;
      
      @Name("roleEdit")
      @Stateful
      @Scope(ScopeType.CONVERSATION)
      public class RoleEditAction implements RoleEdit {
           @In
           private EntityManager     entityManager;
           
           @In(required=true) @Out
           private Role               role;
           
           private List<Role>          roles;
           
           @In
           private Events               events;
           
           @Logger
           private Log                    log;
           
           @SuppressWarnings("unchecked")
           @Begin
           public void editRole( Role role ) {
                Query q = entityManager.createQuery( "from Role where id!=:roleID" );
                q.setParameter( "roleID", role.getId() );
                this.setRoles( q.getResultList() );
                
                if ( this.role != role ) {
                     this.role = role;
                }
                
                /*if ( this.role.getGroups() != null ) {
                     this.groups = new Vector<String>();
                     if ( this.role.getGroups().size() > 0 ) {
                          for( Role r : this.role.getGroups() ) {
                               this.groups.add( r.getName() );
                          }
                     }
                }*/
           }
           
           @End
           public void saveRole() {
                /*if ( this.groups != null ) {
                     List<String>     removal = new Vector<String>();
                     // first remove from all groups
                     // then put into selected groups
                     removal = IdentityManager.instance().getRoleGroups( this.getRole().getName() );
                     for ( String s : this.getGroups() ) {
                          removal.remove( s );
                     }
                     for ( String s : removal ) {
                          IdentityManager.instance().removeRoleFromGroup( this.role.getName(), s );
                     }
                     removal = IdentityManager.instance().getRoleGroups( this.getRole().getName() );
                     for ( String s : this.getGroups() ) {
                          if ( ! removal.contains( s )) {
                               IdentityManager.instance().addRoleToGroup( this.getRole().getName(), s );
                          }
                     }
                }*/
                
                entityManager.merge( role );
                events.raiseEvent( "org.nycentral.events.roleChanged" );
                if ( log.isDebugEnabled() ) {
                     log.debug( "Role data changed: {0}", role );
                }
           }
           
           @Destroy
           @Remove
           @End
           public void cancel() {
                
           }
      
           public Role getRole() {
                return role;
           }
      
           public void setRole(Role role) {
                this.role = role;
           }
      
           public List<Role> getRoles() {
                return roles;
           }
      
           public void setRoles(List<Role> roles) {
                this.roles = roles;
           }
      
      }
      



      My xhtml file rendering everything:


      <!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:ui="http://java.sun.com/jsf/facelets"
                             xmlns:h="http://java.sun.com/jsf/html"
                             xmlns:f="http://java.sun.com/jsf/core"
                       xmlns:s="http://jboss.com/products/seam/taglib"
                       template="roledetail.xhtml">
      
           <ui:define name="details">
                <s:decorate template="coredata.xhtml">
                     <ui:define name="fieldName"><h:inputText id="RoleName" value="#{role.name}" required="true" /></ui:define>
                     <ui:define name="fieldMemberOf">
                          <h:selectManyListbox id="groupCheckboxes" value="#{role.groups}" layout="pageDirection">
                               <s:selectItems value="#{roleEdit.roles}" var="group" label="#{group.name}" />
                               <s:convertEntity />
                          </h:selectManyListbox>
                     </ui:define>
                </s:decorate>
           </ui:define>
           
           <ui:define name="actionButtons">
                <h:commandButton action="#{roleEdit.saveRole}" value="#{messages['org.nycentral.labels.SaveChanges']}" />
                <s:button action="#{roleEdit.cancel}" value="#{messages['org.nycentral.labels.Cancel']}" />
           </ui:define>
      
      </ui:composition>
      



      As you can see I used s:selectItems and s:convertEntity; that works well for selecting the items, but they do not get pre-selected if there are already some objects in role.groups.


      To summarize:



      • I'm in a stateful bean, in a conversation

      • I do have a list of Roles in the groups variable (double checked with debugger and seam.debug)

      • The list gets rendered with the available options to select (which is non-empty)

      • The list of already assigned groups matches with items in the available selections (that means, role.groups contains items that can also be found in roleEdit.roles)

      • Pressing the Save button does store the selected items; if I have no items selected, the role.groups gets emptied as expected



      The only thing not working is the preselection of items. I've seen a couple of postings that stated that if I have an a4j/ajax declaration in my xhtml, s:selectItems will not work. I tried to remove any ajax related stuff, but that didn't help either...


      For example, I edit a role, select e.g. Administrators, press Save and in my view screen, I can see it got saved (database select confirms it). I reenter the edit screen, but Administrators is not selected... really strange.


      My overall page looks something like


      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:rich="http://richfaces.org/rich"
          xmlns:s="http://jboss.com/products/seam/taglib"
          xmlns:a4j="http://richfaces.org/a4j">
      
      <ui:composition template="../layout/template.xhtml">
           <ui:define name="body">
                <div class="section">
                     <h1>#{role.name}</h1>
                </div>
                
                <h:form id="detailsForm">
                     <rich:panel id="detailsTable">
                          <f:facet name="header">#{messages['org.nycentral.labels.CoreData']}</f:facet>
                          <a4j:region>
                               <h:messages />
                               <ui:insert name="details" />
                          </a4j:region>
      
                     </rich:panel>
                     
                     <div class="actionButtons">
                          <ui:insert name="actionButtons" />
                     </div>
                </h:form>
           </ui:define>
      </ui:composition>
          
      </html>
      



      Still, I can't see why it doesn't work. Maybe I'm not seeing the forest for its trees, I'm really stumped. I've been trying to resolve this for two days now. I'm about to pull my hairs out :)


      If anyone has any ideas, I'd be really thankful


      chris