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:
Avgustin Marinov
2025-09-26 14:19:25 +03:00
committed by GitHub
parent d0fe120a3b
commit b181b66ee5
45 changed files with 308 additions and 211 deletions

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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) {}

View File

@@ -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();

View File

@@ -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 = '*';

View File

@@ -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 = '*';

View File

@@ -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);

View File

@@ -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"),

View File

@@ -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