From dd500b4d53cf0ac50f7cad2283fa4104fa48fa03 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Tue, 10 Sep 2024 11:38:31 +0300 Subject: [PATCH] Refactor RSQL searach fields related classes (3) (#1836) Signed-off-by: Marinov Avgustin --- .../repository/DistributionSetFields.java | 14 ++-- .../hawkbit/repository/RsqlQueryField.java | 17 ----- .../repository/SoftwareModuleFields.java | 17 ++++- .../hawkbit/repository/TargetFields.java | 2 +- .../jpa/rsql/AbstractRSQLVisitor.java | 68 +++++++++++++------ .../jpa/rsql/FieldValidationRsqlVisitor.java | 2 +- .../jpa/rsql/JpaQueryRsqlVisitor.java | 55 ++++++++------- .../jpa/rsql/JpaQueryRsqlVisitorG2.java | 65 ++++++++---------- 8 files changed, 127 insertions(+), 113 deletions(-) diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetFields.java index 00dfc8652..b1a403587 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetFields.java @@ -25,7 +25,7 @@ import java.util.Optional; public enum DistributionSetFields implements RsqlQueryField { ID("id"), - TYPE("type.key"), + TYPE("type", "key"), NAME("name"), DESCRIPTION("description"), CREATEDAT("createdAt"), @@ -33,27 +33,27 @@ public enum DistributionSetFields implements RsqlQueryField { VERSION("version"), COMPLETE("complete"), MODULE("modules", SoftwareModuleFields.ID.getJpaEntityFieldName(), SoftwareModuleFields.NAME.getJpaEntityFieldName()), - TAG("tags.name"), + TAG("tags", "name"), METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), VALID("valid"); private final String jpaEntityFieldName; - private final Entry subEntityMapTuple; private final List subEntityAttributes; + private final Entry subEntityMapTuple; DistributionSetFields(final String jpaEntityFieldName) { - this(jpaEntityFieldName, null, Collections.emptyList()); + this(jpaEntityFieldName, Collections.emptyList(), null); } DistributionSetFields(final String jpaEntityFieldName, final String... subEntityAttributes) { - this(jpaEntityFieldName, null, List.of(subEntityAttributes)); + this(jpaEntityFieldName, List.of(subEntityAttributes), null); } DistributionSetFields(final String jpaEntityFieldName, final Entry subEntityMapTuple) { - this(jpaEntityFieldName, subEntityMapTuple, Collections.emptyList()); + this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple); } - DistributionSetFields(final String jpaEntityFieldName, final Entry subEntityMapTuple, List subEntityAttributes) { + DistributionSetFields(final String jpaEntityFieldName, List subEntityAttributes, final Entry subEntityMapTuple) { this.jpaEntityFieldName = jpaEntityFieldName; this.subEntityMapTuple = subEntityMapTuple; this.subEntityAttributes = subEntityAttributes; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RsqlQueryField.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RsqlQueryField.java index a454b5e8f..6d9d972d1 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RsqlQueryField.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RsqlQueryField.java @@ -35,23 +35,6 @@ public interface RsqlQueryField { @NotNull String getJpaEntityFieldName(); - /** - * Returns the sub attributes - * - * @param propertyFieldName the given field - * @return array consisting of sub attributes - */ - default String[] getSubAttributes(final String propertyFieldName) { - if (isMap()) { - final String[] subAttributes = propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX, 2); - // [0] field name | [1] key name (could miss, e.g. for target attributes) - final String mapKeyName = subAttributes.length == 2 ? subAttributes[1] : null; - return ObjectUtils.isEmpty(mapKeyName) ? new String[] { getJpaEntityFieldName() } : new String[] { getJpaEntityFieldName(), mapKeyName }; - } else { - return propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX); - } - } - /** * Contains the sub entity the given field. * diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleFields.java index 34c9c5261..437979404 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleFields.java @@ -12,6 +12,8 @@ package org.eclipse.hawkbit.repository; import lombok.Getter; import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Collections; +import java.util.List; import java.util.Map.Entry; import java.util.Optional; @@ -22,22 +24,31 @@ import java.util.Optional; public enum SoftwareModuleFields implements RsqlQueryField { ID("id"), - TYPE("type.key"), + TYPE("type", "key"), NAME("name"), DESCRIPTION("description"), VERSION("version"), METADATA("metadata", new SimpleImmutableEntry<>("key", "value")); private final String jpaEntityFieldName; - private Entry subEntityMapTuple; + private final List subEntityAttributes; + private final Entry subEntityMapTuple; SoftwareModuleFields(final String jpaEntityFieldName) { - this(jpaEntityFieldName, null); + this(jpaEntityFieldName, Collections.emptyList(), null); } + SoftwareModuleFields(final String jpaEntityFieldName, final String... subEntityAttributes) { + this(jpaEntityFieldName, List.of(subEntityAttributes), null); + } SoftwareModuleFields(final String jpaEntityFieldName, final Entry subEntityMapTuple) { + this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple); + } + + SoftwareModuleFields(final String jpaEntityFieldName, List subEntityAttributes, final Entry subEntityMapTuple) { this.jpaEntityFieldName = jpaEntityFieldName; this.subEntityMapTuple = subEntityMapTuple; + this.subEntityAttributes = subEntityAttributes; } @Override diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java index 72e36657d..1a24f838f 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java @@ -35,7 +35,7 @@ public enum TargetFields implements RsqlQueryField { ATTRIBUTE("controllerAttributes"), ASSIGNEDDS("assignedDistributionSet", "name", "version"), INSTALLEDDS("installedDistributionSet", "name", "version"), - TAG("tags.name"), + TAG("tags", "name"), LASTCONTROLLERREQUESTAT("lastTargetQuery"), METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java index decb29043..983314f94 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java @@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.RsqlQueryField; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import cz.jirutka.rsql.parser.ast.ComparisonNode; +import org.springframework.util.ObjectUtils; @Slf4j public abstract class AbstractRSQLVisitor & RsqlQueryField> { @@ -28,14 +29,14 @@ public abstract class AbstractRSQLVisitor & RsqlQueryField> { private final Class rsqlQueryFieldType; @Value - protected class RsqlField { + protected class QuertPath { A enumValue; - String[] subAttributes; + String[] jpaPath; - private RsqlField(final A enumValue, final String[] subAttributes) { + private QuertPath(final A enumValue, final String[] jpaPath) { this.enumValue = enumValue; - this.subAttributes = subAttributes; + this.jpaPath = jpaPath; } } @@ -43,49 +44,74 @@ public abstract class AbstractRSQLVisitor & RsqlQueryField> { this.rsqlQueryFieldType = rsqlQueryFieldType; } - protected RsqlField getRsqlField(final ComparisonNode node) { - final String[] graph = node.getSelector().split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX); - final String enumName = graph.length == 0 ? node.getSelector() : graph[0]; + protected QuertPath getQuertPath(final ComparisonNode node) { + final String[] path = node.getSelector().split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX); + if (path.length == 0) { + throw createRSQLParameterUnsupportedException(node, null); + } + + final String enumName = path[0].toUpperCase(); log.debug("get field identifier by name {} of enum type {}", enumName, rsqlQueryFieldType); try { - final A enumValue = Enum.valueOf(rsqlQueryFieldType, enumName.toUpperCase()); - final String[] subAttributes = enumValue.getSubAttributes(node.getSelector()); + final A enumValue = Enum.valueOf(rsqlQueryFieldType, enumName); + String[] split = getSplit(enumValue, node.getSelector()); // validate if (enumValue.isMap()) { // . - if (subAttributes.length != 2) { + if (split.length != 2) { throw new RSQLParameterUnsupportedFieldException( "The syntax of the given map search parameter field {" + node.getSelector() + "} is wrong. Syntax is: ."); } } else { // sub entity need minimum 1 dot - if (!enumValue.getSubEntityAttributes().isEmpty() && subAttributes.length < 2) { - throw createRSQLParameterUnsupportedException(node, null); + if (!enumValue.getSubEntityAttributes().isEmpty() && split.length < 2) { + if (enumValue.getSubEntityAttributes().size() == 1) { // single sub attribute - so default + split = new String[] { split[0], enumValue.getSubEntityAttributes().get(0) }; + } else { + throw createRSQLParameterUnsupportedException(node, null); + } } } - // build property and validate sub attributes - final StringBuilder fieldNameBuilder = new StringBuilder(enumValue.getJpaEntityFieldName()); - for (int i = 1; i < subAttributes.length; i++) { - final String propertyField = getFormattedSubEntityAttribute(enumValue, subAttributes[i]); + // validate and normalize (replace enum name with JPA entity field name and format the rest) prepare jpa path + split[0] = enumValue.getJpaEntityFieldName(); + for (int i = 1; i < split.length; i++) { + split[i] = getFormattedSubEntityAttribute(enumValue, split[i]); - if (!enumValue.containsSubEntityAttribute(propertyField)) { - if (i != subAttributes.length - 1 || !enumValue.isMap()) { + if (!enumValue.containsSubEntityAttribute(split[i])) { + if (i != split.length - 1 || !enumValue.isMap()) { throw createRSQLParameterUnsupportedException(node, null); } // otherwise - the key of map is not in the sub entity attributes } - - fieldNameBuilder.append(RsqlQueryField.SUB_ATTRIBUTE_SEPARATOR).append(propertyField); } - return new RsqlField(enumValue, enumValue.getSubAttributes(fieldNameBuilder.toString())); + return new QuertPath(enumValue, split); } catch (final IllegalArgumentException e) { throw createRSQLParameterUnsupportedException(node, e); } } + /** + * Returns the sub attributes + * + * @param rsqlFieldName the given field + * @return array consisting of sub attributes + */ + private String[] getSplit(final A enumValue, final String rsqlFieldName) { + if (enumValue.isMap()) { + final String[] subAttributes = rsqlFieldName.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX, 2); + // [0] field name | [1] key name (could miss, e.g. for target attributes) + final String mapKeyName = subAttributes.length == 2 ? subAttributes[1] : null; + return ObjectUtils.isEmpty(mapKeyName) ? + new String[] { enumValue.getJpaEntityFieldName() } : + new String[] { enumValue.getJpaEntityFieldName(), mapKeyName }; + } else { + return rsqlFieldName.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX); + } + } + /** * @param node current processing node * @param rootException in case there is a cause otherwise {@code null} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/FieldValidationRsqlVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/FieldValidationRsqlVisitor.java index ec46ffb85..4a09ec01e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/FieldValidationRsqlVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/FieldValidationRsqlVisitor.java @@ -48,7 +48,7 @@ public class FieldValidationRsqlVisitor & RsqlQueryField> exte @Override public Void visit(final ComparisonNode node, final String param) { // get AND validates - getRsqlField(node); + getQuertPath(node); return null; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java index 66b79814f..296662c8f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java @@ -171,12 +171,12 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends * @param enumField * field from a FieldNameProvider to resolve on the persistence * layer - * @param rsqlField RSQL field + * @param queryPath RSQL field * @return the Path for a field */ @SuppressWarnings("unchecked") - private Path getFieldPath(final A enumField, final RsqlField rsqlField) { - return (Path) getFieldPath(root, rsqlField.getSubAttributes(), enumField.isMap(), + private Path getFieldPath(final A enumField, final QuertPath queryPath) { + return (Path) getFieldPath(root, queryPath.getJpaPath(), enumField.isMap(), this::getJoinFieldPath).orElseThrow( () -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null)); } @@ -217,19 +217,19 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends // https://jira.sonarsource.com/browse/SONARJAVA-1478 @SuppressWarnings({ "squid:S2095" }) public List visit(final ComparisonNode node, final String param) { - final RsqlField rsqlField = getRsqlField(node); + final QuertPath queryPath = getQuertPath(node); final List values = node.getArguments(); final List transformedValues = new ArrayList<>(); - final Path fieldPath = getFieldPath(rsqlField.getEnumValue(), rsqlField); + final Path fieldPath = getFieldPath(queryPath.getEnumValue(), queryPath); for (final String value : values) { - transformedValues.add(convertValueIfNecessary(node, rsqlField.getEnumValue(), value, fieldPath)); + transformedValues.add(convertValueIfNecessary(node, queryPath.getEnumValue(), value, fieldPath)); } this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); - return mapToPredicate(node, fieldPath, node.getArguments(), transformedValues, rsqlField); + return mapToPredicate(node, fieldPath, node.getArguments(), transformedValues, queryPath); } private static boolean areJoinsNeeded(final ComparisonNode node) { @@ -305,7 +305,7 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends } private List mapToPredicate(final ComparisonNode node, final Path fieldPath, - final List values, final List transformedValues, final RsqlField rsqlField) { + final List values, final List transformedValues, final QuertPath queryPath) { String value = values.get(0); // if lookup is available, replace macros ... @@ -313,16 +313,16 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends value = virtualPropertyReplacer.replace(value); } - final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, rsqlField.getEnumValue()); + final Predicate mapPredicate = mapToMapPredicate(fieldPath, queryPath); - final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(rsqlField.getEnumValue(), fieldPath), - transformedValues, value, rsqlField); + final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(queryPath.getEnumValue(), fieldPath), + transformedValues, value, queryPath); return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); } private Predicate addOperatorPredicate(final ComparisonNode node, final Path fieldPath, - final List transformedValues, final String value, final RsqlField rsqlField) { + final List transformedValues, final String value, final QuertPath queryPath) { // only 'equal' and 'notEqual' can handle transformed value like // enums. The JPA API cannot handle object types for greaterThan etc @@ -334,7 +334,7 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends case "==": return getEqualToPredicate(transformedValue, fieldPath); case "!=": - return getNotEqualToPredicate(transformedValue, fieldPath, rsqlField); + return getNotEqualToPredicate(transformedValue, fieldPath, queryPath); case "=gt=": return cb.greaterThan(pathOfString(fieldPath), value); case "=ge=": @@ -346,7 +346,7 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends case "=in=": return in(pathOfString(fieldPath), transformedValues); case "=out=": - return getOutPredicate(transformedValues, rsqlField, fieldPath); + return getOutPredicate(transformedValues, queryPath, fieldPath); default: throw new RSQLParameterSyntaxException( "operator symbol {" + operator + "} is either not supported or not implemented"); @@ -355,17 +355,17 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends private Predicate getOutPredicate( final List transformedValues, - final RsqlField rsqlField, final Path fieldPath) { - final String[] fieldNames = rsqlField.getSubAttributes(); + final QuertPath queryPath, final Path fieldPath) { + final String[] fieldNames = queryPath.getJpaPath(); - if (isSimpleField(fieldNames, rsqlField.getEnumValue().isMap())) { + if (isSimpleField(fieldNames, queryPath.getEnumValue().isMap())) { final Path pathOfString = pathOfString(fieldPath); return cb.or(cb.isNull(pathOfString), cb.not(in(pathOfString, transformedValues))); } clearOuterJoinsIfNotNeeded(); - return toNotExistsSubQueryPredicate(fieldNames, rsqlField.getEnumValue(), expressionToCompare -> in(expressionToCompare, transformedValues)); + return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), expressionToCompare -> in(expressionToCompare, transformedValues)); } private Path getMapValueFieldPath(final A enumField, final Path fieldPath) { @@ -378,20 +378,19 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends } @SuppressWarnings("unchecked") - private Predicate mapToMapPredicate(final ComparisonNode node, final Path fieldPath, final A enumField) { - if (!enumField.isMap()) { + private Predicate mapToMapPredicate(final Path fieldPath, final QuertPath queryPath) { + if (!queryPath.getEnumValue().isMap()) { return null; } - final String[] graph = enumField.getSubAttributes(node.getSelector()); - + final String[] graph = queryPath.getJpaPath(); final String keyValue = graph[graph.length - 1]; if (fieldPath instanceof MapJoin) { // Currently we support only string key. So below cast is safe. return equal((Expression) (((MapJoin) fieldPath).key()), keyValue); } - final String keyFieldName = enumField.getSubEntityMapTuple().map(Entry::getKey) + final String keyFieldName = queryPath.getEnumValue().getSubEntityMapTuple().map(Entry::getKey) .orElseThrow(() -> new UnsupportedOperationException( "For the fields, defined as Map, only Map java type or tuple in the form of " + "SimpleImmutableEntry are allowed. Neither of those could be found!")); @@ -419,7 +418,7 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends } private Predicate getNotEqualToPredicate(final Object transformedValue, final Path fieldPath, - final RsqlField rsqlField) { + final QuertPath queryPath) { if (transformedValue == null) { return cb.isNotNull(pathOfString(fieldPath)); @@ -430,9 +429,9 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), "")); } - final String[] fieldNames = rsqlField.getSubAttributes(); + final String[] fieldNames = queryPath.getJpaPath(); - if (isSimpleField(fieldNames, rsqlField.getEnumValue().isMap())) { + if (isSimpleField(fieldNames, queryPath.getEnumValue().isMap())) { if (isPattern(transformedValueStr)) { // a pattern, use like return cb.or(cb.isNull(pathOfString(fieldPath)), notLike(pathOfString(fieldPath), toSQL(transformedValueStr))); } else { @@ -443,9 +442,9 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends clearOuterJoinsIfNotNeeded(); if (isPattern(transformedValueStr)) { // a pattern, use like - return toNotExistsSubQueryPredicate(fieldNames, rsqlField.getEnumValue(), expressionToCompare -> like(expressionToCompare, toSQL(transformedValueStr))); + return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), expressionToCompare -> like(expressionToCompare, toSQL(transformedValueStr))); } else { - return toNotExistsSubQueryPredicate(fieldNames, rsqlField.getEnumValue(), expressionToCompare -> equal(expressionToCompare, transformedValueStr)); + return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), expressionToCompare -> equal(expressionToCompare, transformedValueStr)); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java index d6ca46454..26f9528d1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java @@ -110,30 +110,30 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> @Override public List visit(final ComparisonNode node, final String param) { - final RsqlField rsqlField = getRsqlField(node); + final QuertPath queryField = getQuertPath(node); final List values = node.getArguments(); final List transformedValues = new ArrayList<>(); - final Path fieldPath = getFieldPath(root, rsqlField); + final Path fieldPath = getFieldPath(root, queryField); for (final String value : values) { - transformedValues.add(convertValueIfNecessary(node, rsqlField.getEnumValue(), fieldPath, value)); + transformedValues.add(convertValueIfNecessary(node, queryField.getEnumValue(), fieldPath, value)); } this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); - return mapToPredicate(node, rsqlField, fieldPath, node.getArguments(), transformedValues); + return mapToPredicate(node, queryField, fieldPath, node.getArguments(), transformedValues); } - private List mapToPredicate(final ComparisonNode node, final RsqlField rsqlField, + private List mapToPredicate(final ComparisonNode node, final QuertPath queryField, final Path fieldPath, final List values, final List transformedValues) { // if lookup is available, replace macros ... final String value = virtualPropertyReplacer == null ? values.get(0) : virtualPropertyReplacer.replace(values.get(0)); - final Predicate mapPredicate = mapToMapPredicate(node, rsqlField.getEnumValue(), fieldPath); - final Predicate valuePredicate = addOperatorPredicate(node, rsqlField, - getValueFieldPath(rsqlField.getEnumValue(), fieldPath), transformedValues, value); + final Predicate mapPredicate = queryField.getEnumValue().isMap() ? mapToMapPredicate(queryField, fieldPath) : null; + final Predicate valuePredicate = addOperatorPredicate(node, queryField, + getValueFieldPath(queryField.getEnumValue(), fieldPath), transformedValues, value); return Collections.singletonList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); } @@ -145,26 +145,21 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } } @SuppressWarnings("unchecked") - private Predicate mapToMapPredicate(final ComparisonNode node, final A enumField, final Path fieldPath) { - if (!enumField.isMap()) { - return null; - } - - final String[] graph = enumField.getSubAttributes(node.getSelector()); - + private Predicate mapToMapPredicate(final QuertPath queryField, final Path fieldPath) { + final String[] graph = queryField.getJpaPath(); final String keyValue = graph[graph.length - 1]; if (fieldPath instanceof MapJoin) { // Currently we support only string key. So below cast is safe. return equal((Expression) (((MapJoin) fieldPath).key()), keyValue); } - final String keyFieldName = enumField.getSubEntityMapTuple().map(Entry::getKey) + final String keyFieldName = queryField.getEnumValue().getSubEntityMapTuple().map(Entry::getKey) .orElseThrow(() -> new UnsupportedOperationException( "For the fields, defined as Map, only Map java type or tuple in the form of " + "SimpleImmutableEntry are allowed. Neither of those could be found!")); return equal(fieldPath.get(keyFieldName), keyValue); } - private Predicate addOperatorPredicate(final ComparisonNode node, final RsqlField rsqlField, + private Predicate addOperatorPredicate(final ComparisonNode node, final QuertPath queryField, final Path fieldPath, final List transformedValues, final String value) { // only 'equal' and 'notEqual' can handle transformed value like enums. // The JPA API cannot handle object types for greaterThan etc. methods. @@ -172,13 +167,13 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> final String operator = node.getOperator().getSymbol(); return switch (operator) { case "==" -> getEqualToPredicate(fieldPath, transformedValue); - case "!=" -> getNotEqualToPredicate(rsqlField, fieldPath, transformedValue); + case "!=" -> getNotEqualToPredicate(queryField, fieldPath, transformedValue); case "=gt=" -> cb.greaterThan(pathOfString(fieldPath), value); case "=ge=" -> cb.greaterThanOrEqualTo(pathOfString(fieldPath), value); case "=lt=" -> cb.lessThan(pathOfString(fieldPath), value); case "=le=" -> cb.lessThanOrEqualTo(pathOfString(fieldPath), value); case "=in=" -> in(pathOfString(fieldPath), transformedValues); - case "=out=" -> getOutPredicate(rsqlField, fieldPath, transformedValues); + case "=out=" -> getOutPredicate(queryField, fieldPath, transformedValues); default -> throw new RSQLParameterSyntaxException( "Operator symbol {" + operator + "} is either not supported or not implemented"); }; @@ -203,7 +198,7 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> return cb.equal(fieldPath, transformedValue); } - private Predicate getNotEqualToPredicate(final RsqlField rsqlField, + private Predicate getNotEqualToPredicate(final QuertPath queryField, final Path fieldPath, final Object transformedValue) { if (transformedValue == null) { return cb.isNotNull(fieldPath); @@ -214,9 +209,9 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> return cb.and(cb.isNotNull(fieldPath), cb.notEqual(pathOfString(fieldPath), "")); } - final String[] fieldNames = rsqlField.getSubAttributes(); + final String[] fieldNames = queryField.getJpaPath(); - if (isSimpleField(fieldNames, rsqlField.getEnumValue().isMap())) { + if (isSimpleField(fieldNames, queryField.getEnumValue().isMap())) { if (isPattern(transformedValueStr)) { // a pattern, use like return cb.or(cb.isNull(fieldPath), notLike(pathOfString(fieldPath), toSQL(transformedValueStr))); } else { @@ -225,7 +220,7 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } clearJoinsIfNotNeeded(); - return toNotExistsSubQueryPredicate(rsqlField, expressionToCompare -> + return toNotExistsSubQueryPredicate(queryField, expressionToCompare -> isPattern(transformedValueStr) ? // a pattern, use like like(expressionToCompare, toSQL(transformedValueStr)) : equal(expressionToCompare, transformedValueStr)); @@ -234,22 +229,22 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> return toNullOrNotEqualPredicate(fieldPath, transformedValue); } - private Predicate getOutPredicate(final RsqlField rsqlField, final Path fieldPath, + private Predicate getOutPredicate(final QuertPath queryField, final Path fieldPath, final List transformedValues) { - final String[] subAttributes = rsqlField.getSubAttributes(); + final String[] subAttributes = queryField.getJpaPath(); - if (isSimpleField(subAttributes, rsqlField.getEnumValue().isMap())) { + if (isSimpleField(subAttributes, queryField.getEnumValue().isMap())) { return cb.or(cb.isNull(fieldPath), cb.not(in(pathOfString(fieldPath), transformedValues))); } clearJoinsIfNotNeeded(); - return toNotExistsSubQueryPredicate(rsqlField, expressionToCompare -> in(expressionToCompare, transformedValues)); + return toNotExistsSubQueryPredicate(queryField, expressionToCompare -> in(expressionToCompare, transformedValues)); } - private Path getFieldPath(final Root root, final RsqlField rsqlField) { - final String[] split = rsqlField.getSubAttributes(); + private Path getFieldPath(final Root root, final QuertPath queryField) { + final String[] split = queryField.getJpaPath(); Path fieldPath = null; - for (int i = 0, end = rsqlField.getEnumValue().isMap() ? split.length - 1 : split.length; i < end; i++) { + for (int i = 0, end = queryField.getEnumValue().isMap() ? split.length - 1 : split.length; i < end; i++) { final String fieldNameSplit = split[i]; fieldPath = fieldPath == null ? getPath(root, fieldNameSplit) : fieldPath.get(fieldNameSplit); } @@ -358,14 +353,14 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } @SuppressWarnings({ "unchecked", "rawtypes" }) - private Predicate toNotExistsSubQueryPredicate(final RsqlField rsqlField, final Function, Predicate> subQueryPredicateProvider) { + private Predicate toNotExistsSubQueryPredicate(final QuertPath queryField, final Function, Predicate> subQueryPredicateProvider) { final Class javaType = root.getJavaType(); final Subquery subquery = query.subquery(javaType); final Root subqueryRoot = subquery.from(javaType); - final Predicate equalPredicate = cb.equal(root.get(rsqlField.getEnumValue().identifierFieldName()), - subqueryRoot.get(rsqlField.getEnumValue().identifierFieldName())); - final Expression expressionToCompare = getExpressionToCompare(rsqlField.getEnumValue(), - getFieldPath(subqueryRoot, rsqlField)); + final Predicate equalPredicate = cb.equal(root.get(queryField.getEnumValue().identifierFieldName()), + subqueryRoot.get(queryField.getEnumValue().identifierFieldName())); + final Expression expressionToCompare = getExpressionToCompare(queryField.getEnumValue(), + getFieldPath(subqueryRoot, queryField)); final Predicate subQueryPredicate = subQueryPredicateProvider.apply(expressionToCompare); subquery.select(subqueryRoot).where(cb.and(equalPredicate, subQueryPredicate)); return cb.not(cb.exists(subquery));