diff --git a/nrich-registry/src/main/java/net/croz/nrich/registry/data/service/DefaultRegistryDataService.java b/nrich-registry/src/main/java/net/croz/nrich/registry/data/service/DefaultRegistryDataService.java index f79a927a7..f138d9b2a 100644 --- a/nrich-registry/src/main/java/net/croz/nrich/registry/data/service/DefaultRegistryDataService.java +++ b/nrich-registry/src/main/java/net/croz/nrich/registry/data/service/DefaultRegistryDataService.java @@ -9,6 +9,7 @@ import net.croz.nrich.registry.core.model.RegistryDataConfigurationHolder; import net.croz.nrich.registry.core.support.ManagedTypeWrapper; import net.croz.nrich.search.api.converter.StringToEntityPropertyMapConverter; +import net.croz.nrich.search.api.model.SearchConfiguration; import net.croz.nrich.search.api.model.sort.SortDirection; import net.croz.nrich.search.api.model.sort.SortProperty; import net.croz.nrich.search.api.util.PageableUtil; @@ -139,6 +140,7 @@ private List interceptorList() { private Page

registryListInternal(ListRegistryRequest request) { @SuppressWarnings("unchecked") RegistryDataConfiguration registryDataConfiguration = (RegistryDataConfiguration) registryDataConfigurationHolder.findRegistryConfigurationForClass(request.getClassFullName()); + SearchConfiguration> searchConfiguration = registryDataConfiguration.getSearchConfiguration(); @SuppressWarnings("unchecked") JpaQueryBuilder queryBuilder = (JpaQueryBuilder) classNameQueryBuilderMap.get(request.getClassFullName()); @@ -152,11 +154,12 @@ private Page

registryListInternal(ListRegistryRequest request) { Map searchRequestMap = Collections.emptyMap(); if (request.getSearchParameter() != null) { searchRequestMap = stringToEntityPropertyMapConverter.convert( - request.getSearchParameter().getQuery(), request.getSearchParameter().getPropertyNameList(), managedTypeWrapper.getIdentifiableType() + request.getSearchParameter().getQuery(), request.getSearchParameter().getPropertyNameList(), managedTypeWrapper.getIdentifiableType(), + searchConfiguration.getSearchPropertyConfiguration() ); } - CriteriaQuery

query = queryBuilder.buildQuery(searchRequestMap, registryDataConfiguration.getSearchConfiguration(), pageable.getSort()); + CriteriaQuery

query = queryBuilder.buildQuery(searchRequestMap, searchConfiguration, pageable.getSort()); TypedQuery

typedQuery = entityManager.createQuery(query); diff --git a/nrich-search-api/src/main/java/net/croz/nrich/search/api/converter/StringToEntityPropertyMapConverter.java b/nrich-search-api/src/main/java/net/croz/nrich/search/api/converter/StringToEntityPropertyMapConverter.java index 68b3ca607..bde624ce3 100644 --- a/nrich-search-api/src/main/java/net/croz/nrich/search/api/converter/StringToEntityPropertyMapConverter.java +++ b/nrich-search-api/src/main/java/net/croz/nrich/search/api/converter/StringToEntityPropertyMapConverter.java @@ -1,5 +1,7 @@ package net.croz.nrich.search.api.converter; +import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration; + import javax.persistence.metamodel.ManagedType; import java.util.List; import java.util.Map; @@ -13,11 +15,12 @@ public interface StringToEntityPropertyMapConverter { /** * Returns a map containing property name and property value. Resolved from propertyToSearchList found on {@link ManagedType} that can be converted from passed in string. * - * @param value value to convert - * @param propertyToSearchList list of properties to convert to - * @param managedType entity managed type + * @param value value to convert + * @param propertyToSearchList list of properties to convert to + * @param managedType entity managed type + * @param searchPropertyConfiguration search property configuration * @return map with all properties for which conversion succeeded */ - Map convert(String value, List propertyToSearchList, ManagedType managedType); + Map convert(String value, List propertyToSearchList, ManagedType managedType, SearchPropertyConfiguration searchPropertyConfiguration); } diff --git a/nrich-search/src/main/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverter.java b/nrich-search/src/main/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverter.java index 4066b60b5..c888c8150 100644 --- a/nrich-search/src/main/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverter.java +++ b/nrich-search/src/main/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverter.java @@ -3,8 +3,10 @@ import lombok.RequiredArgsConstructor; import net.croz.nrich.search.api.converter.StringToEntityPropertyMapConverter; import net.croz.nrich.search.api.converter.StringToTypeConverter; +import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration; import net.croz.nrich.search.model.AttributeHolder; import net.croz.nrich.search.support.JpaEntityAttributeResolver; +import net.croz.nrich.search.util.PropertyNameUtil; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -20,7 +22,7 @@ public class DefaultStringToEntityPropertyMapConverter implements StringToEntity private final List> converterList; @Override - public Map convert(String value, List propertyToSearchList, ManagedType managedType) { + public Map convert(String value, List propertyToSearchList, ManagedType managedType, SearchPropertyConfiguration searchPropertyConfiguration) { if (value == null || CollectionUtils.isEmpty(propertyToSearchList)) { return Collections.emptyMap(); } @@ -34,7 +36,13 @@ public Map convert(String value, List propertyToSearchLi propertyToSearchList.forEach(property -> { AttributeHolder attributeHolder = attributeResolver.resolveAttributeByPath(property); - if (attributeHolder.getAttribute() == null) { + if (!attributeHolder.isFound()) { + String propertyWithoutSuffix = PropertyNameUtil.propertyNameWithoutSuffix(property, searchPropertyConfiguration); + + attributeHolder = attributeResolver.resolveAttributeByPath(propertyWithoutSuffix); + } + + if (!attributeHolder.isFound()) { return; } diff --git a/nrich-search/src/main/java/net/croz/nrich/search/parser/SearchDataParser.java b/nrich-search/src/main/java/net/croz/nrich/search/parser/SearchDataParser.java index 66a76d9fc..d04e8b4a2 100644 --- a/nrich-search/src/main/java/net/croz/nrich/search/parser/SearchDataParser.java +++ b/nrich-search/src/main/java/net/croz/nrich/search/parser/SearchDataParser.java @@ -13,6 +13,7 @@ import net.croz.nrich.search.model.SearchDataParserConfiguration; import net.croz.nrich.search.support.JpaEntityAttributeResolver; import net.croz.nrich.search.util.PathResolvingUtil; +import net.croz.nrich.search.util.PropertyNameUtil; import org.springframework.util.StringUtils; import javax.persistence.metamodel.Attribute; @@ -111,18 +112,7 @@ private boolean shouldIncludeField(List ignoredFieldList, Field field) { } private String fieldNameWithoutSuffixAndPrefix(String originalFieldName, String prefix) { - SearchPropertyConfiguration searchPropertyConfiguration = searchConfiguration.getSearchPropertyConfiguration(); - String[] suffixListToRemove = new String[] { - searchPropertyConfiguration.getRangeQueryFromIncludingSuffix(), searchPropertyConfiguration.getRangeQueryFromSuffix(), searchPropertyConfiguration.getRangeQueryToIncludingSuffix(), - searchPropertyConfiguration.getRangeQueryToSuffix(), searchPropertyConfiguration.getCollectionQuerySuffix() - }; - String fieldName = originalFieldName; - for (String suffix : suffixListToRemove) { - if (originalFieldName.endsWith(suffix)) { - fieldName = originalFieldName.substring(0, originalFieldName.lastIndexOf(suffix)); - break; - } - } + String fieldName = PropertyNameUtil.propertyNameWithoutSuffix(originalFieldName, searchConfiguration.getSearchPropertyConfiguration()); if (prefix != null && fieldName.length() > prefix.length()) { return StringUtils.uncapitalize(fieldName.substring(prefix.length())); diff --git a/nrich-search/src/main/java/net/croz/nrich/search/repository/JpaStringSearchExecutor.java b/nrich-search/src/main/java/net/croz/nrich/search/repository/JpaStringSearchExecutor.java index 49bab94fb..a105e32f9 100644 --- a/nrich-search/src/main/java/net/croz/nrich/search/repository/JpaStringSearchExecutor.java +++ b/nrich-search/src/main/java/net/croz/nrich/search/repository/JpaStringSearchExecutor.java @@ -17,6 +17,7 @@ import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.metamodel.ManagedType; import java.util.List; import java.util.Map; import java.util.Optional; @@ -28,20 +29,20 @@ public class JpaStringSearchExecutor implements StringSearchExecutor { private final EntityManager entityManager; - private final JpaEntityInformation jpaEntityInformation; - private final JpaQueryBuilder queryBuilder; + private final ManagedType managedType; + public JpaStringSearchExecutor(StringToEntityPropertyMapConverter stringToEntityPropertyMapConverter, EntityManager entityManager, JpaEntityInformation jpaEntityInformation) { this.stringToEntityPropertyMapConverter = stringToEntityPropertyMapConverter; this.entityManager = entityManager; - this.jpaEntityInformation = jpaEntityInformation; this.queryBuilder = new JpaQueryBuilder<>(entityManager, jpaEntityInformation.getJavaType()); + this.managedType = jpaEntityInformation.getRequiredIdAttribute().getDeclaringType(); } @Override public

Optional

findOne(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration) { - Map searchMap = convertToMap(searchTerm, propertyToSearchList); + Map searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration); CriteriaQuery

query = queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted()); @@ -55,7 +56,7 @@ public

Optional

findOne(String searchTerm, List propertyToSearchL @Override public

List

findAll(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration) { - Map searchMap = convertToMap(searchTerm, propertyToSearchList); + Map searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration); CriteriaQuery

query = queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted()); @@ -64,7 +65,7 @@ public

List

findAll(String searchTerm, List propertyToSearchList, @Override public

List

findAll(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration, Sort sort) { - Map searchMap = convertToMap(searchTerm, propertyToSearchList); + Map searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration); CriteriaQuery

query = queryBuilder.buildQuery(searchMap, searchConfiguration, sort); @@ -73,7 +74,7 @@ public

List

findAll(String searchTerm, List propertyToSearchList, @Override public

Page

findAll(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration, Pageable pageable) { - Map searchMap = convertToMap(searchTerm, propertyToSearchList); + Map searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration); CriteriaQuery

query = queryBuilder.buildQuery(searchMap, searchConfiguration, pageable.getSort()); TypedQuery

typedQuery = entityManager.createQuery(query); @@ -89,20 +90,20 @@ public

Page

findAll(String searchTerm, List propertyToSearchList, @Override public

long count(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration) { - Map searchMap = convertToMap(searchTerm, propertyToSearchList); + Map searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration); return executeCountQuery(queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted())); } @Override public

boolean exists(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration) { - Map searchMap = convertToMap(searchTerm, propertyToSearchList); + Map searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration); return executeCountQuery(queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted())) > 0; } - private Map convertToMap(String searchTerm, List propertyToSearchList) { - return stringToEntityPropertyMapConverter.convert(searchTerm, propertyToSearchList, jpaEntityInformation.getRequiredIdAttribute().getDeclaringType()); + private Map convertToMap(String searchTerm, List propertyToSearchList, SearchConfiguration> searchConfiguration) { + return stringToEntityPropertyMapConverter.convert(searchTerm, propertyToSearchList, managedType, searchConfiguration.getSearchPropertyConfiguration()); } private long executeCountQuery(CriteriaQuery query) { diff --git a/nrich-search/src/main/java/net/croz/nrich/search/util/PropertyNameUtil.java b/nrich-search/src/main/java/net/croz/nrich/search/util/PropertyNameUtil.java new file mode 100644 index 000000000..a1b399c66 --- /dev/null +++ b/nrich-search/src/main/java/net/croz/nrich/search/util/PropertyNameUtil.java @@ -0,0 +1,26 @@ +package net.croz.nrich.search.util; + +import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration; + +public final class PropertyNameUtil { + + private PropertyNameUtil() { + } + + public static String propertyNameWithoutSuffix(String originalPropertyName, SearchPropertyConfiguration searchPropertyConfiguration) { + String[] suffixListToRemove = new String[] { + searchPropertyConfiguration.getRangeQueryFromIncludingSuffix(), searchPropertyConfiguration.getRangeQueryFromSuffix(), searchPropertyConfiguration.getRangeQueryToIncludingSuffix(), + searchPropertyConfiguration.getRangeQueryToSuffix(), searchPropertyConfiguration.getCollectionQuerySuffix() + }; + + String propertyName = originalPropertyName; + for (String suffix : suffixListToRemove) { + if (originalPropertyName.endsWith(suffix)) { + propertyName = originalPropertyName.substring(0, originalPropertyName.lastIndexOf(suffix)); + break; + } + } + + return propertyName; + } +} diff --git a/nrich-search/src/test/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverterTest.java b/nrich-search/src/test/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverterTest.java index bee126afa..fb5f8a06e 100644 --- a/nrich-search/src/test/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverterTest.java +++ b/nrich-search/src/test/java/net/croz/nrich/search/converter/DefaultStringToEntityPropertyMapConverterTest.java @@ -1,6 +1,7 @@ package net.croz.nrich.search.converter; import net.croz.nrich.search.SearchTestConfiguration; +import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration; import net.croz.nrich.search.converter.stub.DefaultStringToEntityPropertyMapConverterTestEntity; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -10,6 +11,7 @@ import javax.persistence.PersistenceContext; import javax.persistence.metamodel.ManagedType; import java.util.Arrays; +import java.util.List; import java.util.Map; import static net.croz.nrich.search.converter.testutil.ConverterGeneratingUtil.dateOf; @@ -18,6 +20,8 @@ @SpringJUnitConfig(SearchTestConfiguration.class) class DefaultStringToEntityPropertyMapConverterTest { + private static final SearchPropertyConfiguration PROPERTY_CONFIGURATION = SearchPropertyConfiguration.defaultSearchPropertyConfiguration(); + @Autowired private DefaultStringToEntityPropertyMapConverter stringToEntityPropertyMapConverter; @@ -30,7 +34,7 @@ void shouldConvertStringToEntityPropertyMap() { String value = "01.01.1970."; // when - Map result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "date", "nestedEntity.nestedName"), managedTypeOfTestEntity()); + Map result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "date", "nestedEntity.nestedName"), managedTypeOfTestEntity(), PROPERTY_CONFIGURATION); // then assertThat(result).containsEntry("name", value).containsEntry("date", dateOf(value)).containsEntry("nestedEntity.nestedName", value); @@ -42,7 +46,7 @@ void shouldNotFailOnNonExistingAttributes() { String value = "name"; // when - Map result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity()); + Map result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity(), PROPERTY_CONFIGURATION); // then assertThat(result).containsEntry("name", value); @@ -55,7 +59,7 @@ void shouldReturnEmptyMapWhenValueIsNull() { String value = null; // when - Map result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity()); + Map result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity(), PROPERTY_CONFIGURATION); // then assertThat(result).isEmpty(); @@ -67,12 +71,25 @@ void shouldReturnEmptyMapWhenPropertyListIsEmpty() { String value = "value"; // when - Map result = stringToEntityPropertyMapConverter.convert(value, null, managedTypeOfTestEntity()); + Map result = stringToEntityPropertyMapConverter.convert(value, null, managedTypeOfTestEntity(), PROPERTY_CONFIGURATION); // then assertThat(result).isEmpty(); } + @Test + void shouldConvertPropertyWithSuffix() { + // given + String value = "01.01.1970."; + List propertyToSearchList = Arrays.asList("dateFrom", "dateFromIncluding", "dateTo", "dateToIncluding"); + + // when + Map result = stringToEntityPropertyMapConverter.convert(value, propertyToSearchList, managedTypeOfTestEntity(), PROPERTY_CONFIGURATION); + + // then + assertThat(result).containsKeys(propertyToSearchList.toArray(new String[0])).containsValue(dateOf(value)); + } + private ManagedType managedTypeOfTestEntity() { return entityManager.getMetamodel().managedType((Class) DefaultStringToEntityPropertyMapConverterTestEntity.class); } diff --git a/nrich-search/src/test/java/net/croz/nrich/search/repository/JpaStringSearchExecutorTest.java b/nrich-search/src/test/java/net/croz/nrich/search/repository/JpaStringSearchExecutorTest.java index f9f7abd4e..58a33ac75 100644 --- a/nrich-search/src/test/java/net/croz/nrich/search/repository/JpaStringSearchExecutorTest.java +++ b/nrich-search/src/test/java/net/croz/nrich/search/repository/JpaStringSearchExecutorTest.java @@ -4,6 +4,7 @@ import net.croz.nrich.search.api.model.SearchConfiguration; import net.croz.nrich.search.repository.stub.TestEntityStringSearchRepository; import net.croz.nrich.search.repository.stub.TestStringSearchEntity; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -36,11 +37,13 @@ class JpaStringSearchExecutorTest { @PersistenceContext private EntityManager entityManager; - @Test - void shouldFindOne() { - // given + @BeforeEach + void setup() { generateListForStringSearch(entityManager); + } + @Test + void shouldFindOne() { // when Optional result = testEntityStringSearchRepository.findOne("01.01.1970.", Collections.singletonList("localDate"), EMPTY_CONFIGURATION); @@ -50,9 +53,6 @@ void shouldFindOne() { @Test void shouldReturnEmptyOptionalWhenNoResultsHaveBeenFound() { - // given - generateListForStringSearch(entityManager); - // when Optional result = testEntityStringSearchRepository.findOne("01.01.2000.", Collections.singletonList("localDate"), EMPTY_CONFIGURATION); @@ -63,8 +63,6 @@ void shouldReturnEmptyOptionalWhenNoResultsHaveBeenFound() { @Test void shouldFindOneMatchingAnyProperty() { // given - generateListForStringSearch(entityManager); - SearchConfiguration> searchConfiguration = SearchConfiguration.emptyConfigurationMatchingAny(); // when @@ -76,9 +74,6 @@ void shouldFindOneMatchingAnyProperty() { @Test void shouldFindAll() { - // given - generateListForStringSearch(entityManager); - // when List result = testEntityStringSearchRepository.findAll("name 1", Collections.singletonList("name"), EMPTY_CONFIGURATION); @@ -88,9 +83,6 @@ void shouldFindAll() { @Test void shouldFindAllWithSort() { - // given - generateListForStringSearch(entityManager); - // when List result = testEntityStringSearchRepository.findAll("name", Collections.singletonList("name"), EMPTY_CONFIGURATION, Sort.by(Sort.Order.desc("name"))); @@ -101,9 +93,6 @@ void shouldFindAllWithSort() { @Test void shouldFindAllWithPaging() { - // given - generateListForStringSearch(entityManager); - // when Page result = testEntityStringSearchRepository.findAll("51", Collections.singletonList("age"), EMPTY_CONFIGURATION, PageRequest.of(0, 1)); @@ -113,9 +102,6 @@ void shouldFindAllWithPaging() { @Test void shouldReturnWholeResultListWhenRequestIsUnpaged() { - // given - generateListForStringSearch(entityManager); - // when Page result = testEntityStringSearchRepository.findAll("10", Collections.singletonList("ageFrom"), EMPTY_CONFIGURATION, Pageable.unpaged()); @@ -127,9 +113,6 @@ void shouldReturnWholeResultListWhenRequestIsUnpaged() { @Test void shouldCount() { - // given - generateListForStringSearch(entityManager); - // when long result = testEntityStringSearchRepository.count("51", Collections.singletonList("age"), EMPTY_CONFIGURATION); @@ -139,9 +122,6 @@ void shouldCount() { @Test void shouldReturnZeroWhenThereAreNoResults() { - // given - generateListForStringSearch(entityManager); - // when long result = testEntityStringSearchRepository.count("5555", Collections.singletonList("age"), EMPTY_CONFIGURATION); @@ -151,9 +131,6 @@ void shouldReturnZeroWhenThereAreNoResults() { @Test void shouldReturnTrueWhenEntityExists() { - // given - generateListForStringSearch(entityManager); - // when boolean result = testEntityStringSearchRepository.exists("51", Collections.singletonList("age"), EMPTY_CONFIGURATION); @@ -163,13 +140,19 @@ void shouldReturnTrueWhenEntityExists() { @Test void shouldReturnFalseWhenEntityDoesntExist() { - // given - generateListForStringSearch(entityManager); - // when boolean result = testEntityStringSearchRepository.exists("51111", Collections.singletonList("age"), EMPTY_CONFIGURATION); // then assertThat(result).isFalse(); } + + @Test + void shouldFindByRangeQuery() { + // when + long result = testEntityStringSearchRepository.count("02.01.1970.", Collections.singletonList("localDateFrom"), EMPTY_CONFIGURATION); + + // then + assertThat(result).isEqualTo(3); + } }