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
@@ -8,6 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository;
|
package org.eclipse.hawkbit.repository;
|
||||||
|
|
||||||
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describing the fields of the DistributionSet model which can be used in the
|
* Describing the fields of the DistributionSet model which can be used in the
|
||||||
* REST API e.g. for sorting etc.
|
* REST API e.g. for sorting etc.
|
||||||
@@ -59,35 +63,39 @@ public enum DistributionSetFields implements FieldNameProvider {
|
|||||||
/**
|
/**
|
||||||
* The metadata.
|
* The metadata.
|
||||||
*/
|
*/
|
||||||
METADATA("metadata", "key", "value");
|
METADATA("metadata", new SimpleImmutableEntry<>("key", "value"));
|
||||||
|
|
||||||
private final String fieldName;
|
private final String fieldName;
|
||||||
private String keyFieldName;
|
private boolean mapField;
|
||||||
private String valueFieldName;
|
private Entry<String, String> subEntityMapTuple;
|
||||||
|
|
||||||
private DistributionSetFields(final String fieldName) {
|
private DistributionSetFields(final String fieldName) {
|
||||||
this(fieldName, null, null);
|
this(fieldName, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DistributionSetFields(final String fieldName, final String keyFieldName, final String valueFieldName) {
|
private DistributionSetFields(final String fieldName, final Entry<String, String> subEntityMapTuple) {
|
||||||
|
this(fieldName, true, subEntityMapTuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DistributionSetFields(final String fieldName, final boolean mapField,
|
||||||
|
final Entry<String, String> subEntityMapTuple) {
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this.keyFieldName = keyFieldName;
|
this.mapField = mapField;
|
||||||
this.valueFieldName = valueFieldName;
|
this.subEntityMapTuple = subEntityMapTuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValueFieldName() {
|
public Optional<Entry<String, String>> getSubEntityMapTuple() {
|
||||||
return valueFieldName;
|
return Optional.ofNullable(subEntityMapTuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKeyFieldName() {
|
public boolean isMap() {
|
||||||
return keyFieldName;
|
return mapField;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFieldName() {
|
public String getFieldName() {
|
||||||
return fieldName;
|
return fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ package org.eclipse.hawkbit.repository;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for declaring the name of the field described in the database
|
* An interface for declaring the name of the field described in the database
|
||||||
@@ -65,21 +67,10 @@ public interface FieldNameProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The database column for the key
|
* @return a key/value tuple of a sub entity.
|
||||||
*
|
|
||||||
* @return key fieldname
|
|
||||||
*/
|
*/
|
||||||
default String getKeyFieldName() {
|
default Optional<Entry<String, String>> getSubEntityMapTuple() {
|
||||||
return null;
|
return Optional.empty();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database column for the value
|
|
||||||
*
|
|
||||||
* @return key fieldname
|
|
||||||
*/
|
|
||||||
default String getValueFieldName() {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +79,6 @@ public interface FieldNameProvider {
|
|||||||
* @return <true> is a map <false> is not a map
|
* @return <true> is a map <false> is not a map
|
||||||
*/
|
*/
|
||||||
default boolean isMap() {
|
default boolean isMap() {
|
||||||
return getKeyFieldName() != null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository;
|
package org.eclipse.hawkbit.repository;
|
||||||
|
|
||||||
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describing the fields of the SoftwareModule model which can be used in the
|
* Describing the fields of the SoftwareModule model which can be used in the
|
||||||
* REST API e.g. for sorting etc.
|
* REST API e.g. for sorting etc.
|
||||||
@@ -40,34 +44,39 @@ public enum SoftwareModuleFields implements FieldNameProvider {
|
|||||||
/**
|
/**
|
||||||
* The metadata.
|
* The metadata.
|
||||||
*/
|
*/
|
||||||
METADATA("metadata", "key", "value");
|
METADATA("metadata", new SimpleImmutableEntry<>("key", "value"));
|
||||||
|
|
||||||
private final String fieldName;
|
private final String fieldName;
|
||||||
private String keyFieldName;
|
private boolean mapField;
|
||||||
private String valueFieldName;
|
private Entry<String, String> subEntityMapTuple;
|
||||||
|
|
||||||
private SoftwareModuleFields(final String fieldName) {
|
private SoftwareModuleFields(final String fieldName) {
|
||||||
this(fieldName, null, null);
|
this(fieldName, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SoftwareModuleFields(final String fieldName, final String keyFieldName, final String valueFieldName) {
|
private SoftwareModuleFields(final String fieldName, final Entry<String, String> subEntityMapTuple) {
|
||||||
|
this(fieldName, true, subEntityMapTuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SoftwareModuleFields(final String fieldName, final boolean mapField,
|
||||||
|
final Entry<String, String> subEntityMapTuple) {
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this.keyFieldName = keyFieldName;
|
this.mapField = mapField;
|
||||||
this.valueFieldName = valueFieldName;
|
this.subEntityMapTuple = subEntityMapTuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Entry<String, String>> getSubEntityMapTuple() {
|
||||||
|
return Optional.ofNullable(subEntityMapTuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMap() {
|
||||||
|
return mapField;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFieldName() {
|
public String getFieldName() {
|
||||||
return fieldName;
|
return fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKeyFieldName() {
|
|
||||||
return keyFieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValueFieldName() {
|
|
||||||
return valueFieldName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository;
|
package org.eclipse.hawkbit.repository;
|
||||||
|
|
||||||
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describing the fields of the Target model which can be used in the REST API
|
* Describing the fields of the Target model which can be used in the REST API
|
||||||
@@ -77,28 +80,40 @@ public enum TargetFields implements FieldNameProvider {
|
|||||||
/**
|
/**
|
||||||
* Last time the DDI or DMF client polled.
|
* Last time the DDI or DMF client polled.
|
||||||
*/
|
*/
|
||||||
LASTCONTROLLERREQUESTAT("lastTargetQuery");
|
LASTCONTROLLERREQUESTAT("lastTargetQuery"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata.
|
||||||
|
*/
|
||||||
|
METADATA("metadata", new SimpleImmutableEntry<>("key", "value"));
|
||||||
|
|
||||||
private final String fieldName;
|
private final String fieldName;
|
||||||
private List<String> subEntityAttribues;
|
private List<String> subEntityAttribues;
|
||||||
private boolean mapField;
|
private boolean mapField;
|
||||||
|
private Entry<String, String> subEntityMapTuple;
|
||||||
|
|
||||||
TargetFields(final String fieldName) {
|
private TargetFields(final String fieldName) {
|
||||||
this(fieldName, false, Collections.emptyList());
|
this(fieldName, false, Collections.emptyList(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetFields(final String fieldName, final boolean isMapField) {
|
private TargetFields(final String fieldName, final boolean isMapField) {
|
||||||
this(fieldName, isMapField, Collections.emptyList());
|
this(fieldName, isMapField, Collections.emptyList(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetFields(final String fieldName, final String... subEntityAttribues) {
|
private TargetFields(final String fieldName, final String... subEntityAttribues) {
|
||||||
this(fieldName, false, Arrays.asList(subEntityAttribues));
|
this(fieldName, false, Arrays.asList(subEntityAttribues), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetFields(final String fieldName, final boolean mapField, final List<String> subEntityAttribues) {
|
private TargetFields(final String fieldName, final Entry<String, String> subEntityMapTuple) {
|
||||||
|
this(fieldName, true, Collections.emptyList(), subEntityMapTuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TargetFields(final String fieldName, final boolean mapField, final List<String> subEntityAttribues,
|
||||||
|
final Entry<String, String> subEntityMapTuple) {
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this.mapField = mapField;
|
this.mapField = mapField;
|
||||||
this.subEntityAttribues = subEntityAttribues;
|
this.subEntityAttribues = subEntityAttribues;
|
||||||
|
this.subEntityMapTuple = subEntityMapTuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -106,6 +121,11 @@ public enum TargetFields implements FieldNameProvider {
|
|||||||
return subEntityAttribues;
|
return subEntityAttribues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Entry<String, String>> getSubEntityMapTuple() {
|
||||||
|
return Optional.ofNullable(subEntityMapTuple);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMap() {
|
public boolean isMap() {
|
||||||
return mapField;
|
return mapField;
|
||||||
|
|||||||
@@ -36,34 +36,27 @@ public enum TargetFilterQueryFields implements FieldNameProvider {
|
|||||||
|
|
||||||
private final String fieldName;
|
private final String fieldName;
|
||||||
private List<String> subEntityAttributes;
|
private List<String> subEntityAttributes;
|
||||||
private boolean mapField;
|
|
||||||
|
|
||||||
TargetFilterQueryFields(final String fieldName) {
|
private TargetFilterQueryFields(final String fieldName) {
|
||||||
this(fieldName, false, Collections.emptyList());
|
this(fieldName, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetFilterQueryFields(final String fieldName, final String... subEntityAttribues) {
|
private TargetFilterQueryFields(final String fieldName, final String... subEntityAttribues) {
|
||||||
this(fieldName, false, Arrays.asList(subEntityAttribues));
|
this(fieldName, Arrays.asList(subEntityAttribues));
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetFilterQueryFields(final String fieldName, final boolean mapField, final List<String> subEntityAttribues) {
|
private TargetFilterQueryFields(final String fieldName, final List<String> subEntityAttribues) {
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this.mapField = mapField;
|
|
||||||
this.subEntityAttributes = subEntityAttribues;
|
this.subEntityAttributes = subEntityAttribues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getSubEntityAttributes() {
|
|
||||||
return subEntityAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMap() {
|
|
||||||
return mapField;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFieldName() {
|
public String getFieldName() {
|
||||||
return fieldName;
|
return fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSubEntityAttributes() {
|
||||||
|
return subEntityAttributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public enum TargetMetadataFields implements FieldNameProvider {
|
|||||||
|
|
||||||
private final String fieldName;
|
private final String fieldName;
|
||||||
|
|
||||||
TargetMetadataFields(final String fieldName) {
|
private TargetMetadataFields(final String fieldName) {
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,17 @@ public class RSQLParameterSyntaxException extends AbstractServerRtException {
|
|||||||
super(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX);
|
super(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new RSQLParameterSyntaxException with
|
||||||
|
* {@link SpServerError#SP_REST_RSQL_SEARCH_PARAM_SYNTAX} error.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* the message of the exception
|
||||||
|
*/
|
||||||
|
public RSQLParameterSyntaxException(final String message) {
|
||||||
|
super(message, SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new RSQLSyntaxException with
|
* Creates a new RSQLSyntaxException with
|
||||||
* {@link SpServerError#SP_REST_RSQL_SEARCH_PARAM_SYNTAX} error.
|
* {@link SpServerError#SP_REST_RSQL_SEARCH_PARAM_SYNTAX} error.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.util.HashMap;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -287,7 +288,7 @@ public final class RSQLUtility {
|
|||||||
|
|
||||||
final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR);
|
final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR);
|
||||||
|
|
||||||
validateMapParamter(propertyEnum, node, graph);
|
validateMapParameter(propertyEnum, node, graph);
|
||||||
|
|
||||||
// sub entity need minium 1 dot
|
// sub entity need minium 1 dot
|
||||||
if (!propertyEnum.getSubEntityAttributes().isEmpty() && graph.length < 2) {
|
if (!propertyEnum.getSubEntityAttributes().isEmpty() && graph.length < 2) {
|
||||||
@@ -314,13 +315,15 @@ public final class RSQLUtility {
|
|||||||
return fieldNameBuilder.toString();
|
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()) {
|
if (!propertyEnum.isMap()) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!propertyEnum.getSubEntityAttributes().isEmpty()) {
|
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
|
// enum.key
|
||||||
@@ -540,48 +543,37 @@ public final class RSQLUtility {
|
|||||||
value = virtualPropertyReplacer.replace(value);
|
value = virtualPropertyReplacer.replace(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Predicate> singleList = new ArrayList<>();
|
|
||||||
|
|
||||||
final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, enumField);
|
final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, enumField);
|
||||||
if (mapPredicate != null) {
|
|
||||||
singleList.add(mapPredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath), transformedValues, transformedValue,
|
final Predicate valuePredicate = addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath),
|
||||||
value, singleList, database);
|
transformedValues, transformedValue, value, database);
|
||||||
return Collections.unmodifiableList(singleList);
|
|
||||||
|
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<Object> transformedValues, final Object transformedValue, final String value,
|
||||||
final List<Predicate> singleList, final Database database) {
|
final Database database) {
|
||||||
switch (node.getOperator().getSymbol()) {
|
switch (node.getOperator().getSymbol()) {
|
||||||
case "==":
|
case "==":
|
||||||
singleList.add(getEqualToPredicate(transformedValue, fieldPath, database));
|
return getEqualToPredicate(transformedValue, fieldPath, database);
|
||||||
break;
|
|
||||||
case "!=":
|
case "!=":
|
||||||
singleList.add(getNotEqualToPredicate(transformedValue, fieldPath, database));
|
return getNotEqualToPredicate(transformedValue, fieldPath, database);
|
||||||
break;
|
|
||||||
case "=gt=":
|
case "=gt=":
|
||||||
singleList.add(cb.greaterThan(pathOfString(fieldPath), value));
|
return cb.greaterThan(pathOfString(fieldPath), value);
|
||||||
break;
|
|
||||||
case "=ge=":
|
case "=ge=":
|
||||||
singleList.add(cb.greaterThanOrEqualTo(pathOfString(fieldPath), value));
|
return cb.greaterThanOrEqualTo(pathOfString(fieldPath), value);
|
||||||
break;
|
|
||||||
case "=lt=":
|
case "=lt=":
|
||||||
singleList.add(cb.lessThan(pathOfString(fieldPath), value));
|
return cb.lessThan(pathOfString(fieldPath), value);
|
||||||
break;
|
|
||||||
case "=le=":
|
case "=le=":
|
||||||
singleList.add(cb.lessThanOrEqualTo(pathOfString(fieldPath), value));
|
return cb.lessThanOrEqualTo(pathOfString(fieldPath), value);
|
||||||
break;
|
|
||||||
case "=in=":
|
case "=in=":
|
||||||
singleList.add(getInPredicate(transformedValues, fieldPath));
|
return getInPredicate(transformedValues, fieldPath);
|
||||||
break;
|
|
||||||
case "=out=":
|
case "=out=":
|
||||||
singleList.add(getOutPredicate(transformedValues, fieldPath));
|
return getOutPredicate(transformedValues, fieldPath);
|
||||||
break;
|
|
||||||
default:
|
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) {
|
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;
|
||||||
}
|
}
|
||||||
return fieldPath.get(enumField.getValueFieldName());
|
return fieldPath.get(valueFieldNameFromSubEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -636,7 +631,11 @@ public final class RSQLUtility {
|
|||||||
keyValue.toUpperCase());
|
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,
|
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.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.TargetFields;
|
import org.eclipse.hawkbit.repository.TargetFields;
|
||||||
@@ -78,7 +80,7 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
|||||||
context.setSyntaxError(false);
|
context.setSyntaxError(false);
|
||||||
suggestionContext.getSuggestions().addAll(getLogicalOperatorSuggestion(rsqlQuery));
|
suggestionContext.getSuggestions().addAll(getLogicalOperatorSuggestion(rsqlQuery));
|
||||||
} catch (final RSQLParameterSyntaxException | RSQLParserException ex) {
|
} 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));
|
errorContext.setErrorMessage(getCustomMessage(ex.getCause().getMessage(), expectedTokens));
|
||||||
suggestionContext.setSuggestions(expectedTokens);
|
suggestionContext.setSuggestions(expectedTokens);
|
||||||
LOGGER.trace("Syntax exception on parsing :", ex);
|
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) {
|
private static Collection<? extends SuggestToken> getLogicalOperatorSuggestion(final String rsqlQuery) {
|
||||||
if (!rsqlQuery.endsWith(" ")) {
|
if (!rsqlQuery.endsWith(" ")) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
} else {
|
||||||
if (rsqlQuery.endsWith(" ")) {
|
|
||||||
final int currentQueryLength = rsqlQuery.length();
|
final int currentQueryLength = rsqlQuery.length();
|
||||||
// only return and/or suggestion when there is a space at the end
|
// only return and/or suggestion when there is a space at the end
|
||||||
final Collection<String> tokenImages = TokenDescription.getTokenImage(TokenDescription.LOGICAL_OP);
|
final Collection<String> tokenImages = TokenDescription.getTokenImage(TokenDescription.LOGICAL_OP);
|
||||||
@@ -106,18 +107,19 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
|||||||
}
|
}
|
||||||
return logicalOps;
|
return logicalOps;
|
||||||
}
|
}
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setExceptionDetails(final Exception ex, final List<SuggestToken> expectedTokens) {
|
private static void setExceptionDetails(final String rsqlQuery, final Exception ex,
|
||||||
expectedTokens.addAll(getNextTokens(ex));
|
final List<SuggestToken> expectedTokens) {
|
||||||
}
|
final ParseException parseException = findParseException(ex);
|
||||||
|
|
||||||
private static List<SuggestToken> getNextTokens(final Exception e) {
|
|
||||||
final ParseException parseException = findParseException(e);
|
|
||||||
if (parseException == null) {
|
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 List<SuggestToken> listTokens = new ArrayList<>();
|
||||||
final ParseExceptionWrapper parseExceptionWrapper = new ParseExceptionWrapper(parseException);
|
final ParseExceptionWrapper parseExceptionWrapper = new ParseExceptionWrapper(parseException);
|
||||||
final int[][] expectedTokenSequence = parseExceptionWrapper.getExpectedTokenSequence();
|
final int[][] expectedTokenSequence = parseExceptionWrapper.getExpectedTokenSequence();
|
||||||
@@ -146,6 +148,22 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
|||||||
return listTokens;
|
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,
|
private static void addSuggestionOnTokenImage(final List<SuggestToken> listTokens, final int nextTokenBeginColumn,
|
||||||
final int currentTokenEndColumn, final int[] is) {
|
final int currentTokenEndColumn, final int[] is) {
|
||||||
for (final int i : 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) {
|
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,
|
private static boolean shouldSuggestTopLevelFieldNames(final String currentTokenImageName,
|
||||||
@@ -295,6 +314,12 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
|||||||
.map(TargetFields::getSubEntityAttributes).flatMap(List::stream).count() > 0;
|
.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,
|
private static List<SuggestToken> toTopSuggestToken(final int beginToken, final int endToken,
|
||||||
final String tokenImageName) {
|
final String tokenImageName) {
|
||||||
return FIELD_NAMES.stream()
|
return FIELD_NAMES.stream()
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
"TargetMetadata");
|
"TargetMetadata");
|
||||||
verifyThrownExceptionBy(() -> targetManagement.getMetaDataByControllerId(NOT_EXIST_ID, "xxx"), "Target");
|
verifyThrownExceptionBy(() -> targetManagement.getMetaDataByControllerId(NOT_EXIST_ID, "xxx"), "Target");
|
||||||
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerId(PAGE, NOT_EXIST_ID), "Target");
|
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerId(PAGE, NOT_EXIST_ID), "Target");
|
||||||
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, NOT_EXIST_ID, "name==*"),
|
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, NOT_EXIST_ID, "key==*"),
|
||||||
"Target");
|
"Target");
|
||||||
verifyThrownExceptionBy(
|
verifyThrownExceptionBy(
|
||||||
() -> targetManagement.updateMetadata(NOT_EXIST_ID, entityFactory.generateTargetMetadata("xxx", "xxx")),
|
() -> targetManagement.updateMetadata(NOT_EXIST_ID, entityFactory.generateTargetMetadata("xxx", "xxx")),
|
||||||
@@ -921,7 +921,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final String knownValue = "targetMetaKnownValue";
|
final String knownValue = "targetMetaKnownValue";
|
||||||
|
|
||||||
final Target target = testdataFactory.createTarget("targetIdWithMetadata");
|
final Target target = testdataFactory.createTarget("targetIdWithMetadata");
|
||||||
final JpaTargetMetadata createdMetadata = insertTargetMetadata(knownKey, target, knownValue);
|
final JpaTargetMetadata createdMetadata = insertTargetMetadata(knownKey, knownValue, target);
|
||||||
|
|
||||||
assertThat(createdMetadata).isNotNull();
|
assertThat(createdMetadata).isNotNull();
|
||||||
assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey);
|
assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey);
|
||||||
@@ -930,8 +930,8 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
|
assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JpaTargetMetadata insertTargetMetadata(final String knownKey, final Target target,
|
private JpaTargetMetadata insertTargetMetadata(final String knownKey, final String knownValue,
|
||||||
final String knownValue) {
|
final Target target) {
|
||||||
final JpaTargetMetadata metadata = new JpaTargetMetadata(knownKey, knownValue, target);
|
final JpaTargetMetadata metadata = new JpaTargetMetadata(knownKey, knownValue, target);
|
||||||
return (JpaTargetMetadata) targetManagement
|
return (JpaTargetMetadata) targetManagement
|
||||||
.createMetaData(target.getControllerId(), Collections.singletonList(metadata)).get(0);
|
.createMetaData(target.getControllerId(), Collections.singletonList(metadata)).get(0);
|
||||||
@@ -945,12 +945,12 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final Target target1 = testdataFactory.createTarget("target1");
|
final Target target1 = testdataFactory.createTarget("target1");
|
||||||
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget();
|
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget();
|
||||||
for (int i = 0; i < maxMetaData; ++i) {
|
for (int i = 0; i < maxMetaData; ++i) {
|
||||||
assertThat(insertTargetMetadata("k" + i, target1, "v" + i)).isNotNull();
|
assertThat(insertTargetMetadata("k" + i, "v" + i, target1)).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
// quota exceeded
|
// quota exceeded
|
||||||
assertThatExceptionOfType(QuotaExceededException.class)
|
assertThatExceptionOfType(QuotaExceededException.class)
|
||||||
.isThrownBy(() -> insertTargetMetadata("k" + maxMetaData, target1, "v" + maxMetaData));
|
.isThrownBy(() -> insertTargetMetadata("k" + maxMetaData, "v" + maxMetaData, target1));
|
||||||
|
|
||||||
// add multiple meta data entries at once
|
// add multiple meta data entries at once
|
||||||
final Target target2 = testdataFactory.createTarget("target2");
|
final Target target2 = testdataFactory.createTarget("target2");
|
||||||
@@ -966,7 +966,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final Target target3 = testdataFactory.createTarget("target3");
|
final Target target3 = testdataFactory.createTarget("target3");
|
||||||
final int firstHalf = Math.round(maxMetaData / 2);
|
final int firstHalf = Math.round(maxMetaData / 2);
|
||||||
for (int i = 0; i < firstHalf; ++i) {
|
for (int i = 0; i < firstHalf; ++i) {
|
||||||
insertTargetMetadata("k" + i, target3, "v" + i);
|
insertTargetMetadata("k" + i, "v" + i, target3);
|
||||||
}
|
}
|
||||||
// add too many data entries
|
// add too many data entries
|
||||||
final int secondHalf = maxMetaData - firstHalf;
|
final int secondHalf = maxMetaData - firstHalf;
|
||||||
@@ -994,7 +994,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
assertThat(target.getOptLockRevision()).isEqualTo(1);
|
assertThat(target.getOptLockRevision()).isEqualTo(1);
|
||||||
|
|
||||||
// create target meta data entry
|
// create target meta data entry
|
||||||
insertTargetMetadata(knownKey, target, knownValue);
|
insertTargetMetadata(knownKey, knownValue, target);
|
||||||
|
|
||||||
Target changedLockRevisionTarget = targetManagement.get(target.getId()).get();
|
Target changedLockRevisionTarget = targetManagement.get(target.getId()).get();
|
||||||
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2);
|
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2);
|
||||||
@@ -1024,16 +1024,8 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@Description("Queries and loads the metadata related to a given target.")
|
@Description("Queries and loads the metadata related to a given target.")
|
||||||
public void findAllTargetMetadataByControllerId() {
|
public void findAllTargetMetadataByControllerId() {
|
||||||
// create targets
|
// create targets
|
||||||
final Target target1 = testdataFactory.createTarget("target1");
|
final Target target1 = createTargetWithMetadata("target1", 10);
|
||||||
final Target target2 = testdataFactory.createTarget("target2");
|
final Target target2 = createTargetWithMetadata("target2", 8);
|
||||||
|
|
||||||
for (int index = 0; index < 10; index++) {
|
|
||||||
insertTargetMetadata("key" + index, target1, "value" + index);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 0; index < 8; index++) {
|
|
||||||
insertTargetMetadata("key" + index, target2, "value" + index);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Page<TargetMetadata> metadataOfTarget1 = targetManagement
|
final Page<TargetMetadata> metadataOfTarget1 = targetManagement
|
||||||
.findMetaDataByControllerId(new PageRequest(0, 100), target1.getControllerId());
|
.findMetaDataByControllerId(new PageRequest(0, 100), target1.getControllerId());
|
||||||
@@ -1047,4 +1039,47 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
assertThat(metadataOfTarget2.getNumberOfElements()).isEqualTo(8);
|
assertThat(metadataOfTarget2.getNumberOfElements()).isEqualTo(8);
|
||||||
assertThat(metadataOfTarget2.getTotalElements()).isEqualTo(8);
|
assertThat(metadataOfTarget2.getTotalElements()).isEqualTo(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Target createTargetWithMetadata(final String controllerId, final int count) {
|
||||||
|
final Target target = testdataFactory.createTarget(controllerId);
|
||||||
|
|
||||||
|
for (int index = 1; index <= count; index++) {
|
||||||
|
insertTargetMetadata("key" + index, controllerId + "-value" + index, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Test that RSQL filter finds targets with metadata and/or controllerId.")
|
||||||
|
public void findTargetsByRsqlWithMetadata() {
|
||||||
|
final String controllerId1 = "target1";
|
||||||
|
final String controllerId2 = "target2";
|
||||||
|
createTargetWithMetadata(controllerId1, 2);
|
||||||
|
createTargetWithMetadata(controllerId2, 2);
|
||||||
|
|
||||||
|
final String rsqlAndControllerIdFilter = "id==target1 and metadata.key1==target1-value1";
|
||||||
|
final String rsqlAndControllerIdWithWrongKeyFilter = "id==* and metadata.unknown==value1";
|
||||||
|
final String rsqlAndControllerIdNotEqualFilter = "id==* and metadata.key2!=target1-value2";
|
||||||
|
final String rsqlOrControllerIdFilter = "id==target1 or metadata.key1==*value1";
|
||||||
|
final String rsqlOrControllerIdWithWrongKeyFilter = "id==target2 or metadata.unknown==value1";
|
||||||
|
final String rsqlOrControllerIdNotEqualFilter = "id==target1 or metadata.key1!=target1-value1";
|
||||||
|
|
||||||
|
assertThat(targetManagement.count()).as("Total targets").isEqualTo(2);
|
||||||
|
validateFoundTargetsByRsql(rsqlAndControllerIdFilter, controllerId1);
|
||||||
|
validateFoundTargetsByRsql(rsqlAndControllerIdWithWrongKeyFilter);
|
||||||
|
validateFoundTargetsByRsql(rsqlAndControllerIdNotEqualFilter, controllerId2);
|
||||||
|
validateFoundTargetsByRsql(rsqlOrControllerIdFilter, controllerId1, controllerId2);
|
||||||
|
validateFoundTargetsByRsql(rsqlOrControllerIdWithWrongKeyFilter, controllerId2);
|
||||||
|
validateFoundTargetsByRsql(rsqlOrControllerIdNotEqualFilter, controllerId1, controllerId2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateFoundTargetsByRsql(final String rsqlFilter, final String... controllerIds) {
|
||||||
|
final Page<Target> foundTargetsByMetadataAndControllerId = targetManagement.findByRsql(PAGE, rsqlFilter);
|
||||||
|
|
||||||
|
assertThat(foundTargetsByMetadataAndControllerId.getTotalElements()).as("Targets count in RSQL filter is wrong")
|
||||||
|
.isEqualTo(controllerIds.length);
|
||||||
|
assertThat(foundTargetsByMetadataAndControllerId.getContent().stream().map(Target::getControllerId))
|
||||||
|
.as("Targets found by RSQL filter have wrong controller ids").containsExactlyInAnyOrder(controllerIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test filter distribution set by tag")
|
@Description("Test filter distribution set by tag name")
|
||||||
public void testFilterByTag() {
|
public void testFilterByTag() {
|
||||||
assertRSQLQuery(DistributionSetFields.TAG.name() + "==Tag1", 2);
|
assertRSQLQuery(DistributionSetFields.TAG.name() + "==Tag1", 2);
|
||||||
// does not include untagged sets
|
// does not include untagged sets
|
||||||
@@ -124,7 +124,7 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test filter distribution set by type")
|
@Description("Test filter distribution set by type key")
|
||||||
public void testFilterByType() {
|
public void testFilterByType() {
|
||||||
assertRSQLQuery(DistributionSetFields.TYPE.name() + "==" + TestdataFactory.DS_TYPE_DEFAULT, 4);
|
assertRSQLQuery(DistributionSetFields.TYPE.name() + "==" + TestdataFactory.DS_TYPE_DEFAULT, 4);
|
||||||
assertRSQLQuery(DistributionSetFields.TYPE.name() + "==noExist*", 0);
|
assertRSQLQuery(DistributionSetFields.TYPE.name() + "==noExist*", 0);
|
||||||
@@ -133,7 +133,7 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("")
|
@Description("Test filter distribution set by metadata")
|
||||||
public void testFilterByMetadata() {
|
public void testFilterByMetadata() {
|
||||||
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==metaValue", 1);
|
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==metaValue", 1);
|
||||||
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==*v*", 2);
|
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==*v*", 2);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class RSQLSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test filter software module by type")
|
@Description("Test filter software module by type key")
|
||||||
public void testFilterByType() {
|
public void testFilterByType() {
|
||||||
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==" + TestdataFactory.SM_TYPE_APP, 2);
|
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==" + TestdataFactory.SM_TYPE_APP, 2);
|
||||||
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "!=" + TestdataFactory.SM_TYPE_APP, 3);
|
assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "!=" + TestdataFactory.SM_TYPE_APP, 3);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
attributes.put("revision", "1.1");
|
attributes.put("revision", "1.1");
|
||||||
target = controllerManagement.updateControllerAttributes(target.getControllerId(), attributes, null);
|
target = controllerManagement.updateControllerAttributes(target.getControllerId(), attributes, null);
|
||||||
target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(target.getControllerId(), LOCALHOST);
|
target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(target.getControllerId(), LOCALHOST);
|
||||||
|
createTargetMetadata(target.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "metaValue"));
|
||||||
|
|
||||||
target2 = targetManagement
|
target2 = targetManagement
|
||||||
.create(entityFactory.target().create().controllerId("targetId1234").description("targetId1234"));
|
.create(entityFactory.target().create().controllerId("targetId1234").description("targetId1234"));
|
||||||
@@ -56,6 +57,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), attributes, null);
|
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), attributes, null);
|
||||||
target2 = controllerManagement.findOrRegisterTargetIfItDoesNotexist(target2.getControllerId(), LOCALHOST);
|
target2 = controllerManagement.findOrRegisterTargetIfItDoesNotexist(target2.getControllerId(), LOCALHOST);
|
||||||
|
createTargetMetadata(target2.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "value"));
|
||||||
|
|
||||||
final Target target3 = testdataFactory.createTarget("targetId1235");
|
final Target target3 = testdataFactory.createTarget("targetId1235");
|
||||||
final Target target4 = testdataFactory.createTarget("targetId1236");
|
final Target target4 = testdataFactory.createTarget("targetId1236");
|
||||||
@@ -168,7 +170,7 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test filter target by tag")
|
@Description("Test filter target by tag name")
|
||||||
public void testFilterByTag() {
|
public void testFilterByTag() {
|
||||||
assertRSQLQuery(TargetFields.TAG.name() + "==Tag1", 2);
|
assertRSQLQuery(TargetFields.TAG.name() + "==Tag1", 2);
|
||||||
assertRSQLQuery(TargetFields.TAG.name() + "!=Tag1", 2);
|
assertRSQLQuery(TargetFields.TAG.name() + "!=Tag1", 2);
|
||||||
@@ -194,6 +196,18 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
|||||||
assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=${OVERDUE_TS}", 2);
|
assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=${OVERDUE_TS}", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Test filter target by metadata")
|
||||||
|
public void testFilterByMetadata() {
|
||||||
|
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==metaValue", 1);
|
||||||
|
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==*v*", 2);
|
||||||
|
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==noExist*", 0);
|
||||||
|
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1);
|
||||||
|
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1);
|
||||||
|
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist==metaValue", 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) {
|
private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) {
|
||||||
final Page<Target> findTargetPage = targetManagement.findByRsql(PAGE, rsqlParam);
|
final Page<Target> findTargetPage = targetManagement.findByRsql(PAGE, rsqlParam);
|
||||||
final long countTargetsAll = findTargetPage.getTotalElements();
|
final long countTargetsAll = findTargetPage.getTotalElements();
|
||||||
|
|||||||
@@ -115,15 +115,23 @@ public class RSQLUtilityTest {
|
|||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException for target attributes map, caused by wrong RSQL syntax (key was not present)");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wrongRSQL = TargetFields.ATTRIBUTE + ".unkwon.wrong==abc";
|
wrongRSQL = TargetFields.ATTRIBUTE + ".unknown.wrong==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException for target attributes map, caused by wrong RSQL syntax (key includes dots)");
|
||||||
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
wrongRSQL = TargetFields.METADATA + ".unknown.wrong==abc";
|
||||||
|
try {
|
||||||
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
fail("Missing expected RSQLParameterSyntaxException for target metadata map, caused by wrong RSQL syntax (key includes dots)");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +139,7 @@ public class RSQLUtilityTest {
|
|||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, DistributionSetFields.class, null, testDb)
|
RSQLUtility.parse(wrongRSQL, DistributionSetFields.class, null, testDb)
|
||||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException for distribution set metadata map, caused by wrong RSQL syntax (key was not present)");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import org.eclipse.hawkbit.repository.model.MetaData;
|
|||||||
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
|
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetMetadata;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
|
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
|
||||||
@@ -297,6 +298,14 @@ public abstract class AbstractIntegrationTest {
|
|||||||
return distributionSetManagement.createMetaData(dsId, md);
|
return distributionSetManagement.createMetaData(dsId, md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TargetMetadata createTargetMetadata(final String controllerId, final MetaData md) {
|
||||||
|
return createTargetMetadata(controllerId, Collections.singletonList(md)).get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<TargetMetadata> createTargetMetadata(final String controllerId, final List<MetaData> md) {
|
||||||
|
return targetManagement.createMetaData(controllerId, md);
|
||||||
|
}
|
||||||
|
|
||||||
protected Long getOsModule(final DistributionSet ds) {
|
protected Long getOsModule(final DistributionSet ds) {
|
||||||
return ds.findFirstModuleByType(osType).get().getId();
|
return ds.findFirstModuleByType(osType).get().getId();
|
||||||
}
|
}
|
||||||
@@ -395,8 +404,8 @@ public abstract class AbstractIntegrationTest {
|
|||||||
protected static String getTestSchedule(final int minutesToAdd) {
|
protected static String getTestSchedule(final int minutesToAdd) {
|
||||||
ZonedDateTime currentTime = ZonedDateTime.now();
|
ZonedDateTime currentTime = ZonedDateTime.now();
|
||||||
currentTime = currentTime.plusMinutes(minutesToAdd);
|
currentTime = currentTime.plusMinutes(minutesToAdd);
|
||||||
return String.format("0 %d %d %d %d ? %d", currentTime.getMinute(), currentTime.getHour(),
|
return String.format("%d %d %d %d %d ? %d", currentTime.getSecond(), currentTime.getMinute(),
|
||||||
currentTime.getDayOfMonth(), currentTime.getMonthValue(), currentTime.getYear());
|
currentTime.getHour(), currentTime.getDayOfMonth(), currentTime.getMonthValue(), currentTime.getYear());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String getTestDuration(final int duration) {
|
protected static String getTestDuration(final int duration) {
|
||||||
|
|||||||
@@ -393,7 +393,6 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
private void assertAttributesUpdateNotRequestedAfterFailedDeployment(Target target, final DistributionSet ds)
|
private void assertAttributesUpdateNotRequestedAfterFailedDeployment(Target target, final DistributionSet ds)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next();
|
target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next();
|
||||||
assignDistributionSet(ds.getId(), target.getControllerId());
|
|
||||||
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
|
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
|
||||||
.getContent().get(0);
|
.getContent().get(0);
|
||||||
sendDeploymentActionFeedback(target, action,
|
sendDeploymentActionFeedback(target, action,
|
||||||
@@ -413,12 +412,10 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
assertThatAttributesUpdateIsRequested(target.getControllerId());
|
assertThatAttributesUpdateIsRequested(target.getControllerId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertThatAttributesUpdateIsRequested(final String targetControllerId)
|
private void assertThatAttributesUpdateIsRequested(final String targetControllerId) throws Exception {
|
||||||
throws Exception {
|
mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), targetControllerId)
|
||||||
mvc.perform(
|
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
|
||||||
get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), targetControllerId)
|
.andExpect(jsonPath("$._links.configData.href").isNotEmpty());
|
||||||
.accept(MediaType.APPLICATION_JSON))
|
|
||||||
.andExpect(status().isOk()).andExpect(jsonPath("$._links.configData.href").isNotEmpty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertThatAttributesUpdateIsNotRequested(final String targetControllerId) throws Exception {
|
private void assertThatAttributesUpdateIsNotRequested(final String targetControllerId) throws Exception {
|
||||||
@@ -427,8 +424,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
.andExpect(jsonPath("$._links.configData").doesNotExist());
|
.andExpect(jsonPath("$._links.configData").doesNotExist());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action,
|
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String feedback)
|
||||||
final String feedback) throws Exception {
|
throws Exception {
|
||||||
return mvc.perform(post("/{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback",
|
return mvc.perform(post("/{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback",
|
||||||
tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()).content(feedback)
|
tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()).content(feedback)
|
||||||
.contentType(MediaType.APPLICATION_JSON));
|
.contentType(MediaType.APPLICATION_JSON));
|
||||||
|
|||||||
Reference in New Issue
Block a user