net.croz.nrich.search.parser.SearchDataParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nrich-search Show documentation
Show all versions of nrich-search Show documentation
Provides an easy way of querying of JPA entities through automatic query creation from passed in data (either strings or classes that hold restriction values)
The newest version!
/*
* Copyright 2020-2023 CROZ d.o.o, the original author or authors.
*
* 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
*
* https://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 net.croz.nrich.search.parser;
import lombok.RequiredArgsConstructor;
import net.croz.nrich.search.api.model.operator.DefaultSearchOperator;
import net.croz.nrich.search.api.model.operator.SearchOperator;
import net.croz.nrich.search.api.model.operator.SearchOperatorOverride;
import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration;
import net.croz.nrich.search.api.model.property.SearchPropertyMapping;
import net.croz.nrich.search.bean.MapSupportingDirectFieldAccessFallbackBeanWrapper;
import net.croz.nrich.search.model.AttributeHolder;
import net.croz.nrich.search.model.AttributeHolderWithPath;
import net.croz.nrich.search.model.Restriction;
import net.croz.nrich.search.model.SearchDataParserConfiguration;
import net.croz.nrich.search.support.JpaEntityAttributeResolver;
import net.croz.nrich.search.util.FieldExtractionUtil;
import net.croz.nrich.search.util.PathResolvingUtil;
import net.croz.nrich.search.util.PropertyNameUtil;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.ManagedType;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
@RequiredArgsConstructor
public class SearchDataParser {
private final ManagedType> managedType;
private final Object searchData;
private final SearchDataParserConfiguration searchConfiguration;
public Set resolveRestrictionList() {
return resolveRestrictionList(null);
}
public Set resolveRestrictionList(String propertyPrefix) {
return resolveRestrictionListInternal(new MapSupportingDirectFieldAccessFallbackBeanWrapper(searchData), propertyPrefix, null, managedType, new HashSet<>(), false);
}
private Set resolveRestrictionListInternal(MapSupportingDirectFieldAccessFallbackBeanWrapper wrapper, String propertyPrefix, String path,
ManagedType> managedType, Set restrictionList, boolean isPluralAttribute) {
List fieldNameList = resolveFieldNameList(wrapper);
JpaEntityAttributeResolver attributeResolver = new JpaEntityAttributeResolver(managedType);
fieldNameList.forEach(originalFieldName -> {
String fieldNameWithoutPrefixAndSuffix = fieldNameWithoutSuffixAndPrefix(originalFieldName, propertyPrefix);
Object value = wrapper.getPropertyValue(originalFieldName);
if (shouldSkipValue(value)) {
return;
}
AttributeHolder attributeHolder = attributeResolver.resolveAttributeByPath(fieldNameWithoutPrefixAndSuffix);
if (attributeHolder.isFound()) {
String currentPath = resolveCurrentPath(path, fieldNameWithoutPrefixAndSuffix);
if (attributeHolder.managedType() != null) {
MapSupportingDirectFieldAccessFallbackBeanWrapper currentWrapper = new MapSupportingDirectFieldAccessFallbackBeanWrapper(value);
resolveRestrictionListInternal(currentWrapper, propertyPrefix, currentPath, attributeHolder.managedType(), restrictionList, attributeHolder.isPlural());
return;
}
// element collections have null managed type but should be treated as plural attributes
boolean isCurrentAttributePlural = attributeHolder.isPlural() || attributeHolder.isElementCollection() || isPluralAttribute;
restrictionList.add(createAttributeRestriction(attributeHolder.attribute().getJavaType(), originalFieldName, currentPath, value, isCurrentAttributePlural));
}
else if (searchUsingPropertyMapping(searchConfiguration)) {
AttributeHolderWithPath attributeWithPath = resolveAttributeFromSearchConfigurationOrPrefix(attributeResolver, originalFieldName);
if (attributeWithPath.isFound()) {
attributeHolder = attributeWithPath.attributeHolder();
restrictionList.add(createAttributeRestriction(attributeHolder.attribute().getJavaType(), originalFieldName, attributeWithPath.path(), value, attributeHolder.isPlural()));
}
}
});
return restrictionList;
}
private List resolveFieldNameList(MapSupportingDirectFieldAccessFallbackBeanWrapper wrapper) {
List configuredIgnoredFieldList = searchConfiguration.getSearchPropertyConfiguration().getSearchIgnoredPropertyList();
List ignoredFieldList = configuredIgnoredFieldList == null ? Collections.emptyList() : configuredIgnoredFieldList;
if (wrapper.getEntityAsMap() != null) {
return wrapper.getEntityAsMap().keySet().stream()
.filter(key -> !ignoredFieldList.contains(key))
.toList();
}
return FieldExtractionUtil.getAllFields(wrapper.getRootClass()).stream()
.filter(field -> shouldIncludeField(ignoredFieldList, field))
.map(Field::getName)
.toList();
}
private boolean shouldIncludeField(List ignoredFieldList, Field field) {
return !(ignoredFieldList.contains(field.getName()) || field.isSynthetic()
|| Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers()));
}
private String fieldNameWithoutSuffixAndPrefix(String originalFieldName, String prefix) {
String fieldName = PropertyNameUtil.propertyNameWithoutSuffix(originalFieldName, searchConfiguration.getSearchPropertyConfiguration());
if (prefix != null && fieldName.length() > prefix.length()) {
return StringUtils.uncapitalize(fieldName.substring(prefix.length()));
}
return fieldName;
}
private boolean shouldSkipValue(Object value) {
boolean skipValue;
if (value instanceof Collection> collection) {
skipValue = CollectionUtils.isEmpty(collection);
}
else {
skipValue = value == null;
}
return skipValue;
}
private Restriction createAttributeRestriction(Class> attributeType, String attributeName, String path, Object value, boolean isPluralAttribute) {
boolean isRangeSearchSupported = isRangeSearchSupported(attributeType);
SearchOperator resolvedOperator = resolveFromSearchConfiguration(searchConfiguration, path, attributeType);
SearchPropertyConfiguration searchPropertyConfiguration = searchConfiguration.getSearchPropertyConfiguration();
SearchOperator operator = DefaultSearchOperator.EQ;
if (resolvedOperator != null) {
operator = resolvedOperator;
}
else if (Collection.class.isAssignableFrom(value.getClass())) {
operator = DefaultSearchOperator.IN;
}
else if (String.class.isAssignableFrom(attributeType)) {
operator = DefaultSearchOperator.ILIKE;
}
else if (isRangeSearchSupported) {
if (attributeName.endsWith(searchPropertyConfiguration.getRangeQueryFromIncludingSuffix())) {
operator = DefaultSearchOperator.GE;
}
else if (attributeName.endsWith(searchPropertyConfiguration.getRangeQueryFromSuffix())) {
operator = DefaultSearchOperator.GT;
}
else if (attributeName.endsWith(searchPropertyConfiguration.getRangeQueryToIncludingSuffix())) {
operator = DefaultSearchOperator.LE;
}
else if (attributeName.endsWith(searchPropertyConfiguration.getRangeQueryToSuffix())) {
operator = DefaultSearchOperator.LT;
}
}
return new Restriction(path, operator, value, isPluralAttribute);
}
private boolean isRangeSearchSupported(Class> attributeType) {
return searchConfiguration.getSearchPropertyConfiguration().getRangeQuerySupportedClassList() != null
&& searchConfiguration.getSearchPropertyConfiguration().getRangeQuerySupportedClassList().stream().anyMatch(type -> type.isAssignableFrom(attributeType));
}
private String resolveCurrentPath(String path, String fieldNameWithoutPrefixAndSuffix) {
return path == null ? fieldNameWithoutPrefixAndSuffix : PathResolvingUtil.joinPath(path, fieldNameWithoutPrefixAndSuffix);
}
private AttributeHolderWithPath resolveAttributeFromSearchConfigurationOrPrefix(JpaEntityAttributeResolver attributeResolver, String originalFieldName) {
String mappedPath = findPathUsingMapping(searchConfiguration.getPropertyMappingList(), originalFieldName);
if (mappedPath == null) {
return resolveAttributeByPrefix(attributeResolver, originalFieldName, new ArrayList<>());
}
return new AttributeHolderWithPath(mappedPath, attributeResolver.resolveAttributeByPath(mappedPath));
}
private AttributeHolderWithPath resolveAttributeByPrefix(JpaEntityAttributeResolver attributeResolver, String path, List previousPathList) {
String mappedPath = findPathUsingAttributePrefix(path, attributeResolver.managedType());
if (mappedPath == null) {
return AttributeHolderWithPath.notFound();
}
AttributeHolder attributeHolder = attributeResolver.resolveAttributeByPath(mappedPath);
if (attributeHolder.isFound()) {
String fullPath = PathResolvingUtil.joinPath(previousPathList, mappedPath);
return new AttributeHolderWithPath(fullPath, attributeHolder);
}
else {
String[] currentPath = PathResolvingUtil.convertToPathList(mappedPath);
String currentPrefix = currentPath[0];
attributeHolder = attributeResolver.resolveAttributeByPath(currentPrefix);
if (attributeHolder.isFound()) {
String leftOverPath = PathResolvingUtil.removeFirstPathElement(currentPath);
previousPathList.add(currentPrefix);
return resolveAttributeByPrefix(new JpaEntityAttributeResolver(attributeHolder.managedType()), leftOverPath, previousPathList);
}
}
return AttributeHolderWithPath.notFound();
}
private String findPathUsingMapping(List propertyMappingList, String fieldName) {
return Optional.ofNullable(propertyMappingList)
.orElse(Collections.emptyList())
.stream()
.filter(mapping -> fieldName.equals(mapping.name()))
.map(SearchPropertyMapping::path)
.findAny()
.orElse(null);
}
private String findPathUsingAttributePrefix(String originalFieldName, ManagedType> managedType) {
List attributeNameList = managedType.getAttributes().stream()
.filter(attribute -> attribute.isAssociation() || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED)
.map(Attribute::getName)
.toList();
return attributeNameList.stream()
.filter(attribute -> isFieldNameValid(originalFieldName, attribute))
.map(attribute -> PathResolvingUtil.joinPath(attribute, StringUtils.uncapitalize(originalFieldName.substring(attribute.length()))))
.findFirst()
.orElse(null);
}
private boolean searchUsingPropertyMapping(SearchDataParserConfiguration searchConfiguration) {
return searchConfiguration.isResolvePropertyMappingUsingPrefix() || searchConfiguration.getPropertyMappingList() != null;
}
private SearchOperator resolveFromSearchConfiguration(SearchDataParserConfiguration searchConfiguration, String path, Class> attributeType) {
SearchOperator operator = null;
SearchOperatorOverride operatorOverride = findOperatorOverride(searchConfiguration.getSearchOperatorOverrideList(), value -> path.equals(value.getPropertyPath()));
if (operatorOverride == null) {
Predicate operatorOverridePredicate = value -> value.getPropertyType() != null && attributeType.isAssignableFrom(value.getPropertyType());
operatorOverride = findOperatorOverride(searchConfiguration.getSearchOperatorOverrideList(), operatorOverridePredicate);
}
if (operatorOverride != null) {
operator = operatorOverride.getSearchOperator();
}
return operator;
}
private SearchOperatorOverride findOperatorOverride(List searchOperatorOverrideList, Predicate searchOperatorOverridePredicate) {
return Optional.ofNullable(searchOperatorOverrideList)
.orElse(Collections.emptyList())
.stream()
.filter(searchOperatorOverridePredicate)
.findFirst()
.orElse(null);
}
private boolean isFieldNameValid(String fieldName, String attribute) {
return fieldName.startsWith(attribute) && fieldName.length() > attribute.length();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy