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 <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-09-23 10:56:26 +03:00
committed by GitHub
parent d98ab779a2
commit 9ab0a8628e
11 changed files with 55 additions and 42 deletions

View File

@@ -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;

View File

@@ -20,7 +20,7 @@ package org.eclipse.hawkbit.repository;
public interface FieldValueConverter<T extends Enum<T>> {
/**
* 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

View File

@@ -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 <code>null</code>> if no default is available
*/
default String getDefaultSubEntityAttribute() {
final List<String> 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";
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
* <li>check for * and convert EQ/NEQ to LIKE/NOT_LIKE</li>
* <li>replace the rsql fields (enum values) with the JPA entity field names</li>
* <li>checks sub-attributes (if allowed)</li>
* <li>append the default sub-attributes if needed)</li>
* <li>append the default sub-attributes if needed</li>
* <li>apply value conversion for implementing with FieldValueConverter</li>
* </ul>
*/
@@ -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

View File

@@ -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 {

View File

@@ -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<A extends Enum<A> & 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);
}

View File

@@ -493,8 +493,8 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & 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<String> expressionToCompare = getExpressionToCompare(innerFieldPath, enumField);
final Predicate subQueryPredicate = subQueryPredicateProvider.apply(expressionToCompare);

View File

@@ -341,8 +341,8 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & 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)))))));
}

View File

@@ -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);