Bean validation is suppressed when an exception is thrown by a validator. I wonder what is the correct way of handling this. 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>
The 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();
}
}
}
The entity:
/** 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 the 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 is that if I put a username longer than 45 chars MySql throws an exception - the output of cause()
is:
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
The way I handle it the "Max 45 chars"
message is shown.
But this handling (with explicit check on the error code) is a bit smelly. On the other hand, if I do not throw a ValidatorException (and just catch (Exception e) {}
which is also smelly) the bean validation kicks in ( the one induced by @Size(max = 45)
) but I still see the exception trace in the glassfish logs.
Questions
- Is this way of handling it correct (should I
catch (Exception ignore) {}
in the validator or check the exception I get manually - ideally I would be able to require the validator be run after the (all) bean validation(s) inUser
- and only if these validations pass) - How do I suppress the Exception being printed in the server logs ?