Overdue target filter based on makro resolution of placeholders
Signed-off-by: Marcel Mager (INST-IOT/ESB) <Marcel.Mager@bosch-si.com>
This commit is contained in:
@@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.SystemManagementHolder;
|
|||||||
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder;
|
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
|
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantConfigurationManagementHolder;
|
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantConfigurationManagementHolder;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.rsql.VirtualPropertyMakroResolver;
|
||||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
@@ -390,4 +391,15 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
|||||||
public EntityFactory entityFactory() {
|
public EntityFactory entityFactory() {
|
||||||
return new JpaEntityFactory();
|
return new JpaEntityFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link VirtualPropertyMakroResolver} bean.
|
||||||
|
*
|
||||||
|
* @return a new {@link VirtualPropertyMakroResolver}
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public VirtualPropertyMakroResolver virtualPropertyMakroResolver() {
|
||||||
|
return new VirtualPropertyMakroResolver();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
|||||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
|
||||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.rsql.VirtualPropertyMakroResolver;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
|
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
@@ -44,6 +45,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TargetFilterQueryRepository targetFilterQueryRepository;
|
private TargetFilterQueryRepository targetFilterQueryRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VirtualPropertyMakroResolver virtualPropMakroResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Modifying
|
@Modifying
|
||||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||||
@@ -111,7 +115,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verifyTargetFilterQuerySyntax(final String query) {
|
public boolean verifyTargetFilterQuerySyntax(final String query) {
|
||||||
RSQLUtility.parse(query, TargetFields.class);
|
RSQLUtility.parse(query, TargetFields.class, virtualPropMakroResolver);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
|
|||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
||||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.rsql.VirtualPropertyMakroResolver;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
@@ -102,6 +103,9 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AfterTransactionCommitExecutor afterCommit;
|
private AfterTransactionCommitExecutor afterCommit;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VirtualPropertyMakroResolver virtualPropMakroResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Target findTargetByControllerID(final String controllerId) {
|
public Target findTargetByControllerID(final String controllerId) {
|
||||||
return targetRepository.findByControllerId(controllerId);
|
return targetRepository.findByControllerId(controllerId);
|
||||||
@@ -152,12 +156,15 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Slice<Target> findTargetsAll(final TargetFilterQuery targetFilterQuery, final Pageable pageable) {
|
public Slice<Target> findTargetsAll(final TargetFilterQuery targetFilterQuery, final Pageable pageable) {
|
||||||
return findTargetsBySpec(RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class), pageable);
|
return findTargetsBySpec(
|
||||||
|
RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class, virtualPropMakroResolver),
|
||||||
|
pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<Target> findTargetsAll(final String targetFilterQuery, final Pageable pageable) {
|
public Page<Target> findTargetsAll(final String targetFilterQuery, final Pageable pageable) {
|
||||||
return findTargetsBySpec(RSQLUtility.parse(targetFilterQuery, TargetFields.class), pageable);
|
return findTargetsBySpec(RSQLUtility.parse(targetFilterQuery, TargetFields.class, virtualPropMakroResolver),
|
||||||
|
pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Page<Target> findTargetsBySpec(final Specification<JpaTarget> spec, final Pageable pageable) {
|
private Page<Target> findTargetsBySpec(final Specification<JpaTarget> spec, final Pageable pageable) {
|
||||||
@@ -224,7 +231,8 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
public Page<Target> findTargetByAssignedDistributionSet(final Long distributionSetID, final String rsqlParam,
|
public Page<Target> findTargetByAssignedDistributionSet(final Long distributionSetID, final String rsqlParam,
|
||||||
final Pageable pageReq) {
|
final Pageable pageReq) {
|
||||||
|
|
||||||
final Specification<JpaTarget> spec = RSQLUtility.parse(rsqlParam, TargetFields.class);
|
final Specification<JpaTarget> spec = RSQLUtility.parse(rsqlParam, TargetFields.class,
|
||||||
|
virtualPropMakroResolver);
|
||||||
|
|
||||||
return convertPage(
|
return convertPage(
|
||||||
targetRepository
|
targetRepository
|
||||||
@@ -252,7 +260,8 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
public Page<Target> findTargetByInstalledDistributionSet(final Long distributionSetId, final String rsqlParam,
|
public Page<Target> findTargetByInstalledDistributionSet(final Long distributionSetId, final String rsqlParam,
|
||||||
final Pageable pageable) {
|
final Pageable pageable) {
|
||||||
|
|
||||||
final Specification<JpaTarget> spec = RSQLUtility.parse(rsqlParam, TargetFields.class);
|
final Specification<JpaTarget> spec = RSQLUtility.parse(rsqlParam, TargetFields.class,
|
||||||
|
virtualPropMakroResolver);
|
||||||
|
|
||||||
return convertPage(
|
return convertPage(
|
||||||
targetRepository
|
targetRepository
|
||||||
@@ -544,7 +553,8 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetRoot.get(JpaTarget_.id),
|
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetRoot.get(JpaTarget_.id),
|
||||||
targetRoot.get(JpaTarget_.controllerId), targetRoot.get(JpaTarget_.name), targetRoot.get(sortProperty));
|
targetRoot.get(JpaTarget_.controllerId), targetRoot.get(JpaTarget_.name), targetRoot.get(sortProperty));
|
||||||
|
|
||||||
final Specification<JpaTarget> spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class);
|
final Specification<JpaTarget> spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class,
|
||||||
|
virtualPropMakroResolver);
|
||||||
final List<Specification<JpaTarget>> specList = new ArrayList<>();
|
final List<Specification<JpaTarget>> specList = new ArrayList<>();
|
||||||
specList.add(spec);
|
specList.add(spec);
|
||||||
|
|
||||||
@@ -627,13 +637,15 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long countTargetByTargetFilterQuery(final TargetFilterQuery targetFilterQuery) {
|
public Long countTargetByTargetFilterQuery(final TargetFilterQuery targetFilterQuery) {
|
||||||
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class);
|
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class,
|
||||||
|
virtualPropMakroResolver);
|
||||||
return targetRepository.count(specs);
|
return targetRepository.count(specs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long countTargetByTargetFilterQuery(final String targetFilterQuery) {
|
public Long countTargetByTargetFilterQuery(final String targetFilterQuery) {
|
||||||
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery, TargetFields.class);
|
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery, TargetFields.class,
|
||||||
|
virtualPropMakroResolver);
|
||||||
return targetRepository.count(specs);
|
return targetRepository.count(specs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import javax.persistence.criteria.Path;
|
|||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.text.StrLookup;
|
||||||
|
import org.apache.commons.lang3.text.StrSubstitutor;
|
||||||
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
||||||
import org.eclipse.hawkbit.repository.FieldValueConverter;
|
import org.eclipse.hawkbit.repository.FieldValueConverter;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||||
@@ -47,9 +49,8 @@ import cz.jirutka.rsql.parser.ast.RSQLOperators;
|
|||||||
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class which is able to parse RSQL strings into an spring data
|
* A utility class which is able to parse RSQL strings into an spring data {@link Specification} which then can be
|
||||||
* {@link Specification} which then can be enhanced sql queries to filter
|
* enhanced sql queries to filter entities. RSQL parser library: https://github.com/jirutka/rsql-parser
|
||||||
* entities. RSQL parser library: https://github.com/jirutka/rsql-parser
|
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Equal to : ==</li>
|
* <li>Equal to : ==</li>
|
||||||
@@ -59,16 +60,24 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
|||||||
* <li>Greater than operator : =gt= or ></li>
|
* <li>Greater than operator : =gt= or ></li>
|
||||||
* <li>Greater than or equal to : =ge= or >=</li>
|
* <li>Greater than or equal to : =ge= or >=</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* <p>
|
||||||
* Examples of RSQL expressions in both FIQL-like and alternative notation:
|
* Examples of RSQL expressions in both FIQL-like and alternative notation:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>version==2.0.0</li>
|
* <li>version==2.0.0</li>
|
||||||
* <li>name==targetId1;description==plugAndPlay</li>
|
* <li>name==targetId1;description==plugAndPlay</li>
|
||||||
* <li>name==targetId1 and description==plugAndPlay</li>
|
* <li>name==targetId1 and description==plugAndPlay</li>
|
||||||
* <li>name==targetId1;description==plugAndPlay</li>
|
|
||||||
* <li>name==targetId1 and description==plugAndPlay</li>
|
|
||||||
* <li>name==targetId1,description==plugAndPlay,updateStatus==UNKNOWN</li>
|
* <li>name==targetId1,description==plugAndPlay,updateStatus==UNKNOWN</li>
|
||||||
* <li>name==targetId1 or description==plugAndPlay or updateStatus==UNKNOWN</li>
|
* <li>name==targetId1 or description==plugAndPlay or updateStatus==UNKNOWN</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* There is also a mechanism that allows to refer to known makros that can resolved by an optional {@link StrLookup}
|
||||||
|
* (cp. {@link VirtualPropertyMakroResolver}).<br>
|
||||||
|
* An example that queries for all overdue targets using the ${OVERDUE_TS} placeholder introduced by
|
||||||
|
* {@link VirtualPropertyMakroResolver} looks like this:<br>
|
||||||
|
* <em>lastControllerRequestAt=le=${OVERDUE_TS}</em><br>
|
||||||
|
* It is possible to escape a makro expression by using a second '$': $${OVERDUE_TS} would prevent the ${OVERDUE_TS}
|
||||||
|
* token from being expanded.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public final class RSQLUtility {
|
public final class RSQLUtility {
|
||||||
|
|
||||||
@@ -99,7 +108,28 @@ public final class RSQLUtility {
|
|||||||
*/
|
*/
|
||||||
public static <A extends Enum<A> & FieldNameProvider, T> Specification<T> parse(final String rsql,
|
public static <A extends Enum<A> & FieldNameProvider, T> Specification<T> parse(final String rsql,
|
||||||
final Class<A> fieldNameProvider) {
|
final Class<A> fieldNameProvider) {
|
||||||
return new RSQLSpecification<>(rsql.toLowerCase(), fieldNameProvider);
|
return new RSQLSpecification<>(rsql.toLowerCase(), fieldNameProvider, new VirtualPropertyMakroResolver());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parses an RSQL valid string into an JPA {@link Specification} which then can be used to filter for JPA entities
|
||||||
|
* with the given RSQL query.
|
||||||
|
*
|
||||||
|
* @param rsql
|
||||||
|
* the rsql query
|
||||||
|
* @param fieldNameProvider
|
||||||
|
* the enum class type which implements the {@link FieldNameProvider}
|
||||||
|
* @param makroLookup
|
||||||
|
* holds the logic how the known makros have to be resolved; may be <code>null</code>
|
||||||
|
* @return an specification which can be used with JPA
|
||||||
|
* @throws RSQLParameterUnsupportedFieldException
|
||||||
|
* if a field in the RSQL string is used but not provided by the given {@code fieldNameProvider}
|
||||||
|
* @throws RSQLParameterSyntaxException
|
||||||
|
* if the RSQL syntax is wrong
|
||||||
|
*/
|
||||||
|
public static <A extends Enum<A> & FieldNameProvider, T> Specification<T> parse(final String rsql,
|
||||||
|
final Class<A> fieldNameProvider, StrLookup<String> makroLookup) {
|
||||||
|
return new RSQLSpecification<>(rsql.toLowerCase(), fieldNameProvider, makroLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,10 +160,12 @@ public final class RSQLUtility {
|
|||||||
|
|
||||||
private final String rsql;
|
private final String rsql;
|
||||||
private final Class<A> enumType;
|
private final Class<A> enumType;
|
||||||
|
private final StrLookup<String> makroLookup;
|
||||||
|
|
||||||
private RSQLSpecification(final String rsql, final Class<A> enumType) {
|
private RSQLSpecification(final String rsql, final Class<A> enumType, StrLookup<String> makroLookup) {
|
||||||
this.rsql = rsql;
|
this.rsql = rsql;
|
||||||
this.enumType = enumType;
|
this.enumType = enumType;
|
||||||
|
this.makroLookup = makroLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -141,7 +173,8 @@ public final class RSQLUtility {
|
|||||||
|
|
||||||
final Node rootNode = parseRsql(rsql);
|
final Node rootNode = parseRsql(rsql);
|
||||||
|
|
||||||
final JpqQueryRSQLVisitor<A, T> jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType);
|
final JpqQueryRSQLVisitor<A, T> jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType,
|
||||||
|
makroLookup);
|
||||||
final List<Predicate> accept = rootNode.<List<Predicate>, String> accept(jpqQueryRSQLVisitor);
|
final List<Predicate> accept = rootNode.<List<Predicate>, String> accept(jpqQueryRSQLVisitor);
|
||||||
|
|
||||||
if (accept != null && !accept.isEmpty()) {
|
if (accept != null && !accept.isEmpty()) {
|
||||||
@@ -171,13 +204,19 @@ public final class RSQLUtility {
|
|||||||
private final Root<T> root;
|
private final Root<T> root;
|
||||||
private final CriteriaBuilder cb;
|
private final CriteriaBuilder cb;
|
||||||
private final Class<A> enumType;
|
private final Class<A> enumType;
|
||||||
|
private final StrLookup<String> makroLookup;
|
||||||
|
private final StrSubstitutor substitutor;
|
||||||
|
|
||||||
private final SimpleTypeConverter simpleTypeConverter;
|
private final SimpleTypeConverter simpleTypeConverter;
|
||||||
|
|
||||||
private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType) {
|
private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType,
|
||||||
|
StrLookup<String> makroLookup) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.cb = cb;
|
this.cb = cb;
|
||||||
this.enumType = enumType;
|
this.enumType = enumType;
|
||||||
|
this.makroLookup = makroLookup;
|
||||||
|
this.substitutor = new StrSubstitutor(makroLookup, StrSubstitutor.DEFAULT_PREFIX,
|
||||||
|
StrSubstitutor.DEFAULT_SUFFIX, StrSubstitutor.DEFAULT_ESCAPE);
|
||||||
simpleTypeConverter = new SimpleTypeConverter();
|
simpleTypeConverter = new SimpleTypeConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,7 +464,14 @@ public final class RSQLUtility {
|
|||||||
// enums. The JPA API
|
// enums. The JPA API
|
||||||
// cannot handle object types for greaterThan etc methods.
|
// cannot handle object types for greaterThan etc methods.
|
||||||
final Object transformedValue = transformedValues.get(0);
|
final Object transformedValue = transformedValues.get(0);
|
||||||
final String value = values.get(0);
|
|
||||||
|
final String value;
|
||||||
|
if (makroLookup != null) { // if substitutor is available, replace makros ...
|
||||||
|
value = substitutor.replace(values.get(0));
|
||||||
|
} else {
|
||||||
|
value = values.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
final List<Predicate> singleList = new ArrayList<>();
|
final List<Predicate> singleList = new ArrayList<>();
|
||||||
|
|
||||||
final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, enumField);
|
final Predicate mapPredicate = mapToMapPredicate(node, fieldPath, enumField);
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016 Bosch Software Innovations GmbH and others.
|
||||||
|
*
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.text.StrLookup;
|
||||||
|
import org.apache.commons.lang3.text.StrSubstitutor;
|
||||||
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantConfigurationManagementHolder;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds makro capabilities to RSQL expressions that are used to filter for targets.
|
||||||
|
* <p>
|
||||||
|
* Some (virtual) properties do not have a representation in the database (in general these properties are time-related,
|
||||||
|
* or more explicitly, they deal with time intervals).<br>
|
||||||
|
* Such a virtual property needs to be calculated on Java-side before it may be used in a target filter query that is
|
||||||
|
* passed to the database. Therefore a placeholder is used in the RSQL expression that is expanded in the
|
||||||
|
* {@link RSQLUtility} by a {@link StrSubstitutor}. This {@link StrSubstitutor} is configured with an instance of
|
||||||
|
* {@link VirtualPropertyMakroResolver} to resolve the known makros.
|
||||||
|
* <p>
|
||||||
|
* A virtual property may either be a system value like the current date (aka <em>now_ts</em>) or a value derived from
|
||||||
|
* (tenant-specific) system configuration (e.g. <em>overdue_ts</em>).
|
||||||
|
* <p>
|
||||||
|
* Known values are:<br>
|
||||||
|
* <ul>
|
||||||
|
* <li><em>now_ts</em>: maps to system UTC time in milliseconds since Unix epoch as long value</li>
|
||||||
|
* <li><em>overdue_ts</em>: is a calculated value: <em>overdue_ts = now_ts - pollingInterval - pollingOverdueInterval
|
||||||
|
* </em>; pollingInterval and pollingOverdueInterval are retrieved from tenant-specific system configuration.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class VirtualPropertyMakroResolver extends StrLookup<String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String lookup(String rhs) {
|
||||||
|
String resolved = null;
|
||||||
|
|
||||||
|
if ("now_ts".equals(rhs.toLowerCase())) {
|
||||||
|
resolved = String.valueOf(Instant.now().toEpochMilli());
|
||||||
|
} else if ("overdue_ts".equals(rhs.toLowerCase())) {
|
||||||
|
resolved = String.valueOf(Instant.now().toEpochMilli() //
|
||||||
|
- getDurationForKey(TenantConfigurationKey.POLLING_TIME_INTERVAL).toMillis() //
|
||||||
|
- getDurationForKey(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL).toMillis());
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Duration getDurationForKey(TenantConfigurationKey key) {
|
||||||
|
return DurationHelper.formattedStringToDuration(getRawStringForKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRawStringForKey(TenantConfigurationKey key) {
|
||||||
|
return getTenantConfigurationManagement().getConfigurationValue(key, String.class).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
TenantConfigurationManagement getTenantConfigurationManagement() {
|
||||||
|
return TenantConfigurationManagementHolder.getInstance().getTenantConfigurationManagement();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,14 +9,8 @@
|
|||||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.*;
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.reset;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
@@ -26,18 +20,24 @@ import javax.persistence.criteria.Predicate;
|
|||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
import javax.persistence.metamodel.Attribute;
|
import javax.persistence.metamodel.Attribute;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.text.StrLookup;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
||||||
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
|
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
|
||||||
import org.eclipse.hawkbit.repository.TargetFields;
|
import org.eclipse.hawkbit.repository.TargetFields;
|
||||||
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Spy;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import ru.yandex.qatools.allure.annotations.Description;
|
||||||
import ru.yandex.qatools.allure.annotations.Features;
|
import ru.yandex.qatools.allure.annotations.Features;
|
||||||
import ru.yandex.qatools.allure.annotations.Stories;
|
import ru.yandex.qatools.allure.annotations.Stories;
|
||||||
|
|
||||||
@@ -48,6 +48,12 @@ import ru.yandex.qatools.allure.annotations.Stories;
|
|||||||
// method name as short text
|
// method name as short text
|
||||||
public class RSQLUtilityTest {
|
public class RSQLUtilityTest {
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
VirtualPropertyMakroResolver makroResolver = new VirtualPropertyMakroResolver();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
TenantConfigurationManagement confMgmt;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Root<Object> baseSoftwareModuleRootMock;
|
private Root<Object> baseSoftwareModuleRootMock;
|
||||||
|
|
||||||
@@ -59,11 +65,16 @@ public class RSQLUtilityTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Attribute attribute;
|
private Attribute attribute;
|
||||||
|
|
||||||
|
private static final TenantConfigurationValue<String> TEST_POLLING_TIME_INTERVAL = TenantConfigurationValue
|
||||||
|
.<String>builder().value("00:05:00").build();
|
||||||
|
private static final TenantConfigurationValue<String> TEST_POLLING_OVERDUE_TIME_INTERVAL = TenantConfigurationValue
|
||||||
|
.<String>builder().value("00:07:37").build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void wrongRsqlSyntaxThrowSyntaxException() {
|
public void wrongRsqlSyntaxThrowSyntaxException() {
|
||||||
final String wrongRSQL = "name==abc;d";
|
final String wrongRSQL = "name==abc;d";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterSyntaxException e) {
|
} catch (final RSQLParameterSyntaxException e) {
|
||||||
@@ -75,7 +86,7 @@ public class RSQLUtilityTest {
|
|||||||
final String wrongRSQL = "unknownField==abc";
|
final String wrongRSQL = "unknownField==abc";
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("Missing an expected RSQLParameterUnsupportedFieldException because of unknown RSQL field");
|
fail("Missing an expected RSQLParameterUnsupportedFieldException because of unknown RSQL field");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -87,7 +98,8 @@ public class RSQLUtilityTest {
|
|||||||
public void wrongRsqlMapSyntaxThrowSyntaxException() {
|
public void wrongRsqlMapSyntaxThrowSyntaxException() {
|
||||||
String wrongRSQL = TargetFields.ATTRIBUTE + "==abc";
|
String wrongRSQL = TargetFields.ATTRIBUTE + "==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock,
|
||||||
criteriaBuilderMock);
|
criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -95,7 +107,8 @@ public class RSQLUtilityTest {
|
|||||||
|
|
||||||
wrongRSQL = TargetFields.ATTRIBUTE + ".unkwon.wrong==abc";
|
wrongRSQL = TargetFields.ATTRIBUTE + ".unkwon.wrong==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock,
|
||||||
criteriaBuilderMock);
|
criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -103,7 +116,7 @@ public class RSQLUtilityTest {
|
|||||||
|
|
||||||
wrongRSQL = DistributionSetFields.METADATA + "==abc";
|
wrongRSQL = DistributionSetFields.METADATA + "==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, DistributionSetFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(wrongRSQL, DistributionSetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -115,7 +128,8 @@ public class RSQLUtilityTest {
|
|||||||
public void wrongRsqlSubEntitySyntaxThrowSyntaxException() {
|
public void wrongRsqlSubEntitySyntaxThrowSyntaxException() {
|
||||||
String wrongRSQL = TargetFields.ASSIGNEDDS + "==abc";
|
String wrongRSQL = TargetFields.ASSIGNEDDS + "==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock,
|
||||||
criteriaBuilderMock);
|
criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -123,7 +137,8 @@ public class RSQLUtilityTest {
|
|||||||
|
|
||||||
wrongRSQL = TargetFields.ASSIGNEDDS + ".unknownField==abc";
|
wrongRSQL = TargetFields.ASSIGNEDDS + ".unknownField==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock,
|
||||||
criteriaBuilderMock);
|
criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -131,7 +146,8 @@ public class RSQLUtilityTest {
|
|||||||
|
|
||||||
wrongRSQL = TargetFields.ASSIGNEDDS + ".unknownField.ToMuch==abc";
|
wrongRSQL = TargetFields.ASSIGNEDDS + ".unknownField.ToMuch==abc";
|
||||||
try {
|
try {
|
||||||
RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock,
|
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock,
|
||||||
criteriaBuilderMock);
|
criteriaBuilderMock);
|
||||||
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -146,11 +162,11 @@ public class RSQLUtilityTest {
|
|||||||
when(baseSoftwareModuleRootMock.get("version")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("version")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||||
.thenReturn(mock(Predicate.class));
|
.thenReturn(mock(Predicate.class));
|
||||||
|
|
||||||
// test
|
// test
|
||||||
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
// verfication
|
// verfication
|
||||||
@@ -164,12 +180,12 @@ public class RSQLUtilityTest {
|
|||||||
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||||
.thenReturn(mock(Predicate.class));
|
.thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
||||||
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||||
// test
|
// test
|
||||||
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
// verfication
|
// verfication
|
||||||
@@ -185,12 +201,12 @@ public class RSQLUtilityTest {
|
|||||||
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||||
.thenReturn(mock(Predicate.class));
|
.thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
||||||
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||||
// test
|
// test
|
||||||
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
// verfication
|
// verfication
|
||||||
@@ -206,10 +222,10 @@ public class RSQLUtilityTest {
|
|||||||
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||||
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||||
.thenReturn(mock(Predicate.class));
|
.thenReturn(mock(Predicate.class));
|
||||||
// test
|
// test
|
||||||
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
// verfication
|
// verfication
|
||||||
@@ -226,7 +242,8 @@ public class RSQLUtilityTest {
|
|||||||
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
|
|
||||||
// test
|
// test
|
||||||
RSQLUtility.parse(correctRsql, TestFieldEnum.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock,
|
RSQLUtility.parse(correctRsql, TestFieldEnum.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
|
criteriaQueryMock,
|
||||||
criteriaBuilderMock);
|
criteriaBuilderMock);
|
||||||
|
|
||||||
// verfication
|
// verfication
|
||||||
@@ -244,7 +261,7 @@ public class RSQLUtilityTest {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// test
|
// test
|
||||||
RSQLUtility.parse(correctRsql, TestFieldEnum.class).toPredicate(baseSoftwareModuleRootMock,
|
RSQLUtility.parse(correctRsql, TestFieldEnum.class, null).toPredicate(baseSoftwareModuleRootMock,
|
||||||
criteriaQueryMock, criteriaBuilderMock);
|
criteriaQueryMock, criteriaBuilderMock);
|
||||||
fail("missing RSQLParameterUnsupportedFieldException for wrong enum value");
|
fail("missing RSQLParameterUnsupportedFieldException for wrong enum value");
|
||||||
} catch (final RSQLParameterUnsupportedFieldException e) {
|
} catch (final RSQLParameterUnsupportedFieldException e) {
|
||||||
@@ -252,6 +269,65 @@ public class RSQLUtilityTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests the resolution of overdue_ts placeholder in context of a RSQL expression.")
|
||||||
|
public void correctRsqlWithOverdueMakro() {
|
||||||
|
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
final String overdueProp = "overdue_ts";
|
||||||
|
final String overduePropPlaceholder = "${" + overdueProp + "}";
|
||||||
|
final String correctRsql = "testfield=le=" + overduePropPlaceholder;
|
||||||
|
when(baseSoftwareModuleRootMock.get("testfield")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) String.class);
|
||||||
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
|
when(criteriaBuilderMock.<String>lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder)))
|
||||||
|
.thenReturn(mock(Predicate.class));
|
||||||
|
|
||||||
|
// test
|
||||||
|
Predicate result = RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMakroLookup())
|
||||||
|
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
|
// verfication
|
||||||
|
verify(makroResolver, times(1)).lookup(overdueProp);
|
||||||
|
// the makro is already replaced when passed to #lessThanOrEqualTo -> the method is never invoked with the
|
||||||
|
// placeholder:
|
||||||
|
verify(criteriaBuilderMock, never()).lessThanOrEqualTo(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
|
eq(overduePropPlaceholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests RSQL expression with an unknown placeholder.")
|
||||||
|
public void correctRsqlWithUnknownMakro() {
|
||||||
|
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
final String overdueProp = "unknown";
|
||||||
|
final String overduePropPlaceholder = "${" + overdueProp + "}";
|
||||||
|
final String correctRsql = "testfield=le=" + overduePropPlaceholder;
|
||||||
|
when(baseSoftwareModuleRootMock.get("testfield")).thenReturn(baseSoftwareModuleRootMock);
|
||||||
|
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) String.class);
|
||||||
|
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||||
|
when(criteriaBuilderMock.<String>lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder)))
|
||||||
|
.thenReturn(mock(Predicate.class));
|
||||||
|
|
||||||
|
// test
|
||||||
|
Predicate result = RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMakroLookup())
|
||||||
|
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||||
|
|
||||||
|
// verfication
|
||||||
|
verify(makroResolver, times(1)).lookup(overdueProp);
|
||||||
|
// the makro is unknown and hence never replaced -> #lessThanOrEqualTo is invoked with the placeholder:
|
||||||
|
verify(criteriaBuilderMock, times(1)).lessThanOrEqualTo(eq(pathOfString(baseSoftwareModuleRootMock)),
|
||||||
|
eq(overduePropPlaceholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrLookup<String> setupMakroLookup() {
|
||||||
|
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class))
|
||||||
|
.thenReturn(TEST_POLLING_TIME_INTERVAL);
|
||||||
|
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class))
|
||||||
|
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
|
||||||
|
when(makroResolver.getTenantConfigurationManagement()).thenReturn(confMgmt);
|
||||||
|
|
||||||
|
return makroResolver;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <Y> Path<Y> pathOfString(final Path<?> path) {
|
private <Y> Path<Y> pathOfString(final Path<?> path) {
|
||||||
return (Path<Y>) path;
|
return (Path<Y>) path;
|
||||||
@@ -263,9 +339,7 @@ public class RSQLUtilityTest {
|
|||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see
|
* @see org.eclipse.hawkbit.server.rest.resource.model.FieldNameProvider# getFieldName()
|
||||||
* org.eclipse.hawkbit.server.rest.resource.model.FieldNameProvider#
|
|
||||||
* getFieldName()
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getFieldName() {
|
public String getFieldName() {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016 Bosch Software Innovations GmbH and others.
|
||||||
|
*
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.text.StrSubstitutor;
|
||||||
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import ru.yandex.qatools.allure.annotations.Description;
|
||||||
|
import ru.yandex.qatools.allure.annotations.Features;
|
||||||
|
import ru.yandex.qatools.allure.annotations.Stories;
|
||||||
|
|
||||||
|
@Features("Unit Tests - Repository")
|
||||||
|
@Stories("Placeholder resolution for virtual properties")
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class VirtualPropertyMakroResolverTest {
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
VirtualPropertyMakroResolver resolverUnderTest = new VirtualPropertyMakroResolver();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
TenantConfigurationManagement confMgmt;
|
||||||
|
|
||||||
|
StrSubstitutor substitutor;
|
||||||
|
|
||||||
|
Long nowTestTime;
|
||||||
|
|
||||||
|
private static final TenantConfigurationValue<String> TEST_POLLING_TIME_INTERVAL = TenantConfigurationValue
|
||||||
|
.<String> builder().value("00:05:00").build();
|
||||||
|
private static final TenantConfigurationValue<String> TEST_POLLING_OVERDUE_TIME_INTERVAL = TenantConfigurationValue
|
||||||
|
.<String> builder().value("00:07:37").build();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
nowTestTime = Instant.now().toEpochMilli();
|
||||||
|
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class))
|
||||||
|
.thenReturn(TEST_POLLING_TIME_INTERVAL);
|
||||||
|
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class))
|
||||||
|
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
|
||||||
|
when(resolverUnderTest.getTenantConfigurationManagement()).thenReturn(confMgmt);
|
||||||
|
|
||||||
|
this.substitutor = new StrSubstitutor(resolverUnderTest, StrSubstitutor.DEFAULT_PREFIX,
|
||||||
|
StrSubstitutor.DEFAULT_SUFFIX, StrSubstitutor.DEFAULT_ESCAPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests resolution of NOW_TS by using a StrSubstitutor configured with the VirtualPropertyMakroResolver.")
|
||||||
|
public void resolveNowTimestampPlaceholder() {
|
||||||
|
String placeholder = "${NOW_TS}";
|
||||||
|
String testString = "lhs=lt=" + placeholder;
|
||||||
|
|
||||||
|
String resolvedPlaceholders = substitutor.replace(testString);
|
||||||
|
assertFalse(resolvedPlaceholders.contains(placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests resolution of OVERDUE_TS by using a StrSubstitutor configured with the VirtualPropertyMakroResolver.")
|
||||||
|
public void resolveOverdueTimestampPlaceholder() {
|
||||||
|
String placeholder = "${OVERDUE_TS}";
|
||||||
|
String testString = "lhs=lt=" + placeholder;
|
||||||
|
|
||||||
|
String resolvedPlaceholders = substitutor.replace(testString);
|
||||||
|
assertFalse(resolvedPlaceholders.contains(placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests case insensititity of VirtualPropertyMakroResolver.")
|
||||||
|
public void resolveOverdueTimestampPlaceholderLowerCase() {
|
||||||
|
String placeholder = "${overdue_ts}";
|
||||||
|
String testString = "lhs=lt=" + placeholder;
|
||||||
|
|
||||||
|
String resolvedPlaceholders = substitutor.replace(testString);
|
||||||
|
assertFalse(resolvedPlaceholders.contains(placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests VirtualPropertyMakroResolver with a placeholder unknown to VirtualPropertyMakroResolver.")
|
||||||
|
public void handleUnknownPlaceholder() {
|
||||||
|
String placeholder = "${unknown}";
|
||||||
|
String testString = "lhs=lt=" + placeholder;
|
||||||
|
|
||||||
|
String resolvedPlaceholders = substitutor.replace(testString);
|
||||||
|
assertTrue(resolvedPlaceholders.contains(placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests escape mechanism for placeholders (syntax is $${SOME_PLACEHOLDER}).")
|
||||||
|
public void handleEscapedPlaceholder() {
|
||||||
|
String placeholder = "${OVERDUE_TS}";
|
||||||
|
String escaptedPlaceholder = StrSubstitutor.DEFAULT_ESCAPE + placeholder;
|
||||||
|
String testString = "lhs=lt=" + escaptedPlaceholder;
|
||||||
|
|
||||||
|
String resolvedPlaceholders = substitutor.replace(testString);
|
||||||
|
assertTrue(resolvedPlaceholders.contains(placeholder));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user