diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionFields.java index 5b40f7ad5..e08f63cb6 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionFields.java @@ -42,11 +42,6 @@ public enum ActionFields implements RsqlQueryField, FieldValueConverter subEntityAttributes; - ActionFields(final String jpaEntityFieldName) { - this.jpaEntityFieldName = jpaEntityFieldName; - this.subEntityAttributes = Collections.emptyList(); - } - ActionFields(final String jpaEntityFieldName, final String... subEntityAttributes) { this.jpaEntityFieldName = jpaEntityFieldName; this.subEntityAttributes = List.of(subEntityAttributes); 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 7259c3a03..7a993ed6f 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 @@ -34,33 +34,19 @@ public enum DistributionSetFields implements RsqlQueryField { COMPLETE("complete"), MODULE("modules", SoftwareModuleFields.ID.getJpaEntityFieldName(), SoftwareModuleFields.NAME.getJpaEntityFieldName()), TAG("tags", "name"), - METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), + METADATA("metadata"), VALID("valid"); private final String jpaEntityFieldName; private final List subEntityAttributes; - private final Entry subEntityMapTuple; - - DistributionSetFields(final String jpaEntityFieldName) { - this(jpaEntityFieldName, Collections.emptyList(), null); - } DistributionSetFields(final String jpaEntityFieldName, final String... subEntityAttributes) { - this(jpaEntityFieldName, List.of(subEntityAttributes), null); - } - - DistributionSetFields(final String jpaEntityFieldName, final Entry subEntityMapTuple) { - this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple); - } - - DistributionSetFields(final String jpaEntityFieldName, List subEntityAttributes, final Entry subEntityMapTuple) { this.jpaEntityFieldName = jpaEntityFieldName; - this.subEntityMapTuple = subEntityMapTuple; - this.subEntityAttributes = subEntityAttributes; + this.subEntityAttributes = List.of(subEntityAttributes); } @Override - public Optional> getSubEntityMapTuple() { - return Optional.ofNullable(subEntityMapTuple); + public boolean isMap() { + return this == METADATA; } } \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagFields.java index ab936e920..844e255b7 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagFields.java @@ -27,17 +27,13 @@ public enum DistributionSetTagFields implements RsqlQueryField { NAME(TagFields.NAME.getJpaEntityFieldName()), DESCRIPTION(TagFields.DESCRIPTION.getJpaEntityFieldName()), COLOUR(TagFields.COLOUR.getJpaEntityFieldName()), - DISTRIBUTIONSET("assignedToDistributionSet", + DISTRIBUTIONSET( + "assignedToDistributionSet", DistributionSetFields.ID.getJpaEntityFieldName(), DistributionSetFields.NAME.getJpaEntityFieldName()); private final String jpaEntityFieldName; private final List subEntityAttributes; - DistributionSetTagFields(final String jpaEntityFieldName) { - this.jpaEntityFieldName = jpaEntityFieldName; - this.subEntityAttributes = Collections.emptyList(); - } - DistributionSetTagFields(final String jpaEntityFieldName, final String... subEntityAttributes) { this.jpaEntityFieldName = jpaEntityFieldName; this.subEntityAttributes = List.of(subEntityAttributes); 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 4d84fa638..589b64607 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 @@ -13,8 +13,7 @@ package org.eclipse.hawkbit.repository; * A value convert which converts given string based values into an object which * can be used for building generic queries. Mapping external API values e.g. * REST API to inside representation on database. E.g. mapping 'pending' or - * 'finished' values in rest queries to Action#isActive boolean - * value. + * 'finished' values in rest queries to Action#isActive boolean value. * * @param the enum parameter */ diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java index d13676a71..98da41470 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java @@ -24,18 +24,15 @@ public enum RolloutFields implements RsqlQueryField { NAME("name"), DESCRIPTION("description"), STATUS("status"), - DISTRIBUTIONSET("distributionSet", DistributionSetFields.ID.getJpaEntityFieldName(), + DISTRIBUTIONSET( + "distributionSet", + DistributionSetFields.ID.getJpaEntityFieldName(), DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName(), DistributionSetFields.TYPE.getJpaEntityFieldName()); private final String jpaEntityFieldName; private final List subEntityAttributes; - RolloutFields(final String jpaEntityFieldName) { - this.jpaEntityFieldName = jpaEntityFieldName; - this.subEntityAttributes = Collections.emptyList(); - } - RolloutFields(final String jpaEntityFieldName, final String... subEntityAttributes) { this.jpaEntityFieldName = jpaEntityFieldName; this.subEntityAttributes = List.of(subEntityAttributes); 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 031e375c2..ae5007dcb 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 @@ -34,30 +34,6 @@ public interface RsqlQueryField { @NotNull String getJpaEntityFieldName(); - /** - * Contains the sub entity the given field. - * - * @param propertyField the given field - * @return true contains false contains not - */ - default boolean containsSubEntityAttribute(final String propertyField) { - final List subEntityAttributes = getSubEntityAttributes(); - if (subEntityAttributes.contains(propertyField)) { - return true; - } - - for (final String attribute : subEntityAttributes) { - final String[] graph = attribute.split(SUB_ATTRIBUTE_SPLIT_REGEX); - for (final String subAttribute : graph) { - if (subAttribute.equalsIgnoreCase(propertyField)) { - return true; - } - } - } - - return false; - } - /** * @return all sub entities attributes. */ @@ -65,22 +41,6 @@ public interface RsqlQueryField { return Collections.emptyList(); } - /** - * @return a key/value tuple of a sub entity. - */ - default Optional> getSubEntityMapTuple() { - return Optional.empty(); - } - - /** - * Is the entity field a {@link Map} consisting of key-value pairs. - * - * @return true is a map false is not a map - */ - default boolean isMap() { - return getSubEntityMapTuple().isPresent(); - } - /** * Returns the name of the field, that identifies the entity. * @@ -89,4 +49,13 @@ public interface RsqlQueryField { default String identifierFieldName() { return "id"; } + + /** + * Is the entity field a {@link Map} consisting of key-value pairs. + * + * @return true is a map false is not a map + */ + default boolean isMap() { + return false; + } } \ No newline at end of file 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 e9cfa679f..709646d21 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 @@ -45,25 +45,10 @@ public enum TargetFields implements RsqlQueryField { private final String jpaEntityFieldName; private final List subEntityAttributes; - private final Entry subEntityMapTuple; - - TargetFields(final String jpaEntityFieldName) { - this(jpaEntityFieldName, Collections.emptyList(), null); - } TargetFields(final String jpaEntityFieldName, final String... subEntityAttributes) { - this(jpaEntityFieldName, List.of(subEntityAttributes), null); - } - - TargetFields(final String jpaEntityFieldName, final List subEntityAttributes, final Entry subEntityMapTuple) { this.jpaEntityFieldName = jpaEntityFieldName; - this.subEntityAttributes = subEntityAttributes; - this.subEntityMapTuple = subEntityMapTuple; - } - - @Override - public Optional> getSubEntityMapTuple() { - return Optional.ofNullable(subEntityMapTuple); + this.subEntityAttributes = List.of(subEntityAttributes); } @Override diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java index cbd0b4b28..acdc1b3aa 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java @@ -25,7 +25,7 @@ public enum TargetFilterQueryFields implements RsqlQueryField { AUTOASSIGNDISTRIBUTIONSET("autoAssignDistributionSet", "name", "version"); private final String jpaEntityFieldName; - private List subEntityAttributes; + private final List subEntityAttributes; TargetFilterQueryFields(final String jpaEntityFieldName) { this(jpaEntityFieldName, Collections.emptyList()); 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 index bb93cd647..14ca3a024 100644 --- a/hawkbit-core/src/test/java/org/eclipse/hawkbit/repository/FileNameFieldsTest.java +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/repository/FileNameFieldsTest.java @@ -36,12 +36,6 @@ class FileNameFieldsTest { assertThat(matchingClasses).isNotEmpty(); matchingClasses.forEach(providerClass -> { assertThat(providerClass.getEnumConstants()).isNotEmpty(); - for (final RsqlQueryField 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"); - } - } }); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java index e0c9b461d..21c0e892b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/AbstractRSQLVisitor.java @@ -60,7 +60,7 @@ public abstract class AbstractRSQLVisitor & RsqlQueryField> { for (int i = 1; i < split.length; i++) { split[i] = getFormattedSubEntityAttribute(enumValue, split[i]); - if (!enumValue.containsSubEntityAttribute(split[i])) { + if (!containsSubEntityAttribute(enumValue, split[i])) { if (i != split.length - 1 || !enumValue.isMap()) { throw createRSQLParameterUnsupportedException(node, null); } // otherwise - the key of map is not in the sub entity attributes @@ -86,6 +86,32 @@ public abstract class AbstractRSQLVisitor & RsqlQueryField> { } } + + + /** + * Contains the sub entity the given field. + * + * @param propertyField the given field + * @return true contains false contains not + */ + private boolean containsSubEntityAttribute(final A enumField, final String propertyField) { + final List subEntityAttributes = enumField.getSubEntityAttributes(); + if (subEntityAttributes.contains(propertyField)) { + return true; + } + + for (final String attribute : subEntityAttributes) { + final String[] graph = attribute.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX); + for (final String subAttribute : graph) { + if (subAttribute.equalsIgnoreCase(propertyField)) { + return true; + } + } + } + + return false; + } + /** * @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/JpaQueryRsqlVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java index df36fbe0a..21711e930 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitor.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Bosch.IO GmbH and others + * Copyright (c) 2025 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -16,7 +16,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; @@ -344,9 +343,8 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends final Predicate mapPredicate = mapToMapPredicate(fieldPath, queryPath); - final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(queryPath.getEnumValue(), fieldPath), - transformedValues, value, queryPath); + final Predicate valuePredicate = addOperatorPredicate(node, fieldPath, transformedValues, value, queryPath); return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); } @@ -398,15 +396,6 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends expressionToCompare -> in(expressionToCompare, transformedValues)); } - private Path getMapValueFieldPath(final A enumField, final Path fieldPath) { - final String valueFieldNameFromSubEntity = enumField.getSubEntityMapTuple().map(Entry::getValue).orElse(null); - - if (!enumField.isMap() || valueFieldNameFromSubEntity == null) { - return fieldPath; - } - return fieldPath.get(valueFieldNameFromSubEntity); - } - @SuppressWarnings("unchecked") private Predicate mapToMapPredicate(final Path fieldPath, final QueryPath queryPath) { if (!queryPath.getEnumValue().isMap()) { @@ -415,16 +404,8 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends final String[] graph = queryPath.getJpaPath(); final String keyValue = graph[graph.length - 1]; - if (fieldPath instanceof MapJoin) { - // Currently we support only string key. So below cast is safe. - return equal((Expression) (((MapJoin) fieldPath).key()), keyValue); - } - - final String keyFieldName = queryPath.getEnumValue().getSubEntityMapTuple().map(Entry::getKey) - .orElseThrow(() -> new UnsupportedOperationException( - "For the fields, defined as Map, only Map java type or tuple in the form of " + - "SimpleImmutableEntry are allowed. Neither of those could be found!")); - return equal(fieldPath.get(keyFieldName), keyValue); + // Currently we support only string key. So below cast is safe. + return equal((Expression) (((MapJoin) fieldPath).key()), keyValue); } private Predicate getEqualToPredicate(final Object transformedValue, final Path fieldPath) { @@ -514,17 +495,12 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends @SuppressWarnings({ "rawtypes", "unchecked" }) private Expression getExpressionToCompare(final Path innerFieldPath, final A enumField) { - if (!enumField.isMap()) { - return pathOfString(innerFieldPath); - } - if (innerFieldPath instanceof MapJoin) { + if (enumField.isMap()){ // Currently we support only string key. So below cast is safe. return (Expression) (((MapJoin) pathOfString(innerFieldPath)).value()); + } else { + return pathOfString(innerFieldPath); } - final String valueFieldName = enumField.getSubEntityMapTuple().map(Entry::getValue) - .orElseThrow(() -> new UnsupportedOperationException( - "For the fields, defined as Map, only Map java type or tuple in the form of SimpleImmutableEntry are allowed. Neither of those could be found!")); - return pathOfString(innerFieldPath).get(valueFieldName); } private String toSQL(final String transformedValue) { 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 7abeb5d08..652dac437 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 @@ -9,14 +9,12 @@ */ package org.eclipse.hawkbit.repository.jpa.rsql; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.function.Function; import jakarta.persistence.criteria.CriteriaBuilder; @@ -125,7 +123,7 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> if (values.get(0) == null) { // IS operator for maps and null value is treated as doesn't exist correspondingly ((PluralJoin) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath)); - return cb.isNull(getValueFieldPath(queryPath, fieldPath)); + return cb.isNull(fieldPath); } } else if (node.getOperator() == RSQLUtility.NOT) { if (values.size() != 1) { @@ -133,13 +131,12 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } // NOT operator for maps and null value is treated as does exist correspondingly ((PluralJoin) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath)); - final Path valueFieldPath = getValueFieldPath(queryPath, fieldPath); if (values.get(0) == null) { // special handling of "exists" - return cb.isNotNull(valueFieldPath); + return cb.isNotNull(fieldPath); } else { // special handling or "not equal" or null (same as != but with possible optimized join - no subquery) - return toNotEqualToPredicate(queryPath, valueFieldPath, values.get(0)); + return toNotEqualToPredicate(queryPath, fieldPath, values.get(0)); } } mapEntryKeyPredicate = toMapEntryKeyPredicate(queryPath, fieldPath); @@ -147,38 +144,15 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> mapEntryKeyPredicate = null; } - final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, getValueFieldPath(queryPath, fieldPath), values); + final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, fieldPath, values); return mapEntryKeyPredicate == null ? valuePredicate : cb.and(mapEntryKeyPredicate, valuePredicate); } + @SuppressWarnings("unchecked") private Predicate toMapEntryKeyPredicate(final QueryPath queryPath, final Path fieldPath) { final String[] graph = queryPath.getJpaPath(); - return equal(mapEntryKeyPath(queryPath, fieldPath), graph[graph.length - 1]); - } - - @SuppressWarnings("unchecked") - private Path mapEntryKeyPath(final QueryPath queryPath, final Path fieldPath) { - if (fieldPath instanceof MapJoin) { - // Currently we support only string key. So below cast is safe. - return (Path) ((MapJoin) fieldPath).key(); - } - - return fieldPath.get(queryPath.getEnumValue().getSubEntityMapTuple() - .map(Entry::getKey) - .orElseThrow(() -> new UnsupportedOperationException(String.format( - "For the fields, defined as Map, only %s java type or tuple in the form of %s are allowed!", - Map.class.getName(), AbstractMap.SimpleImmutableEntry.class.getName())))); - } - - private Path getValueFieldPath(final QueryPath queryPath, final Path fieldPath) { - final A enumField = queryPath.getEnumValue(); - if (enumField.isMap()) { - final Path mapValuePath = enumField.getSubEntityMapTuple().map(Entry::getValue).map(fieldPath::get).orElse(null); - return mapValuePath == null ? fieldPath : mapValuePath; - } else { - return fieldPath; - } + return equal((Path) ((MapJoin) fieldPath).key(), graph[graph.length - 1]); } private Predicate toOperatorAndValuePredicate( @@ -359,20 +333,12 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> @SuppressWarnings({ "rawtypes", "unchecked" }) private Path getExpressionToCompare(final A enumField, final Path fieldPath) { - if (!enumField.isMap()) { - return pathOfString(fieldPath); - } - if (fieldPath instanceof MapJoin) { + if (enumField.isMap()) { // Currently we support only string key. So below cast is safe. return (Path) (((MapJoin) fieldPath).value()); + } else { + return pathOfString(fieldPath); } - return enumField.getSubEntityMapTuple() - .map(Entry::getValue) - .map(fieldPath::get) - .orElseThrow(() -> - new UnsupportedOperationException( - "For the fields, defined as Map, only Map java type or tuple in the form of SimpleImmutableEntry are allowed." + - " Neither of those could be found!")); } // result is String, enum value, boolean or null