Fix RSQL filter in batch update query in assign target group (#3107)

* Fix RSQL filter in batch update query in assign target group

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* Stick with old approach when using hibernate

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* avoid fully qualified classname

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* Refer review changes

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* add link to the bug reported to eclipse link

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

---------

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
Stanislav Trailov
2026-06-02 10:12:43 +03:00
committed by GitHub
parent b0e279a3e8
commit 794455064f
2 changed files with 48 additions and 4 deletions

View File

@@ -22,6 +22,8 @@ import java.util.List;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetGroupRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetGroupRestApi;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -229,4 +231,30 @@ public class MgmtTargetGroupResourceTest extends AbstractManagementApiIntegratio
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2"))) .andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2")))
.andExpect(jsonPath("content.[2].controllerId", Matchers.equalTo("target3"))); .andExpect(jsonPath("content.[2].controllerId", Matchers.equalTo("target3")));
} }
@Test
void shouldAssignGroupToTargetsFilteredByTagNotEqual() throws Exception {
targetManagement.create(builder().controllerId("target1").build());
targetManagement.create(builder().controllerId("target2").build());
targetManagement.create(builder().controllerId("target3").build());
final TargetTag tag1 = targetTagManagement.create(TargetTagManagement.Create.builder().name("tag1").build());
final TargetTag tag2 = targetTagManagement.create(TargetTagManagement.Create.builder().name("tag2").build());
targetManagement.assignTag(List.of("target1"), tag1.getId());
targetManagement.assignTag(List.of("target2"), tag1.getId());
targetManagement.assignTag(List.of("target3"), tag2.getId());
mvc.perform(put(MgmtTargetGroupRestApi.TARGETGROUPS_V1 + "/FilteredGroup")
.contentType(MediaType.APPLICATION_JSON)
.param("q", "tag!=tag1"))
.andExpect(status().isNoContent());
mvc.perform(get(MgmtTargetGroupRestApi.TARGETGROUPS_V1 + "/FilteredGroup/assigned")
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("content", Matchers.hasSize(1)))
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target3")));
}
} }

View File

@@ -31,6 +31,7 @@ import jakarta.persistence.criteria.CriteriaUpdate;
import jakarta.persistence.criteria.MapJoin; import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.MapAttribute; import jakarta.persistence.metamodel.MapAttribute;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
@@ -39,9 +40,11 @@ import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
@@ -336,11 +339,24 @@ public class JpaTargetManagement
final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaUpdate<JpaTarget> criteriaUpdateQuery = cb.createCriteriaUpdate(JpaTarget.class); final CriteriaUpdate<JpaTarget> criteriaUpdateQuery = cb.createCriteriaUpdate(JpaTarget.class);
final Root<JpaTarget> root = criteriaUpdateQuery.getRoot(); final Root<JpaTarget> updateRoot = criteriaUpdateQuery.getRoot();
criteriaUpdateQuery.set("group", group); criteriaUpdateQuery.set("group", group);
// get predicate from rsql specification using a dummy query in order to execute batch update
final Predicate predicate = rsqlSpecification.toPredicate(root, entityManager.getCriteriaBuilder().createQuery(JpaTarget.class), cb); if (Jpa.JPA_VENDOR == Jpa.JpaVendor.ECLIPSELINK) {
criteriaUpdateQuery.where(predicate); // EclipseLink: use subquery approach — applying predicate directly to the UPDATE root
// fails for NOT EXISTS due to UpdateAllQuery's @Id resolution bug
// BUG Reported: https://github.com/eclipse-ee4j/eclipselink/issues/2757
final Subquery<Long> subquery = criteriaUpdateQuery.subquery(Long.class);
final Root<JpaTarget> subRoot = subquery.from(JpaTarget.class);
subquery.select(subRoot.get(AbstractJpaBaseEntity_.ID));
subquery.where(rsqlSpecification.toPredicate(subRoot, cb.createQuery(JpaTarget.class), cb));
criteriaUpdateQuery.where(updateRoot.get(AbstractJpaBaseEntity_.ID).in(subquery));
} else {
// Hibernate: apply predicate directly to the UPDATE root — Hibernate handles
// NOT EXISTS subqueries correctly in CriteriaUpdate context
final Predicate predicate = rsqlSpecification.toPredicate(updateRoot, cb.createQuery(JpaTarget.class), cb);
criteriaUpdateQuery.where(predicate);
}
entityManager.createQuery(criteriaUpdateQuery).executeUpdate(); entityManager.createQuery(criteriaUpdateQuery).executeUpdate();
} }