Add some id based searches and software module search by type (#2681)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-09-19 15:45:43 +03:00
committed by GitHub
parent e19b11290d
commit dce133dfae
8 changed files with 77 additions and 12 deletions

View File

@@ -21,7 +21,10 @@ import lombok.Getter;
public enum DistributionSetFields implements RsqlQueryField { public enum DistributionSetFields implements RsqlQueryField {
ID("id"), ID("id"),
TYPE("type", "key", "name"), TYPE("type",
DistributionSetTypeFields.ID.getJpaEntityFieldName(),
DistributionSetTypeFields.KEY.getJpaEntityFieldName(),
DistributionSetTypeFields.NAME.getJpaEntityFieldName()),
NAME("name"), NAME("name"),
DESCRIPTION("description"), DESCRIPTION("description"),
CREATEDAT("createdAt"), CREATEDAT("createdAt"),

View File

@@ -20,7 +20,10 @@ import lombok.Getter;
public enum SoftwareModuleFields implements RsqlQueryField { public enum SoftwareModuleFields implements RsqlQueryField {
ID("id"), ID("id"),
TYPE("type", "key"), TYPE("type",
SoftwareModuleTypeFields.ID.getJpaEntityFieldName(),
SoftwareModuleTypeFields.KEY.getJpaEntityFieldName(),
SoftwareModuleTypeFields.NAME.getJpaEntityFieldName()),
NAME("name"), NAME("name"),
DESCRIPTION("description"), DESCRIPTION("description"),
VERSION("version"), VERSION("version"),

View File

@@ -32,16 +32,17 @@ public enum TargetFields implements RsqlQueryField {
IPADDRESS("address"), IPADDRESS("address"),
ATTRIBUTE("controllerAttributes"), ATTRIBUTE("controllerAttributes"),
GROUP("group"), GROUP("group"),
ASSIGNEDDS( ASSIGNEDDS("assignedDistributionSet",
"assignedDistributionSet",
DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()), DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()),
INSTALLEDDS( INSTALLEDDS("installedDistributionSet",
"installedDistributionSet",
DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()), DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()),
TAG("tags", TagFields.NAME.getJpaEntityFieldName()), TAG("tags", TagFields.NAME.getJpaEntityFieldName()),
LASTCONTROLLERREQUESTAT("lastTargetQuery"), LASTCONTROLLERREQUESTAT("lastTargetQuery"),
METADATA("metadata"), 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 String jpaEntityFieldName;
private final List<String> subEntityAttributes; private final List<String> subEntityAttributes;

View File

@@ -24,6 +24,7 @@ import static org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator.NOT
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
@@ -41,8 +42,10 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.FieldValueConverter; import org.eclipse.hawkbit.repository.FieldValueConverter;
import org.eclipse.hawkbit.repository.RsqlQueryField; 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.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.ql.Node; 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 IS = new ComparisonOperator("=is=", "=eq=");
public static final ComparisonOperator NOT = new ComparisonOperator("=not=", "=ne="); public static final ComparisonOperator NOT = new ComparisonOperator("=not=", "=ne=");
private static final RSQLParser PARSER; private static final RSQLParser RSQL_PARSER;
static { static {
final Set<ComparisonOperator> operators = new HashSet<>(RSQLOperators.defaultOperators()); final Set<ComparisonOperator> operators = new HashSet<>(RSQLOperators.defaultOperators());
// == and != alternatives just treating "null" string as null not as a "null" // == and != alternatives just treating "null" string as null not as a "null"
operators.add(IS); operators.add(IS);
operators.add(NOT); operators.add(NOT);
PARSER = new RSQLParser(operators); RSQL_PARSER = new RSQLParser(operators);
} }
public static Node parse(final String rsql) { public static Node parse(final String rsql) {
@@ -88,7 +91,7 @@ public class RsqlParser {
private static Node parse(final String rsql, final Function<String, Key> keyResolver) { private static Node parse(final String rsql, final Function<String, Key> keyResolver) {
try { try {
return PARSER return RSQL_PARSER
.parse(rsql) .parse(rsql)
.accept(new RsqlVisitor(keyResolver)); .accept(new RsqlVisitor(keyResolver));
} catch (final RSQLParserException e) { } catch (final RSQLParserException e) {
@@ -121,6 +124,12 @@ public class RsqlParser {
} else if (enumValue.getSubEntityAttributes().size() == 1) { } else if (enumValue.getSubEntityAttributes().size() == 1) {
// single sub attribute - so, treat it as a default // single sub attribute - so, treat it as a default
attribute = enumValue.getJpaEntityFieldName() + SUB_ATTRIBUTE_SEPARATOR + enumValue.getSubEntityAttributes().get(0); 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 { } else {
throw new RSQLParameterUnsupportedFieldException( throw new RSQLParameterUnsupportedFieldException(
String.format( 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<Object> converter) {} private record Key(String path, UnaryOperator<Object> converter) {}

View File

@@ -71,6 +71,11 @@ import org.springframework.orm.jpa.vendor.Database;
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RsqlUtility { 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(); private static final RsqlUtility SINGLETON = new RsqlUtility();
public enum RsqlToSpecBuilder { public enum RsqlToSpecBuilder {

View File

@@ -13,6 +13,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -23,9 +24,12 @@ import cz.jirutka.rsql.parser.ast.ComparisonOperator;
import cz.jirutka.rsql.parser.ast.RSQLOperators; import cz.jirutka.rsql.parser.ast.RSQLOperators;
import lombok.Value; import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.RsqlQueryField; import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder; import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
@@ -72,6 +76,12 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
if (!enumValue.getSubEntityAttributes().isEmpty() && split.length < 2) { if (!enumValue.getSubEntityAttributes().isEmpty() && split.length < 2) {
if (enumValue.getSubEntityAttributes().size() == 1) { // single sub attribute - so add is as a default if (enumValue.getSubEntityAttributes().size() == 1) { // single sub attribute - so add is as a default
split = new String[] { split[0], enumValue.getSubEntityAttributes().get(0) }; 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 { } else {
throw createRSQLParameterUnsupportedException(node, null); throw createRSQLParameterUnsupportedException(node, null);
} }

View File

@@ -135,6 +135,16 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
*/ */
@Test @Test
void testFilterByType() { 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, 2);
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "!=" + TestdataFactory.SM_TYPE_APP, 4); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "!=" + TestdataFactory.SM_TYPE_APP, 4);
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==noExist*", 0); assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==noExist*", 0);
@@ -142,6 +152,18 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "=out=(" + TestdataFactory.SM_TYPE_APP + ")", 4); 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 * Test filter software module by metadata
*/ */

View File

@@ -396,6 +396,16 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> RsqlUtility.getInstance().validateRsqlFor("wrongfield == abcd", TargetFields.class, JpaTarget.class)); .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 * Test filter by target type key
*/ */
@@ -424,7 +434,7 @@ class RsqlTargetFieldTest extends AbstractJpaIntegrationTest {
@Test @Test
void shouldFilterTargetsByTypeIdAndDescription() { void shouldFilterTargetsByTypeIdAndDescription() {
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class) assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
.isThrownBy(() -> assertRSQLQuery("targettype.ID==1", 0)); .isThrownBy(() -> assertRSQLQuery("targettype.IDD==1", 0));
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class) assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
.isThrownBy(() -> assertRSQLQuery("targettype.description==Description", 0)); .isThrownBy(() -> assertRSQLQuery("targettype.description==Description", 0));
} }