-
1. Re: JSF -> Hibernate validations, too much noise ; )
francof Nov 3, 2008 4:43 PM (in response to hardaur)Gerald,
I am a Hibernate newbie.
I ran into the same issue when I was looking for a solution. I built a custom Hibernate validator, it has been working for me. It is a generic solution and can be applied on a single column that you are checking uniqueness for - not sure it is the most elegant solution but here it is.
@Target({ METHOD, FIELD }) @Retention(RUNTIME) @Documented @ValidatorClass(UniqueSimpleKeyValidator.class) public @interface UniqueSimpleKey { String tableName() ; String pkColumnName(); String message() default "This code already exists"; }
public class UniqueSimpleKeyValidator implements Validator<UniqueSimpleKey>, Serializable { private static final long serialVersionUID = 1L; private String tableName ; private String pkColumnName; public void initialize(UniqueSimpleKey parameters) { tableName = parameters.tableName(); tableName = ( tableName == null ) ? "" : tableName.trim(); pkColumnName = parameters.pkColumnName(); pkColumnName = ( pkColumnName == null ) ? "" : pkColumnName.trim(); } public boolean isValid(Object value) { //System.out.println("\n\n In UniqueSimpleKeyValidator"); // Ignore checks for required - they should be handled by other validators. if (value == null) { return true; } //TODO UniqueSimpleKeyValidator - Allow for this test on integer, date PK values String stringValue = new String(); /*Integer intValue = 0; */ if (value instanceof String) { stringValue = (String) value; stringValue = stringValue.trim(); if (stringValue.trim().length() == 0) { return true; } } if ( tableName.length() == 0 || pkColumnName.length() == 0 ) return false; EntityManager em = (EntityManager) Component.getInstance("entityManager"); String query = "SELECT " + pkColumnName + " FROM " + tableName + " WHERE " + pkColumnName + " = ?1"; try { Query qryObject = em.createNativeQuery(query); // START check for pkColumnName of string type qryObject.setParameter(1, stringValue); String oldValue = new String(); oldValue = (String)qryObject.getSingleResult(); if (oldValue.equals(stringValue)) return true; // END check for pkColumnName of string type // This constraint will be checked on add & edits of this entity. It will also be fired when // Hibernate attempts to save or update the entity. // At the database level, the field should have a unique constraint. } catch (NoResultException nre) { return true; } return true; } }
Then on your entity column, you can use this constraint like so
@Column(name = "USER_NAME") @Length(max = 10) @UniqueSimpleKey (tableName = "APPUSER", pkColumnName = "USER_NAME", message="This user already exists")
Best regards
Franco -
2. Re: JSF -> Hibernate validations, too much noise ; )
jguglielmin Nov 3, 2008 5:28 PM (in response to hardaur)Not sure why you wouldn't want to use the partialSubmit or the validator property on the input component, so will show a way to use these. For example, you don't want to allow a user to use an already registered username.
<s:decorate id="usernameField" template="layout/edit.xhtml"> <ui:definename="label">Username</ui:define> <ice:inputText id="username" value="#{newUser.username}" validator="#{registerAction.validateUserName}" size="20" required="true" partialSubmit="true"/> </s:decorate>
and the code behind the validation would be
public void validateUserName(FacesContext context, UIComponent validate, Object value){ //since we have our own validator, need to check for hibernate annotation validations checkHibernateAnnotations(context, validate, value); String username=(String)value; if (!isUsernameAvailable(username)) { FacesMessage msg = new FacesMessage("username already taken"); context.addMessage(validate.getClientId(context), msg); usernameValid=false; }else usernameValid=true; }
With the following helper methods.
public boolean isUsernameAvailable(String username) { return entityManager.createQuery( "select m from Member m where m.username = ?1") .setParameter(1, username).getResultList().size() == 0; }
private void checkHibernateAnnotations(FacesContext context, UIComponent validate, Object value) { ModelValidator mv = new ModelValidator(); mv.validate(context, validate, value); }
This last method is required to check the hibernate annotations on the entity class for user since the ModelValidator class is not normally fired at this time. In this case the hibernate annotation used may be length, but you could use any of the other annotations for email, etc to combine whatever facility hibernate has as well as writing your own validation in the validator method. The ModelValidator is part of the seam ui library. Then as your user enters information, it is checked (via ajax) at each component to ensure the entry is correct, rather than just at form submit.
-
3. Re: JSF -> Hibernate validations, too much noise ; )
hardaur Nov 3, 2008 5:54 PM (in response to hardaur)Franco, that's almost exactly what I was trying to do, I just couldn't get a good instance of the entityManager in my validator class.
However,
I did a little more testing and found out what would suit my purposes best, I think.
I didn't want to use the icefaces validator tag for exactly the reason you mention Judy; that it would bypass the hibernate validations afterwards. I hadn't found a way around that (thank you). But, I did go back to the valueChangeListener and without doing partialSubmit it works like a charm (and hits the hibernate validators). So. . .
<s:decorate id="usernameDecoration" template="/layout/edit.xhtml"> <ui:define name="label">Username</ui:define> <ice:inputText id="username" required="true" length="64" value="#{user.username}" valueChangeListener="#{registrationWizard.validateUniqueUsername}"/>
and
public void validateUniqueUsername(ValueChangeEvent e) { String username = (String) e.getNewValue(); int num = entityManager.createQuery( "select u.username from User u where u.username = :username") .setParameter("username", username).getResultList().size(); if (num != 0) { facesMessages.addToControl(e.getComponent().getId(), "Username already used, please choose another!"); } }
I didn't want to do partialSubmits becaus this will hopefully be a very high traffic site and I didn't want that much trivial traffic. This way, I'm only verifying on submit of each page of the wizard.
Thanks, learned something valuable from both of you!
G
-
4. Re: JSF -> Hibernate validations, too much noise ; )
francof Nov 5, 2008 3:12 PM (in response to hardaur)Please disregard my solution.
I am having issues myself. It turns out my custom validation worked fine when I edit an entity. When I tried to create a new entity, the validation caused all sorts of headaches for me.
I was getting this -org.hibernate.HibernateException: Found two representations of same collection: com.cmsc.cast.model.Tax.items at org.hibernate.engine.Collections.processReachableCollection(Collections.java:153)
I know you are thinking that is something else.
Debugging through my code, I realized that the culprit was my custom validator. It seems to me like it has to do with the Entity manager instance not being cleared after the validator completes. When I do not use this validator, my inserts work fine.I prefer keeping all validations on the model, so I will try to give it another day. If I can't figure it out, I will have to go with Judy's solution too.
Thanks
Franco
-
5. Re: JSF -> Hibernate validations, too much noise ; )
francof Nov 5, 2008 5:41 PM (in response to hardaur)I resolved it.
After line
Query qryObject = em.createNativeQuery(query);
I addded line
qryObject.setFlushMode(FlushModeType.COMMIT);