Feature target metadata filter (#767)
* implemented RSQL query filter for target metadata Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * refactored rsql fields providers code for targets, distribution sets and software modules for consistency, fixed predicate grouping for map fields in rsql utility Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * extended tests for target management rsql queries with target metadata Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * extended target management test for RSQL not equal case of target metadata, added a suggestion of comparator operators when map key ends with = or ! symbol in Target Filter View Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * Fixed peer review findings Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * small test fixes: seconds are respected while scheduling the maintenance window, redundant ds-target assignment statement removed Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>
This commit is contained in:
committed by
Dominic Schabel
parent
a460f61e13
commit
b2dfd4a99e
@@ -15,6 +15,7 @@ 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.stream.Collectors;
|
||||
@@ -287,7 +288,7 @@ public final class RSQLUtility {
|
||||
|
||||
final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR);
|
||||
|
||||
validateMapParamter(propertyEnum, node, graph);
|
||||
validateMapParameter(propertyEnum, node, graph);
|
||||
|
||||
// sub entity need minium 1 dot
|
||||
if (!propertyEnum.getSubEntityAttributes().isEmpty() && graph.length < 2) {
|
||||
@@ -314,13 +315,15 @@ public final class RSQLUtility {
|
||||
return fieldNameBuilder.toString();
|
||||
}
|
||||
|
||||
private void validateMapParamter(final A propertyEnum, final ComparisonNode node, final String[] graph) {
|
||||
private void validateMapParameter(final A propertyEnum, final ComparisonNode node, final String[] graph) {
|
||||
if (!propertyEnum.isMap()) {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (!propertyEnum.getSubEntityAttributes().isEmpty()) {
|
||||
throw new UnsupportedOperationException("Currently subentity attributes for maps are not supported");
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently subentity attributes for maps are not supported, alternatively you could use the key/value tuple, defined by SimpleImmutableEntry class");
|
||||
}
|
||||
|
||||
// enum.key
|
||||
@@ -540,48 +543,37 @@ public final class RSQLUtility {
|
||||
value = virtualPropertyReplacer.replace(value);
|
||||
}
|
||||
|
||||
final List<Predicate> singleList = new ArrayList<>();
|
||||
|
||||
final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, enumField);
|
||||
if (mapPredicate != null) {
|
||||
singleList.add(mapPredicate);
|
||||
}
|
||||
|
||||
addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath), transformedValues, transformedValue,
|
||||
value, singleList, database);
|
||||
return Collections.unmodifiableList(singleList);
|
||||
final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath),
|
||||
transformedValues, transformedValue, value, database);
|
||||
|
||||
return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
|
||||
}
|
||||
|
||||
private void addOperatorPredicate(final ComparisonNode node, final Path<Object> fieldPath,
|
||||
private Predicate addOperatorPredicate(final ComparisonNode node, final Path<Object> fieldPath,
|
||||
final List<Object> transformedValues, final Object transformedValue, final String value,
|
||||
final List<Predicate> singleList, final Database database) {
|
||||
final Database database) {
|
||||
switch (node.getOperator().getSymbol()) {
|
||||
case "==":
|
||||
singleList.add(getEqualToPredicate(transformedValue, fieldPath, database));
|
||||
break;
|
||||
return getEqualToPredicate(transformedValue, fieldPath, database);
|
||||
case "!=":
|
||||
singleList.add(getNotEqualToPredicate(transformedValue, fieldPath, database));
|
||||
break;
|
||||
return getNotEqualToPredicate(transformedValue, fieldPath, database);
|
||||
case "=gt=":
|
||||
singleList.add(cb.greaterThan(pathOfString(fieldPath), value));
|
||||
break;
|
||||
return cb.greaterThan(pathOfString(fieldPath), value);
|
||||
case "=ge=":
|
||||
singleList.add(cb.greaterThanOrEqualTo(pathOfString(fieldPath), value));
|
||||
break;
|
||||
return cb.greaterThanOrEqualTo(pathOfString(fieldPath), value);
|
||||
case "=lt=":
|
||||
singleList.add(cb.lessThan(pathOfString(fieldPath), value));
|
||||
break;
|
||||
return cb.lessThan(pathOfString(fieldPath), value);
|
||||
case "=le=":
|
||||
singleList.add(cb.lessThanOrEqualTo(pathOfString(fieldPath), value));
|
||||
break;
|
||||
return cb.lessThanOrEqualTo(pathOfString(fieldPath), value);
|
||||
case "=in=":
|
||||
singleList.add(getInPredicate(transformedValues, fieldPath));
|
||||
break;
|
||||
return getInPredicate(transformedValues, fieldPath);
|
||||
case "=out=":
|
||||
singleList.add(getOutPredicate(transformedValues, fieldPath));
|
||||
break;
|
||||
return getOutPredicate(transformedValues, fieldPath);
|
||||
default:
|
||||
LOGGER.info("operator symbol {} is either not supported or not implemented");
|
||||
throw new RSQLParameterSyntaxException("operator symbol {" + node.getOperator().getSymbol()
|
||||
+ "} is either not supported or not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,10 +608,13 @@ public final class RSQLUtility {
|
||||
}
|
||||
|
||||
private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> fieldPath) {
|
||||
if (!enumField.isMap() || enumField.getValueFieldName() == null) {
|
||||
final String valueFieldNameFromSubEntity = enumField.getSubEntityMapTuple().map(Entry::getValue)
|
||||
.orElse(null);
|
||||
|
||||
if (!enumField.isMap() || valueFieldNameFromSubEntity == null) {
|
||||
return fieldPath;
|
||||
}
|
||||
return fieldPath.get(enumField.getValueFieldName());
|
||||
return fieldPath.get(valueFieldNameFromSubEntity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -636,7 +631,11 @@ public final class RSQLUtility {
|
||||
keyValue.toUpperCase());
|
||||
}
|
||||
|
||||
return cb.equal(cb.upper(fieldPath.get(enumField.getKeyFieldName())), keyValue.toUpperCase());
|
||||
final String keyFieldName = enumField.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 cb.equal(cb.upper(fieldPath.get(keyFieldName)), keyValue.toUpperCase());
|
||||
}
|
||||
|
||||
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath,
|
||||
|
||||
@@ -16,6 +16,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
@@ -78,7 +80,7 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
context.setSyntaxError(false);
|
||||
suggestionContext.getSuggestions().addAll(getLogicalOperatorSuggestion(rsqlQuery));
|
||||
} catch (final RSQLParameterSyntaxException | RSQLParserException ex) {
|
||||
setExceptionDetails(new Exception(ex.getCause().getCause()), expectedTokens);
|
||||
setExceptionDetails(rsqlQuery, new Exception(ex.getCause().getCause()), expectedTokens);
|
||||
errorContext.setErrorMessage(getCustomMessage(ex.getCause().getMessage(), expectedTokens));
|
||||
suggestionContext.setSuggestions(expectedTokens);
|
||||
LOGGER.trace("Syntax exception on parsing :", ex);
|
||||
@@ -94,8 +96,7 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
private static Collection<? extends SuggestToken> getLogicalOperatorSuggestion(final String rsqlQuery) {
|
||||
if (!rsqlQuery.endsWith(" ")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (rsqlQuery.endsWith(" ")) {
|
||||
} else {
|
||||
final int currentQueryLength = rsqlQuery.length();
|
||||
// only return and/or suggestion when there is a space at the end
|
||||
final Collection<String> tokenImages = TokenDescription.getTokenImage(TokenDescription.LOGICAL_OP);
|
||||
@@ -106,18 +107,19 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
}
|
||||
return logicalOps;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static void setExceptionDetails(final Exception ex, final List<SuggestToken> expectedTokens) {
|
||||
expectedTokens.addAll(getNextTokens(ex));
|
||||
}
|
||||
|
||||
private static List<SuggestToken> getNextTokens(final Exception e) {
|
||||
final ParseException parseException = findParseException(e);
|
||||
private static void setExceptionDetails(final String rsqlQuery, final Exception ex,
|
||||
final List<SuggestToken> expectedTokens) {
|
||||
final ParseException parseException = findParseException(ex);
|
||||
if (parseException == null) {
|
||||
return Collections.emptyList();
|
||||
expectedTokens.addAll(getComparatorOperatorSuggestions(rsqlQuery));
|
||||
} else {
|
||||
expectedTokens.addAll(getNextTokens(parseException));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<SuggestToken> getNextTokens(final ParseException parseException) {
|
||||
final List<SuggestToken> listTokens = new ArrayList<>();
|
||||
final ParseExceptionWrapper parseExceptionWrapper = new ParseExceptionWrapper(parseException);
|
||||
final int[][] expectedTokenSequence = parseExceptionWrapper.getExpectedTokenSequence();
|
||||
@@ -146,6 +148,22 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
return listTokens;
|
||||
}
|
||||
|
||||
private static List<SuggestToken> getComparatorOperatorSuggestions(final String rsqlQuery) {
|
||||
// only return comparator operators suggestions when there is a '=' or
|
||||
// '!' symbol at the end
|
||||
final String mapKeyOperatorPattern = "(\\w+)\\.\\w+[=!]{1}$";
|
||||
final Matcher mapKeyOperatorMatcher = Pattern.compile(mapKeyOperatorPattern).matcher(rsqlQuery);
|
||||
|
||||
if (mapKeyOperatorMatcher.find() && FieldNameDescription.isMap(mapKeyOperatorMatcher.group(1))) {
|
||||
final int currentQueryLength = rsqlQuery.length() - 1;
|
||||
final Collection<String> tokenImages = TokenDescription.getTokenImage(TokenDescription.COMPARATOR);
|
||||
return tokenImages.stream().map(tokenImage -> new SuggestToken(currentQueryLength,
|
||||
currentQueryLength + tokenImage.length(), null, tokenImage)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static void addSuggestionOnTokenImage(final List<SuggestToken> listTokens, final int nextTokenBeginColumn,
|
||||
final int currentTokenEndColumn, final int[] is) {
|
||||
for (final int i : is) {
|
||||
@@ -179,7 +197,8 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
}
|
||||
|
||||
private static boolean shouldSuggestDotToken(final String currentTokenImageName, final boolean containsDot) {
|
||||
return !containsDot && FieldNameDescription.hasSubEntries(currentTokenImageName);
|
||||
return !containsDot && (FieldNameDescription.hasSubEntries(currentTokenImageName)
|
||||
|| FieldNameDescription.isMap(currentTokenImageName));
|
||||
}
|
||||
|
||||
private static boolean shouldSuggestTopLevelFieldNames(final String currentTokenImageName,
|
||||
@@ -295,6 +314,12 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
.map(TargetFields::getSubEntityAttributes).flatMap(List::stream).count() > 0;
|
||||
}
|
||||
|
||||
private static boolean isMap(final String tokenImageName) {
|
||||
return Arrays.stream(TargetFields.values())
|
||||
.filter(field -> field.toString().equalsIgnoreCase(tokenImageName)).findFirst()
|
||||
.map(TargetFields::isMap).orElse(false);
|
||||
}
|
||||
|
||||
private static List<SuggestToken> toTopSuggestToken(final int beginToken, final int endToken,
|
||||
final String tokenImageName) {
|
||||
return FIELD_NAMES.stream()
|
||||
|
||||
Reference in New Issue
Block a user