From 9ab0a8628e74ccd5f0acee8cb82709bdad05e38a Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Tue, 23 Sep 2025 10:56:26 +0300 Subject: [PATCH] First level suppor for RsqlQueryFields shortcut support (#2686) * now it is possible to have a showrtcut for a sub attributes (i.e. calling it directly with enum name, e.g. type -> type.key) with directly specifying the defaultSubEntityAttribute * no need to have single sub attribute in order to have a default sub attribute * added TYPE search field for TargetFields (sinonim of targettype) * targettype is deprecated - to be decided if and when to be removed * returned back "type" direct search (with meaning type.key) for DistributionSet and SoftwareModule as non-depricated * add serche with "type" as type.key for Target Signed-off-by: Avgustin Marinov --- .../repository/DistributionSetFields.java | 5 ++++ .../repository/FieldValueConverter.java | 2 +- .../hawkbit/repository/RsqlQueryField.java | 12 +++++++- .../repository/SoftwareModuleFields.java | 6 ++++ .../hawkbit/repository/TargetFields.java | 13 +++++++++ .../repository/jpa/rsql/RsqlParser.java | 28 ++++++++----------- .../repository/jpa/rsql/RsqlUtility.java | 5 ---- .../jpa/rsql/legacy/AbstractRSQLVisitor.java | 15 ++-------- .../jpa/rsql/legacy/JpaQueryRsqlVisitor.java | 4 +-- .../rsql/legacy/JpaQueryRsqlVisitorG2.java | 4 +-- .../jpa/rsql/RsqlSoftwareModuleFieldTest.java | 3 +- 11 files changed, 55 insertions(+), 42 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 62544ca51..0cec77bbd 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 @@ -46,6 +46,11 @@ public enum DistributionSetFields implements RsqlQueryField { this.subEntityAttributes = List.of(subEntityAttributes); } + @Override + public String getDefaultSubEntityAttribute() { + return this == TYPE ? DistributionSetTypeFields.KEY.getJpaEntityFieldName() : RsqlQueryField.super.getDefaultSubEntityAttribute(); + } + @Override public boolean isMap() { return this == METADATA; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldValueConverter.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldValueConverter.java index 589b64607..5880b5a86 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldValueConverter.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldValueConverter.java @@ -20,7 +20,7 @@ package org.eclipse.hawkbit.repository; public interface FieldValueConverter> { /** - * Converts the given {@code value} into the representation to build ageneric query. + * Converts the given {@code value} into the representation to build a generic query. * * @param enumValue the enum value to build the value for * @param value the value in string representation 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 b36736539..9ec676c0d 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 @@ -39,12 +39,22 @@ public interface RsqlQueryField { return Collections.emptyList(); } + /** + * Return the default sub entity attribute if available. This allows to skip that sub-attribute and call with "shortcut" - the enum name + * + * @return the default sub-attribute or null> if no default is available + */ + default String getDefaultSubEntityAttribute() { + final List subAttributes = getSubEntityAttributes(); + return subAttributes.size() == 1 ? subAttributes.get(0) : null; + } + /** * Returns the name of the field, that identifies the entity. * * @return the name of the identifier, by default 'id' */ - default String identifierFieldName() { + default String getIdentifierFieldName() { return "id"; } 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 f6b524e37..0729a0700 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 @@ -41,6 +41,12 @@ public enum SoftwareModuleFields implements RsqlQueryField { this.subEntityAttributes = List.of(subEntityAttributes); } + + @Override + public String getDefaultSubEntityAttribute() { + return this == TYPE ? SoftwareModuleTypeFields.KEY.getJpaEntityFieldName() : RsqlQueryField.super.getDefaultSubEntityAttribute(); + } + @Override public boolean isMap() { return this == METADATA; 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 54670aea1..0bbd287b7 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 @@ -39,6 +39,14 @@ public enum TargetFields implements RsqlQueryField { TAG("tags", TagFields.NAME.getJpaEntityFieldName()), LASTCONTROLLERREQUESTAT("lastTargetQuery"), METADATA("metadata"), + TYPE("targetType", + TargetTypeFields.ID.getJpaEntityFieldName(), + TargetTypeFields.KEY.getJpaEntityFieldName(), + TargetTypeFields.NAME.getJpaEntityFieldName()), + // kept just for backward compatibility for backward compatibility + // could be removed only if in the systems there are no active auto assignments or rollouts (dynamic or starting) with that condition + // to be reconsidered if and when to be removed + @Deprecated(forRemoval = true, since = "0.10.0") TARGETTYPE("targetType", TargetTypeFields.ID.getJpaEntityFieldName(), TargetTypeFields.KEY.getJpaEntityFieldName(), @@ -52,6 +60,11 @@ public enum TargetFields implements RsqlQueryField { this.subEntityAttributes = List.of(subEntityAttributes); } + @Override + public String getDefaultSubEntityAttribute() { + return this == TYPE ? TargetTypeFields.KEY.getJpaEntityFieldName() : RsqlQueryField.super.getDefaultSubEntityAttribute(); + } + @Override public boolean isMap() { return this == ATTRIBUTE || this == METADATA; diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlParser.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlParser.java index 2097c3d87..49c85487a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlParser.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlParser.java @@ -24,7 +24,6 @@ import static org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator.NOT import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -42,10 +41,8 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.DistributionSetFields; import org.eclipse.hawkbit.repository.FieldValueConverter; import org.eclipse.hawkbit.repository.RsqlQueryField; -import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.jpa.ql.Node; @@ -58,7 +55,7 @@ import org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison; *
  • check for * and convert EQ/NEQ to LIKE/NOT_LIKE
  • *
  • replace the rsql fields (enum values) with the JPA entity field names
  • *
  • checks sub-attributes (if allowed)
  • - *
  • append the default sub-attributes if needed)
  • + *
  • append the default sub-attributes if needed
  • *
  • apply value conversion for implementing with FieldValueConverter
  • * */ @@ -121,20 +118,17 @@ public class RsqlParser { // just enum name for a complex type (with sub-attributes), should have single (default!) sub-attribute if (enumValue.isMap()) { throw new RSQLParameterUnsupportedFieldException("No key specified for a map type " + enumValue); - } else if (enumValue.getSubEntityAttributes().size() == 1) { - // single sub attribute - so, treat it as a default - attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + enumValue.getSubEntityAttributes().get(0); - } else if (RsqlUtility.SM_DS_SEARCH_BY_TYPE_BACKWARD_COMPATIBILITY && - "type".equalsIgnoreCase(enumValue.getJpaEntityFieldName()) && - (Objects.equals(rsqlQueryFieldType, SoftwareModuleFields.class) || - Objects.equals(rsqlQueryFieldType, DistributionSetFields.class))) { - // backward compatibility - type for SoftwareModuleTypeFields && DistributionSetFields means type.key - attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + "key"; } else { - throw new RSQLParameterUnsupportedFieldException( - String.format( - "The given search parameter field {%s} requires one of the following sub-attributes %s", - key, enumValue.getSubEntityAttributes())); + final String defaultSubEntityAttribute = enumValue.getDefaultSubEntityAttribute(); + if (defaultSubEntityAttribute != null) { + // single sub attribute - so, treat it as a default + attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + defaultSubEntityAttribute; + } else { + throw new RSQLParameterUnsupportedFieldException( + String.format( + "The given search parameter field {%s} requires one of the following sub-attributes %s", + key, enumValue.getSubEntityAttributes())); + } } } } else { // field name with sub-attribute diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlUtility.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlUtility.java index b2987e982..8cf6c67dc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlUtility.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlUtility.java @@ -71,11 +71,6 @@ import org.springframework.orm.jpa.vendor.Database; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class RsqlUtility { - // to be removed in future releases, use type.key instead of type for software module and distribution set RSQL queries - @Deprecated(forRemoval = true, since = "0.10.0") - public static final boolean SM_DS_SEARCH_BY_TYPE_BACKWARD_COMPATIBILITY = - "true".equalsIgnoreCase(System.getProperty("hawkbit.rsql.sm-ds-search-by-type.backward-compatibility", "true")); - private static final RsqlUtility SINGLETON = new RsqlUtility(); public enum RsqlToSpecBuilder { diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java index 6041fd438..ab72ba2a2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/AbstractRSQLVisitor.java @@ -13,7 +13,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -24,12 +23,9 @@ 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.DistributionSetFields; import org.eclipse.hawkbit.repository.RsqlQueryField; -import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder; -import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility; import org.springframework.util.ObjectUtils; /** @@ -74,14 +70,9 @@ public abstract class AbstractRSQLVisitor & RsqlQueryField> { if (!enumValue.isMap()) { // sub entity need minimum 1 dot if (!enumValue.getSubEntityAttributes().isEmpty() && split.length < 2) { - if (enumValue.getSubEntityAttributes().size() == 1) { // single sub attribute - so add is as a default - split = new String[] { split[0], enumValue.getSubEntityAttributes().get(0) }; - } else if (RsqlUtility.SM_DS_SEARCH_BY_TYPE_BACKWARD_COMPATIBILITY && - "type".equals(node.getSelector()) && - (Objects.equals(rsqlQueryFieldType, SoftwareModuleFields.class) || - Objects.equals(rsqlQueryFieldType, DistributionSetFields.class))) { - // backward compatibility - type for DistributionSetFields means type.key - split = new String[] { split[0], "key" }; + final String defaultSubEntityAttribute = enumValue.getDefaultSubEntityAttribute(); + if (defaultSubEntityAttribute != null) { // single sub attribute - so add is as a default + split = new String[] { split[0], defaultSubEntityAttribute }; } else { throw createRSQLParameterUnsupportedException(node, null); } diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java index ecbba586e..a46be46c8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitor.java @@ -493,8 +493,8 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends final Class javaType = root.getJavaType(); final Subquery subquery = query.subquery(javaType); final Root subqueryRoot = subquery.from(javaType); - final Predicate equalPredicate = cb.equal(root.get(enumField.identifierFieldName()), - subqueryRoot.get(enumField.identifierFieldName())); + final Predicate equalPredicate = cb.equal(root.get(enumField.getIdentifierFieldName()), + subqueryRoot.get(enumField.getIdentifierFieldName())); final Path innerFieldPath = getInnerFieldPath(subqueryRoot, fieldNames, enumField.isMap()); final Expression expressionToCompare = getExpressionToCompare(innerFieldPath, enumField); final Predicate subQueryPredicate = subQueryPredicateProvider.apply(expressionToCompare); diff --git a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java index 8fec04fc0..dfeee3710 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java +++ b/hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/JpaQueryRsqlVisitorG2.java @@ -341,8 +341,8 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> subquery.select(subqueryRoot) .where(cb.and( cb.equal( - root.get(queryPath.getEnumValue().identifierFieldName()), - subqueryRoot.get(queryPath.getEnumValue().identifierFieldName())), + root.get(queryPath.getEnumValue().getIdentifierFieldName()), + subqueryRoot.get(queryPath.getEnumValue().getIdentifierFieldName())), subQueryPredicateProvider.apply( getExpressionToCompare(queryPath.getEnumValue(), getFieldPath(subqueryRoot, queryPath))))))); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlSoftwareModuleFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlSoftwareModuleFieldTest.java index 1c6556975..835368821 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlSoftwareModuleFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlSoftwareModuleFieldTest.java @@ -143,8 +143,7 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest { } @Test - void testFilterByTypeBackwardCompatibility() { - assertThat(RsqlUtility.SM_DS_SEARCH_BY_TYPE_BACKWARD_COMPATIBILITY).isTrue(); + void testFilterByTypeShortcut() { assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==" + TestdataFactory.SM_TYPE_APP, 2); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "!=" + TestdataFactory.SM_TYPE_APP, 4); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==noExist*", 0);