package de.ppi.fuwesta.oval.validation; import static org.fest.reflect.core.Reflection.field; import java.util.HashMap; import java.util.Map; 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 org.springframework.beans.factory.annotation.Autowired; import de.ppi.fuwesta.jpa.helper.JpaQueryExecutor; /** * Check which validates whether one property or a set of properties is unique. * */ public class UniqueCheck extends AbstractAnnotationCheck<Unique> { /** The unique key context. */ private String uniqueKeyContext = ""; /** The primary key column. */ private String primaryKeyColumn = "id"; /** * The default message-key. */ static final String MESSAGE = "validation.unique"; /** * Helper to runs JPA-Queries. */ @Autowired private JpaQueryExecutor jpa; /** * Instantiates a new unique check. */ public UniqueCheck() { setMessage(MESSAGE); } @Override public void configure(Unique constraintAnnotation) { uniqueKeyContext = constraintAnnotation.value(); setMessage(constraintAnnotation.message()); primaryKeyColumn = constraintAnnotation.primaryKeyColumn(); } @Override public Map<String, String> createMessageVariables() { Map<String, String> messageVariables = new HashMap<String, String>(); messageVariables.put("uk-context", uniqueKeyContext); return messageVariables; } /** * Returns all properties which are part of the unique-key. * * @param uniqueKey the field which contains the unique-key annotation. * @return all properties which are part of the unique-key. */ private String[] getPropertyNames(String uniqueKey) { String completeUniqueKey; if (StringUtils.isNotEmpty(uniqueKeyContext)) { 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 Object model = validatedObject; final Object keyValue = field(primaryKeyColumn).ofType(Object.class).in(model).get(); // 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]; for (int i = 0; i < propertyNames.length; i++) { values[i] = field(propertyNames[i]).ofType(Object.class).in(model) .get(); if (i > 0) { jpql.append(" And "); } jpql.append("o.").append(propertyNames[i]).append(" = ? "); } if (isUpdate) { values[propertyNames.length] = keyValue; jpql.append(" and ").append(primaryKeyColumn).append(" <> ?"); } return Long.parseLong(jpa .queryWithSingleResult(jpql.toString(), values).toString()) == 0L; } }