Permalink
Browse files

[#370] Enhancement uniquecheck

  • Loading branch information...
1 parent c94e159 commit b4fff56831976be5111d3c47aa70122297d2686d @pepite pepite committed Nov 7, 2011
@@ -0,0 +1,25 @@
+package play.data.validation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import net.sf.oval.configuration.annotation.Constraint;
+import play.db.jpa.GenericModel;
+
+/**
+ * Check that a field or or field in a context is unique.
+ * You set the context as a list (comma, semicolon or space separated)
+ * of properties of your {@link GenericModel}.
+ *
+ * Message key: validation.unique
+ * $1: field name
+ * $2: properties which define a context in which the column must be unique
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Constraint(checkWith = UniqueCheck.class)
+public @interface Unique {
+ String value() default "";
+ String message() default UniqueCheck.mes;
+}
@@ -0,0 +1,110 @@
+package play.data.validation;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.TreeMap;
+import net.sf.oval.Validator;
+import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
+import net.sf.oval.context.FieldContext;
+import net.sf.oval.context.OValContext;
+import org.apache.commons.lang.StringUtils;
+import play.db.jpa.GenericModel;
+import play.db.jpa.JPQL;
+import play.db.jpa.Model;
+import play.exceptions.UnexpectedException;
+
+/**
+ * Check which proof if one or a set of properties is unique.
+ *
+ */
+public class UniqueCheck extends AbstractAnnotationCheck<Unique> {
+
+ final static String mes = "validation.unique";
+ private String uniqueKeyContext = null;
+
+ @Override
+ public void configure(Unique constraintAnnotation) {
+ uniqueKeyContext = constraintAnnotation.value();
+ setMessage(constraintAnnotation.message());
+ }
+
+ @Override
+ public Map<String, String> createMessageVariables() {
+ Map<String, String> messageVariables = new TreeMap<String, String>();
+ messageVariables.put("2-properties", uniqueKeyContext);
+ return messageVariables;
+ }
+
+ private String[] getPropertyNames(String uniqueKey) {
+ String completeUniqueKey;
+ if (uniqueKeyContext.length() > 0) {
+ completeUniqueKey = uniqueKeyContext + ";" + uniqueKey;
+ } else {
+ completeUniqueKey = uniqueKey;
+ }
+ return completeUniqueKey.split("[,;\\s][\\s]*");
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSatisfied(Object validatedObject, Object value,
+ OValContext context, Validator validator) {
+ requireMessageVariablesRecreation();
+ if (value == null) {
+ return true;
+ }
+ final String[] propertyNames = getPropertyNames(
+ ((FieldContext) context).getField().getName());
+ final GenericModel model = (GenericModel) validatedObject;
+ final Model.Factory factory = Model.Manager.factoryFor(model.getClass());
+ final String keyProperty = StringUtils.capitalize(factory.keyName());
+ final Object keyValue = factory.keyValue(model);
+ //In case of an update make sure that we won't read the current record from database.
+ final boolean isUpdate = (keyValue != null);
+ final String entityName = model.getClass().getName();
+ final StringBuffer jpql = new StringBuffer("SELECT COUNT(o) FROM ");
+ jpql.append(entityName).append(" AS o where ");
+ final Object[] values = new Object[isUpdate ? propertyNames.length + 1 :
+ propertyNames.length];
+ final Class clazz = validatedObject.getClass();
+ for (int i = 0; i < propertyNames.length; i++) {
+ Field field = getField(clazz, propertyNames[i]);
+ field.setAccessible(true);
+ try {
+ values[i] = field.get(model);
+ } catch (Exception ex) {
+ throw new UnexpectedException(ex);
+ }
+ if (i > 0) {
+ jpql.append(" And ");
+ }
+ jpql.append("o.").append(propertyNames[i]).append(" = ? ");
+ }
+ if (isUpdate) {
+ values[propertyNames.length] = keyValue;
+ jpql.append(" and ").append(keyProperty).append(" <> ?");
+ }
+ return JPQL.instance.count(entityName, jpql.toString(), values) == 0L;
+ }
+
+ private Field getField(Class clazz, String fieldName) {
+ Class c = clazz;
+ try {
+ while (!c.equals(Object.class)) {
+ try {
+ return c.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException e) {
+ c = c.getSuperclass();
+ }
+ }
+ } catch (Exception e) {
+ throw new UnexpectedException("Error while determining the field " +
+ fieldName + " for an object of type " + clazz);
+ }
+ throw new UnexpectedException("Cannot get the field " + fieldName +
+ " for an object of type " + clazz);
+ }
+}
@@ -0,0 +1,21 @@
+package models;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.OneToMany;
+
+import play.db.jpa.Model;
+
+@Entity
+public class Author extends Model {
+
+ public String name;
+
+ @OneToMany(mappedBy="author")
+ public List<Book> books;
+
+ public Date birthday;
+
+}
@@ -1,24 +1,21 @@
package models;
import javax.persistence.Entity;
-import javax.persistence.FieldResult;
+import javax.persistence.ManyToOne;
import play.db.jpa.Model;
+import play.data.validation.Unique;
@Entity
public class Book extends Model {
-
- public Book(String title, String author, String content, int shelfNumber) {
- this.title = title;
- this.author = author;
- this.content = content;
- this.shelfNumber = shelfNumber;
- }
-
- public Book() {}
-
+ @Unique("author")
public String title;
- public String author;
- public String content;
- public int shelfNumber;
+
+ @Unique
+ public String isbn;
+
+ @ManyToOne()
+ public Author author;
+
+
}
@@ -0,0 +1,54 @@
+import static org.junit.Assert.*;
+import models.Author;
+import models.Book;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import play.data.validation.Error;
+import play.data.validation.Validation;
+import play.data.validation.Validation.ValidationResult;
+import play.test.Fixtures;
+import play.test.UnitTest;
+
+
+/**
+ * Test the @Unique-annotation
+ *
+ */
+public class UniqueTest extends UnitTest {
+
+ @Before
+ public void setUp() {
+ Fixtures.deleteDatabase();
+ Fixtures.loadModels("uniqueTestdata.yml");
+ }
+ @Test
+ public void testSingleColumn() {
+ Book firstBook = Book.find("byIsbn", "1").first();
+ firstBook.isbn = "2";
+ ValidationResult res = Validation.current().valid(firstBook);
+ assertFalse(res.ok);
+ assertNotNull(Validation.errors(".isbn"));
+ Error error = Validation.errors(".isbn").get(0);
+ assertEquals("validation.unique", error.message());
+ }
+
+ @Test
+ public void testMultiColumn() {
+ Author bob = Author.find("byName", "Bob").first();
+ Book firstBook = Book.find("byIsbn", "1").first();
+ firstBook.author = bob;
+ ValidationResult res = Validation.current().valid(firstBook);
+ assertFalse(res.ok);
+ assertNotNull(Validation.errors(".title"));
+ Error error = Validation.errors(".title").get(0);
+ assertEquals("validation.unique", error.message());
+
+ Validation.clear();
+ firstBook.title = "Bobs Book";
+ res = Validation.current().valid(firstBook);
+ assertTrue(res.ok);
+ }
+
+}
@@ -0,0 +1,21 @@
+# Test data
+
+Author(anton):
+ name: Anton
+ birthday: 01.04.1960
+
+Author(bob):
+ name: Bob
+ birthday: 01.04.1970
+
+Book(AntonsBook):
+ author: anton
+ title: MyBook
+ isbn: 1
+
+Book(BobsBook):
+ author: bob
+ title: MyBook
+ isbn: 2
+
+

0 comments on commit b4fff56

Please sign in to comment.