Skip to content

Commit

Permalink
Merge pull request #91 from croz-ltd/feature_supportRangeQueriesInStr…
Browse files Browse the repository at this point in the history
…ingSearch

Feature support range queries in string search
  • Loading branch information
jsajlovic authored Apr 14, 2022
2 parents d66dfbb + 2c89a5e commit 59efdec
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -139,6 +140,7 @@ private List<RegistryDataInterceptor> interceptorList() {
private <T, P> Page<P> registryListInternal(ListRegistryRequest request) {
@SuppressWarnings("unchecked")
RegistryDataConfiguration<T, P> registryDataConfiguration = (RegistryDataConfiguration<T, P>) registryDataConfigurationHolder.findRegistryConfigurationForClass(request.getClassFullName());
SearchConfiguration<T, P, Map<String, Object>> searchConfiguration = registryDataConfiguration.getSearchConfiguration();

@SuppressWarnings("unchecked")
JpaQueryBuilder<T> queryBuilder = (JpaQueryBuilder<T>) classNameQueryBuilderMap.get(request.getClassFullName());
Expand All @@ -152,11 +154,12 @@ private <T, P> Page<P> registryListInternal(ListRegistryRequest request) {
Map<String, Object> 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<P> query = queryBuilder.buildQuery(searchRequestMap, registryDataConfiguration.getSearchConfiguration(), pageable.getSort());
CriteriaQuery<P> query = queryBuilder.buildQuery(searchRequestMap, searchConfiguration, pageable.getSort());

TypedQuery<P> typedQuery = entityManager.createQuery(query);

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String, Object> convert(String value, List<String> propertyToSearchList, ManagedType<?> managedType);
Map<String, Object> convert(String value, List<String> propertyToSearchList, ManagedType<?> managedType, SearchPropertyConfiguration searchPropertyConfiguration);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,7 +22,7 @@ public class DefaultStringToEntityPropertyMapConverter implements StringToEntity
private final List<StringToTypeConverter<?>> converterList;

@Override
public Map<String, Object> convert(String value, List<String> propertyToSearchList, ManagedType<?> managedType) {
public Map<String, Object> convert(String value, List<String> propertyToSearchList, ManagedType<?> managedType, SearchPropertyConfiguration searchPropertyConfiguration) {
if (value == null || CollectionUtils.isEmpty(propertyToSearchList)) {
return Collections.emptyMap();
}
Expand All @@ -34,7 +36,13 @@ public Map<String, Object> convert(String value, List<String> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -111,18 +112,7 @@ private boolean shouldIncludeField(List<String> 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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,20 +29,20 @@ public class JpaStringSearchExecutor<T> implements StringSearchExecutor<T> {

private final EntityManager entityManager;

private final JpaEntityInformation<T, ?> jpaEntityInformation;

private final JpaQueryBuilder<T> queryBuilder;

private final ManagedType<?> managedType;

public JpaStringSearchExecutor(StringToEntityPropertyMapConverter stringToEntityPropertyMapConverter, EntityManager entityManager, JpaEntityInformation<T, ?> jpaEntityInformation) {
this.stringToEntityPropertyMapConverter = stringToEntityPropertyMapConverter;
this.entityManager = entityManager;
this.jpaEntityInformation = jpaEntityInformation;
this.queryBuilder = new JpaQueryBuilder<>(entityManager, jpaEntityInformation.getJavaType());
this.managedType = jpaEntityInformation.getRequiredIdAttribute().getDeclaringType();
}

@Override
public <P> Optional<P> findOne(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList);
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);

CriteriaQuery<P> query = queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted());

Expand All @@ -55,7 +56,7 @@ public <P> Optional<P> findOne(String searchTerm, List<String> propertyToSearchL

@Override
public <P> List<P> findAll(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList);
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);

CriteriaQuery<P> query = queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted());

Expand All @@ -64,7 +65,7 @@ public <P> List<P> findAll(String searchTerm, List<String> propertyToSearchList,

@Override
public <P> List<P> findAll(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration, Sort sort) {
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList);
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);

CriteriaQuery<P> query = queryBuilder.buildQuery(searchMap, searchConfiguration, sort);

Expand All @@ -73,7 +74,7 @@ public <P> List<P> findAll(String searchTerm, List<String> propertyToSearchList,

@Override
public <P> Page<P> findAll(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration, Pageable pageable) {
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList);
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);

CriteriaQuery<P> query = queryBuilder.buildQuery(searchMap, searchConfiguration, pageable.getSort());
TypedQuery<P> typedQuery = entityManager.createQuery(query);
Expand All @@ -89,20 +90,20 @@ public <P> Page<P> findAll(String searchTerm, List<String> propertyToSearchList,

@Override
public <P> long count(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList);
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);

return executeCountQuery(queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted()));
}

@Override
public <P> boolean exists(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList);
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);

return executeCountQuery(queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted())) > 0;
}

private Map<String, Object> convertToMap(String searchTerm, List<String> propertyToSearchList) {
return stringToEntityPropertyMapConverter.convert(searchTerm, propertyToSearchList, jpaEntityInformation.getRequiredIdAttribute().getDeclaringType());
private Map<String, Object> convertToMap(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, ?, Map<String, Object>> searchConfiguration) {
return stringToEntityPropertyMapConverter.convert(searchTerm, propertyToSearchList, managedType, searchConfiguration.getSearchPropertyConfiguration());
}

private long executeCountQuery(CriteriaQuery<?> query) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -18,6 +20,8 @@
@SpringJUnitConfig(SearchTestConfiguration.class)
class DefaultStringToEntityPropertyMapConverterTest {

private static final SearchPropertyConfiguration PROPERTY_CONFIGURATION = SearchPropertyConfiguration.defaultSearchPropertyConfiguration();

@Autowired
private DefaultStringToEntityPropertyMapConverter stringToEntityPropertyMapConverter;

Expand All @@ -30,7 +34,7 @@ void shouldConvertStringToEntityPropertyMap() {
String value = "01.01.1970.";

// when
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "date", "nestedEntity.nestedName"), managedTypeOfTestEntity());
Map<String, Object> 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);
Expand All @@ -42,7 +46,7 @@ void shouldNotFailOnNonExistingAttributes() {
String value = "name";

// when
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity());
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity(), PROPERTY_CONFIGURATION);

// then
assertThat(result).containsEntry("name", value);
Expand All @@ -55,7 +59,7 @@ void shouldReturnEmptyMapWhenValueIsNull() {
String value = null;

// when
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity());
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, Arrays.asList("name", "nonExisting"), managedTypeOfTestEntity(), PROPERTY_CONFIGURATION);

// then
assertThat(result).isEmpty();
Expand All @@ -67,12 +71,25 @@ void shouldReturnEmptyMapWhenPropertyListIsEmpty() {
String value = "value";

// when
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, null, managedTypeOfTestEntity());
Map<String, Object> result = stringToEntityPropertyMapConverter.convert(value, null, managedTypeOfTestEntity(), PROPERTY_CONFIGURATION);

// then
assertThat(result).isEmpty();
}

@Test
void shouldConvertPropertyWithSuffix() {
// given
String value = "01.01.1970.";
List<String> propertyToSearchList = Arrays.asList("dateFrom", "dateFromIncluding", "dateTo", "dateToIncluding");

// when
Map<String, Object> 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);
}
Expand Down
Loading

0 comments on commit 59efdec

Please sign in to comment.