Fix G3 - deep attributes (#2462)
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
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.jpa.rsql.Node.Comparison.Operator.EQ;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.GT;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.GTE;
|
||||
@@ -94,6 +95,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) {
|
||||
final int firstSeparatorIndex = key.indexOf(SUB_ATTRIBUTE_SEPARATOR);
|
||||
final String enumName = (firstSeparatorIndex == -1 ? key : key.substring(0, firstSeparatorIndex)).toUpperCase();
|
||||
@@ -126,18 +128,18 @@ public class RsqlParser {
|
||||
}
|
||||
}
|
||||
} else { // field name with sub-attribute
|
||||
final String subAttribute = key.substring(firstSeparatorIndex + 1);
|
||||
|
||||
if (enumValue.isMap()) {
|
||||
// map, the part after the enum name is the key of the map
|
||||
attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + subAttribute;
|
||||
attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + key.substring(firstSeparatorIndex + 1);
|
||||
} else if (enumValue.getSubEntityAttributes().isEmpty()) {
|
||||
// simple type without sub-attributes, so the sub-attribute is not allowed
|
||||
throw new RSQLParameterUnsupportedFieldException("Sub-attributes not supported for simple field " + enumValue);
|
||||
} else {
|
||||
final String[] subAttribute = key.substring(firstSeparatorIndex + 1).split(SUB_ATTRIBUTE_SPLIT_REGEX, 2);
|
||||
attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + enumValue.getSubEntityAttributes().stream()
|
||||
.filter(attr -> attr.equalsIgnoreCase(subAttribute)) // case normalized
|
||||
.filter(attr -> attr.equalsIgnoreCase(subAttribute[0])) // case normalized
|
||||
.findFirst()
|
||||
.map(attr -> subAttribute.length == 1 ? attr : attr + key.substring(firstSeparatorIndex + 1 + attr.length()))
|
||||
.orElseThrow(() -> new RSQLParameterUnsupportedFieldException(
|
||||
String.format(
|
||||
"The given search field {%s} has unsupported sub-attributes. Supported sub-attributes are %s",
|
||||
@@ -174,6 +176,7 @@ public class RsqlParser {
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S3776") // java:S3776 - group in single method for easier read of whole logic
|
||||
@Override
|
||||
public Node visit(final ComparisonNode node, final String param) {
|
||||
final String nodeSelector = node.getSelector();
|
||||
|
||||
@@ -31,6 +31,7 @@ import jakarta.annotation.Nonnull;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
import jakarta.persistence.criteria.Join;
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
import jakarta.persistence.criteria.MapJoin;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
@@ -170,13 +171,13 @@ public class SpecificationBuilder<T> {
|
||||
} else {
|
||||
if (op == LIKE && LIKE_WILDCARD_STR.equals(comparison.getValue())) {
|
||||
// optimized (?) has non-null/empty attribute - do or with on instead of subquery
|
||||
return cb.and(cb.isNotNull(pathResolver.getPath(attribute).get(split[1])));
|
||||
return cb.and(cb.isNotNull(deepGetPath(pathResolver.getPath(attribute), split[1])));
|
||||
}
|
||||
return compare(comparison, pathResolver.getPath(attribute).get(split[1]));
|
||||
return compare(comparison, deepGetPath(pathResolver.getPath(attribute), split[1]));
|
||||
}
|
||||
} else { // singular attribute (BASIC and EMBEDDABLE) or plural (ListAttribute of entities)
|
||||
final Path<?> attributePath = pathResolver.getPath(attribute);
|
||||
return compare(comparison, split.length > 1 ? attributePath.get(split[1]) : attributePath);
|
||||
return compare(comparison, split.length > 1 ? deepGetPath(attributePath, split[1]) : attributePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +213,7 @@ public class SpecificationBuilder<T> {
|
||||
final Root<? extends T> subqueryRoot = subquery.from(javaType);
|
||||
final Path<?> joinPath = subAttributeName == null // if null it is a map
|
||||
? ((MapJoin<?, ?, ?>) subqueryRoot.join(pluralAttribute.getName(), JoinType.LEFT)).value()
|
||||
: subqueryRoot.join(pluralAttribute.getName(), JoinType.LEFT).get(subAttributeName);
|
||||
: deepGetPath(subqueryRoot.join(pluralAttribute.getName(), JoinType.LEFT), subAttributeName);
|
||||
final Path<String> fieldPath = joinPath instanceof MapJoin<?, ?, ?> mapJoin
|
||||
? (Path<String>) mapJoin.value()
|
||||
: stringPath(joinPath);
|
||||
@@ -320,6 +321,22 @@ public class SpecificationBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private static Path<?> deepGetPath(final Path<?> path, final String subAttributeName) {
|
||||
return deepGetPath(path, subAttributeName.split("\\."), 0);
|
||||
}
|
||||
private static Path<?> deepGetPath(final Path<?> path, final String[] subAttributeNameSplit, int startIndex) {
|
||||
final String subAttributeName = subAttributeNameSplit[startIndex++];
|
||||
if (startIndex == subAttributeNameSplit.length) {
|
||||
return path.get(subAttributeName);
|
||||
} else { // else its a deeper path so request left join
|
||||
if (path instanceof Join<?,?> join) {
|
||||
return deepGetPath(join.join(subAttributeName, JoinType.LEFT), subAttributeNameSplit, startIndex);
|
||||
} else {
|
||||
throw new RSQLParameterSyntaxException("Unexpected sub attribute " + subAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Path<String> stringPath(final Path<?> path) {
|
||||
return (Path<String>) path;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rsqllegacy;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsqllegacy.AbstractRSQLVisitor.OPERATORS;
|
||||
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
|
||||
|
||||
import java.util.List;
|
||||
@@ -44,20 +45,16 @@ public class SpecificationBuilderLegacy<A extends Enum<A> & RsqlQueryField, T> {
|
||||
|
||||
public Specification<T> specification(final String rsql) {
|
||||
return (root, query, cb) -> {
|
||||
final Node rootNode = parseRsql(rsql);
|
||||
final RsqlConfigHolder rsqlConfigHolder = RsqlConfigHolder.getInstance();
|
||||
|
||||
final Node rootNode = parseRsql(rsql, rsqlConfigHolder);
|
||||
query.distinct(true);
|
||||
|
||||
final boolean ensureIgnoreCase = !rsqlConfigHolder.isCaseInsensitiveDB() && rsqlConfigHolder.isIgnoreCase();
|
||||
final RSQLVisitor<List<Predicate>, String> jpqQueryRSQLVisitor =
|
||||
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G1
|
||||
? new JpaQueryRsqlVisitor<>(
|
||||
root, cb, rsqlQueryFieldType,
|
||||
virtualPropertyReplacer, database, query,
|
||||
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase())
|
||||
: new JpaQueryRsqlVisitorG2<>(
|
||||
rsqlQueryFieldType, root, query, cb,
|
||||
database, virtualPropertyReplacer,
|
||||
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance()
|
||||
.isIgnoreCase());
|
||||
rsqlConfigHolder.getRsqlToSpecBuilder() == 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);
|
||||
|
||||
if (CollectionUtils.isEmpty(accept)) {
|
||||
@@ -68,17 +65,14 @@ public class SpecificationBuilderLegacy<A extends Enum<A> & RsqlQueryField, T> {
|
||||
};
|
||||
}
|
||||
|
||||
private static Node parseRsql(final String rsql) {
|
||||
private static Node parseRsql(final String rsql, final RsqlConfigHolder rsqlConfigHolder) {
|
||||
log.debug("Parsing rsql string {}", rsql);
|
||||
try {
|
||||
return new RSQLParser(AbstractRSQLVisitor.OPERATORS).parse(
|
||||
RsqlConfigHolder.getInstance().isCaseInsensitiveDB() || RsqlConfigHolder.getInstance().isIgnoreCase()
|
||||
? rsql.toLowerCase()
|
||||
: rsql);
|
||||
return new RSQLParser(OPERATORS).parse(rsqlConfigHolder.isCaseInsensitiveDB() || rsqlConfigHolder.isIgnoreCase() ? rsql.toLowerCase() : rsql);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw new RSQLParameterSyntaxException("RSQL filter must not be null", e);
|
||||
} catch (final RSQLParserException e) {
|
||||
throw new RSQLParameterSyntaxException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user