Extend Query Langage Support (#2696)
* add option for parser replacement - so, one could replace the query language and don't use RSQL * add easy way to modify query root Node * add easy Node modification utilities * code refactored and naming more 'QL' than 'RSQL' oriented Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.repository.jpa.ql;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
@@ -38,6 +39,25 @@ public interface Node {
|
||||
return op(this, Logical.Operator.OR, other);
|
||||
}
|
||||
|
||||
// utility method that maps this node with a mapper that could modify comparisons - e.g. change keys, values, operators, or whatever
|
||||
// if there are no changes the same instance is returned
|
||||
default Node map(final UnaryOperator<Comparison> mapper) {
|
||||
if (this instanceof Comparison comparison) {
|
||||
return mapper.apply(comparison);
|
||||
} else {
|
||||
final List<Node> mappedChildren = new ArrayList<>();
|
||||
boolean modified = false;
|
||||
for (final Node child : ((Logical) this).getChildren()) {
|
||||
final Node mapped = child.map(mapper);
|
||||
mappedChildren.add(mapped);
|
||||
if (!mapped.equals(child)) {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified ? new Logical(((Logical) this).getOp(), mappedChildren) : this;
|
||||
}
|
||||
}
|
||||
|
||||
static Logical op(final Node node1, final Logical.Operator op, final Node node2) {
|
||||
if (node1 instanceof Logical logical1 && logical1.getOp() == op) {
|
||||
if (node2 instanceof Logical logical2 && logical2.getOp() == op) { // same op
|
||||
@@ -121,7 +141,7 @@ public interface Node {
|
||||
|
||||
private final String symbol;
|
||||
|
||||
Operator(String symbol) {
|
||||
Operator(final String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,23 +7,26 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||
package org.eclipse.hawkbit.repository.jpa.ql;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
|
||||
import cz.jirutka.rsql.parser.RSQLParserException;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.text.StrLookup;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.exception.QueryException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.legacy.SpecificationBuilderLegacy;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||
@@ -69,20 +72,20 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
@Slf4j
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class RsqlUtility {
|
||||
public class QLSupport {
|
||||
|
||||
private static final RsqlUtility SINGLETON = new RsqlUtility();
|
||||
private static final QLSupport SINGLETON = new QLSupport();
|
||||
|
||||
public enum RsqlToSpecBuilder {
|
||||
public enum SpecBuilder {
|
||||
LEGACY_G1, // legacy RSQL visitor
|
||||
LEGACY_G2, // G2 RSQL visitor
|
||||
G3 // G3 RSQL visitor - still experimental / yet default
|
||||
G3 // G3 specification builder - still experimental / yet default
|
||||
}
|
||||
|
||||
/**
|
||||
* If RSQL comparison operators shall ignore the case. If ignore case is <code>true</code> "x == ax" will match "x == aX"
|
||||
* If QL comparison operators shall ignore the case. If ignore case is <code>true</code> "x == ax" will match "x == aX"
|
||||
*/
|
||||
@Value("${hawkbit.rsql.ignore-case:true}")
|
||||
@Value("${hawkbit.ql.ignore-case:true}")
|
||||
private boolean ignoreCase;
|
||||
/**
|
||||
* Declares if the database is case-insensitive, by default assumes <code>false</code>. In case it is case-sensitive and,
|
||||
@@ -100,8 +103,10 @@ public class RsqlUtility {
|
||||
@Setter // for tests only
|
||||
@Deprecated(forRemoval = true, since = "0.6.0")
|
||||
@Value("${hawkbit.rsql.rsql-to-spec-builder:G3}") //
|
||||
private RsqlToSpecBuilder rsqlToSpecBuilder;
|
||||
private SpecBuilder specBuilder;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private QueryParser parser;
|
||||
private VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
private Database database;
|
||||
private EntityManager entityManager;
|
||||
@@ -109,10 +114,15 @@ public class RsqlUtility {
|
||||
/**
|
||||
* @return The holder singleton instance.
|
||||
*/
|
||||
public static RsqlUtility getInstance() {
|
||||
public static QLSupport getInstance() {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
void setQueryParser(final QueryParser parser) {
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setVirtualPropertyReplacer(final VirtualPropertyReplacer virtualPropertyReplacer) {
|
||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||
@@ -132,37 +142,71 @@ public class RsqlUtility {
|
||||
* Builds a JPA {@link Specification} which corresponds with the given RSQL query. The specification can be used to filter for JPA entities
|
||||
* with the given RSQL query.
|
||||
*
|
||||
* @param rsql the rsql query to be parsed
|
||||
* @param rsqlQueryFieldType the enum class type which implements the {@link RsqlQueryField}
|
||||
* @param query the rsql query to be parsed
|
||||
* @param queryFieldType the enum class type which implements the {@link QueryField}
|
||||
* @return a specification which can be used with JPA
|
||||
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
|
||||
* given {@code fieldNameProvider}
|
||||
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
|
||||
*/
|
||||
public <A extends Enum<A> & RsqlQueryField, T> Specification<T> buildRsqlSpecification(
|
||||
final String rsql, final Class<A> rsqlQueryFieldType) {
|
||||
if (rsqlToSpecBuilder == RsqlToSpecBuilder.G3) {
|
||||
return new SpecificationBuilder<T>(virtualPropertyReplacer, !caseInsensitiveDB && ignoreCase, database)
|
||||
.specification(RsqlParser.parse(caseInsensitiveDB || ignoreCase ? rsql.toLowerCase() : rsql, rsqlQueryFieldType));
|
||||
public <A extends Enum<A> & QueryField, T> Specification<T> buildSpec(
|
||||
final String query, final Class<A> queryFieldType) {
|
||||
if (specBuilder == SpecBuilder.G3) {
|
||||
return new SpecificationBuilder<T>(!caseInsensitiveDB && ignoreCase, database)
|
||||
.specification(parser.parse(caseInsensitiveDB || ignoreCase ? query.toLowerCase() : query, queryFieldType));
|
||||
} else {
|
||||
return new SpecificationBuilderLegacy<A, T>(rsqlQueryFieldType, virtualPropertyReplacer, database).specification(rsql);
|
||||
return new SpecificationBuilderLegacy<A, T>(queryFieldType, virtualPropertyReplacer, database).specification(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the RSQL string
|
||||
* Validates the query string
|
||||
*
|
||||
* @param rsql RSQL string to validate
|
||||
* @param rsqlQueryFieldType the enum class type which implements the {@link RsqlQueryField}
|
||||
* @param query query string to validate
|
||||
* @param queryFieldType the enum class type which implements the {@link QueryField}
|
||||
* @param jpaType the JPA entity type to validate against
|
||||
* @throws RSQLParserException if RSQL syntax is invalid
|
||||
* @throws RSQLParameterUnsupportedFieldException if RSQL key is not allowed
|
||||
* @throws QueryException if query is invalid
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <A extends Enum<A> & RsqlQueryField> void validateRsqlFor(
|
||||
final String rsql, final Class<A> rsqlQueryFieldType, final Class<?> jpaType) {
|
||||
public <A extends Enum<A> & QueryField> void validateQuery(final String query, final Class<A> queryFieldType, final Class<?> jpaType) {
|
||||
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<?> criteriaQuery = criteriaBuilder.createQuery(jpaType);
|
||||
buildRsqlSpecification(rsql, rsqlQueryFieldType).toPredicate(criteriaQuery.from((Class) jpaType), criteriaQuery, criteriaBuilder);
|
||||
buildSpec(query, queryFieldType).toPredicate(criteriaQuery.from((Class) jpaType), criteriaQuery, criteriaBuilder);
|
||||
}
|
||||
|
||||
public interface QueryParser {
|
||||
|
||||
<T extends Enum<T> & QueryField> Node parse(final String query, final Class<T> queryFieldType) throws QueryException;
|
||||
}
|
||||
|
||||
public static class DefaultQueryParser implements QueryParser {
|
||||
|
||||
private VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
|
||||
@Autowired(required = false)
|
||||
void setVirtualPropertyReplacer(final VirtualPropertyReplacer virtualPropertyReplacer) {
|
||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Enum<T> & QueryField> Node parse(final String query, final Class<T> queryFieldType) throws QueryException {
|
||||
return RsqlParser.parse(query, queryFieldType).map(this::map);
|
||||
}
|
||||
|
||||
protected Comparison map(final Comparison comparison) {
|
||||
final String key = mapKey(comparison.getKey(), comparison).toString();
|
||||
final Object value = mapValue(comparison.getValue(), comparison);
|
||||
return key.equals(comparison.getKey()) && Objects.equals(value, comparison.getValue())
|
||||
? comparison : Comparison.builder().key(key).op(comparison.getOp()).value(value).build();
|
||||
}
|
||||
|
||||
// just extension points for subclasses
|
||||
protected Object mapKey(final String key, final Comparison comparison) {
|
||||
return key;
|
||||
}
|
||||
|
||||
protected Object mapValue(final Object value, final Comparison comparison) {
|
||||
return value instanceof String strValue ? virtualPropertyReplacer.replace(strValue) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,14 +63,10 @@ public class SpecificationBuilder<T> {
|
||||
|
||||
private static final char ESCAPE_CHAR = '\\';
|
||||
|
||||
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
private final boolean ensureIgnoreCase;
|
||||
private final Database database;
|
||||
|
||||
public SpecificationBuilder(
|
||||
final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final boolean ensureIgnoreCase, final Database database) {
|
||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||
public SpecificationBuilder(final boolean ensureIgnoreCase, final Database database) {
|
||||
this.ensureIgnoreCase = ensureIgnoreCase;
|
||||
this.database = database;
|
||||
}
|
||||
@@ -244,10 +240,6 @@ public class SpecificationBuilder<T> {
|
||||
private List<Object> getValues(final Comparison comparison, final Class<?> javaType) {
|
||||
final Object value = comparison.getValue();
|
||||
final List<Object> values = (value == null ? NULL_VALUE : (value instanceof List<?> list ? list : List.of(value))).stream()
|
||||
// if lookup is available, replace macros ...
|
||||
.map(element -> virtualPropertyReplacer != null && element instanceof String strElement
|
||||
? virtualPropertyReplacer.replace(strElement)
|
||||
: element)
|
||||
// converts value to the correct type
|
||||
.map(element -> element instanceof String strElement
|
||||
? convertValueIfNecessary(strElement, javaType, comparison)
|
||||
|
||||
@@ -17,9 +17,9 @@ import jakarta.persistence.TypedQuery;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder;
|
||||
|
||||
public class HawkbitQlToSql {
|
||||
|
||||
@@ -31,8 +31,8 @@ public class HawkbitQlToSql {
|
||||
isEclipselink = entityManager.getProperties().keySet().stream().anyMatch(key -> key.startsWith("eclipselink."));
|
||||
}
|
||||
|
||||
public <T, A extends Enum<A> & RsqlQueryField> String toSQL(
|
||||
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final RsqlToSpecBuilder rsqlToSpecBuilder) {
|
||||
public <T, A extends Enum<A> & QueryField> String toSQL(
|
||||
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final SpecBuilder rsqlToSpecBuilder) {
|
||||
final CriteriaQuery<T> query = createQuery(domainClass, fieldsClass, rsql, rsqlToSpecBuilder);
|
||||
final TypedQuery<?> typedQuery = entityManager.createQuery(query);
|
||||
if (isEclipselink) {
|
||||
@@ -54,20 +54,20 @@ public class HawkbitQlToSql {
|
||||
}
|
||||
}
|
||||
|
||||
private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(
|
||||
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final RsqlToSpecBuilder rsqlToSpecBuilder) {
|
||||
private <T, A extends Enum<A> & QueryField> CriteriaQuery<T> createQuery(
|
||||
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final SpecBuilder rsqlToSpecBuilder) {
|
||||
final CriteriaQuery<T> query = entityManager.getCriteriaBuilder().createQuery(domainClass);
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final RsqlToSpecBuilder defaultRsqlToSpecBuilder = RsqlUtility.getInstance().getRsqlToSpecBuilder();
|
||||
final SpecBuilder defaultRsqlToSpecBuilder = QLSupport.getInstance().getSpecBuilder();
|
||||
if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) {
|
||||
RsqlUtility.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
|
||||
QLSupport.getInstance().setSpecBuilder(rsqlToSpecBuilder);
|
||||
}
|
||||
try {
|
||||
return query.where(RsqlUtility.getInstance().<A, T> buildRsqlSpecification(rsql, fieldsClass)
|
||||
return query.where(QLSupport.getInstance().<A, T> buildSpec(rsql, fieldsClass)
|
||||
.toPredicate(query.from(domainClass), cb.createQuery(domainClass), cb));
|
||||
} finally {
|
||||
if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) {
|
||||
RsqlUtility.getInstance().setRsqlToSpecBuilder(defaultRsqlToSpecBuilder);
|
||||
QLSupport.getInstance().setSpecBuilder(defaultRsqlToSpecBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.RsqlQueryField.SUB_ATTRIBUTE_SEPARATOR;
|
||||
import static org.eclipse.hawkbit.repository.RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX;
|
||||
import static org.eclipse.hawkbit.repository.QueryField.SUB_ATTRIBUTE_SEPARATOR;
|
||||
import static org.eclipse.hawkbit.repository.QueryField.SUB_ATTRIBUTE_SPLIT_REGEX;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator.EQ;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator.GT;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator.GTE;
|
||||
@@ -42,7 +42,7 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.FieldValueConverter;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.Node;
|
||||
@@ -82,8 +82,8 @@ public class RsqlParser {
|
||||
return parse(rsql, (Function<String, Key>) null);
|
||||
}
|
||||
|
||||
public static <T extends Enum<T> & RsqlQueryField> Node parse(final String rsql, final Class<T> rsqlQueryFieldType) {
|
||||
return parse(rsql, rsqlQueryFieldType == null ? null : key -> resolveKey(key, rsqlQueryFieldType));
|
||||
public static <T extends Enum<T> & QueryField> Node parse(final String rsql, final Class<T> queryFieldType) {
|
||||
return parse(rsql, queryFieldType == null ? null : key -> resolveKey(key, queryFieldType));
|
||||
}
|
||||
|
||||
private static Node parse(final String rsql, final Function<String, Key> keyResolver) {
|
||||
@@ -97,7 +97,7 @@ public class RsqlParser {
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S3776") // java:S3776 - group in single method for easier read of whole logic
|
||||
private static <T extends Enum<T> & RsqlQueryField> Key resolveKey(final String key, final Class<T> rsqlQueryFieldType) {
|
||||
private static <T extends Enum<T> & QueryField> Key resolveKey(final String key, final Class<T> rsqlQueryFieldType) {
|
||||
final int firstSeparatorIndex = key.indexOf(SUB_ATTRIBUTE_SEPARATOR);
|
||||
final String enumName = (firstSeparatorIndex == -1 ? key : key.substring(0, firstSeparatorIndex)).toUpperCase();
|
||||
log.debug("Get field identifier by name {} of enum type {}", enumName, rsqlQueryFieldType);
|
||||
@@ -151,9 +151,7 @@ public class RsqlParser {
|
||||
}
|
||||
}
|
||||
|
||||
return new
|
||||
|
||||
Key(attribute, RsqlVisitor.valueConverter(enumValue));
|
||||
return new Key(attribute, RsqlVisitor.valueConverter(enumValue));
|
||||
}
|
||||
|
||||
private record Key(String path, UnaryOperator<Object> converter) {}
|
||||
|
||||
@@ -23,7 +23,7 @@ import cz.jirutka.rsql.parser.ast.ComparisonOperator;
|
||||
import cz.jirutka.rsql.parser.ast.RSQLOperators;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -33,7 +33,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "0.9.0")
|
||||
@Slf4j
|
||||
public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
|
||||
public abstract class AbstractRSQLVisitor<A extends Enum<A> & QueryField> {
|
||||
|
||||
static final ComparisonOperator IS = new ComparisonOperator("=is=", "=eq=");
|
||||
static final ComparisonOperator NOT = new ComparisonOperator("=not=", "=ne=");
|
||||
@@ -56,7 +56,7 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
|
||||
|
||||
@SuppressWarnings("java:S1066") // java:S1066 - more readable with separate "if" statements
|
||||
protected QueryPath getQueryPath(final ComparisonNode node) {
|
||||
final int firstSeparatorIndex = node.getSelector().indexOf(RsqlQueryField.SUB_ATTRIBUTE_SEPARATOR);
|
||||
final int firstSeparatorIndex = node.getSelector().indexOf(QueryField.SUB_ATTRIBUTE_SEPARATOR);
|
||||
final String enumName = (firstSeparatorIndex == -1
|
||||
? node.getSelector()
|
||||
: node.getSelector().substring(0, firstSeparatorIndex)).toUpperCase();
|
||||
@@ -99,14 +99,14 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
|
||||
|
||||
private String[] getSplit(final A enumValue, final String rsqlFieldName) {
|
||||
if (enumValue.isMap()) {
|
||||
final String[] split = rsqlFieldName.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX, 2);
|
||||
final String[] split = rsqlFieldName.split(QueryField.SUB_ATTRIBUTE_SPLIT_REGEX, 2);
|
||||
if (split.length != 2 || ObjectUtils.isEmpty(split[1])) {
|
||||
throw new RSQLParameterUnsupportedFieldException(
|
||||
"The syntax of the given map search parameter field {" + rsqlFieldName + "} is wrong. Syntax is: <enum name>.<key name>");
|
||||
}
|
||||
return split;
|
||||
} else {
|
||||
return rsqlFieldName.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX);
|
||||
return rsqlFieldName.split(QueryField.SUB_ATTRIBUTE_SPLIT_REGEX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
|
||||
}
|
||||
|
||||
for (final String attribute : subEntityAttributes) {
|
||||
final String[] graph = attribute.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX);
|
||||
final String[] graph = attribute.split(QueryField.SUB_ATTRIBUTE_SPLIT_REGEX);
|
||||
for (final String subAttribute : graph) {
|
||||
if (subAttribute.equalsIgnoreCase(propertyField)) {
|
||||
return true;
|
||||
@@ -158,7 +158,7 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
|
||||
.filter(enumField -> enumField.getSubEntityAttributes().isEmpty()).map(enumField -> {
|
||||
final String enumFieldName = enumField.name().toLowerCase();
|
||||
if (enumField.isMap()) {
|
||||
return enumFieldName + RsqlQueryField.SUB_ATTRIBUTE_SEPARATOR + "keyName";
|
||||
return enumFieldName + QueryField.SUB_ATTRIBUTE_SEPARATOR + "keyName";
|
||||
} else {
|
||||
return enumFieldName;
|
||||
}
|
||||
@@ -167,7 +167,7 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
|
||||
.filter(enumField -> !enumField.getSubEntityAttributes().isEmpty()).flatMap(enumField -> {
|
||||
final List<String> subEntity = enumField
|
||||
.getSubEntityAttributes().stream().map(fieldName -> enumField.name().toLowerCase()
|
||||
+ RsqlQueryField.SUB_ATTRIBUTE_SEPARATOR + fieldName)
|
||||
+ QueryField.SUB_ATTRIBUTE_SEPARATOR + fieldName)
|
||||
.toList();
|
||||
|
||||
return subEntity.stream();
|
||||
|
||||
@@ -43,7 +43,7 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.eclipse.hawkbit.repository.FieldValueConverter;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
@@ -65,7 +65,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "0.6.0")
|
||||
@Slf4j
|
||||
public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends AbstractRSQLVisitor<A>
|
||||
public class JpaQueryRsqlVisitor<A extends Enum<A> & QueryField, T> extends AbstractRSQLVisitor<A>
|
||||
implements RSQLVisitor<List<Predicate>, String> {
|
||||
|
||||
public static final Character LIKE_WILDCARD = '*';
|
||||
|
||||
@@ -41,7 +41,7 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.eclipse.hawkbit.repository.FieldValueConverter;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder;
|
||||
@@ -62,7 +62,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "0.9.0")
|
||||
@Slf4j
|
||||
public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
|
||||
public class JpaQueryRsqlVisitorG2<A extends Enum<A> & QueryField, T>
|
||||
extends AbstractRSQLVisitor<A> implements RSQLVisitor<List<Predicate>, String> {
|
||||
|
||||
public static final Character LIKE_WILDCARD = '*';
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.rsql.legacy;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.legacy.AbstractRSQLVisitor.OPERATORS;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G1;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,10 +21,10 @@ import cz.jirutka.rsql.parser.RSQLParserException;
|
||||
import cz.jirutka.rsql.parser.ast.Node;
|
||||
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
@@ -35,7 +35,7 @@ import org.springframework.util.CollectionUtils;
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "0.9.0")
|
||||
@Slf4j
|
||||
public class SpecificationBuilderLegacy<A extends Enum<A> & RsqlQueryField, T> {
|
||||
public class SpecificationBuilderLegacy<A extends Enum<A> & QueryField, T> {
|
||||
|
||||
private final Class<A> rsqlQueryFieldType;
|
||||
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
@@ -50,14 +50,14 @@ public class SpecificationBuilderLegacy<A extends Enum<A> & RsqlQueryField, T> {
|
||||
|
||||
public Specification<T> specification(final String rsql) {
|
||||
return (root, query, cb) -> {
|
||||
final RsqlUtility rsqlUtility = RsqlUtility.getInstance();
|
||||
final QLSupport rsqlUtility = QLSupport.getInstance();
|
||||
|
||||
final Node rootNode = parseRsql(rsql, rsqlUtility);
|
||||
query.distinct(true);
|
||||
|
||||
final boolean ensureIgnoreCase = !rsqlUtility.isCaseInsensitiveDB() && rsqlUtility.isIgnoreCase();
|
||||
final RSQLVisitor<List<Predicate>, String> jpqQueryRSQLVisitor =
|
||||
rsqlUtility.getRsqlToSpecBuilder() == LEGACY_G1
|
||||
rsqlUtility.getSpecBuilder() == LEGACY_G1
|
||||
? new JpaQueryRsqlVisitor<>(root, cb, rsqlQueryFieldType, virtualPropertyReplacer, database, query, ensureIgnoreCase)
|
||||
: new JpaQueryRsqlVisitorG2<>(rsqlQueryFieldType, root, query, cb, database, virtualPropertyReplacer, ensureIgnoreCase);
|
||||
final List<Predicate> accept = rootNode.accept(jpqQueryRSQLVisitor);
|
||||
@@ -70,7 +70,7 @@ public class SpecificationBuilderLegacy<A extends Enum<A> & RsqlQueryField, T> {
|
||||
};
|
||||
}
|
||||
|
||||
private static Node parseRsql(final String rsql, final RsqlUtility rsqlUtility) {
|
||||
private static Node parseRsql(final String rsql, final QLSupport rsqlUtility) {
|
||||
log.debug("Parsing rsql string {}", rsql);
|
||||
try {
|
||||
return new RSQLParser(OPERATORS).parse(rsqlUtility.isCaseInsensitiveDB() || rsqlUtility.isIgnoreCase() ? rsql.toLowerCase() : rsql);
|
||||
|
||||
@@ -9,17 +9,16 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.ql;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G1;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G2;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G2;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder;
|
||||
import org.eclipse.hawkbit.repository.QueryField;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.legacy.SpecificationBuilderLegacy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
@@ -31,13 +30,13 @@ class SpecificationBuilderLegacyTest extends SpecificationBuilderTest {
|
||||
|
||||
private final SpecificationBuilderLegacy<RootField, Root> builder = new SpecificationBuilderLegacy<>(RootField.class, null, Database.H2);
|
||||
|
||||
private static void runWithRsqlToSpecBuilder(final Runnable runnable, final RsqlToSpecBuilder rsqlToSpecBuilder) {
|
||||
final RsqlToSpecBuilder defaultBuilder = RsqlUtility.getInstance().getRsqlToSpecBuilder();
|
||||
RsqlUtility.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
|
||||
private static void runWithRsqlToSpecBuilder(final Runnable runnable, final SpecBuilder rsqlToSpecBuilder) {
|
||||
final SpecBuilder defaultBuilder = QLSupport.getInstance().getSpecBuilder();
|
||||
QLSupport.getInstance().setSpecBuilder(rsqlToSpecBuilder);
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
RsqlUtility.getInstance().setRsqlToSpecBuilder(defaultBuilder);
|
||||
QLSupport.getInstance().setSpecBuilder(defaultBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ class SpecificationBuilderLegacyTest extends SpecificationBuilderTest {
|
||||
}
|
||||
|
||||
@Getter
|
||||
private enum RootField implements RsqlQueryField {
|
||||
private enum RootField implements QueryField {
|
||||
|
||||
INTVALUE("intValue"),
|
||||
STRVALUE("strValue"),
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.ql;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.G3;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G1;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G2;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.G3;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G1;
|
||||
import static org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder.LEGACY_G2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -22,9 +22,9 @@ import java.util.stream.StreamSupport;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport.SpecBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.utils.HawkbitQlToSql;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
@@ -51,7 +51,7 @@ class SpecificationBuilderTest {
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
private final SpecificationBuilder<Root> builder = new SpecificationBuilder<>(null, false, Database.H2);
|
||||
private final SpecificationBuilder<Root> builder = new SpecificationBuilder<>(false, Database.H2);
|
||||
|
||||
@Test
|
||||
void singularStringAttribute() {
|
||||
@@ -573,8 +573,8 @@ class SpecificationBuilderTest {
|
||||
return builder.specification(RsqlParser.parse(rsql));
|
||||
}
|
||||
|
||||
private static RsqlUtility.RsqlToSpecBuilder getRsqlToSpecBuilder() {
|
||||
return RsqlUtility.getInstance().getRsqlToSpecBuilder();
|
||||
private static SpecBuilder getRsqlToSpecBuilder() {
|
||||
return QLSupport.getInstance().getSpecBuilder();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
|
||||
Reference in New Issue
Block a user