From 5c8d64d2847b2a27a43361168e1a9bc2b35434a2 Mon Sep 17 00:00:00 2001 From: mpostelnicu Date: Mon, 19 Oct 2020 08:44:32 +0300 Subject: [PATCH 1/3] #325 simple optimistic lock, without error page customization --- .../forms/wicket/page/user/EditUserPage.java | 4 +- .../persistence/dao/GenericPersistable.java | 13 ++++++ .../main/resources/liquibase-changelog.xml | 42 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java index 351539a0..880245bf 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java @@ -205,8 +205,6 @@ public SaveEditPageButton getSaveEditPageButton() { @Override protected void onSubmit(final AjaxRequestTarget target) { - super.onSubmit(target); - final Person person = editForm.getModelObject(); // encode the password if (person.getChangeProfilePassword()) { @@ -218,7 +216,7 @@ protected void onSubmit(final AjaxRequestTarget target) { person.setChangePasswordNextSignIn(false); } - jpaService.save(person); + super.onSubmit(target); setResponsePage(EditUserPage.this.getResponsePage()); } }; diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/GenericPersistable.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/GenericPersistable.java index e70bcee3..1b89f526 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/GenericPersistable.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/GenericPersistable.java @@ -22,12 +22,21 @@ import nl.dries.wicket.hibernate.dozer.proxy.Proxied; import org.springframework.data.jpa.domain.AbstractPersistable; +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import javax.persistence.Version; + /** * @author mpostelnicu * */ +@MappedSuperclass public class GenericPersistable extends AbstractPersistable implements Serializable { + @Version + @Column(name = "optlock") + private Integer version; + /** * Custom serialization for id is needed since Spring Data JPA 2.x AbstractPersistable no longer implements * Serializable. @@ -48,4 +57,8 @@ private void readObject(final ObjectInputStream in) throws IOException, ClassNot in.defaultReadObject(); } + + public Integer getVersion() { + return version; + } } diff --git a/persistence/src/main/resources/liquibase-changelog.xml b/persistence/src/main/resources/liquibase-changelog.xml index 7ebe70e1..904251b3 100644 --- a/persistence/src/main/resources/liquibase-changelog.xml +++ b/persistence/src/main/resources/liquibase-changelog.xml @@ -60,4 +60,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f826f3d2b571d0ed3d9ba3b7536450da880093c5 Mon Sep 17 00:00:00 2001 From: Octavian Ciubotaru Date: Fri, 23 Oct 2020 16:33:21 +0300 Subject: [PATCH 2/3] #325 Show an error dialog if the object was modified by someone else --- .../wicket/page/edit/AbstractEditPage.html | 1 + .../wicket/page/edit/AbstractEditPage.java | 90 +++++++++++++------ .../page/edit/AbstractEditPage.properties | 2 + .../forms/wicket/page/user/EditUserPage.java | 6 +- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html index 04371415..ce5c4283 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html @@ -17,6 +17,7 @@
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java index 66e48448..a9f3b0f5 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java @@ -12,12 +12,12 @@ package org.devgateway.toolkit.forms.wicket.page.edit; import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; -import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationMessage; import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.TextContentModal; import de.agilecoders.wicket.core.markup.html.bootstrap.form.BootstrapForm; import de.agilecoders.wicket.core.util.Attributes; import de.agilecoders.wicket.extensions.markup.html.bootstrap.ladda.LaddaAjaxButton; import nl.dries.wicket.hibernate.dozer.DozerModel; +import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; @@ -30,7 +30,6 @@ import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.apache.wicket.util.time.Duration; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; import org.apache.wicket.validation.ValidationError; @@ -51,6 +50,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import javax.persistence.EntityManager; import java.io.Serializable; @@ -116,6 +116,8 @@ private T newInstance() { protected TextContentModal deleteFailedModal; + protected TextContentModal saveFailedModal; + @SpringBean private EntityManager entityManager; @@ -178,6 +180,7 @@ protected void onSubmit(final AjaxRequestTarget target) { protected TextContentModal createDeleteFailedModal() { final TextContentModal modal = new TextContentModal("deleteFailedModal", new ResourceModel("delete_error_message")); + modal.header(new ResourceModel("error")); final LaddaAjaxButton deleteButton = new LaddaAjaxButton("button", Buttons.Type.Info) { @Override protected void onSubmit(final AjaxRequestTarget target) { @@ -198,6 +201,30 @@ protected void onEvent(final AjaxRequestTarget target) { return modal; } + protected TextContentModal createSaveFailedModal() { + final TextContentModal modal = new TextContentModal("saveFailedModal", + new ResourceModel("optimistic_lock_error_message")); + modal.header(new ResourceModel("error")); + final LaddaAjaxButton okButton = new LaddaAjaxButton("button", Buttons.Type.Info) { + @Override + protected void onSubmit(final AjaxRequestTarget target) { + setResponsePage(listPageClass); + } + }; + okButton.setDefaultFormProcessing(false); + okButton.setLabel(Model.of("OK")); + modal.addButton(okButton); + + modal.add(new AjaxEventBehavior("hidden.bs.modal") { + @Override + protected void onEvent(final AjaxRequestTarget target) { + setResponsePage(listPageClass); + } + }); + + return modal; + } + /** * Traverses all fields and refreshes the ones that are not valid, so that * we can see the errors @@ -277,6 +304,9 @@ public EditForm(final String id) { deleteFailedModal = createDeleteFailedModal(); add(deleteFailedModal); + saveFailedModal = createSaveFailedModal(); + add(saveFailedModal); + // don't display the delete button if we just create a new entity if (entityId == null) { deleteButton.setVisibilityAllowed(false); @@ -298,7 +328,7 @@ protected void onSubmit(final AjaxRequestTarget target) { } /** - * Generic funcionality for the save page button, this can be extended + * Generic functionality for the save page button, this can be extended * further by subclasses * * @author mpostelnicu @@ -316,32 +346,36 @@ public SaveEditPageButton(final String id, final IModel model) { @Override protected void onSubmit(final AjaxRequestTarget target) { - // save the object and go back to the list page - T saveable = editForm.getModelObject(); - - // saves the entity and flushes the changes - jpaService.saveAndFlush(saveable); - - // clears session and detaches all entities that are currently - // attached - entityManager.clear(); - - // we flush the mondrian/wicket/reports cache to ensure it gets rebuilt - flushReportingCaches(); + try { + // save the object and go back to the list page + T saveable = editForm.getModelObject(); + + // saves the entity and flushes the changes + jpaService.saveAndFlush(saveable); + + // clears session and detaches all entities that are currently + // attached + entityManager.clear(); + + // we flush the mondrian/wicket/reports cache to ensure it gets rebuilt + flushReportingCaches(); + + // only redirect if redirect is true + if (redirectToSelf) { + // we need to close the blockUI if it's opened and enable all + // the buttons + target.appendJavaScript("$.unblockUI();"); + target.appendJavaScript("$('#" + editForm.getMarkupId() + " button').prop('disabled', false);"); + } else if (redirect) { + setResponsePage(getResponsePage(), getParameterPage()); + } - // only redirect if redirect is true - if (redirectToSelf) { - // we need to close the blockUI if it's opened and enable all - // the buttons - target.appendJavaScript("$.unblockUI();"); - target.appendJavaScript("$('#" + editForm.getMarkupId() + " button').prop('disabled', false);"); - } else if (redirect) { - setResponsePage(getResponsePage(), getParameterPage()); + // redirect is set back to true, which is the default behavior + redirect = true; + redirectToSelf = false; + } catch (ObjectOptimisticLockingFailureException e) { + saveFailedModal.show(target); } - - // redirect is set back to true, which is the default behavior - redirect = true; - redirectToSelf = false; } /** @@ -349,7 +383,7 @@ protected void onSubmit(final AjaxRequestTarget target) { * * @return */ - protected Class getResponsePage() { + protected Class getResponsePage() { return listPageClass; } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.properties index 19745128..313d965e 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.properties @@ -24,4 +24,6 @@ deleteButton=Delete saveButton=Save saveDraft=Save Draft formHasErrors=The form has errors or is missing information and cannot be submitted. Please scroll up, fix all the errors (highlighted in red) and re-submit! +error=Error delete_error_message=Cannot delete entity, it is in use by the system +optimistic_lock_error_message=Changes were not saved. This object was modified by someone else. diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java index 880245bf..7569b814 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java @@ -217,7 +217,11 @@ protected void onSubmit(final AjaxRequestTarget target) { } super.onSubmit(target); - setResponsePage(EditUserPage.this.getResponsePage()); + } + + @Override + protected Class getResponsePage() { + return EditUserPage.this.getResponsePage(); } }; } From 3ace498a167da3bf1687fbdc27f86232ca639567 Mon Sep 17 00:00:00 2001 From: nmandrescu <17854915+nmandrescu@users.noreply.github.com> Date: Wed, 11 Nov 2020 12:40:52 +0200 Subject: [PATCH 3/3] unique property validator --- .../validators/UniquePropertyValidator.java | 60 +++++++++++++++++++ .../UniquePropertyValidator.properties | 1 + .../wicket/page/edit/EditTestFormPage.java | 4 ++ .../repository/TestFormRepository.java | 4 +- .../norepository/SpecificationUtils.java | 26 ++++++++ .../UniquePropertyRepository.java | 38 ++++++++++++ .../persistence/service/TestFormService.java | 2 +- .../service/TestFormServiceImpl.java | 6 ++ .../service/UniquePropertyService.java | 31 ++++++++++ 9 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.java create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/validators/UniquePropertyValidator.properties create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/SpecificationUtils.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/repository/norepository/UniquePropertyRepository.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/service/UniquePropertyService.java 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 5d4435a3..b5a37614 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; + } + +}