/*
* Copyright 2011 Martin Grotzke
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.hashcode.validation;
import static de.hashcode.validation.ReflectionUtils.getIdField;
import static de.hashcode.validation.ReflectionUtils.getPropertyValue;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* The validator for beans annotated with {@link UniqueKey}. The validator
* {@link UniqueKeyValidator} allows annotated bean to use read-only properties
* (immutable). Also an update of the entity is allowed, without having to use
* different validation groups for create/edit.
*
* @author <a href="mailto:martin.grotzke@googlemail.com">Martin Grotzke</a>
*/
public class UniqueKeyValidator implements ConstraintValidator<UniqueKey, Serializable>, EntityManagerAwareValidator {
private EntityManager entityManager;
private UniqueKey constraintAnnotation;
public UniqueKeyValidator() {
}
public UniqueKeyValidator(final EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public void setEntityManager(final EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
@Override
public void initialize(final UniqueKey constraintAnnotation) {
this.constraintAnnotation = constraintAnnotation;
}
@Override
public boolean isValid(final Serializable target, final ConstraintValidatorContext context) {
if (entityManager == null) {
// eclipselink may be configured with a BeanValidationListener that
// validates an entity on prePersist
// In this case we don't want to and we cannot check anything (the
// entityManager is not set)
//
// Alternatively, you can disalbe bean validation during jpa
// operations
// by adding the property "javax.persistence.validation.mode" with
// value "NONE" to persistence.xml
return true;
}
final Class<?> entityClass = target.getClass();
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object> criteriaQuery = criteriaBuilder.createQuery();
final Root<?> root = criteriaQuery.from(entityClass);
try {
final Object propertyValue = getPropertyValue(target, constraintAnnotation.property());
final Predicate uniquePropertyPredicate = criteriaBuilder.equal(root.get(constraintAnnotation.property()),
propertyValue);
final Field idField = getIdField(entityClass);
final String idProperty = idField.getName();
final Object idValue = getPropertyValue(target, idProperty);
if (idValue != null) {
final Predicate idNotEqualsPredicate = criteriaBuilder.notEqual(root.get(idProperty), idValue);
criteriaQuery.where(uniquePropertyPredicate, idNotEqualsPredicate);
} else {
criteriaQuery.where(uniquePropertyPredicate);
}
} catch (final Exception e) {
throw new RuntimeException("An error occurred when trying to create the jpa predicate for the @UniqueKey '"
+ constraintAnnotation.property() + "' on bean " + entityClass + ".", e);
}
final List<Object> resultSet = entityManager.createQuery(criteriaQuery).getResultList();
if (!resultSet.isEmpty()) {
context.buildConstraintViolationWithTemplate(constraintAnnotation.message())
.addNode(constraintAnnotation.property()).addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
return true;
}
}