Fix EntityMatcher case sentsitivity config (#2706)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
@@ -59,6 +60,7 @@ import org.eclipse.hawkbit.repository.model.Target;
|
|||||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
||||||
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
@@ -147,7 +149,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
* Ensures that server returns a not found response in case of empty controller ID.
|
* Ensures that server returns a not found response in case of empty controller ID.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
||||||
void rootRsWithoutId() throws Exception {
|
void rootRsWithoutId() throws Exception {
|
||||||
mvc.perform(get("/controller/v1/"))
|
mvc.perform(get("/controller/v1/"))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -218,12 +220,19 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
@WithUser(principal = "knownpricipal")
|
@WithUser(principal = "knownpricipal")
|
||||||
@ExpectEvents({
|
@ExpectEvents({
|
||||||
@Expect(type = TargetCreatedEvent.class, count = 1),
|
@Expect(type = TargetCreatedEvent.class, count = 2),
|
||||||
@Expect(type = TargetPollEvent.class, count = 1),
|
@Expect(type = TargetUpdatedEvent.class, count = 1), // assign to group
|
||||||
|
@Expect(type = TargetPollEvent.class, count = 2),
|
||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
||||||
void pollWithModifiedWithOverridesGlobalPollingTime() throws Exception {
|
void pollWithModifiedWithOverridesGlobalPollingTime() throws Exception {
|
||||||
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00", () -> callAs(
|
SecurityContextSwitch.callAsPrivileged(() -> {
|
||||||
|
final Target target = testdataFactory.createTarget("not4711");
|
||||||
|
targetManagement.assignTargetsWithGroup("Europe", List.of(target.getControllerId()));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00, group == 'Europe' -> 00:05:05", () -> callAs(
|
||||||
withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||||
() -> {
|
() -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
||||||
@@ -231,6 +240,12 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")));
|
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")));
|
||||||
|
|
||||||
|
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), "not4711"))
|
||||||
|
.andDo(MockMvcResultPrinter.print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||||
|
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:05:05")));
|
||||||
return null;
|
return null;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,13 +38,19 @@ import org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator;
|
|||||||
public class EntityMatcher {
|
public class EntityMatcher {
|
||||||
|
|
||||||
private final Node root;
|
private final Node root;
|
||||||
|
private final boolean ignoreCase;
|
||||||
|
|
||||||
private EntityMatcher(final Node root) {
|
private EntityMatcher(final Node root, final boolean ignoreCase) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
this.ignoreCase = ignoreCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EntityMatcher of(final Node root) {
|
public static EntityMatcher of(final Node root) {
|
||||||
return new EntityMatcher(root);
|
return of(root, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityMatcher of(final Node root, final boolean ignoreCase) {
|
||||||
|
return new EntityMatcher(root, ignoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> boolean match(final T t) {
|
public <T> boolean match(final T t) {
|
||||||
@@ -52,7 +58,7 @@ public class EntityMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"java:S3776", "java:S3358", "java:S1125", "java:S6541"}) // better readable this way
|
@SuppressWarnings({"java:S3776", "java:S3358", "java:S1125", "java:S6541"}) // better readable this way
|
||||||
private static <T> boolean match(final T t, final Node node) {
|
private <T> boolean match(final T t, final Node node) {
|
||||||
if (node instanceof Node.Comparison comparison) {
|
if (node instanceof Node.Comparison comparison) {
|
||||||
final String[] split = comparison.getKey().split("\\.", 2);
|
final String[] split = comparison.getKey().split("\\.", 2);
|
||||||
try {
|
try {
|
||||||
@@ -65,7 +71,7 @@ public class EntityMatcher {
|
|||||||
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
|
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return compare(
|
return compareIgnoreCaseAware(
|
||||||
fieldValue == null ? null : ((Map<?, ?>) fieldValue).get(split[1]),
|
fieldValue == null ? null : ((Map<?, ?>) fieldValue).get(split[1]),
|
||||||
op,
|
op,
|
||||||
map(
|
map(
|
||||||
@@ -76,14 +82,14 @@ public class EntityMatcher {
|
|||||||
final BiPredicate<Object, Operator> compare;
|
final BiPredicate<Object, Operator> compare;
|
||||||
if (split.length == 1) {
|
if (split.length == 1) {
|
||||||
value = map(comparison.getValue(), getReturnType(fieldGetter));
|
value = map(comparison.getValue(), getReturnType(fieldGetter));
|
||||||
compare = (e, operator) -> compare(e, operator, value);
|
compare = (e, operator) -> compareIgnoreCaseAware(e, operator, value);
|
||||||
} else {
|
} else {
|
||||||
final Getter valueGetter = getGetter(
|
final Getter valueGetter = getGetter(
|
||||||
(Class<?>) ((ParameterizedType) fieldGetter.type()).getActualTypeArguments()[0], split[1]);
|
(Class<?>) ((ParameterizedType) fieldGetter.type()).getActualTypeArguments()[0], split[1]);
|
||||||
value = map(comparison.getValue(), getReturnType(valueGetter));
|
value = map(comparison.getValue(), getReturnType(valueGetter));
|
||||||
compare = (e, operator) -> {
|
compare = (e, operator) -> {
|
||||||
try {
|
try {
|
||||||
return compare(map(e == null ? null : valueGetter.get(e), getReturnType(valueGetter)), operator, value);
|
return compareIgnoreCaseAware(map(e == null ? null : valueGetter.get(e), getReturnType(valueGetter)), operator, value);
|
||||||
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
||||||
throw new IllegalArgumentException(ex);
|
throw new IllegalArgumentException(ex);
|
||||||
}
|
}
|
||||||
@@ -100,7 +106,7 @@ public class EntityMatcher {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (split.length == 1) {
|
if (split.length == 1) {
|
||||||
return compare(fieldValue, op, map(comparison.getValue(), getReturnType(fieldGetter)));
|
return compareIgnoreCaseAware(fieldValue, op, map(comparison.getValue(), getReturnType(fieldGetter)));
|
||||||
} else {
|
} else {
|
||||||
if (split[1].contains(".")) {
|
if (split[1].contains(".")) {
|
||||||
// nested field access
|
// nested field access
|
||||||
@@ -108,13 +114,13 @@ public class EntityMatcher {
|
|||||||
final Getter nestedFieldGetter = getGetter(getReturnType(fieldGetter), nestedSplit[0]);
|
final Getter nestedFieldGetter = getGetter(getReturnType(fieldGetter), nestedSplit[0]);
|
||||||
final Getter valueGetter = getGetter(getReturnType(nestedFieldGetter), nestedSplit[1]);
|
final Getter valueGetter = getGetter(getReturnType(nestedFieldGetter), nestedSplit[1]);
|
||||||
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.get(fieldValue);
|
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.get(fieldValue);
|
||||||
return compare(
|
return compareIgnoreCaseAware(
|
||||||
nestedFieldValue == null ? null : valueGetter.get(nestedFieldValue),
|
nestedFieldValue == null ? null : valueGetter.get(nestedFieldValue),
|
||||||
op,
|
op,
|
||||||
map(comparison.getValue(), getReturnType(valueGetter)));
|
map(comparison.getValue(), getReturnType(valueGetter)));
|
||||||
} else {
|
} else {
|
||||||
final Getter valueGetter = getGetter(getReturnType(fieldGetter), split[1]);
|
final Getter valueGetter = getGetter(getReturnType(fieldGetter), split[1]);
|
||||||
return compare(
|
return compareIgnoreCaseAware(
|
||||||
fieldValue == null ? null : valueGetter.get(fieldValue),
|
fieldValue == null ? null : valueGetter.get(fieldValue),
|
||||||
op,
|
op,
|
||||||
map(comparison.getValue(), getReturnType(valueGetter)));
|
map(comparison.getValue(), getReturnType(valueGetter)));
|
||||||
@@ -134,7 +140,24 @@ public class EntityMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("java:S3011") // java:S3011 uses reflection to private members antway
|
private boolean compareIgnoreCaseAware(final Object entityValue, final Operator op, final Object comparisonValue) {
|
||||||
|
return compare(ignoreCase(entityValue), op, ignoreCase(comparisonValue));
|
||||||
|
}
|
||||||
|
private Object ignoreCase(final Object o) {
|
||||||
|
if (!ignoreCase || o == null) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
// if here - ignoreCase in true and we have non-null value
|
||||||
|
if (o instanceof String str) {
|
||||||
|
return str.toLowerCase();
|
||||||
|
} else if (o instanceof Collection<?> collection) {
|
||||||
|
return collection.stream().map(this::ignoreCase).toList();
|
||||||
|
} else {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S3011") // java:S3011 uses reflection to private members anyway
|
||||||
private static <T> Getter getGetter(final Class<T> t, final String fieldName) throws NoSuchMethodException {
|
private static <T> Getter getGetter(final Class<T> t, final String fieldName) throws NoSuchMethodException {
|
||||||
final String[] parts = fieldName.split("\\.");
|
final String[] parts = fieldName.split("\\.");
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
@@ -216,22 +239,22 @@ public class EntityMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean compare(final Object o1, final Operator op, final Object o2) {
|
private static boolean compare(final Object entityValue, final Operator op, final Object comparisonValue) {
|
||||||
if ((o1 == null || o2 == null) && // null is not comparable!
|
if ((entityValue == null || comparisonValue == null) && // null is not comparable!
|
||||||
(op == GT || op == GTE || op == LT || op == LTE)) {
|
(op == GT || op == GTE || op == LT || op == LTE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return switch (op) {
|
return switch (op) {
|
||||||
case EQ -> Objects.equals(o1, o2);
|
case EQ -> Objects.equals(entityValue, comparisonValue);
|
||||||
case NE -> !Objects.equals(o1, o2);
|
case NE -> !Objects.equals(entityValue, comparisonValue);
|
||||||
case GT -> compare(o1, o2) > 0;
|
case GT -> compare(entityValue, comparisonValue) > 0;
|
||||||
case GTE -> compare(o1, o2) >= 0;
|
case GTE -> compare(entityValue, comparisonValue) >= 0;
|
||||||
case LT -> compare(o1, o2) < 0;
|
case LT -> compare(entityValue, comparisonValue) < 0;
|
||||||
case LTE -> compare(o1, o2) <= 0;
|
case LTE -> compare(entityValue, comparisonValue) <= 0;
|
||||||
case IN -> in(o1, o2);
|
case IN -> in(entityValue, comparisonValue);
|
||||||
case NOT_IN -> !in(o1, o2);
|
case NOT_IN -> !in(entityValue, comparisonValue);
|
||||||
case LIKE -> like(o2, o1);
|
case LIKE -> like(comparisonValue, entityValue);
|
||||||
case NOT_LIKE -> !like(o2, o1);
|
case NOT_LIKE -> !like(comparisonValue, entityValue);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,8 +161,10 @@ public class QLSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "java:S1117" }) // it is again ignoreCase
|
||||||
public <A extends Enum<A> & QueryField> EntityMatcher entityMatcher(final String query, final Class<A> queryFieldType) {
|
public <A extends Enum<A> & QueryField> EntityMatcher entityMatcher(final String query, final Class<A> queryFieldType) {
|
||||||
return EntityMatcher.of(parser.parse(caseInsensitiveDB || ignoreCase ? query.toLowerCase() : query, queryFieldType));
|
final boolean ignoreCase = caseInsensitiveDB || this.ignoreCase; // sync with DB and case sensitivity requirements
|
||||||
|
return EntityMatcher.of(parser.parse(ignoreCase ? query.toLowerCase() : query, queryFieldType), ignoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user