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:
Bondar Bogdan
2018-11-28 14:09:52 +01:00
committed by Dominic Schabel
parent a460f61e13
commit b2dfd4a99e
16 changed files with 272 additions and 153 deletions

View File

@@ -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,

View File

@@ -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()