Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve JPA Provider portability - RSQL #2131

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,15 @@ public interface SoftwareModuleTypeManagement

/**
* @param key to search for
* @return {@link SoftwareModuleType} in the repository with given
* {@link SoftwareModuleType#getKey()}
* @return {@link SoftwareModuleType} in the repository with given {@link SoftwareModuleType#getKey()}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<SoftwareModuleType> getByKey(@NotEmpty String key);

/**
* @param name to search for
* @return all {@link SoftwareModuleType}s in the repository with given
* {@link SoftwareModuleType#getName()}
* @return all {@link SoftwareModuleType}s in the repository with given {@link SoftwareModuleType#getName()}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<SoftwareModuleType> getByName(@NotEmpty String name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
Expand Down Expand Up @@ -748,24 +749,17 @@ private Void updateLastTargetQueries(final String tenant, final List<TargetPoll>
}

/**
* Sets {@link Target#getLastTargetQuery()} by native SQL in order to avoid
* raising opt lock revision as this update is not mission critical and in
* fact only written by {@link ControllerManagement}, i.e. the target
* itself.
* Sets {@link Target#getLastTargetQuery()} by native SQL in order to avoid raising opt lock revision as this update is not mission-critical
* and in fact only written by {@link ControllerManagement}, i.e. the target itself.
*/
private void setLastTargetQuery(final String tenant, final long currentTimeMillis, final List<String> chunk) {
final Map<String, String> paramMapping = new HashMap<>(chunk.size());

for (int i = 0; i < chunk.size(); i++) {
paramMapping.put("cid" + i, chunk.get(i));
}

final Query updateQuery = entityManager.createNativeQuery(
"UPDATE sp_target SET last_target_query = #last_target_query WHERE controller_id IN ("
+ formatQueryInStatementParams(paramMapping.keySet()) + ") AND tenant = #tenant");
"UPDATE sp_target SET last_target_query = " + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "last_target_query " +
"WHERE controller_id IN (" + Jpa.formatNativeQueryInClause("cid", chunk) + ")" +
" AND tenant = " + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "tenant");

paramMapping.forEach(updateQuery::setParameter);
updateQuery.setParameter("last_target_query", currentTimeMillis);
Jpa.setNativeQueryInParameter(updateQuery, "cid", chunk);
updateQuery.setParameter("tenant", tenant);

final int updated = updateQuery.executeUpdate();
Expand Down Expand Up @@ -1108,4 +1102,4 @@ public boolean equals(final Object obj) {
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ public void setCreatedAt(final long createdAt) {
}
}

@Column(name = "created_at", updatable = false, nullable = false)
@Access(AccessType.PROPERTY)
public long getCreatedAt() {
return createdAt;
}

@LastModifiedBy
public void setLastModifiedBy(final String lastModifiedBy) {
if (isController()) {
Expand All @@ -113,13 +119,7 @@ public void setLastModifiedBy(final String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}

// maybe needed to have correct createdAt value in the database
@Access(AccessType.PROPERTY)
public long getCreatedAt() {
return createdAt;
}

// seems needed to have correct lastModifiedBy value in the database
@Column(name = "last_modified_by", nullable = false, length = USERNAME_FIELD_LENGTH)
@Access(AccessType.PROPERTY)
public String getLastModifiedBy() {
return lastModifiedBy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,29 @@
* Repository for {@link SoftwareModuleType}.
*/
@Transactional(readOnly = true)
public interface SoftwareModuleTypeRepository
extends BaseEntityRepository<JpaSoftwareModuleType> {
public interface SoftwareModuleTypeRepository extends BaseEntityRepository<JpaSoftwareModuleType> {

/**
* @param key to search for
* @return all {@link SoftwareModuleType}s in the repository with given
* {@link SoftwareModuleType#getKey()}
* @return all {@link SoftwareModuleType}s in the repository with given {@link SoftwareModuleType#getKey()}
*/
Optional<SoftwareModuleType> findByKey(String key);

/**
* @param name to search for
* @return all {@link SoftwareModuleType}s in the repository with given
* {@link SoftwareModuleType#getName()}
* @return all {@link SoftwareModuleType}s in the repository with given {@link SoftwareModuleType#getName()}
*/
Optional<SoftwareModuleType> findByName(String name);

/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety
* reasons (this is a "delete everything" query after all) we add the tenant
* manually to query even if this will by done by {@link EntityManager}
* anyhow. The DB should take care of optimizing this away.
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety reasons (this is a "delete everything" query
* after all) we add the tenant manually to query even if this is done by {@link EntityManager} anyhow. The DB should take
* care of optimizing this away.
*
* @param tenant to delete data from
*/
@Modifying
@Transactional
@Query("DELETE FROM JpaSoftwareModuleType t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Path;
Expand Down Expand Up @@ -185,7 +183,7 @@ private Predicate mapToMapPredicate(final QuertPath queryField, final Path<?> fi
final String keyValue = graph[graph.length - 1];
if (fieldPath instanceof MapJoin) {
// Currently we support only string key. So below cast is safe.
return equal((Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue);
return equal((Path<String>) (((MapJoin<?, ?, ?>) fieldPath).key()), keyValue);
}

final String keyFieldName = queryField.getEnumValue().getSubEntityMapTuple().map(Entry::getKey)
Expand Down Expand Up @@ -225,7 +223,7 @@ private Predicate getEqualToPredicate(final Path<?> fieldPath, final Object tran
return cb.or(cb.isNull(fieldPath), cb.equal(pathOfString(fieldPath), ""));
}

final Expression<String> stringExpression = pathOfString(fieldPath);
final Path<String> stringExpression = pathOfString(fieldPath);
if (isPattern(transformedValueStr)) { // a pattern, use like
return like(stringExpression, toSQL(transformedValueStr));
} else {
Expand Down Expand Up @@ -311,14 +309,10 @@ private Path<?> getPath(final Root<?> root, final String fieldNameSplit) {
}
}

private Object convertValueIfNecessary(
final ComparisonNode node, final A fieldName, final Path<?> fieldPath, final String value) {
// in case the value of an RSQL query e.g. type==application is an
// enum we need to handle it separately because JPA needs the
// correct java-type to build an expression. So String and numeric
// values JPA can do it by its own but not for classes like enums.
// So we need to transform the given value string into the enum
// class.
private Object convertValueIfNecessary(final ComparisonNode node, final A fieldName, final Path<?> fieldPath, final String value) {
// in case the value of an RSQL query e.g. type==application is an enum we need to handle it separately because JPA needs the
// correct java-type to build an expression. So String and numeric values JPA can do it by its own but not for classes like enums.
// So we need to transform the given value string into the enum class.
final Class<?> javaType = fieldPath.getJavaType();
if (javaType != null && javaType.isEnum()) {
return transformEnumValue(node, javaType, value);
Expand All @@ -327,7 +321,7 @@ private Object convertValueIfNecessary(
return convertFieldConverterValue(node, fieldName, value);
}

if (Boolean.TYPE.equals(javaType)) {
if (Boolean.TYPE.equals(javaType) || Boolean.class.equals(javaType)) {
return convertBooleanValue(node, javaType, value);
}

Expand Down Expand Up @@ -368,7 +362,7 @@ private Predicate toNullOrNotEqualPredicate(final Path<?> fieldPath, final Objec

@SuppressWarnings({ "unchecked", "rawtypes" })
private Predicate toNotExistsSubQueryPredicate(final QuertPath queryField, final Path<?> fieldPath,
final Function<Expression<String>, Predicate> subQueryPredicateProvider) {
final Function<Path<String>, Predicate> subQueryPredicateProvider) {
// if a subquery the field's parent joins are not actually used
if (!inOr) {
// so, if not in or (hence not reused) we remove them. Parent shall be a Join
Expand All @@ -383,24 +377,27 @@ private Predicate toNotExistsSubQueryPredicate(final QuertPath queryField, final
.where(cb.and(
cb.equal(root.get(queryField.getEnumValue().identifierFieldName()),
subqueryRoot.get(queryField.getEnumValue().identifierFieldName())),
subQueryPredicateProvider.apply(getExpressionToCompare(queryField.getEnumValue(),
getFieldPath(subqueryRoot, queryField)))))));
subQueryPredicateProvider.apply(
getExpressionToCompare(queryField.getEnumValue(),
getFieldPath(subqueryRoot, queryField)))))));
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private Expression<String> getExpressionToCompare(final A enumField, final Path fieldPath) {
private Path<String> getExpressionToCompare(final A enumField, final Path fieldPath) {
if (!enumField.isMap()) {
return pathOfString(fieldPath);
}
if (fieldPath instanceof MapJoin) {
// Currently we support only string key. So below cast is safe.
return (Expression<String>) (((MapJoin<?, ?, ?>) fieldPath).value());
return (Path<String>) (((MapJoin<?, ?, ?>) fieldPath).value());
}
return enumField.getSubEntityMapTuple()
.map(Entry::getValue)
.map(valueFieldName -> fieldPath.<String> get(valueFieldName))
.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!"));
.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!"));
}

private String toSQL(final String transformedValue) {
Expand Down Expand Up @@ -437,33 +434,50 @@ private List<Predicate> acceptChildren(final LogicalNode node) {
return children;
}

private Predicate equal(final Expression<String> expressionToCompare, final String sqlValue) {
return cb.equal(caseWise(cb, expressionToCompare), caseWise(sqlValue));
}

private Predicate notEqual(final Expression<String> expressionToCompare, String transformedValueStr) {
return cb.notEqual(caseWise(cb, expressionToCompare), caseWise(transformedValueStr));
private Predicate equal(final Path<String> expressionToCompare, final String sqlValue) {
if (caseWise(expressionToCompare)) {
return cb.equal(cb.upper(expressionToCompare), sqlValue.toUpperCase());
} else {
return cb.equal(expressionToCompare, sqlValue);
}
}

private Predicate like(final Expression<String> expressionToCompare, final String sqlValue) {
return cb.like(caseWise(cb, expressionToCompare), caseWise(sqlValue), ESCAPE_CHAR);
private Predicate notEqual(final Path<String> expressionToCompare, String transformedValueStr) {
if (caseWise(expressionToCompare)) {
return cb.notEqual(cb.upper(expressionToCompare), transformedValueStr.toUpperCase());
} else {
return cb.notEqual(expressionToCompare, transformedValueStr);
}
}

private Predicate notLike(final Expression<String> expressionToCompare, final String sqlValue) {
return cb.notLike(caseWise(cb, expressionToCompare), caseWise(sqlValue), ESCAPE_CHAR);
private Predicate like(final Path<String> expressionToCompare, final String sqlValue) {
if (caseWise(expressionToCompare)) {
return cb.like(cb.upper(expressionToCompare), sqlValue.toUpperCase(), ESCAPE_CHAR);
} else {
return cb.like(expressionToCompare, sqlValue, ESCAPE_CHAR);
}
}

private Predicate in(final Expression<String> expressionToCompare, final List<Object> transformedValues) {
final List<String> inParams = transformedValues.stream().filter(String.class::isInstance)
.map(String.class::cast).map(this::caseWise).collect(Collectors.toList());
return inParams.isEmpty() ? expressionToCompare.in(transformedValues) : caseWise(cb, expressionToCompare).in(inParams);
private Predicate notLike(final Path<String> expressionToCompare, final String sqlValue) {
if (caseWise(expressionToCompare)) {
return cb.notLike(cb.upper(expressionToCompare), sqlValue.toUpperCase(), ESCAPE_CHAR);
} else {
return cb.notLike(expressionToCompare, sqlValue, ESCAPE_CHAR);
}
}

private Expression<String> caseWise(final CriteriaBuilder cb, final Expression<String> expression) {
return ensureIgnoreCase ? cb.upper(expression) : expression;
private Predicate in(final Path<String> expressionToCompare, final List<Object> transformedValues) {
if (ensureIgnoreCase && expressionToCompare.getJavaType() == String.class) {
final List<String> inParams = transformedValues.stream()
.filter(String.class::isInstance)
.map(String.class::cast).map(String::toUpperCase).toList();
return inParams.isEmpty() ? expressionToCompare.in(transformedValues) : cb.upper(expressionToCompare).in(inParams);
} else {
return expressionToCompare.in(transformedValues);
}
}

private String caseWise(final String str) {
return ensureIgnoreCase ? str.toUpperCase() : str;
private boolean caseWise(final Path<?> fieldPath) {
return ensureIgnoreCase && fieldPath.getJavaType() == String.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ public void testFilterByParameterId() {
return;
}

assertRSQLQuery(ActionFields.ID.name() + "==*", 11);
assertRSQLQuery(ActionFields.ID.name() + "==noexist*", 0);
assertRSQLQuery(ActionFields.ID.name() + "=in=(" + action.getId() + ",10000000)", 1);
assertRSQLQuery(ActionFields.ID.name() + "=out=(" + action.getId() + ",10000000)", 10);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ public void testFilterByParameterId() {
return;
}

assertRSQLQuery(DistributionSetFields.ID.name() + "==*", 5);
assertRSQLQuery(DistributionSetFields.ID.name() + "==noexist*", 0);
assertRSQLQuery(DistributionSetFields.ID.name() + "=in=(" + ds.getId() + ",10000000)", 1);
assertRSQLQuery(DistributionSetFields.ID.name() + "=out=(" + ds.getId() + ",10000000)", 4);
}
Expand All @@ -104,8 +102,8 @@ public void testFilterBySoftwareModule() {
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.NAME.name() + "==" + sm.getName(), 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "==" + sm.getId(), 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.NAME.name() + "==noExist", 0);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=in=(" + sm.getId() + ", noExist)", 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=out=(" + sm.getId() + ", noExist)", 4);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=in=(" + sm.getId() + ", -1)", 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=out=(" + sm.getId() + ", -1)", 4);
}

@Test
Expand All @@ -128,10 +126,8 @@ public void testFilterByParameterDescription() {
public void testFilterByParameterVersion() {
assertRSQLQuery(DistributionSetFields.VERSION.name() + "==" + TestdataFactory.DEFAULT_VERSION, 1);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "!=" + TestdataFactory.DEFAULT_VERSION, 4);
assertRSQLQuery(
DistributionSetFields.VERSION.name() + "=in=(" + TestdataFactory.DEFAULT_VERSION + ",1.0.0,1.0.1)", 3);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "=out=(" + TestdataFactory.DEFAULT_VERSION + ",error)",
4);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "=in=(" + TestdataFactory.DEFAULT_VERSION + ",1.0.0,1.0.1)", 3);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "=out=(" + TestdataFactory.DEFAULT_VERSION + ",error)", 4);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ public void testFilterByParameterId() {
return;
}

assertRSQLQuery(RolloutGroupFields.ID.name() + "==*", 4);
assertRSQLQuery(RolloutGroupFields.ID.name() + "==noexist*", 0);
assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ",10000000)", 1);
assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ",10000000)", 3);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ public void testFilterByParameterId() {
return;
}

assertRSQLQuery(SoftwareModuleFields.ID.name() + "==*", 6);
assertRSQLQuery(SoftwareModuleFields.ID.name() + "==noexist*", 0);
assertRSQLQuery(SoftwareModuleFields.ID.name() + "=in=(" + ah.getId() + ",1000000)", 1);
assertRSQLQuery(SoftwareModuleFields.ID.name() + "=out=(" + ah.getId() + ",1000000)", 5);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ public void testFilterByParameterId() {
return;
}

assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==*", 3);
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==noexist*", 0);
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "=in=(" + osType.getId() + ",1000000)", 1);
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "=out=(" + osType.getId() + ",1000000)", 2);
}
Expand Down
Loading
Loading