Refactor RSQL searach fields related classes (3) (#1836)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-09-10 11:38:31 +03:00
committed by GitHub
parent 368c18ed5e
commit dd500b4d53
8 changed files with 127 additions and 113 deletions

View File

@@ -25,7 +25,7 @@ import java.util.Optional;
public enum DistributionSetFields implements RsqlQueryField { public enum DistributionSetFields implements RsqlQueryField {
ID("id"), ID("id"),
TYPE("type.key"), TYPE("type", "key"),
NAME("name"), NAME("name"),
DESCRIPTION("description"), DESCRIPTION("description"),
CREATEDAT("createdAt"), CREATEDAT("createdAt"),
@@ -33,27 +33,27 @@ public enum DistributionSetFields implements RsqlQueryField {
VERSION("version"), VERSION("version"),
COMPLETE("complete"), COMPLETE("complete"),
MODULE("modules", SoftwareModuleFields.ID.getJpaEntityFieldName(), SoftwareModuleFields.NAME.getJpaEntityFieldName()), MODULE("modules", SoftwareModuleFields.ID.getJpaEntityFieldName(), SoftwareModuleFields.NAME.getJpaEntityFieldName()),
TAG("tags.name"), TAG("tags", "name"),
METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), METADATA("metadata", new SimpleImmutableEntry<>("key", "value")),
VALID("valid"); VALID("valid");
private final String jpaEntityFieldName; private final String jpaEntityFieldName;
private final Entry<String, String> subEntityMapTuple;
private final List<String> subEntityAttributes; private final List<String> subEntityAttributes;
private final Entry<String, String> subEntityMapTuple;
DistributionSetFields(final String jpaEntityFieldName) { DistributionSetFields(final String jpaEntityFieldName) {
this(jpaEntityFieldName, null, Collections.emptyList()); this(jpaEntityFieldName, Collections.emptyList(), null);
} }
DistributionSetFields(final String jpaEntityFieldName, final String... subEntityAttributes) { DistributionSetFields(final String jpaEntityFieldName, final String... subEntityAttributes) {
this(jpaEntityFieldName, null, List.of(subEntityAttributes)); this(jpaEntityFieldName, List.of(subEntityAttributes), null);
} }
DistributionSetFields(final String jpaEntityFieldName, final Entry<String, String> subEntityMapTuple) { DistributionSetFields(final String jpaEntityFieldName, final Entry<String, String> subEntityMapTuple) {
this(jpaEntityFieldName, subEntityMapTuple, Collections.emptyList()); this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple);
} }
DistributionSetFields(final String jpaEntityFieldName, final Entry<String, String> subEntityMapTuple, List<String> subEntityAttributes) { DistributionSetFields(final String jpaEntityFieldName, List<String> subEntityAttributes, final Entry<String, String> subEntityMapTuple) {
this.jpaEntityFieldName = jpaEntityFieldName; this.jpaEntityFieldName = jpaEntityFieldName;
this.subEntityMapTuple = subEntityMapTuple; this.subEntityMapTuple = subEntityMapTuple;
this.subEntityAttributes = subEntityAttributes; this.subEntityAttributes = subEntityAttributes;

View File

@@ -35,23 +35,6 @@ public interface RsqlQueryField {
@NotNull @NotNull
String getJpaEntityFieldName(); String getJpaEntityFieldName();
/**
* Returns the sub attributes
*
* @param propertyFieldName the given field
* @return array consisting of sub attributes
*/
default String[] getSubAttributes(final String propertyFieldName) {
if (isMap()) {
final String[] subAttributes = propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX, 2);
// [0] field name | [1] key name (could miss, e.g. for target attributes)
final String mapKeyName = subAttributes.length == 2 ? subAttributes[1] : null;
return ObjectUtils.isEmpty(mapKeyName) ? new String[] { getJpaEntityFieldName() } : new String[] { getJpaEntityFieldName(), mapKeyName };
} else {
return propertyFieldName.split(SUB_ATTRIBUTE_SPLIT_REGEX);
}
}
/** /**
* Contains the sub entity the given field. * Contains the sub entity the given field.
* *

View File

@@ -12,6 +12,8 @@ package org.eclipse.hawkbit.repository;
import lombok.Getter; import lombok.Getter;
import java.util.AbstractMap.SimpleImmutableEntry; import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
@@ -22,22 +24,31 @@ import java.util.Optional;
public enum SoftwareModuleFields implements RsqlQueryField { public enum SoftwareModuleFields implements RsqlQueryField {
ID("id"), ID("id"),
TYPE("type.key"), TYPE("type", "key"),
NAME("name"), NAME("name"),
DESCRIPTION("description"), DESCRIPTION("description"),
VERSION("version"), VERSION("version"),
METADATA("metadata", new SimpleImmutableEntry<>("key", "value")); METADATA("metadata", new SimpleImmutableEntry<>("key", "value"));
private final String jpaEntityFieldName; private final String jpaEntityFieldName;
private Entry<String, String> subEntityMapTuple; private final List<String> subEntityAttributes;
private final Entry<String, String> subEntityMapTuple;
SoftwareModuleFields(final String jpaEntityFieldName) { SoftwareModuleFields(final String jpaEntityFieldName) {
this(jpaEntityFieldName, null); this(jpaEntityFieldName, Collections.emptyList(), null);
} }
SoftwareModuleFields(final String jpaEntityFieldName, final String... subEntityAttributes) {
this(jpaEntityFieldName, List.of(subEntityAttributes), null);
}
SoftwareModuleFields(final String jpaEntityFieldName, final Entry<String, String> subEntityMapTuple) { SoftwareModuleFields(final String jpaEntityFieldName, final Entry<String, String> subEntityMapTuple) {
this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple);
}
SoftwareModuleFields(final String jpaEntityFieldName, List<String> subEntityAttributes, final Entry<String, String> subEntityMapTuple) {
this.jpaEntityFieldName = jpaEntityFieldName; this.jpaEntityFieldName = jpaEntityFieldName;
this.subEntityMapTuple = subEntityMapTuple; this.subEntityMapTuple = subEntityMapTuple;
this.subEntityAttributes = subEntityAttributes;
} }
@Override @Override

View File

@@ -35,7 +35,7 @@ public enum TargetFields implements RsqlQueryField {
ATTRIBUTE("controllerAttributes"), ATTRIBUTE("controllerAttributes"),
ASSIGNEDDS("assignedDistributionSet", "name", "version"), ASSIGNEDDS("assignedDistributionSet", "name", "version"),
INSTALLEDDS("installedDistributionSet", "name", "version"), INSTALLEDDS("installedDistributionSet", "name", "version"),
TAG("tags.name"), TAG("tags", "name"),
LASTCONTROLLERREQUESTAT("lastTargetQuery"), LASTCONTROLLERREQUESTAT("lastTargetQuery"),
METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), METADATA("metadata", new SimpleImmutableEntry<>("key", "value")),
TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName()); TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName());

View File

@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import cz.jirutka.rsql.parser.ast.ComparisonNode; import cz.jirutka.rsql.parser.ast.ComparisonNode;
import org.springframework.util.ObjectUtils;
@Slf4j @Slf4j
public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> { public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
@@ -28,14 +29,14 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
private final Class<A> rsqlQueryFieldType; private final Class<A> rsqlQueryFieldType;
@Value @Value
protected class RsqlField { protected class QuertPath {
A enumValue; A enumValue;
String[] subAttributes; String[] jpaPath;
private RsqlField(final A enumValue, final String[] subAttributes) { private QuertPath(final A enumValue, final String[] jpaPath) {
this.enumValue = enumValue; this.enumValue = enumValue;
this.subAttributes = subAttributes; this.jpaPath = jpaPath;
} }
} }
@@ -43,49 +44,74 @@ public abstract class AbstractRSQLVisitor<A extends Enum<A> & RsqlQueryField> {
this.rsqlQueryFieldType = rsqlQueryFieldType; this.rsqlQueryFieldType = rsqlQueryFieldType;
} }
protected RsqlField getRsqlField(final ComparisonNode node) { protected QuertPath getQuertPath(final ComparisonNode node) {
final String[] graph = node.getSelector().split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX); final String[] path = node.getSelector().split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX);
final String enumName = graph.length == 0 ? node.getSelector() : graph[0]; if (path.length == 0) {
throw createRSQLParameterUnsupportedException(node, null);
}
final String enumName = path[0].toUpperCase();
log.debug("get field identifier by name {} of enum type {}", enumName, rsqlQueryFieldType); log.debug("get field identifier by name {} of enum type {}", enumName, rsqlQueryFieldType);
try { try {
final A enumValue = Enum.valueOf(rsqlQueryFieldType, enumName.toUpperCase()); final A enumValue = Enum.valueOf(rsqlQueryFieldType, enumName);
final String[] subAttributes = enumValue.getSubAttributes(node.getSelector()); String[] split = getSplit(enumValue, node.getSelector());
// validate // validate
if (enumValue.isMap()) { if (enumValue.isMap()) {
// <enum>.<key> // <enum>.<key>
if (subAttributes.length != 2) { if (split.length != 2) {
throw new RSQLParameterUnsupportedFieldException( throw new RSQLParameterUnsupportedFieldException(
"The syntax of the given map search parameter field {" + node.getSelector() + "} is wrong. Syntax is: <enum name>.<key name>"); "The syntax of the given map search parameter field {" + node.getSelector() + "} is wrong. Syntax is: <enum name>.<key name>");
} }
} else { } else {
// sub entity need minimum 1 dot // sub entity need minimum 1 dot
if (!enumValue.getSubEntityAttributes().isEmpty() && subAttributes.length < 2) { if (!enumValue.getSubEntityAttributes().isEmpty() && split.length < 2) {
throw createRSQLParameterUnsupportedException(node, null); if (enumValue.getSubEntityAttributes().size() == 1) { // single sub attribute - so default
split = new String[] { split[0], enumValue.getSubEntityAttributes().get(0) };
} else {
throw createRSQLParameterUnsupportedException(node, null);
}
} }
} }
// build property and validate sub attributes // validate and normalize (replace enum name with JPA entity field name and format the rest) prepare jpa path
final StringBuilder fieldNameBuilder = new StringBuilder(enumValue.getJpaEntityFieldName()); split[0] = enumValue.getJpaEntityFieldName();
for (int i = 1; i < subAttributes.length; i++) { for (int i = 1; i < split.length; i++) {
final String propertyField = getFormattedSubEntityAttribute(enumValue, subAttributes[i]); split[i] = getFormattedSubEntityAttribute(enumValue, split[i]);
if (!enumValue.containsSubEntityAttribute(propertyField)) { if (!enumValue.containsSubEntityAttribute(split[i])) {
if (i != subAttributes.length - 1 || !enumValue.isMap()) { if (i != split.length - 1 || !enumValue.isMap()) {
throw createRSQLParameterUnsupportedException(node, null); throw createRSQLParameterUnsupportedException(node, null);
} // otherwise - the key of map is not in the sub entity attributes } // otherwise - the key of map is not in the sub entity attributes
} }
fieldNameBuilder.append(RsqlQueryField.SUB_ATTRIBUTE_SEPARATOR).append(propertyField);
} }
return new RsqlField(enumValue, enumValue.getSubAttributes(fieldNameBuilder.toString())); return new QuertPath(enumValue, split);
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
throw createRSQLParameterUnsupportedException(node, e); throw createRSQLParameterUnsupportedException(node, e);
} }
} }
/**
* Returns the sub attributes
*
* @param rsqlFieldName the given field
* @return array consisting of sub attributes
*/
private String[] getSplit(final A enumValue, final String rsqlFieldName) {
if (enumValue.isMap()) {
final String[] subAttributes = rsqlFieldName.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX, 2);
// [0] field name | [1] key name (could miss, e.g. for target attributes)
final String mapKeyName = subAttributes.length == 2 ? subAttributes[1] : null;
return ObjectUtils.isEmpty(mapKeyName) ?
new String[] { enumValue.getJpaEntityFieldName() } :
new String[] { enumValue.getJpaEntityFieldName(), mapKeyName };
} else {
return rsqlFieldName.split(RsqlQueryField.SUB_ATTRIBUTE_SPLIT_REGEX);
}
}
/** /**
* @param node current processing node * @param node current processing node
* @param rootException in case there is a cause otherwise {@code null} * @param rootException in case there is a cause otherwise {@code null}

View File

@@ -48,7 +48,7 @@ public class FieldValidationRsqlVisitor<A extends Enum<A> & RsqlQueryField> exte
@Override @Override
public Void visit(final ComparisonNode node, final String param) { public Void visit(final ComparisonNode node, final String param) {
// get AND validates // get AND validates
getRsqlField(node); getQuertPath(node);
return null; return null;
} }

View File

@@ -171,12 +171,12 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
* @param enumField * @param enumField
* field from a FieldNameProvider to resolve on the persistence * field from a FieldNameProvider to resolve on the persistence
* layer * layer
* @param rsqlField RSQL field * @param queryPath RSQL field
* @return the Path for a field * @return the Path for a field
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Path<Object> getFieldPath(final A enumField, final RsqlField rsqlField) { private Path<Object> getFieldPath(final A enumField, final QuertPath queryPath) {
return (Path<Object>) getFieldPath(root, rsqlField.getSubAttributes(), enumField.isMap(), return (Path<Object>) getFieldPath(root, queryPath.getJpaPath(), enumField.isMap(),
this::getJoinFieldPath).orElseThrow( this::getJoinFieldPath).orElseThrow(
() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null)); () -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null));
} }
@@ -217,19 +217,19 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
// https://jira.sonarsource.com/browse/SONARJAVA-1478 // https://jira.sonarsource.com/browse/SONARJAVA-1478
@SuppressWarnings({ "squid:S2095" }) @SuppressWarnings({ "squid:S2095" })
public List<Predicate> visit(final ComparisonNode node, final String param) { public List<Predicate> visit(final ComparisonNode node, final String param) {
final RsqlField rsqlField = getRsqlField(node); final QuertPath queryPath = getQuertPath(node);
final List<String> values = node.getArguments(); final List<String> values = node.getArguments();
final List<Object> transformedValues = new ArrayList<>(); final List<Object> transformedValues = new ArrayList<>();
final Path<Object> fieldPath = getFieldPath(rsqlField.getEnumValue(), rsqlField); final Path<Object> fieldPath = getFieldPath(queryPath.getEnumValue(), queryPath);
for (final String value : values) { for (final String value : values) {
transformedValues.add(convertValueIfNecessary(node, rsqlField.getEnumValue(), value, fieldPath)); transformedValues.add(convertValueIfNecessary(node, queryPath.getEnumValue(), value, fieldPath));
} }
this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node);
return mapToPredicate(node, fieldPath, node.getArguments(), transformedValues, rsqlField); return mapToPredicate(node, fieldPath, node.getArguments(), transformedValues, queryPath);
} }
private static boolean areJoinsNeeded(final ComparisonNode node) { private static boolean areJoinsNeeded(final ComparisonNode node) {
@@ -305,7 +305,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
} }
private List<Predicate> mapToPredicate(final ComparisonNode node, final Path<Object> fieldPath, private List<Predicate> mapToPredicate(final ComparisonNode node, final Path<Object> fieldPath,
final List<String> values, final List<Object> transformedValues, final RsqlField rsqlField) { final List<String> values, final List<Object> transformedValues, final QuertPath queryPath) {
String value = values.get(0); String value = values.get(0);
// if lookup is available, replace macros ... // if lookup is available, replace macros ...
@@ -313,16 +313,16 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
value = virtualPropertyReplacer.replace(value); value = virtualPropertyReplacer.replace(value);
} }
final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, rsqlField.getEnumValue()); final Predicate mapPredicate = mapToMapPredicate(fieldPath, queryPath);
final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(rsqlField.getEnumValue(), fieldPath), final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(queryPath.getEnumValue(), fieldPath),
transformedValues, value, rsqlField); transformedValues, value, queryPath);
return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
} }
private Predicate addOperatorPredicate(final ComparisonNode node, final Path<Object> fieldPath, private Predicate addOperatorPredicate(final ComparisonNode node, final Path<Object> fieldPath,
final List<Object> transformedValues, final String value, final RsqlField rsqlField) { final List<Object> transformedValues, final String value, final QuertPath queryPath) {
// only 'equal' and 'notEqual' can handle transformed value like // only 'equal' and 'notEqual' can handle transformed value like
// enums. The JPA API cannot handle object types for greaterThan etc // enums. The JPA API cannot handle object types for greaterThan etc
@@ -334,7 +334,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
case "==": case "==":
return getEqualToPredicate(transformedValue, fieldPath); return getEqualToPredicate(transformedValue, fieldPath);
case "!=": case "!=":
return getNotEqualToPredicate(transformedValue, fieldPath, rsqlField); return getNotEqualToPredicate(transformedValue, fieldPath, queryPath);
case "=gt=": case "=gt=":
return cb.greaterThan(pathOfString(fieldPath), value); return cb.greaterThan(pathOfString(fieldPath), value);
case "=ge=": case "=ge=":
@@ -346,7 +346,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
case "=in=": case "=in=":
return in(pathOfString(fieldPath), transformedValues); return in(pathOfString(fieldPath), transformedValues);
case "=out=": case "=out=":
return getOutPredicate(transformedValues, rsqlField, fieldPath); return getOutPredicate(transformedValues, queryPath, fieldPath);
default: default:
throw new RSQLParameterSyntaxException( throw new RSQLParameterSyntaxException(
"operator symbol {" + operator + "} is either not supported or not implemented"); "operator symbol {" + operator + "} is either not supported or not implemented");
@@ -355,17 +355,17 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
private Predicate getOutPredicate( private Predicate getOutPredicate(
final List<Object> transformedValues, final List<Object> transformedValues,
final RsqlField rsqlField, final Path<Object> fieldPath) { final QuertPath queryPath, final Path<Object> fieldPath) {
final String[] fieldNames = rsqlField.getSubAttributes(); final String[] fieldNames = queryPath.getJpaPath();
if (isSimpleField(fieldNames, rsqlField.getEnumValue().isMap())) { if (isSimpleField(fieldNames, queryPath.getEnumValue().isMap())) {
final Path<String> pathOfString = pathOfString(fieldPath); final Path<String> pathOfString = pathOfString(fieldPath);
return cb.or(cb.isNull(pathOfString), cb.not(in(pathOfString, transformedValues))); return cb.or(cb.isNull(pathOfString), cb.not(in(pathOfString, transformedValues)));
} }
clearOuterJoinsIfNotNeeded(); clearOuterJoinsIfNotNeeded();
return toNotExistsSubQueryPredicate(fieldNames, rsqlField.getEnumValue(), expressionToCompare -> in(expressionToCompare, transformedValues)); return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), expressionToCompare -> in(expressionToCompare, transformedValues));
} }
private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> fieldPath) { private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> fieldPath) {
@@ -378,20 +378,19 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Predicate mapToMapPredicate(final ComparisonNode node, final Path<Object> fieldPath, final A enumField) { private Predicate mapToMapPredicate(final Path<Object> fieldPath, final QuertPath queryPath) {
if (!enumField.isMap()) { if (!queryPath.getEnumValue().isMap()) {
return null; return null;
} }
final String[] graph = enumField.getSubAttributes(node.getSelector()); final String[] graph = queryPath.getJpaPath();
final String keyValue = graph[graph.length - 1]; final String keyValue = graph[graph.length - 1];
if (fieldPath instanceof MapJoin) { if (fieldPath instanceof MapJoin) {
// Currently we support only string key. So below cast is safe. // Currently we support only string key. So below cast is safe.
return equal((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue); return equal((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue);
} }
final String keyFieldName = enumField.getSubEntityMapTuple().map(Entry::getKey) final String keyFieldName = queryPath.getEnumValue().getSubEntityMapTuple().map(Entry::getKey)
.orElseThrow(() -> new UnsupportedOperationException( .orElseThrow(() -> new UnsupportedOperationException(
"For the fields, defined as Map, only Map java type or tuple in the form of " + "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!")); "SimpleImmutableEntry are allowed. Neither of those could be found!"));
@@ -419,7 +418,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
} }
private Predicate getNotEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath, private Predicate getNotEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath,
final RsqlField rsqlField) { final QuertPath queryPath) {
if (transformedValue == null) { if (transformedValue == null) {
return cb.isNotNull(pathOfString(fieldPath)); return cb.isNotNull(pathOfString(fieldPath));
@@ -430,9 +429,9 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), "")); return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), ""));
} }
final String[] fieldNames = rsqlField.getSubAttributes(); final String[] fieldNames = queryPath.getJpaPath();
if (isSimpleField(fieldNames, rsqlField.getEnumValue().isMap())) { if (isSimpleField(fieldNames, queryPath.getEnumValue().isMap())) {
if (isPattern(transformedValueStr)) { // a pattern, use like if (isPattern(transformedValueStr)) { // a pattern, use like
return cb.or(cb.isNull(pathOfString(fieldPath)), notLike(pathOfString(fieldPath), toSQL(transformedValueStr))); return cb.or(cb.isNull(pathOfString(fieldPath)), notLike(pathOfString(fieldPath), toSQL(transformedValueStr)));
} else { } else {
@@ -443,9 +442,9 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
clearOuterJoinsIfNotNeeded(); clearOuterJoinsIfNotNeeded();
if (isPattern(transformedValueStr)) { // a pattern, use like if (isPattern(transformedValueStr)) { // a pattern, use like
return toNotExistsSubQueryPredicate(fieldNames, rsqlField.getEnumValue(), expressionToCompare -> like(expressionToCompare, toSQL(transformedValueStr))); return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), expressionToCompare -> like(expressionToCompare, toSQL(transformedValueStr)));
} else { } else {
return toNotExistsSubQueryPredicate(fieldNames, rsqlField.getEnumValue(), expressionToCompare -> equal(expressionToCompare, transformedValueStr)); return toNotExistsSubQueryPredicate(fieldNames, queryPath.getEnumValue(), expressionToCompare -> equal(expressionToCompare, transformedValueStr));
} }
} }

View File

@@ -110,30 +110,30 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
@Override @Override
public List<Predicate> visit(final ComparisonNode node, final String param) { public List<Predicate> visit(final ComparisonNode node, final String param) {
final RsqlField rsqlField = getRsqlField(node); final QuertPath queryField = getQuertPath(node);
final List<String> values = node.getArguments(); final List<String> values = node.getArguments();
final List<Object> transformedValues = new ArrayList<>(); final List<Object> transformedValues = new ArrayList<>();
final Path<Object> fieldPath = getFieldPath(root, rsqlField); final Path<Object> fieldPath = getFieldPath(root, queryField);
for (final String value : values) { for (final String value : values) {
transformedValues.add(convertValueIfNecessary(node, rsqlField.getEnumValue(), fieldPath, value)); transformedValues.add(convertValueIfNecessary(node, queryField.getEnumValue(), fieldPath, value));
} }
this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node);
return mapToPredicate(node, rsqlField, fieldPath, node.getArguments(), transformedValues); return mapToPredicate(node, queryField, fieldPath, node.getArguments(), transformedValues);
} }
private List<Predicate> mapToPredicate(final ComparisonNode node, final RsqlField rsqlField, private List<Predicate> mapToPredicate(final ComparisonNode node, final QuertPath queryField,
final Path<Object> fieldPath, final Path<Object> fieldPath,
final List<String> values, final List<Object> transformedValues) { final List<String> values, final List<Object> transformedValues) {
// if lookup is available, replace macros ... // if lookup is available, replace macros ...
final String value = virtualPropertyReplacer == null ? values.get(0) : virtualPropertyReplacer.replace(values.get(0)); final String value = virtualPropertyReplacer == null ? values.get(0) : virtualPropertyReplacer.replace(values.get(0));
final Predicate mapPredicate = mapToMapPredicate(node, rsqlField.getEnumValue(), fieldPath); final Predicate mapPredicate = queryField.getEnumValue().isMap() ? mapToMapPredicate(queryField, fieldPath) : null;
final Predicate valuePredicate = addOperatorPredicate(node, rsqlField, final Predicate valuePredicate = addOperatorPredicate(node, queryField,
getValueFieldPath(rsqlField.getEnumValue(), fieldPath), transformedValues, value); getValueFieldPath(queryField.getEnumValue(), fieldPath), transformedValues, value);
return Collections.singletonList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate); return Collections.singletonList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
} }
@@ -145,26 +145,21 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
} }
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Predicate mapToMapPredicate(final ComparisonNode node, final A enumField, final Path<Object> fieldPath) { private Predicate mapToMapPredicate(final QuertPath queryField, final Path<Object> fieldPath) {
if (!enumField.isMap()) { final String[] graph = queryField.getJpaPath();
return null;
}
final String[] graph = enumField.getSubAttributes(node.getSelector());
final String keyValue = graph[graph.length - 1]; final String keyValue = graph[graph.length - 1];
if (fieldPath instanceof MapJoin) { if (fieldPath instanceof MapJoin) {
// Currently we support only string key. So below cast is safe. // Currently we support only string key. So below cast is safe.
return equal((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue); return equal((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue);
} }
final String keyFieldName = enumField.getSubEntityMapTuple().map(Entry::getKey) final String keyFieldName = queryField.getEnumValue().getSubEntityMapTuple().map(Entry::getKey)
.orElseThrow(() -> new UnsupportedOperationException( .orElseThrow(() -> new UnsupportedOperationException(
"For the fields, defined as Map, only Map java type or tuple in the form of " + "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!")); "SimpleImmutableEntry are allowed. Neither of those could be found!"));
return equal(fieldPath.get(keyFieldName), keyValue); return equal(fieldPath.get(keyFieldName), keyValue);
} }
private Predicate addOperatorPredicate(final ComparisonNode node, final RsqlField rsqlField, private Predicate addOperatorPredicate(final ComparisonNode node, final QuertPath queryField,
final Path<Object> fieldPath, final List<Object> transformedValues, final String value) { final Path<Object> fieldPath, final List<Object> transformedValues, final String value) {
// only 'equal' and 'notEqual' can handle transformed value like enums. // only 'equal' and 'notEqual' can handle transformed value like enums.
// The JPA API cannot handle object types for greaterThan etc. methods. // The JPA API cannot handle object types for greaterThan etc. methods.
@@ -172,13 +167,13 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
final String operator = node.getOperator().getSymbol(); final String operator = node.getOperator().getSymbol();
return switch (operator) { return switch (operator) {
case "==" -> getEqualToPredicate(fieldPath, transformedValue); case "==" -> getEqualToPredicate(fieldPath, transformedValue);
case "!=" -> getNotEqualToPredicate(rsqlField, fieldPath, transformedValue); case "!=" -> getNotEqualToPredicate(queryField, fieldPath, transformedValue);
case "=gt=" -> cb.greaterThan(pathOfString(fieldPath), value); case "=gt=" -> cb.greaterThan(pathOfString(fieldPath), value);
case "=ge=" -> cb.greaterThanOrEqualTo(pathOfString(fieldPath), value); case "=ge=" -> cb.greaterThanOrEqualTo(pathOfString(fieldPath), value);
case "=lt=" -> cb.lessThan(pathOfString(fieldPath), value); case "=lt=" -> cb.lessThan(pathOfString(fieldPath), value);
case "=le=" -> cb.lessThanOrEqualTo(pathOfString(fieldPath), value); case "=le=" -> cb.lessThanOrEqualTo(pathOfString(fieldPath), value);
case "=in=" -> in(pathOfString(fieldPath), transformedValues); case "=in=" -> in(pathOfString(fieldPath), transformedValues);
case "=out=" -> getOutPredicate(rsqlField, fieldPath, transformedValues); case "=out=" -> getOutPredicate(queryField, fieldPath, transformedValues);
default -> throw new RSQLParameterSyntaxException( default -> throw new RSQLParameterSyntaxException(
"Operator symbol {" + operator + "} is either not supported or not implemented"); "Operator symbol {" + operator + "} is either not supported or not implemented");
}; };
@@ -203,7 +198,7 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
return cb.equal(fieldPath, transformedValue); return cb.equal(fieldPath, transformedValue);
} }
private Predicate getNotEqualToPredicate(final RsqlField rsqlField, private Predicate getNotEqualToPredicate(final QuertPath queryField,
final Path<Object> fieldPath, final Object transformedValue) { final Path<Object> fieldPath, final Object transformedValue) {
if (transformedValue == null) { if (transformedValue == null) {
return cb.isNotNull(fieldPath); return cb.isNotNull(fieldPath);
@@ -214,9 +209,9 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
return cb.and(cb.isNotNull(fieldPath), cb.notEqual(pathOfString(fieldPath), "")); return cb.and(cb.isNotNull(fieldPath), cb.notEqual(pathOfString(fieldPath), ""));
} }
final String[] fieldNames = rsqlField.getSubAttributes(); final String[] fieldNames = queryField.getJpaPath();
if (isSimpleField(fieldNames, rsqlField.getEnumValue().isMap())) { if (isSimpleField(fieldNames, queryField.getEnumValue().isMap())) {
if (isPattern(transformedValueStr)) { // a pattern, use like if (isPattern(transformedValueStr)) { // a pattern, use like
return cb.or(cb.isNull(fieldPath), notLike(pathOfString(fieldPath), toSQL(transformedValueStr))); return cb.or(cb.isNull(fieldPath), notLike(pathOfString(fieldPath), toSQL(transformedValueStr)));
} else { } else {
@@ -225,7 +220,7 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
} }
clearJoinsIfNotNeeded(); clearJoinsIfNotNeeded();
return toNotExistsSubQueryPredicate(rsqlField, expressionToCompare -> return toNotExistsSubQueryPredicate(queryField, expressionToCompare ->
isPattern(transformedValueStr) ? // a pattern, use like isPattern(transformedValueStr) ? // a pattern, use like
like(expressionToCompare, toSQL(transformedValueStr)) : like(expressionToCompare, toSQL(transformedValueStr)) :
equal(expressionToCompare, transformedValueStr)); equal(expressionToCompare, transformedValueStr));
@@ -234,22 +229,22 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
return toNullOrNotEqualPredicate(fieldPath, transformedValue); return toNullOrNotEqualPredicate(fieldPath, transformedValue);
} }
private Predicate getOutPredicate(final RsqlField rsqlField, final Path<Object> fieldPath, private Predicate getOutPredicate(final QuertPath queryField, final Path<Object> fieldPath,
final List<Object> transformedValues) { final List<Object> transformedValues) {
final String[] subAttributes = rsqlField.getSubAttributes(); final String[] subAttributes = queryField.getJpaPath();
if (isSimpleField(subAttributes, rsqlField.getEnumValue().isMap())) { if (isSimpleField(subAttributes, queryField.getEnumValue().isMap())) {
return cb.or(cb.isNull(fieldPath), cb.not(in(pathOfString(fieldPath), transformedValues))); return cb.or(cb.isNull(fieldPath), cb.not(in(pathOfString(fieldPath), transformedValues)));
} }
clearJoinsIfNotNeeded(); clearJoinsIfNotNeeded();
return toNotExistsSubQueryPredicate(rsqlField, expressionToCompare -> in(expressionToCompare, transformedValues)); return toNotExistsSubQueryPredicate(queryField, expressionToCompare -> in(expressionToCompare, transformedValues));
} }
private Path<Object> getFieldPath(final Root<?> root, final RsqlField rsqlField) { private Path<Object> getFieldPath(final Root<?> root, final QuertPath queryField) {
final String[] split = rsqlField.getSubAttributes(); final String[] split = queryField.getJpaPath();
Path<Object> fieldPath = null; Path<Object> fieldPath = null;
for (int i = 0, end = rsqlField.getEnumValue().isMap() ? split.length - 1 : split.length; i < end; i++) { for (int i = 0, end = queryField.getEnumValue().isMap() ? split.length - 1 : split.length; i < end; i++) {
final String fieldNameSplit = split[i]; final String fieldNameSplit = split[i];
fieldPath = fieldPath == null ? getPath(root, fieldNameSplit) : fieldPath.get(fieldNameSplit); fieldPath = fieldPath == null ? getPath(root, fieldNameSplit) : fieldPath.get(fieldNameSplit);
} }
@@ -358,14 +353,14 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
private Predicate toNotExistsSubQueryPredicate(final RsqlField rsqlField, final Function<Expression<String>, Predicate> subQueryPredicateProvider) { private Predicate toNotExistsSubQueryPredicate(final QuertPath queryField, final Function<Expression<String>, Predicate> subQueryPredicateProvider) {
final Class<?> javaType = root.getJavaType(); final Class<?> javaType = root.getJavaType();
final Subquery<?> subquery = query.subquery(javaType); final Subquery<?> subquery = query.subquery(javaType);
final Root subqueryRoot = subquery.from(javaType); final Root subqueryRoot = subquery.from(javaType);
final Predicate equalPredicate = cb.equal(root.get(rsqlField.getEnumValue().identifierFieldName()), final Predicate equalPredicate = cb.equal(root.get(queryField.getEnumValue().identifierFieldName()),
subqueryRoot.get(rsqlField.getEnumValue().identifierFieldName())); subqueryRoot.get(queryField.getEnumValue().identifierFieldName()));
final Expression<String> expressionToCompare = getExpressionToCompare(rsqlField.getEnumValue(), final Expression<String> expressionToCompare = getExpressionToCompare(queryField.getEnumValue(),
getFieldPath(subqueryRoot, rsqlField)); getFieldPath(subqueryRoot, queryField));
final Predicate subQueryPredicate = subQueryPredicateProvider.apply(expressionToCompare); final Predicate subQueryPredicate = subQueryPredicateProvider.apply(expressionToCompare);
subquery.select(subqueryRoot).where(cb.and(equalPredicate, subQueryPredicate)); subquery.select(subqueryRoot).where(cb.and(equalPredicate, subQueryPredicate));
return cb.not(cb.exists(subquery)); return cb.not(cb.exists(subquery));