convertEntity problem
hardaur Nov 20, 2008 3:09 AMHello all,
I'm having a bit of a frustrating problem with s:convertEntity and am hoping one of you may be more informed than google ; )
Seam 2.1.0.GA
JBoss 4.2.2.GA
IceFAces 1.7.2
The xhtml:
<!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:s="http://jboss.com/products/seam/taglib" xmlns:ice="http://www.icesoft.com/icefaces/component"> <ui:composition template="/layout/home_template.xhtml"> <ui:define name="body"> <ice:form id="registrationWizardFormID-1"> <center> <ice:panelGroup id="userManagerGroupId-Review" styleClass="displayPanelGroup"> <table class="formTable" cellspacing="0" cellpadding="3"> <tr> <td class="formTableHeader" colspan="3"> Create User </td> </tr> <tr> <td> <ice:panelGroup id="accountInformationID" styleClass="displayPanelGroup"> <table id="accountInformationTableID" class="displayTable" cellspacing="0" cellpadding="3"> <tr> <td class="displayTableHeader" colspan="3"> Account Information </td> </tr> <s:decorate id="usernameDecoration" template="/layout/edit.xhtml"> <ui:define name="label">Username</ui:define> <ice:inputText id="username" required="true" length="64" value="#{userAccount.username}" validator="#{userManager.validateUniqueUsername}"/> </s:decorate> <s:decorate id="passwordDecoration" template="/layout/edit.xhtml"> <ui:define name="label">Password</ui:define> <ice:inputSecret id="password" required="true" length="64" value="#{userAccount.password}" valueChangeListener="#{userManager.validatePasswordChange}"/> </s:decorate> <s:decorate id="comfirmPasswordDecoration" template="/layout/edit.xhtml"> <ui:define name="label">Confirm Password</ui:define> <ice:inputSecret id="confirmPassword" required="true" length="64" value="#{userAccount.confirmPassword}" validator="#{userManager.validateConfirmPassword}"/> </s:decorate> <s:decorate id="emailDecoration" template="/layout/edit.xhtml"> <ui:define name="label">Email Address</ui:define> <ice:inputText id="email" required="true" length="64" value="#{user.email}" validator="#{userManager.validateUniqueEmail}"/> </s:decorate> <s:decorate id="dobDecoration" template="/layout/edit.xhtml"> <ui:define name="label">Birthday</ui:define> <ice:selectInputDate id="dob" value="#{user.dob}" renderAsPopup="true" required="true" partialSubmit="false" renderMonthAsDropdown="true" renderYearAsDropdown="true"> <f:convertDateTime pattern="MM/dd/yyyy"/> </ice:selectInputDate> </s:decorate> <s:decorate id="nicknameDecoration" template="/layout/edit.xhtml" > <ui:define name="label">Display Name</ui:define> <ice:inputText id="nickname" required="false" length="64" value="#{user.nickname}" validator="#{userManager.validateUniqueNickname}"/> </s:decorate> </table> </ice:panelGroup> </td> <td valign="top"> <ice:panelGroup id="subscriptionInformationID" styleClass="displayPanelGroup"> <table id="subscriptionInformationTableID" class="displayTable" cellspacing="0" cellpadding="3"> <tr> <td class="displayTableHeader" colspan="3"> Subscription Information </td> </tr> <s:decorate id="s1DecorationID" template="/layout/display.xhtml"> <ui:define name="label">Sub Information:</ui:define> Not yet </s:decorate> </table> </ice:panelGroup> </td> </tr> <tr> <td> <ice:panelGroup id="personalInformationID" styleClass="displayPanelGroup"> <table id="personalInformationTableID" class="displayTable" cellspacing="0" cellpadding="3"> <tr> <td class="displayTableHeader" colspan="3"> Personal Information </td> </tr> <s:decorate id="firstNameDecorationID" template="/layout/edit.xhtml"> <ui:define name="label">First Name:</ui:define> <ice:inputText id="firstName" required="true" length="64" value="#{user.firstName}"/> </s:decorate> <s:decorate id="middleInitialDecorationID" template="/layout/edit.xhtml"> <ui:define name="label">Middle Initial:</ui:define> <ice:inputText id="middleName" required="false" size="1" length="64" value="#{user.middleInitial}"/> </s:decorate> <s:decorate id="lastNameDecorationID" template="/layout/edit.xhtml"> <ui:define name="label">Last Name:</ui:define> <ice:inputText id="lastName" required="true" length="64" value="#{user.lastName}"/> </s:decorate> <s:decorate id="address1DecorationID" template="/layout/edit.xhtml"> <ui:define name="label">Address 1:</ui:define> <ice:inputText id="address1" required="true" length="64" value="#{user.address1}"/> </s:decorate> <s:decorate id="address2DecorationID" template="/layout/edit.xhtml"> <ui:define name="label">Address 2:</ui:define> <ice:inputText id="address2" required="false" length="64" value="#{user.address2}"/> </s:decorate> <s:decorate id="cityDecorationID" template="/layout/edit.xhtml"> <ui:define name="label">City:</ui:define> <ice:inputText id="city" required="true" length="64" value="#{user.city}"/> </s:decorate> <s:decorate id="territoryDecoration" template="/layout/edit.xhtml" > <ui:define name="label">State</ui:define> <ice:selectOneMenu id="state" value="#{user.state}" required="true"> <f:selectItem itemLabel="Select State" itemValue=""/> <s:selectItems var="_state" value="#{stateList}" label="#{_state.name}"/> <s:convertEntity/> </ice:selectOneMenu> </s:decorate> <s:decorate id="zipDecoration" template="/layout/edit.xhtml" > <ui:define name="label">Zip Code</ui:define> <ice:inputText id="zip" required="true" length="10" size="10" value="#{user.zip}"/> </s:decorate> <s:decorate id="phone1Decoration" template="/layout/edit.xhtml" > <ui:define name="label">Phone 1</ui:define> <ice:inputText id="phone1" required="true" length="64" value="#{user.phone1}"/> </s:decorate> <s:decorate id="phone2Decoration" template="/layout/edit.xhtml" > <ui:define name="label">Phone 2</ui:define> <ice:inputText id="phone2" required="false" length="64" value="#{user.phone2}"/> </s:decorate> </table> </ice:panelGroup> </td> <td valign="top"> <ice:panelGroup id="authorizationInformationID" styleClass="displayPanelGroup"> <table id="authorizationInformationTableID" class="displayTable" cellspacing="0" cellpadding="3"> <tr> <td class="displayTableHeader" colspan="3"> Authorization Information </td> </tr> <tr> <td> <s:decorate id="authhorizationDecorationID" template="/layout/display.xhtml"> <ui:define name="label">Select User Roles:</ui:define> <ice:selectManyCheckbox id="roles" value="#{userAccount.roles}" required="true"> <s:selectItems var="_role" value="#{userRoleList}" label="#{_role.name}"/> <s:convertEntity/> </ice:selectManyCheckbox> </s:decorate> </td> </tr> </table> </ice:panelGroup> </td> <td valign="bottom"> <center> <ice:panelGrid columns="2"> <ice:commandButton id="cancel" immediate="true" action="cancel" value="Cancel" /> <ice:commandButton id="save" action="#{userManager.save}" value="Register!"/> </ice:panelGrid> </center> </td> </tr> </table> </ice:panelGroup> </center> </ice:form> </ui:define> </ui:composition> </html>
The seam component:
package com.domain.application.session.admin; import java.util.Date; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.event.ValueChangeEvent; import javax.persistence.EntityManager; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Begin; import org.jboss.seam.annotations.Conversational; import org.jboss.seam.annotations.End; import org.jboss.seam.annotations.FlushModeType; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.Role; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.Transactional; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.log.Log; import org.jboss.seam.security.Identity; import org.jboss.seam.security.RunAsOperation; import org.jboss.seam.security.management.IdentityManager; import org.jboss.seam.security.management.JpaIdentityStore; import org.jboss.seam.ui.validator.ModelValidator; import com.domain.application.entity.User; import com.domain.application.entity.UserAccount; import com.domain.application.session.entity.UserAccountList; /** * Operations pertaining to creating, registering, editing, disabling and deleting * UserAccounts. * @author gander * */ @Name("userManager") // Role registrationWizard replaces RegistrationWizard.java //@Role(name = "registrationWizard", scope = ScopeType.CONVERSATION) @Scope(ScopeType.CONVERSATION) public class UserManager { @Logger private Log log; @In FacesMessages facesMessages; @In protected EntityManager entityManager; @In protected IdentityManager identityManager; @In private Identity identity; @In (required=false) private UserAccountList userAccountList; @In (required=false) @Out (required=false) private UserAccount userAccount; @Out (required=false) private User user; private String username; private String password; private boolean isWizard = true; /** * Delete the UserAccount injected by userAccount */ public String deleteUser() { String username = userAccount.getUsername(); log.info("Deleting user " + username); if (!identityManager.deleteUser(username)) { facesMessages.add("User " + username + " NOT deleted"); return "fail"; } facesMessages.add("User " + username + " deleted"); userAccountList.refresh(); return "success"; } /** * Handle the manual creation of a new user through the admin system. */ @Begin(join = true, flushMode=FlushModeType.MANUAL) public void startCreateUser() { facesMessages.add("BLAH BLAH BLAH 1"); log.info("Create user starting"); isWizard = false; // create my objects user = new User(); userAccount = new UserAccount(); // set some defaults user.setPasswordLastChanged(new Date()); } /** * Start the registration process, create the new entities and set * some defaults. */ @Begin(join = true, pageflow = "Registration Wizard", flushMode=FlushModeType.MANUAL) public void startWizard() { facesMessages.add("BLAH BLAH BLAH 2"); log.info("User registration wizard starting"); isWizard = true; // create my objects user = new User(); userAccount = new UserAccount(); // set some defaults user.setPasswordLastChanged(new Date()); } /** * Verify that the password and the verifyPassword match. * * @param context * @param validatedComponent * @param value */ @Conversational public void validateConfirmPassword(FacesContext context, UIComponent validatedComponent, Object value) { checkHibernateAnnotations(context, validatedComponent, value); String verifyPassword = (String) value; UIComponent passwordField = validatedComponent .findComponent(":registrationWizardFormID-1:passwordDecoration:password"); String password = (String) ((EditableValueHolder) passwordField) .getValue(); if (!verifyPassword.equals(password)) { ((UIInput) validatedComponent).setValid(false); facesMessages.addToControl(validatedComponent.getId(), "Passwords do not match!"); } } /** * This validation is primarily for after the user has entered password * and confirmPassword and comes back and changes password. In that case * the password confirm will not fire, I'm here to insure that it's taken * care of. I won't do anything if there's been nothing entered for confirm * password. * @param e */ @Conversational public void validatePasswordChange(ValueChangeEvent e) { String password = (String) e.getNewValue(); UIComponent confirmPasswordField = e.getComponent().findComponent( ":registrationWizardFormID-1:comfirmPasswordDecoration:confirmPassword"); System.out.println("confirmPasswordField is " + confirmPasswordField); String confirmPassword = (String) ((EditableValueHolder) confirmPasswordField).getValue(); if (confirmPassword == null || confirmPassword.equals("")) return; if (!password.equals(confirmPassword)) { // note that message is being applied to confirm field, not //password field facesMessages.addToControl(confirmPasswordField.getId(), "Passwords do not match!"); } } /** * Verify that the requested username is unique in the database. * * @param context * @param validatedComponent * @param value */ @Conversational public void validateUniqueUsername(FacesContext context, UIComponent validatedComponent, Object value) { checkHibernateAnnotations(context, validatedComponent, value); String username = (String) value; int num = entityManager.createQuery( "select u.username from UserAccount u where u.username = :username") .setParameter("username", username).getResultList().size(); if (num != 0) { ((UIInput) validatedComponent).setValid(false); facesMessages.addToControl(validatedComponent.getId(), "#{value} already used, please choose another!"); } } /** * Verify that the requested nickname is unique in the database. * * @param context * @param validatedComponent * @param value */ @Conversational public void validateUniqueNickname(FacesContext context, UIComponent validatedComponent, Object value) { checkHibernateAnnotations(context, validatedComponent, value); String nickname = (String) value; int num = entityManager.createQuery( "select u.nickname from User u where u.nickname = :nickname") .setParameter("nickname", nickname).getResultList().size(); if (num != 0) { ((UIInput) validatedComponent).setValid(false); facesMessages.addToControl(validatedComponent.getId(), "#{value} already used, please choose another!"); } } /** * Verify that the email the user is using is unique. * * @param context * @param validatedComponent * @param value */ @Conversational public void validateUniqueEmail(FacesContext context, UIComponent validatedComponent, Object value) { checkHibernateAnnotations(context, validatedComponent, value); String email = (String) value; int num = entityManager.createQuery( "select u.email from User u where u.email = :email").setParameter( "email", email).getResultList().size(); if (num != 0) { ((UIInput) validatedComponent).setValid(false); facesMessages.addToControl(validatedComponent.getId(), "#{value} is already in use, please use another!"); } } @Transactional @End(beforeRedirect = true) public String save() { System.out.println("**********************"); // is the user coming through the registration wizard or is the account // being created from the admin interface? if (isWizard) log.info("Registering : " + userAccount.getUsername()); else log.info("Creating account for " + userAccount.getUsername()); System.out.println("Roles are " + userAccount.getRoles()); // save username and password for later use username = userAccount.getUsername(); password = userAccount.getPassword(); // create the UserAccount new RunAsOperation() { public void execute() { identityManager.createUser(username, password); userAccount.setUser(user); userAccount.setEnabled(true); entityManager.flush(); if (isWizard) { // since userAccount doesn't get created until flush and // grantRole has to have it. so set role and flush again identityManager.grantRole(username, "user"); } entityManager.flush(); } }.addRole("admin") .run(); if (isWizard) { // and go ahead and login the user. identity.getCredentials().setUsername(username); identity.getCredentials().setPassword(password); identity.login(); } return "success"; } /** * UserAccount is created, associate the new entity to the local variable. * @param userAccount */ @Observer(JpaIdentityStore.EVENT_USER_CREATED) public void userAccountCreated(UserAccount userAccount) { this.userAccount = userAccount; } /** * Since we're using JSF validate instead of valueChangeListeners or other * we have to force a check of the hibernate annotation constraints. * * @param context * @param validate * @param value */ private void checkHibernateAnnotations(FacesContext context, UIComponent validate, Object value) { ModelValidator mv = new ModelValidator(); mv.validate(context, validate, value); } }
The EntityQuery:
package com.domain.application.session.entity; import java.util.Arrays; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.framework.EntityQuery; import com.domain.application.entity.UserRole; @Name("userRoleList") public class UserRoleList extends EntityQuery(UserRole) { private static final String EJBQL = "select userRole from UserRole userRole"; private static final String[] RESTRICTIONS = {}; private UserRole userRole = new UserRole(); public UserRoleList() { setEjbql(EJBQL); setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS)); setMaxResults(25); } public UserRole getUserRole() { return userRole; } }
The entity:
package com.domain.application.entity; // Generated Nov 4, 2008 8:35:52 AM by Hibernate Tools 3.2.2.GA import java.util.Set; import javax.persistence.Column; 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.UniqueConstraint; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; import org.hibernate.validator.NotNull; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.security.management.RoleConditional; import org.jboss.seam.annotations.security.management.RoleGroups; import org.jboss.seam.annotations.security.management.RoleName; /** * UserRole generated by hbm2java */ @Entity @Table(name = "user_role", catalog = "pb_dev", uniqueConstraints = @UniqueConstraint(columnNames = "name")) @Name("userRole") public class UserRole extends MappedEntity { private static final long serialVersionUID = 537460588621165918L; private Long id; private String name; private boolean conditional; private Set<UserRole> groups; public UserRole() { } public UserRole(Long id, boolean conditional) { this.id = id; this.conditional = conditional; } public UserRole(Long id, String name, boolean conditional) { this.id = id; this.name = name; this.conditional = conditional; } @Id @GenericGenerator( name = "sequenceGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { @Parameter(name = "optimizer", value = "hilo"), @Parameter(name = "initial_value", value = "1000"), @Parameter(name = "segment_value", value = "user_role"), @Parameter(name = "increment_size", value = "10") } ) @GeneratedValue(generator = "sequenceGenerator") @Column(name = "id", unique = true, nullable = false) public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } @Column(name = "name", unique = true) @RoleName public String getName() { return this.name; } public void setName(String name) { this.name = name; } @RoleGroups @ManyToMany(targetEntity = UserRole.class) @JoinTable(name = "role_groups", joinColumns = @JoinColumn(name = "role_id"), inverseJoinColumns = @JoinColumn(name = "member_of_role_id") ) public Set<UserRole> getGroups() { return groups; } public void setGroups(Set<UserRole> groups) { this.groups = groups; } // Changed from default (BIT) to TINYINT(1) couldn't figure out how to make bit work right. @Column(name = "conditional", nullable = false, columnDefinition = "TINYINT(1)") @NotNull @RoleConditional public boolean isConditional() { return this.conditional; } public void setConditional(boolean conditional) { this.conditional = conditional; } }
And the error:
19:21:11,827 INFO [lifecycle] WARNING: FacesMessage(s) have been enqueued, but may not have been displayed. sourceId=registrationWizardFormID-1:authhorizationDecorationID:roles[severity=(ERROR 2), summary=(value could not be converted to the expected type), detail=(value could not be converted to the expected type)]
The problem is the UserRole multi-select with a s:convertEntity. I'm using EntityQuery and they load and display fine. When the form is submitted, I get the above error and processing stops (there are no stacktraces in console or logs). Honestly, aside from the last 5 hours I've spent googling I don't even know how to troubleshoot this one.
I do know that it's when convertEntity tries to convert the selected item back to a UserRole. I'm stumped past that.
Any pointers would be very, very much appreciated.
Gerald
P.S. Yes, I know that I could use userAction stuff in the form, but due to the interaction between my userAccount and other objects, I don't care for it in that context.