Skip to content

Commit d60701c

Browse files
authored
Add OR support for job queries (#4191)
1 parent 149562b commit d60701c

17 files changed

Lines changed: 2451 additions & 185 deletions

File tree

modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,4 +700,164 @@ public Void execute(CommandContext commandContext) {
700700
});
701701
}
702702

703+
@Test
704+
@Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml")
705+
public void testOrQuery() {
706+
ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
707+
ProcessInstance processInstance2 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
708+
709+
ExternalWorkerJob orderJob1 = managementService.createExternalWorkerJobQuery()
710+
.processInstanceId(processInstance1.getId())
711+
.elementId("externalOrder")
712+
.singleResult();
713+
ExternalWorkerJob customerJob1 = managementService.createExternalWorkerJobQuery()
714+
.processInstanceId(processInstance1.getId())
715+
.elementId("externalCustomer1")
716+
.singleResult();
717+
ExternalWorkerJob orderJob2 = managementService.createExternalWorkerJobQuery()
718+
.processInstanceId(processInstance2.getId())
719+
.elementId("externalOrder")
720+
.singleResult();
721+
assertThat(orderJob1).isNotNull();
722+
assertThat(customerJob1).isNotNull();
723+
assertThat(orderJob2).isNotNull();
724+
725+
// OR query: match by jobId(orderJob1) OR processInstanceId(processInstance2)
726+
List<ExternalWorkerJob> jobs = managementService.createExternalWorkerJobQuery()
727+
.or()
728+
.jobId(orderJob1.getId())
729+
.processInstanceId(processInstance2.getId())
730+
.endOr()
731+
.list();
732+
// orderJob1 + both jobs from processInstance2
733+
assertThat(jobs).hasSize(3);
734+
assertThat(jobs).extracting(ExternalWorkerJob::getId)
735+
.contains(orderJob1.getId(), orderJob2.getId());
736+
737+
// OR query: match by specific job IDs using jobId OR elementId scoped to a process
738+
jobs = managementService.createExternalWorkerJobQuery()
739+
.processInstanceId(processInstance1.getId())
740+
.or()
741+
.jobId(orderJob1.getId())
742+
.elementId("externalCustomer1")
743+
.endOr()
744+
.list();
745+
assertThat(jobs).hasSize(2);
746+
assertThat(jobs).extracting(ExternalWorkerJob::getId)
747+
.containsExactlyInAnyOrder(orderJob1.getId(), customerJob1.getId());
748+
749+
// OR with no match
750+
assertThat(managementService.createExternalWorkerJobQuery()
751+
.or()
752+
.processInstanceId("nonexistent")
753+
.executionId("nonexistent")
754+
.endOr()
755+
.count()).isZero();
756+
}
757+
758+
@Test
759+
@Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml")
760+
public void testOrWithAndQuery() {
761+
ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
762+
ProcessInstance processInstance2 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
763+
764+
// AND (processInstanceId) + OR (elementId OR elementId)
765+
List<ExternalWorkerJob> jobs = managementService.createExternalWorkerJobQuery()
766+
.processInstanceId(processInstance1.getId())
767+
.or()
768+
.elementId("externalOrder")
769+
.elementId("externalCustomer1")
770+
.endOr()
771+
.list();
772+
// In OR the last setter wins, so only externalCustomer1 matches within OR scope
773+
// Combined with AND processInstanceId, returns 1 job
774+
assertThat(jobs).hasSize(1);
775+
assertThat(jobs.get(0).getElementId()).isEqualTo("externalCustomer1");
776+
777+
// Test with different OR fields
778+
ExternalWorkerJob orderJob1 = managementService.createExternalWorkerJobQuery()
779+
.processInstanceId(processInstance1.getId())
780+
.elementId("externalOrder")
781+
.singleResult();
782+
783+
jobs = managementService.createExternalWorkerJobQuery()
784+
.processInstanceId(processInstance1.getId())
785+
.or()
786+
.jobId(orderJob1.getId())
787+
.elementId("externalCustomer1")
788+
.endOr()
789+
.list();
790+
assertThat(jobs).hasSize(2);
791+
792+
// AND with non-matching processInstanceId + OR should return empty
793+
assertThat(managementService.createExternalWorkerJobQuery()
794+
.processInstanceId("nonexistent")
795+
.or()
796+
.elementId("externalOrder")
797+
.elementId("externalCustomer1")
798+
.endOr()
799+
.count()).isZero();
800+
}
801+
802+
@Test
803+
@Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml")
804+
public void testOrQueryErrors() {
805+
runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
806+
807+
assertThatThrownBy(() -> managementService.createExternalWorkerJobQuery().or().or())
808+
.isInstanceOf(FlowableException.class)
809+
.hasMessageContaining("the query is already in an or statement");
810+
811+
assertThatThrownBy(() -> managementService.createExternalWorkerJobQuery().endOr())
812+
.isInstanceOf(FlowableException.class)
813+
.hasMessageContaining("endOr() can only be called after calling or()");
814+
}
815+
816+
@Test
817+
@Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml")
818+
public void testConsecutiveOrQuery() {
819+
ProcessInstance pi1 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
820+
ProcessInstance pi2 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
821+
ProcessInstance pi3 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest");
822+
823+
ExternalWorkerJob job1Order = managementService.createExternalWorkerJobQuery()
824+
.processInstanceId(pi1.getId()).elementId("externalOrder").singleResult();
825+
ExternalWorkerJob job2Order = managementService.createExternalWorkerJobQuery()
826+
.processInstanceId(pi2.getId()).elementId("externalOrder").singleResult();
827+
828+
// First OR group matches {job1Order, all pi2 jobs}, second matches {all pi2 jobs, all pi3 jobs}
829+
// Intersection = pi2's jobs (2 jobs)
830+
List<ExternalWorkerJob> jobs = managementService.createExternalWorkerJobQuery()
831+
.or()
832+
.jobId(job1Order.getId())
833+
.processInstanceId(pi2.getId())
834+
.endOr()
835+
.or()
836+
.processInstanceId(pi2.getId())
837+
.processInstanceId(pi3.getId())
838+
.endOr()
839+
.list();
840+
// Second OR group: last setter wins -> processInstanceId=pi3. So second group matches pi3 only.
841+
// Intersection of {job1Order, pi2 jobs} AND {pi3 jobs} = empty
842+
assertThat(jobs).hasSize(0);
843+
844+
// Let's use different fields instead:
845+
jobs = managementService.createExternalWorkerJobQuery()
846+
.or()
847+
.jobId(job1Order.getId())
848+
.processInstanceId(pi2.getId())
849+
.endOr()
850+
.or()
851+
.jobId(job2Order.getId())
852+
.processInstanceId(pi1.getId())
853+
.endOr()
854+
.list();
855+
// First group: {job1Order} union {pi2's 2 jobs} = {job1Order, job2Order, job2Customer}
856+
// Second group: {job2Order} union {pi1's 2 jobs} = {job2Order, job1Order, job1Customer}
857+
// Intersection: {job1Order, job2Order} - both appear in both groups
858+
assertThat(jobs).hasSize(2);
859+
assertThat(jobs).extracting(ExternalWorkerJob::getId)
860+
.containsExactlyInAnyOrder(job1Order.getId(), job2Order.getId());
861+
}
862+
703863
}

0 commit comments

Comments
 (0)