package edu.ualberta.med.biobank.validator.constraint.impl; import java.io.Serializable; import java.util.List; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.hibernate.EntityMode; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.metadata.ClassMetadata; import edu.ualberta.med.biobank.validator.EventSourceAwareConstraintValidator; import edu.ualberta.med.biobank.validator.constraint.Unique; public class UniqueValidator extends EventSourceAwareConstraintValidator<Object> implements ConstraintValidator<Unique, Object> { private String[] properties; @Override public void initialize(Unique annotation) { this.properties = annotation.properties(); } @Override public boolean isValidInEventSource(Object value, ConstraintValidatorContext context) { if (value == null) { return true; } boolean unique = countRows(value) == 0; if (!unique) { overrideEmptyMessageTemplate(value, context); } return unique; } private void overrideEmptyMessageTemplate(Object value, ConstraintValidatorContext context) { String defaultTemplate = context.getDefaultConstraintMessageTemplate(); if (defaultTemplate.isEmpty()) { ClassMetadata meta = getEventSource().getSessionFactory() .getClassMetadata(value.getClass()); StringBuilder template = new StringBuilder(); template.append("{"); template.append(meta.getMappedClass(EntityMode.POJO).getName()); template.append("."); template.append(Unique.class.getSimpleName()); template.append("["); for (int i = 0, n = properties.length; i < n; i++) { template.append(properties[i]); if (i < n - 1) template.append(","); } template.append("]}"); context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(template.toString()) .addConstraintViolation(); } } private int countRows(Object value) { ClassMetadata meta = getEventSource().getSessionFactory() .getClassMetadata(value.getClass()); String idName = meta.getIdentifierPropertyName(); Serializable id = meta.getIdentifier(value, getEventSource()); DetachedCriteria criteria = DetachedCriteria.forClass(value.getClass()); for (String property : properties) { criteria.add(Restrictions.eq(property, meta.getPropertyValue(value, property, EntityMode.POJO))); } if (id != null) { criteria.add(Restrictions.ne(idName, id)); } criteria.setProjection(Projections.rowCount()); List<?> results = criteria.getExecutableCriteria(getEventSource()).list(); Number count = (Number) results.iterator().next(); // Because actions are queued, it is possible for two objects to have // the same value for a unique field. The pre-insert/update validation // will succeed because they query the database, but when both actions // are flushed, then the database will raise an error. // TODO: query cache for duplicates? return count.intValue(); } }