Simplify RSQL fields (#2416)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -42,11 +42,6 @@ public enum ActionFields implements RsqlQueryField, FieldValueConverter<ActionFi
|
||||
private final String jpaEntityFieldName;
|
||||
private final List<String> 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);
|
||||
|
||||
@@ -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<String> subEntityAttributes;
|
||||
private final Entry<String, String> 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<String, String> subEntityMapTuple) {
|
||||
this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple);
|
||||
}
|
||||
|
||||
DistributionSetFields(final String jpaEntityFieldName, List<String> subEntityAttributes, final Entry<String, String> subEntityMapTuple) {
|
||||
this.jpaEntityFieldName = jpaEntityFieldName;
|
||||
this.subEntityMapTuple = subEntityMapTuple;
|
||||
this.subEntityAttributes = subEntityAttributes;
|
||||
this.subEntityAttributes = List.of(subEntityAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Entry<String, String>> getSubEntityMapTuple() {
|
||||
return Optional.ofNullable(subEntityMapTuple);
|
||||
public boolean isMap() {
|
||||
return this == METADATA;
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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);
|
||||
|
||||
@@ -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 <T> the enum parameter
|
||||
*/
|
||||
|
||||
@@ -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<String> 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);
|
||||
|
||||
@@ -34,30 +34,6 @@ public interface RsqlQueryField {
|
||||
@NotNull
|
||||
String getJpaEntityFieldName();
|
||||
|
||||
/**
|
||||
* Contains the sub entity the given field.
|
||||
*
|
||||
* @param propertyField the given field
|
||||
* @return <code>true</code> contains <code>false</code> contains not
|
||||
*/
|
||||
default boolean containsSubEntityAttribute(final String propertyField) {
|
||||
final List<String> 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<Entry<String, String>> getSubEntityMapTuple() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the entity field a {@link Map} consisting of key-value pairs.
|
||||
*
|
||||
* @return <code>true</code> is a map <code>false</code> 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 <code>true</code> is a map <code>false</code> is not a map
|
||||
*/
|
||||
default boolean isMap() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -45,25 +45,10 @@ public enum TargetFields implements RsqlQueryField {
|
||||
|
||||
private final String jpaEntityFieldName;
|
||||
private final List<String> subEntityAttributes;
|
||||
private final Entry<String, String> 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<String> subEntityAttributes, final Entry<String, String> subEntityMapTuple) {
|
||||
this.jpaEntityFieldName = jpaEntityFieldName;
|
||||
this.subEntityAttributes = subEntityAttributes;
|
||||
this.subEntityMapTuple = subEntityMapTuple;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Entry<String, String>> getSubEntityMapTuple() {
|
||||
return Optional.ofNullable(subEntityMapTuple);
|
||||
this.subEntityAttributes = List.of(subEntityAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,7 @@ public enum TargetFilterQueryFields implements RsqlQueryField {
|
||||
AUTOASSIGNDISTRIBUTIONSET("autoAssignDistributionSet", "name", "version");
|
||||
|
||||
private final String jpaEntityFieldName;
|
||||
private List<String> subEntityAttributes;
|
||||
private final List<String> subEntityAttributes;
|
||||
|
||||
TargetFilterQueryFields(final String jpaEntityFieldName) {
|
||||
this(jpaEntityFieldName, Collections.emptyList());
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & 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<A extends Enum<A> & RsqlQueryField> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Contains the sub entity the given field.
|
||||
*
|
||||
* @param propertyField the given field
|
||||
* @return <code>true</code> contains <code>false</code> contains not
|
||||
*/
|
||||
private boolean containsSubEntityAttribute(final A enumField, final String propertyField) {
|
||||
final List<String> 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}
|
||||
|
||||
@@ -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<A extends Enum<A> & 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<A extends Enum<A> & RsqlQueryField, T> extends
|
||||
expressionToCompare -> in(expressionToCompare, transformedValues));
|
||||
}
|
||||
|
||||
private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> 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<Object> fieldPath, final QueryPath queryPath) {
|
||||
if (!queryPath.getEnumValue().isMap()) {
|
||||
@@ -415,16 +404,8 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & 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<String>) (((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<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue);
|
||||
}
|
||||
|
||||
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath) {
|
||||
@@ -514,17 +495,12 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Expression<String> 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<String>) (((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) {
|
||||
|
||||
@@ -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<A extends Enum<A> & 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<A extends Enum<A> & 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<A extends Enum<A> & 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<String> mapEntryKeyPath(final QueryPath queryPath, final Path<?> fieldPath) {
|
||||
if (fieldPath instanceof MapJoin) {
|
||||
// Currently we support only string key. So below cast is safe.
|
||||
return (Path<String>) ((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<String>) ((MapJoin<?, ?, ?>) fieldPath).key(), graph[graph.length - 1]);
|
||||
}
|
||||
|
||||
private Predicate toOperatorAndValuePredicate(
|
||||
@@ -359,20 +333,12 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Path<String> 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<String>) (((MapJoin<?, ?, ?>) fieldPath).value());
|
||||
} else {
|
||||
return pathOfString(fieldPath);
|
||||
}
|
||||
return enumField.getSubEntityMapTuple()
|
||||
.map(Entry::getValue)
|
||||
.map(fieldPath::<String>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
|
||||
|
||||
Reference in New Issue
Block a user