Merge remote-tracking branch 'eclipse/master'

This commit is contained in:
Kai Zimmermann
2016-02-02 15:29:38 +01:00
6 changed files with 114 additions and 59 deletions

View File

@@ -43,4 +43,19 @@ public class RSQLParameterSyntaxException extends SpServerRtException {
public RSQLParameterSyntaxException(final Throwable cause) {
super(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, cause);
}
/**
* Creates a new RSQLParameterSyntaxException with
* {@link SpServerError#SP_REST_RSQL_SEARCH_PARAM_SYNTAX} error.
*
* @param message
* the message of the exception
* @param cause
* the cause (which is saved for later retrieval by the
* getCause() method). (A null value is permitted, and indicates
* that the cause is nonexistent or unknown.)
*/
public RSQLParameterSyntaxException(final String message, final Throwable cause) {
super(message, SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, cause);
}
}

View File

@@ -27,6 +27,8 @@ import org.eclipse.hawkbit.repository.FieldNameProvider;
import org.eclipse.hawkbit.repository.FieldValueConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeMismatchException;
import org.springframework.data.jpa.domain.Specification;
import cz.jirutka.rsql.parser.RSQLParser;
@@ -164,10 +166,13 @@ public final class RSQLUtility {
private final CriteriaBuilder cb;
private final Class<A> enumType;
private final SimpleTypeConverter simpleTypeConverter;
private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType) {
this.root = root;
this.cb = cb;
this.enumType = enumType;
simpleTypeConverter = new SimpleTypeConverter();
}
@Override
@@ -199,10 +204,8 @@ public final class RSQLUtility {
validateMapParamter(propertyEnum, node, graph);
// sub entity need minium 1 dot
if (!propertyEnum.getSubEntityAttributes().isEmpty()) {
if (graph.length < 2) {
throw createRSQLParameterUnsupportedException(node);
}
if (!propertyEnum.getSubEntityAttributes().isEmpty() && graph.length < 2) {
throw createRSQLParameterUnsupportedException(node);
}
for (int i = 1; i < graph.length; i++) {
@@ -327,7 +330,6 @@ public final class RSQLUtility {
return Enum.valueOf(enumType, enumName.toUpperCase());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object convertValueIfNecessary(final ComparisonNode node, final A fieldName, final String value,
final Path<Object> fieldPath) {
// in case the value of an rsql query e.g. type==application is an
@@ -344,18 +346,41 @@ public final class RSQLUtility {
return transformEnumValue(node, value, javaType);
}
if (fieldName instanceof FieldValueConverter) {
final Object convertedValue = ((FieldValueConverter) fieldName).convertValue(fieldName, value);
if (convertedValue == null) {
throw new RSQLParameterUnsupportedFieldException("field {" + node.getSelector()
+ "} must be one of the following values {"
+ Arrays.toString(((FieldValueConverter) fieldName).possibleValues(fieldName)) + "}", null);
} else {
return convertedValue;
}
return convertFieldConverterValue(node, fieldName, value);
}
if (Boolean.TYPE.equals(javaType)) {
return convertBooleanValue(node, value, javaType);
}
return value;
}
private Object convertBooleanValue(final ComparisonNode node, final String value,
final Class<? extends Object> javaType) {
try {
return simpleTypeConverter.convertIfNecessary(value, javaType);
} catch (final TypeMismatchException e) {
throw new RSQLParameterSyntaxException(
"The value of the given search parameter field {" + node.getSelector()
+ "} is not well formed. Only a boolean (true or false) value will be expected {",
e);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object convertFieldConverterValue(final ComparisonNode node, final A fieldName, final String value) {
final Object convertedValue = ((FieldValueConverter) fieldName).convertValue(fieldName, value);
if (convertedValue == null) {
throw new RSQLParameterUnsupportedFieldException(
"field {" + node.getSelector() + "} must be one of the following values {"
+ Arrays.toString(((FieldValueConverter) fieldName).possibleValues(fieldName)) + "}",
null);
} else {
return convertedValue;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object transformEnumValue(final ComparisonNode node, final String value,
final Class<? extends Object> javaType) {
@@ -377,7 +402,7 @@ public final class RSQLUtility {
}
}
private List<Predicate> mapToPredicate(final ComparisonNode node, Path<Object> fieldPath,
private List<Predicate> mapToPredicate(final ComparisonNode node, final Path<Object> fieldPath,
final List<String> values, final List<Object> transformedValues, final A enumField) {
// only 'equal' and 'notEqual' can handle transformed value like
// enums. The JPA API
@@ -391,17 +416,20 @@ public final class RSQLUtility {
singleList.add(mapPredicate);
}
fieldPath = getMapValueFieldPath(enumField, fieldPath);
addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath), transformedValues, transformedValue,
value, singleList);
return Collections.unmodifiableList(singleList);
}
private void addOperatorPredicate(final ComparisonNode node, final Path<Object> fieldPath,
final List<Object> transformedValues, final Object transformedValue, final String value,
final List<Predicate> singleList) {
switch (node.getOperator().getSymbol()) {
case "=li=":
singleList.add(cb.like(cb.upper(pathOfString(fieldPath)), transformedValue.toString().toUpperCase()));
break;
case "==":
singleList.add(getEqualToPredicate(transformedValue, fieldPath));
break;
case "!=":
singleList.add(cb.notEqual(fieldPath, transformedValue));
singleList.add(getNotEqualToPredicate(transformedValue, fieldPath));
break;
case "=gt=":
singleList.add(cb.greaterThan(pathOfString(fieldPath), value));
@@ -424,13 +452,8 @@ public final class RSQLUtility {
default:
LOGGER.info("operator symbol {} is either not supported or not implemented");
}
return Collections.unmodifiableList(singleList);
}
/**
* @param enumField
* @return
*/
private Path<Object> getMapValueFieldPath(final A enumField, final Path<Object> fieldPath) {
if (!enumField.isMap() || enumField.getValueFieldName() == null) {
return fieldPath;
@@ -446,7 +469,7 @@ public final class RSQLUtility {
final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR);
final String keyValue = graph[graph.length - 1];
if (fieldPath instanceof MapJoin) {
return cb.equal(((MapJoin) fieldPath).key(), keyValue);
return cb.equal(((MapJoin<?, ?, ?>) fieldPath).key(), keyValue);
}
return cb.equal(fieldPath.get(enumField.getKeyFieldName()), keyValue);
@@ -454,12 +477,24 @@ public final class RSQLUtility {
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath) {
if (transformedValue instanceof String) {
final String preFormattedValue = ((String) transformedValue).replace(LIKE_WILDCARD, '%');
return cb.like(cb.upper(pathOfString(fieldPath)), preFormattedValue.toString().toUpperCase());
final String preFormattedValue = escapeValueToSQL((String) transformedValue);
return cb.like(cb.upper(pathOfString(fieldPath)), preFormattedValue.toUpperCase());
}
return cb.equal(fieldPath, transformedValue);
}
private Predicate getNotEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath) {
if (transformedValue instanceof String) {
final String preFormattedValue = escapeValueToSQL((String) transformedValue);
return cb.notLike(cb.upper(pathOfString(fieldPath)), preFormattedValue.toUpperCase());
}
return cb.notEqual(fieldPath, transformedValue);
}
private String escapeValueToSQL(final String transformedValue) {
return transformedValue.replace("%", "\\%").replace(LIKE_WILDCARD, '%');
}
@SuppressWarnings("unchecked")
private <Y> Path<Y> pathOfString(final Path<?> path) {
return (Path<Y>) path;

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.rsql;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import java.util.Arrays;
@@ -43,7 +44,7 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest {
final DistributionSet ds2 = TestDataUtil
.generateDistributionSets("NewDS", 3, softwareManagement, distributionSetManagement).get(0);
ds2.setDescription("DS2");
ds2.setDescription("DS%");
ds2.getMetadata().add(new DistributionSetMetadata("metaKey", ds2, "value"));
distributionSetManagement.updateDistributionSet(ds2);
@@ -76,6 +77,7 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest {
public void testFilterByParameterDescription() {
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS*", 2);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS%", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==noExist*", 0);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=in=(DS,notexist)", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 3);
@@ -94,7 +96,11 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest {
@Description("Test filter distribution set by complete property")
public void testFilterByAttribute() {
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==true", 4);
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==noExist*", 0);
try {
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==noExist*", 0);
fail();
} catch (final RSQLParameterSyntaxException e) {
}
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=in=(true)", 4);
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=out=(true)", 0);
}

View File

@@ -61,6 +61,7 @@ public class RSQLSoftwareModuleFieldTest extends AbstractIntegrationTest {
public void testFilterByParameterName() {
assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==agent-hub", 1);
assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==agent-hub*", 2);
assertRSQLQuery(SoftwareModuleFields.NAME.name() + "!=agent-hub*", 2);
assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==noExist*", 0);
assertRSQLQuery(SoftwareModuleFields.NAME.name() + "=in=(agent-hub,notexist)", 1);
assertRSQLQuery(SoftwareModuleFields.NAME.name() + "=out=(agent-hub,notexist)", 3);
@@ -94,12 +95,12 @@ public class RSQLSoftwareModuleFieldTest extends AbstractIntegrationTest {
}
@Test
@Description("")
@Description("Test filter software module by metadata")
public void testFilterByMetadata() {
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==metaValue", 1);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==*v*", 2);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==noExist*", 0);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=in=(metaValue,value)", 2);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".notExist==metaValue", 0);

View File

@@ -156,7 +156,7 @@ public class RSQLUtilityTest {
}
@Test
public void correctRsqlBuildsNotEqualPredicate() {
public void correctRsqlBuildsNotLikePredicate() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name!=abc";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
@@ -164,13 +164,37 @@ public class RSQLUtilityTest {
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).notEqual(eq(baseSoftwareModuleRootMock), eq("abc"));
verify(criteriaBuilderMock, times(1)).notLike(eq(pathOfString(baseSoftwareModuleRootMock)),
eq("abc".toUpperCase()));
}
@Test
public void correctRsqlBuildsLikePredicateWithPercentage() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name==a%";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
eq("a\\%".toUpperCase()));
}
@Test
@@ -191,27 +215,6 @@ public class RSQLUtilityTest {
verify(criteriaBuilderMock, times(1)).lessThan(eq(pathOfString(baseSoftwareModuleRootMock)), eq("abc"));
}
@Test
public void correctRsqlBuildsLikePredicate() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name=li=abc";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
eq("abc".toUpperCase()));
}
@Test
public void correctRsqlWithEnumValue() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);

View File

@@ -15,8 +15,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -76,9 +74,6 @@ public class TargetResource {
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private EntityManager entityManager;
/**
* Handles the GET request of retrieving a single target within SP.
*