diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.java b/forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.java new file mode 100644 index 00000000..6f957278 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.java @@ -0,0 +1,60 @@ +package org.devgateway.toolkit.forms.validators; + +import org.apache.wicket.Component; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.validation.IValidatable; +import org.apache.wicket.validation.IValidator; +import org.apache.wicket.validation.ValidationError; +import org.devgateway.toolkit.persistence.dao.GenericPersistable; +import org.devgateway.toolkit.persistence.service.UniquePropertyService; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; + +/** + * @author Nadejda Mandrescu + */ +public class UniquePropertyValidator implements IValidator { + private static final long serialVersionUID = -7185120038483258730L; + + protected final UniquePropertyService uniquePropertyService; + protected final Collection entityIdsToIgnore; + protected final String propertyName; + protected final IModel propertyLabel; + + public UniquePropertyValidator(final UniquePropertyService uniquePropertyService, + final long entityIdToIgnore, final String propertyName, final Component component) { + this(uniquePropertyService, Collections.singleton(entityIdToIgnore), propertyName, component); + } + + public UniquePropertyValidator(final UniquePropertyService uniquePropertyService, + final Collection entityIdsToIgnore, final String propertyName, final Component component) { + this(uniquePropertyService, entityIdsToIgnore, propertyName, + new StringResourceModel(propertyName + ".label", component)); + } + + public UniquePropertyValidator(final UniquePropertyService uniquePropertyService, + final Collection entityIdsToIgnore, final String propertyName, final IModel propertyLabel) { + if (entityIdsToIgnore.isEmpty()) { + entityIdsToIgnore.add(-1L); + } + this.uniquePropertyService = uniquePropertyService; + this.entityIdsToIgnore = entityIdsToIgnore; + this.propertyName = propertyName; + this.propertyLabel = propertyLabel; + } + + @Override + public void validate(final IValidatable validatable) { + final V value = validatable.getValue(); + if (uniquePropertyService.existsByProperty(propertyName, value, entityIdsToIgnore)) { + ValidationError error = new ValidationError(this); + error.setVariable("label", propertyLabel.getObject()); + error.setVariable("input", value); + validatable.error(error); + } + } + +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.properties b/forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.properties new file mode 100644 index 00000000..d45cbbc4 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.properties @@ -0,0 +1 @@ +UniquePropertyValidator=${label} equal to '${input}' already exists diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java index 8e92a3c3..ddcb5af6 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java @@ -17,7 +17,9 @@ import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; +import org.apache.wicket.util.lang.Objects; import org.devgateway.toolkit.forms.security.SecurityConstants; +import org.devgateway.toolkit.forms.validators.UniquePropertyValidator; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxPickerBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxToggleBootstrapFormComponent; @@ -77,6 +79,8 @@ protected void onInitialize() { TextFieldBootstrapFormComponent textField = new TextFieldBootstrapFormComponent<>("textField"); editForm.add(textField); textField.required(); + textField.getField().add(new UniquePropertyValidator<>(testFormService, Objects.defaultIfNull(entityId, -1L), + "textField", this)); textField.enableRevisionsView(); diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/TestFormRepository.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/TestFormRepository.java index b432d313..d0e07353 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/TestFormRepository.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/TestFormRepository.java @@ -13,12 +13,14 @@ import org.devgateway.toolkit.persistence.dao.TestForm; import org.devgateway.toolkit.persistence.repository.norepository.BaseJpaRepository; +import org.devgateway.toolkit.persistence.repository.norepository.UniquePropertyRepository; import org.springframework.transaction.annotation.Transactional; /** * @author mpostelnicu */ @Transactional -public interface TestFormRepository extends BaseJpaRepository { +public interface TestFormRepository extends BaseJpaRepository, + UniquePropertyRepository { } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/SpecificationUtils.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/SpecificationUtils.java new file mode 100644 index 00000000..cc0d5fa0 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/SpecificationUtils.java @@ -0,0 +1,26 @@ +package org.devgateway.toolkit.persistence.repository.norepository; + +import org.apache.commons.lang3.StringUtils; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +/** + * @author Nadejda Mandrescu + */ +public final class SpecificationUtils { + private SpecificationUtils() { + } + + public static Expression ignoreCaseLikeValue(final CriteriaBuilder cb, final String value) { + return cb.lower(cb.concat(cb.concat(cb.literal("%"), value), "%")); + } + + public static Predicate equalIgnoreCaseValue(final Root root, final CriteriaBuilder cb, + final String propertyName, final String value) { + return cb.equal(cb.lower(root.get(propertyName)), StringUtils.lowerCase(value)); + } + +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/UniquePropertyRepository.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/UniquePropertyRepository.java new file mode 100644 index 00000000..6853ac8a --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/UniquePropertyRepository.java @@ -0,0 +1,38 @@ +package org.devgateway.toolkit.persistence.repository.norepository; + +import org.devgateway.toolkit.persistence.dao.GenericPersistable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Nadejda Mandrescu + */ +@NoRepositoryBean +@Transactional +public interface UniquePropertyRepository + extends BaseJpaRepository { + + default Specification getFindByPropertySpecification(final String propertyName, final Object propertyValue, + final Collection ignoreIds) { + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + Path propertyPath = root.get(propertyName); + if (String.class.isAssignableFrom(propertyPath.getJavaType())) { + predicates.add(SpecificationUtils.equalIgnoreCaseValue(root, cb, propertyName, (String) propertyValue)); + } else { + predicates.add(cb.equal(propertyPath, propertyValue)); + } + predicates.add(root.get("id").in(ignoreIds).not()); + return cb.and(predicates.toArray(new Predicate[predicates.size()])); + }; + }; + +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormService.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormService.java index 99ed4e64..eae176d6 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormService.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormService.java @@ -2,6 +2,6 @@ import org.devgateway.toolkit.persistence.dao.TestForm; -public interface TestFormService extends BaseJpaService { +public interface TestFormService extends BaseJpaService, UniquePropertyService { } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormServiceImpl.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormServiceImpl.java index f669b5e1..54019f33 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormServiceImpl.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/service/TestFormServiceImpl.java @@ -3,6 +3,7 @@ import org.devgateway.toolkit.persistence.dao.TestForm; import org.devgateway.toolkit.persistence.repository.TestFormRepository; import org.devgateway.toolkit.persistence.repository.norepository.BaseJpaRepository; +import org.devgateway.toolkit.persistence.repository.norepository.UniquePropertyRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; import org.springframework.stereotype.Service; @@ -23,6 +24,11 @@ protected BaseJpaRepository repository() { return testFormRepository; } + @Override + public UniquePropertyRepository uniquePropertyRepository() { + return testFormRepository; + } + @Override public TestForm newInstance() { return new TestForm(); diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/service/UniquePropertyService.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/service/UniquePropertyService.java new file mode 100644 index 00000000..de9b60ca --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/service/UniquePropertyService.java @@ -0,0 +1,31 @@ +package org.devgateway.toolkit.persistence.service; + +import org.devgateway.toolkit.persistence.dao.GenericPersistable; +import org.devgateway.toolkit.persistence.repository.norepository.UniquePropertyRepository; +import org.springframework.data.jpa.domain.Specification; + +import java.io.Serializable; +import java.util.Collection; + +/** + * @author Nadejda Mandrescu + */ +public interface UniquePropertyService { + UniquePropertyRepository uniquePropertyRepository(); + + default T findByProperty(final String propertyName, final Object propertyValue, final Collection ignoreIds) { + final Specification spec = + uniquePropertyRepository().getFindByPropertySpecification(propertyName, propertyValue, ignoreIds); + + return uniquePropertyRepository().findOne(spec).orElse(null); + } + + default boolean existsByProperty(final String propertyName, final Object propertyValue, + final Collection ignoreIds) { + final Specification spec = + uniquePropertyRepository().getFindByPropertySpecification(propertyName, propertyValue, ignoreIds); + + return uniquePropertyRepository().count(spec) > 0; + } + +}