diff --git a/hawkbit-core/pom.xml b/hawkbit-core/pom.xml
index 974ee4a1a..514ded4ba 100644
--- a/hawkbit-core/pom.xml
+++ b/hawkbit-core/pom.xml
@@ -39,7 +39,12 @@
${commons-io.version}
-
+
+
+ io.github.classgraph
+ classgraph
+ test
+
org.springframework.boot
spring-boot-starter-test
diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java
index bc3f0ac6f..9cdcf4c0b 100644
--- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java
+++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java
@@ -46,14 +46,12 @@ public interface FieldNameProvider {
default String[] getSubAttributes(final String propertyFieldName) {
if (isMap()) {
final String[] subAttributes = propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX, 2);
- // [0] field name | [1] key name
+ // [0] field name | [1] key name (could miss, e.g. for target attributes)
final String mapKeyName = subAttributes.length == 2 ? subAttributes[1] : null;
- if (ObjectUtils.isEmpty(mapKeyName)) {
- return new String[] { getFieldName() };
- }
- return new String[] { getFieldName(), mapKeyName };
+ return ObjectUtils.isEmpty(mapKeyName) ? new String[] { getFieldName() } : new String[] { getFieldName(), mapKeyName };
+ } else {
+ return propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX);
}
- return propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX);
}
/**
diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/repository/FileNameFieldsTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/repository/FileNameFieldsTest.java
new file mode 100644
index 000000000..3c964ed7e
--- /dev/null
+++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/repository/FileNameFieldsTest.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.repository;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+import io.qameta.allure.Description;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class FileNameFieldsTest {
+
+ @Test
+ @Description("Verifies that fields classes are correctly implemented")
+ @SuppressWarnings("unchecked")
+ public void repositoryManagementMethodsArePreAuthorizedAnnotated() {
+ final String packageName = getClass().getPackage().getName();
+ try (final ScanResult scanResult = new ClassGraph().acceptPackages(packageName).scan()) {
+ final List extends Class extends FieldNameProvider>> matchingClasses = scanResult.getAllClasses()
+ .stream()
+ .filter(classInPackage -> classInPackage.implementsInterface(FieldNameProvider.class))
+ .map(ClassInfo::loadClass)
+ .map(clazz -> (Class extends FieldNameProvider>) clazz)
+ .toList();
+ assertThat(matchingClasses).isNotEmpty();
+ matchingClasses.forEach(providerClass -> {
+ assertThat(providerClass.getEnumConstants()).isNotEmpty();
+ for (final FieldNameProvider provider : providerClass.getEnumConstants()) {
+ if (provider.isMap() && !provider.getSubEntityAttributes().isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Currently sub-entity attributes for maps are not supported, alternatively you could use the key/value tuple, defined by SimpleImmutableEntry class");
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractFieldNameRSQLVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractFieldNameRSQLVisitor.java
index 540034bb0..d3c02456b 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractFieldNameRSQLVisitor.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractFieldNameRSQLVisitor.java
@@ -43,48 +43,36 @@ public abstract class AbstractFieldNameRSQLVisitor & FieldName
protected String getAndValidatePropertyFieldName(final A propertyEnum, final ComparisonNode node) {
final String[] subAttributes = propertyEnum.getSubAttributes(node.getSelector());
- validateMapParameter(propertyEnum, node, subAttributes);
- // sub entity need minimum 1 dot
- if (!propertyEnum.getSubEntityAttributes().isEmpty() && subAttributes.length < 2) {
- throw createRSQLParameterUnsupportedException(node, null);
- }
-
- final StringBuilder fieldNameBuilder = new StringBuilder(propertyEnum.getFieldName());
- for (int i = 1; i < subAttributes.length; i++) {
- final String propertyField = getFormattedSubEntityAttribute(propertyEnum ,subAttributes[i]);
- fieldNameBuilder.append(FieldNameProvider.SUB_ATTRIBUTE_SEPARATOR).append(propertyField);
-
- // the key of map is not in the graph
- if (propertyEnum.isMap() && subAttributes.length == (i + 1)) {
- continue;
+ if (propertyEnum.isMap()) {
+ // enum.key
+ if (subAttributes.length != 2) {
+ throw new RSQLParameterUnsupportedFieldException(
+ "The syntax of the given map search parameter field {" + node.getSelector() + "} is wrong. Syntax is: .");
}
-
- if (!propertyEnum.containsSubEntityAttribute(propertyField)) {
+ } else {
+ // sub entity need minimum 1 dot
+ if (!propertyEnum.getSubEntityAttributes().isEmpty() && subAttributes.length < 2) {
throw createRSQLParameterUnsupportedException(node, null);
}
}
+ final StringBuilder fieldNameBuilder = new StringBuilder(propertyEnum.getFieldName());
+ for (int i = 1; i < subAttributes.length; i++) {
+ final String propertyField = getFormattedSubEntityAttribute(propertyEnum, subAttributes[i]);
+
+ if (!propertyEnum.containsSubEntityAttribute(propertyField)) {
+ if (i != subAttributes.length - 1 || !propertyEnum.isMap()) {
+ throw createRSQLParameterUnsupportedException(node, null);
+ } // otherwise - the key of map is not in the sub entity attributes
+ }
+
+ fieldNameBuilder.append(FieldNameProvider.SUB_ATTRIBUTE_SEPARATOR).append(propertyField);
+ }
+
return fieldNameBuilder.toString();
}
- private void validateMapParameter(final A propertyEnum, final ComparisonNode node, final String[] subAttributes) {
- if (!propertyEnum.isMap()) {
- return;
- }
-
- if (!propertyEnum.getSubEntityAttributes().isEmpty()) {
- throw new UnsupportedOperationException(
- "Currently sub-entity attributes for maps are not supported, alternatively you could use the key/value tuple, defined by SimpleImmutableEntry class");
- }
-
- // enum.key
- if (subAttributes.length != 2) {
- throw new RSQLParameterUnsupportedFieldException("The syntax of the given map search parameter field {" +
- node.getSelector() + "} is wrong. Syntax is: .");
- }
- }
-
/**
* @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/JpaQueryRsqlVisitorG2.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java
index 0bda5505b..82834f2c0 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
@@ -51,8 +51,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
- * An implementation of the {@link RSQLVisitor} to visit the parsed tokens and
- * build JPA where clauses.
+ * An implementation of the {@link RSQLVisitor} to visit the parsed tokens and build JPA where clauses.
*
* @param the enum for providing the field name of the entity field to filter on.
* @param the entity type referenced by the root
@@ -63,8 +62,8 @@ public class JpaQueryRsqlVisitorG2 & FieldNameProvider, T>
public static final Character LIKE_WILDCARD = '*';
private static final char ESCAPE_CHAR = '\\';
- private static final List NO_JOINS_OPERATOR = List.of("!=", "=out=");
private static final String ESCAPE_CHAR_WITH_ASTERISK = ESCAPE_CHAR +"*";
+ private static final List NO_JOINS_OPERATOR = List.of("!=", "=out=");
private final Root root;
private final CriteriaQuery> query;
@@ -94,11 +93,7 @@ public class JpaQueryRsqlVisitorG2 & FieldNameProvider, T>
@Override
public List visit(final AndNode node, final String param) {
final List children = acceptChildren(node);
- if (children.isEmpty()) {
- return toSingleList(cb.conjunction());
- } else {
- return toSingleList(cb.and(children.toArray(new Predicate[0])));
- }
+ return Collections.singletonList(children.isEmpty() ? cb.conjunction() : cb.and(children.toArray(new Predicate[0])));
}
@Override
@@ -106,11 +101,7 @@ public class JpaQueryRsqlVisitorG2 & FieldNameProvider, T>
inOr = true;
try {
final List children = acceptChildren(node);
- if (children.isEmpty()) {
- return toSingleList(cb.conjunction());
- } else {
- return toSingleList(cb.or(children.toArray(new Predicate[0])));
- }
+ return Collections.singletonList(children.isEmpty() ? cb.conjunction() : cb.or(children.toArray(new Predicate[0])));
} finally {
inOr = false;
javaTypeToPath.clear();
@@ -143,11 +134,17 @@ public class JpaQueryRsqlVisitorG2 & FieldNameProvider, T>
final Predicate mapPredicate = mapToMapPredicate(node, enumField, fieldPath);
final Predicate valuePredicate = addOperatorPredicate(node, enumField, finalProperty,
- getMapValueFieldPath(enumField, fieldPath), transformedValues, value);
+ getValueFieldPath(enumField, fieldPath), transformedValues, value);
- return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
+ return Collections.singletonList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
+ }
+ private Path