-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 
Author Message
 Post subject: Implementing a Validator for Unique Constraints
PostPosted: Thu Jul 26, 2007 2:26 pm 
Newbie

Joined: Thu Jul 26, 2007 1:59 pm
Posts: 3
I'm been trying to extend the Hibernate Validator framework by adding constraint checking for unique values (the username of a user entity for example). Following the documentation, this is what I've come up with so far:

Annotation:
Code:
package com.docfinity.validator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.hibernate.validator.ValidatorClass;

@ValidatorClass(UniqueValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Unique {
    String hql();
    String paramName() default "value";
    String message() default "value is not unique";
}


Validator:
Code:
package com.docfinity.validator;

import org.hibernate.validator.Validator;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class UniqueValidator implements Validator<Unique>, ApplicationContextAware {
    private String hql;
    private String paramName;
   
    private static ApplicationContext applicationContext;
   
    public void initialize(final Unique parameters) {
        this.hql = parameters.hql();
        this.paramName = parameters.paramName();
    }

    public boolean isValid(Object value) {
        // use the application context to get a session to create a query (specified by hql string) to test uniqueness
    }

    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        UniqueValidator.applicationContext = applicationContext;
    }
}



I imagined it being used as follows (on the User entity):
Code:
@Unique(hql="SELECT FROM Users WHERE username = :value")
private String username;


My issues so far with this approach are:
    Is making the applicationContext static considered bad practice? When the variable is not static, the autowiring of the ApplicationContext happens correctly, but when the isValid method gets called, the applicationContext is null.
    Should I be injecting an ApplicationContext at all? Is there a better way to create a Query?
    Is there already something built into the Validator framework that can handle the use case of validating for uniqueness? I'd rather not reinvent the wheel if possible.



Thanks in advance,
Jeff


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 12:27 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
If your SessionFactory is published to JNDI, just do a lookup.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jul 28, 2007 9:45 pm 
Newbie

Joined: Thu Jul 26, 2007 1:59 pm
Posts: 3
Thanks for the response.

Is it appropriate to hold the SessionFactory in a static variable rather than incur the lookup every time? I don't understand how the Validator is instantiated but it doesn't seem to retain class-level fields that have previously been set.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 30, 2007 3:45 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Validator instances are kept per ClassValidator, but in your case, I would say yes, keeping a static ref is fine as long as your ClassValidator instance is discarded when the SessionFactory is discarded (ok in an EAR for example).
But if you ahve 2 SFs in the same app then you're in trouble.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 14, 2007 3:36 pm 
Newbie

Joined: Thu Jul 26, 2007 1:59 pm
Posts: 3
I'm posting my latest version of the Unique Validator just in case it is of any help to anyone else.

Some changes that I made are as follows:
    Changing the annotation to apply to a class instead of a field or method. This allows me to reflectively get multiple field values, since the object being passed to the isValid() method is now the whole class instead of just a single field.
    Removed the need to specify parameters in favor of parsing the parameters from the specified HQL.
    Added a wrapper annotation to support a collection of Unique validators for any particular class.


The code is listed below:

Uniques.java
Code:
package com.docfinity.validator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uniques {
    Unique[] value();
}


Unique.java
Code:
package com.docfinity.validator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.hibernate.validator.ValidatorClass;

@ValidatorClass(UniqueValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Unique {
    /** Query string to execute to determine uniqueness. */
    String hql();
   
    /** The message to display if validation fails. */
    String message() default "The specified field value is not unique.";
}


UniqueValidator.java
Code:
package com.docfinity.validator;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.validator.Validator;

import com.docfinity.business.SystemException;

/**
* An implementation of the Hibernate Validator interface, used to
* determine the uniqueness of a value. <p>
*
* This validator will get the named parameters from the HQL statement and
* retrieve the values for those parameters from the instance of the class
* being validated.
*/
public class UniqueValidator implements Validator<Unique> {
    /** Hibernate session factory for building queries. */
    private static SessionFactory sessionFactory;
   
    /** Query string to determine a value's uniqueness. */
    private String hql;
   
    /** The collection of named parameters in the HQL statement. */
    private String[] params;
   
    /**
     * Initializes the validator instance with properties from the
     * specified annotation parameters.
     *
     * @param parameters    the parameters of the field to validate's Unique annotation
     */
    public void initialize(final Unique parameters) {
        this.hql = parameters.hql();
        this.params = createParameterList(this.hql);
    }
   
    /**
     * Extracts the named parameters from the specified HQL statement.
     *
     * @param query   the HQL statement to parse
     * @return  an array of all the named parameters (of the form :name) found in the provided string
     */
    private String[] createParameterList(final String query) {
        final Matcher matcher = Pattern.compile(":[^\\s]*").matcher(query);
        List<String> paramList = new ArrayList<String>();
        while(matcher.find()) {
            paramList.add(this.hql.substring(matcher.start() + 1, matcher.end()));
        }
       
        return paramList.toArray(new String[paramList.size()]);
    }

    /**
     * Method to determine whether or not the value passes validation. <p>
     *
     * Validation in this case refers to a value being unique.
     *
     * @param value the value to validate for uniqueness
     * @return  true if the value is unique, false otherwise
     */
    public boolean isValid(final Object value) {
        Query query = sessionFactory.getCurrentSession().createQuery(this.hql);
       
        Class valueClass = value.getClass();
        Field field;
        for(int i = 0; i < this.params.length; i++) {
            try {
                field = valueClass.getDeclaredField(this.params[i]);
                field.setAccessible(true);
               
                query.setParameter(this.params[i], (null != field.get(value)) ? field.get(value) : "");
            } catch(final NoSuchFieldException e) {
                throw new SystemException(e.getMessage());
            } catch(final IllegalAccessException e) {
                throw new SystemException(e.getMessage());
            }
        }
       
        return query.list().size() == 0;
    }

    /**
     * Sets the Hibernate SessionFactory to use for building queries for validation.
     *
     * @param sessionFactory    a valid SessionFactory instance
     */
    public void setSessionFactory(final SessionFactory sessionFactory) {
        UniqueValidator.sessionFactory = sessionFactory;
    }
}


An example class using the validator is as follows:
Category.java
Code:
package com.docfinity.classification.entity;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;

import com.docfinity.auditing.AuditType;
import com.docfinity.auditing.Auditable;
import com.docfinity.classification.enums.CategoryStatus;
import com.docfinity.entity.Persistable;
import com.docfinity.entity.auditing.AuditMessage;
import com.docfinity.validator.Unique;

/**
*  Class to define a Category entity. <p>

*  Categories are used to classify groups of related documents/images.  A category
*  can be further broken down into document types, which are a more specific
*  grouping of documents/images within a category.
*/
@Entity
@Table(name = "Categories")
@Unique(hql = "FROM Category c WHERE c.name = :name AND c.id != :id", message = "Category name must be unique.")
public class Category implements Persistable, Auditable {
    /** A description for the category. */
    @Lob
    @Column(nullable = true)
    @NotNull(message = "Category description cannot be null.")
    private String description;
   
    /** The collection of document type classifications within the category. */
    @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "category")
    @OrderBy(value = "name")
    private List<DocumentType> documentTypes;
   
    /** Unique identifier for a category. */
    @Id
    @GenericGenerator(name = "uuid", strategy = "uuid")
    @GeneratedValue(generator = "uuid")
    @Column(columnDefinition = "CHAR(32)")
    private String id;

    /** Unique name for the category. */
    @Column(unique = true)
    @NotNull(message = "Category name cannot be null.")
    @Length(min = 1, max = 75, message = "Category name cannot be less than 1 character and more than 75 characters.")
    private String name;
   
    /** Status of the category. */
    @Column(length = 20)
    @NotNull(message = "Category status cannot be null.")
    @Enumerated(EnumType.STRING)
    private CategoryStatus status;

    /**
     *  Default constructor. <p>
     * 
     *  This constructor should not be called directly.
     */
    public Category() {
        // Default constructor
    }

    ... rest of code omitted ...


If anyone has any ideas on how to improve this functionality, or if there are any glaring issues, please let me know.


Top
 Profile  
 
 Post subject: Re: Implementing a Validator for Unique Constraints
PostPosted: Sat Sep 05, 2009 4:55 pm 
Newbie

Joined: Sat Sep 05, 2009 4:45 pm
Posts: 1
That's a very good initiative jmoroski! It would be very nice, however, if the Hibernate team could add inherent support for a unique constraint validator. Currently I'm using Spring Validator with custom validator classes (@Validator/@ValidatorRef), but that results in a lot of unnecessary code for each class that needs unique constraint validation...


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.