JSF bean validation and exceptions thrown by validators

Bean validation is suppressed when an exception is thrown by the validator. I wonder how to handle this properly. The form:

<h:form id="registration_form">
<h:panelGrid columns="3">
    <h:outputLabel for="username">Username</h:outputLabel>
    <h:inputText id="username" value="#{userController.user.username}">
    <f:validator binding="#{userController$UniqueUsernameValidator}"
        redisplay="true"/> <!-- Not sure about this -->
        <f:ajax event="blur" render="usernameMessage" />
    </h:inputText>
    <h:message id="usernameMessage" for="username" />
    <!-- etc-->
</h:panelGrid>
</h:form>

UserController:

@ManagedBean
@ViewScoped
public class UserController {

    // http://stackoverflow.com/a/10691832/281545
    private User user;
    @EJB
    // do not inject stateful beans !
    private UserService service;

    public User getUser() {
        return user;
    }

    @PostConstruct
    void init() {
        // http://stackoverflow.com/questions/3406555/why-use-postconstruct
        user = new User();
    }

    @ManagedBean
    @RequestScoped
    public static class UniqueUsernameValidator implements Validator {

        // Can't use a Validator (no injection) - see:
        // http://stackoverflow.com/a/7572413/281545
        @EJB
        private UserService service;

        @Override
        public void validate(FacesContext context, UIComponent component,
                Object value) throws ValidatorException {
            if (value == null) return; // Let required="true" handle, if any.
            try {
                if (!service.isUsernameUnique((String) value)) {
                    throw new ValidatorException(new FacesMessage(
                        FacesMessage.SEVERITY_ERROR,
                        "Username is already in use.", null));
                }
            } catch (Exception e) {
                System.out.println(cause(e));
                Throwable cause = e.getCause();
                if (cause instanceof PersistenceException) {
                    Throwable cause2 = cause.getCause();
                    // ((PersistenceException)cause) - only superclass methods
                    if (cause2 instanceof DatabaseException) {
                        // now this I call ugly
                        int errorCode = ((DatabaseException) cause2)
                            .getDatabaseErrorCode(); // no java doc in eclipse
                        if (errorCode == 1406)
                            throw new ValidatorException(new FacesMessage(
                                FacesMessage.SEVERITY_ERROR, "Max 45 chars",
                                null));
                    }
                }
                // TODO: DEGUG, btw the EJBException has null msg
                throw new ValidatorException(new FacesMessage(
                    FacesMessage.SEVERITY_ERROR, cause.getMessage(), null));
            }
        }

        private static String cause(Exception e) {
            StringBuilder sb = new StringBuilder("--->\nEXCEPTION:::::MSG\n"
                + "=================\n");
            for (Throwable t = e; t != null; t = t.getCause())
                sb.append(t.getClass().getSimpleName()).append(":::::")
                    .append(t.getMessage()).append("\n");
            sb.append("FIN\n\n");
            return sb.toString();
        }
    }
}

Essence:

/** The persistent class for the users database table. */
@Entity
@Table(name = "users")
@NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    private int iduser;
    @NotNull(message = "Please enter a username")
    @Pattern(regexp = "[A-Za-z0-9_]{6}[A-Za-z0-9_]*",
            message = "Usernames can have latin characters, the underscore and "
                + "digits and are at least 6 characters")
    @Size(max = 45)
    private String username;
    //etc
}

and service:

@Stateless
public class UserService {

    @PersistenceContext
    private EntityManager em;

    public boolean isUsernameUnique(String username) {
        Query query = em
            .createNativeQuery("SELECT r1_check_unique_username(?)");
        short i = 0;
        query.setParameter(++i, username);
        return (boolean) query.getSingleResult();
    }
}

What happens if I add a username more than 45 characters, MySql throws an exception - output cause():

INFO: --->
EXCEPTION:::::MSG
=================
EJBException:::::null
PersistenceException:::::Exception[EclipseLink-4002](Eclipse Persistence Services\
 - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data\
 too long for column 'username_given' at row 198
Error Code: 1406
Call: SELECT r1_check_unique_username(?)
    bind => [1 parameter bound]
Query: DataReadQuery(sql="SELECT r1_check_unique_username(?)")
DatabaseException:::::
Internal Exception: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data\
 too long for column 'username_given' at row 198
Error Code: 1406
Call: SELECT r1_check_unique_username(?)
    bind => [1 parameter bound]
Query: DataReadQuery(sql="SELECT r1_check_unique_username(?)")
MysqlDataTruncation:::::Data truncation:Data too long for column 'username_given'\
 at row 198
FIN

As I process it, a message is displayed "Max 45 chars".

But this processing (with explicit error code checking) is a little smelly. On the other hand, if I do not throw a ValidatorException (and just catch (Exception e) {}that, which is also smelly), then the bean validation check is triggered (the one that is called @Size(max = 45)), but I still see the exception trace in the glass fish logs.

Questions

  • ( catch (Exception ignore) {} , - , () bean User - )
  • , ?
+3

All Articles