From dce133dfaec1d2f627bc09c2d0b83580060a8517 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Fri, 19 Sep 2025 15:45:43 +0300 Subject: [PATCH] Add some id based searches and software module search by type (#2681) Signed-off-by: Avgustin Marinov --- .../repository/DistributionSetFields.java | 5 ++++- .../repository/SoftwareModuleFields.java | 5 ++++- .../hawkbit/repository/TargetFields.java | 11 +++++----- .../repository/jpa/rsql/RsqlParser.java | 19 ++++++++++++---- .../repository/jpa/rsql/RsqlUtility.java | 5 +++++ .../jpa/rsql/legacy/AbstractRSQLVisitor.java | 10 +++++++++ .../jpa/rsql/RsqlSoftwareModuleFieldTest.java | 22 +++++++++++++++++++ .../jpa/rsql/RsqlTargetFieldTest.java | 12 +++++++++- 8 files changed, 77 insertions(+), 12 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 8db38e6b5..62544ca51 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 @@ -21,7 +21,10 @@ import lombok.Getter; public enum DistributionSetFields implements RsqlQueryField { ID("id"), - TYPE("type", "key", "name"), + TYPE("type", + DistributionSetTypeFields.ID.getJpaEntityFieldName(), + DistributionSetTypeFields.KEY.getJpaEntityFieldName(), + DistributionSetTypeFields.NAME.getJpaEntityFieldName()), NAME("name"), DESCRIPTION("description"), CREATEDAT("createdAt"), 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 f1b4dcae1..f6b524e37 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 @@ -20,7 +20,10 @@ import lombok.Getter; public enum SoftwareModuleFields implements RsqlQueryField { ID("id"), - TYPE("type", "key"), + TYPE("type", + SoftwareModuleTypeFields.ID.getJpaEntityFieldName(), + SoftwareModuleTypeFields.KEY.getJpaEntityFieldName(), + SoftwareModuleTypeFields.NAME.getJpaEntityFieldName()), NAME("name"), DESCRIPTION("description"), VERSION("version"), 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 4c1b39903..54670aea1 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 @@ -32,16 +32,17 @@ public enum TargetFields implements RsqlQueryField { IPADDRESS("address"), ATTRIBUTE("controllerAttributes"), GROUP("group"), - ASSIGNEDDS( - "assignedDistributionSet", + ASSIGNEDDS("assignedDistributionSet", DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()), - INSTALLEDDS( - "installedDistributionSet", + INSTALLEDDS("installedDistributionSet", DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()), TAG("tags", TagFields.NAME.getJpaEntityFieldName()), LASTCONTROLLERREQUESTAT("lastTargetQuery"), METADATA("metadata"), - TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName()); + TARGETTYPE("targetType", + TargetTypeFields.ID.getJpaEntityFieldName(), + TargetTypeFields.KEY.getJpaEntityFieldName(), + TargetTypeFields.NAME.getJpaEntityFieldName()); private final String jpaEntityFieldName; private final List subEntityAttributes; 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 7a1a03803..2097c3d87 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,6 +24,7 @@ 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; @@ -41,8 +42,10 @@ 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; @@ -68,14 +71,14 @@ public class RsqlParser { public static final ComparisonOperator IS = new ComparisonOperator("=is=", "=eq="); public static final ComparisonOperator NOT = new ComparisonOperator("=not=", "=ne="); - private static final RSQLParser PARSER; + private static final RSQLParser RSQL_PARSER; static { final Set operators = new HashSet<>(RSQLOperators.defaultOperators()); // == and != alternatives just treating "null" string as null not as a "null" operators.add(IS); operators.add(NOT); - PARSER = new RSQLParser(operators); + RSQL_PARSER = new RSQLParser(operators); } public static Node parse(final String rsql) { @@ -88,7 +91,7 @@ public class RsqlParser { private static Node parse(final String rsql, final Function keyResolver) { try { - return PARSER + return RSQL_PARSER .parse(rsql) .accept(new RsqlVisitor(keyResolver)); } catch (final RSQLParserException e) { @@ -121,6 +124,12 @@ public class RsqlParser { } 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( @@ -148,7 +157,9 @@ public class RsqlParser { } } - return new Key(attribute, RsqlVisitor.valueConverter(enumValue)); + return new + + Key(attribute, RsqlVisitor.valueConverter(enumValue)); } private record Key(String path, UnaryOperator converter) {} 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 8cf6c67dc..b2987e982 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,6 +71,11 @@ 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 fb0278762..6041fd438 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,6 +13,7 @@ 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; @@ -23,9 +24,12 @@ 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; /** @@ -72,6 +76,12 @@ public abstract class AbstractRSQLVisitor & RsqlQueryField> { 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" }; } else { throw createRSQLParameterUnsupportedException(node, null); } 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 78b0b8ccd..1c6556975 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 @@ -135,6 +135,16 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest { */ @Test void testFilterByType() { + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".key==" + TestdataFactory.SM_TYPE_APP, 2); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".key!=" + TestdataFactory.SM_TYPE_APP, 4); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".key==noExist*", 0); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".key=in=(" + TestdataFactory.SM_TYPE_APP + ")", 2); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".key=out=(" + TestdataFactory.SM_TYPE_APP + ")", 4); + } + + @Test + void testFilterByTypeBackwardCompatibility() { + assertThat(RsqlUtility.SM_DS_SEARCH_BY_TYPE_BACKWARD_COMPATIBILITY).isTrue(); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==" + TestdataFactory.SM_TYPE_APP, 2); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "!=" + TestdataFactory.SM_TYPE_APP, 4); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==noExist*", 0); @@ -142,6 +152,18 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "=out=(" + TestdataFactory.SM_TYPE_APP + ")", 4); } + /** + * Test filter software module by type key + */ + @Test + void testFilterByName() { + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".name==" + appType.getName(), 2); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".name!=" + appType.getName(), 4); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".name==noExist*", 0); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".name=in=(" + appType.getName() + ")", 2); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + ".name=out=(" + appType.getName() + ")", 4); + } + /** * Test filter software module by metadata */ diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java index 7eb1fccd3..ef58fa18d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlTargetFieldTest.java @@ -396,6 +396,16 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { .isThrownBy(() -> RsqlUtility.getInstance().validateRsqlFor("wrongfield == abcd", TargetFields.class, JpaTarget.class)); } + /** + * Test filter by target type key + */ + @Test + void shouldFilterTargetsByTypeId() { + assertRSQLQuery("targettype." + TargetTypeFields.ID.name() + "==" + targetType1.getId(), 1); + assertRSQLQuery("targettype." + TargetTypeFields.ID.name() + "!=" + targetType2.getId(), 4); + assertRSQLQuery("targettype." + TargetTypeFields.ID.name() + "==-1", 0); + } + /** * Test filter by target type key */ @@ -424,7 +434,7 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest { @Test void shouldFilterTargetsByTypeIdAndDescription() { assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class) - .isThrownBy(() -> assertRSQLQuery("targettype.ID==1", 0)); + .isThrownBy(() -> assertRSQLQuery("targettype.IDD==1", 0)); assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class) .isThrownBy(() -> assertRSQLQuery("targettype.description==Description", 0)); }