From b8e5d7119142166c271aefbb8dc65b0d9b1074d5 Mon Sep 17 00:00:00 2001 From: Valentin Zickner Date: Wed, 25 Mar 2026 09:28:18 +0100 Subject: [PATCH 1/3] fix query for suspended external worker jobs --- .../engine/test/api/mgmt/JobQueryTest.java | 41 +++++++++++++++++++ .../service/impl/SuspendedJobQueryImpl.java | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java index 08aa7c93f64..e4b7ec76a5a 100755 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java @@ -342,6 +342,35 @@ public void testQueryByHandlerTypes() { managementService.deleteJob(managementService.createJobQuery().handlerType("Type2").singleResult().getId()); } + @Test + public void testSuspendedJobQueryByType() { + String handlerType = "testSuspendedJobType"; + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).count()).isEqualTo(0); + + createSuspendedJobWithType(Job.JOB_TYPE_MESSAGE, handlerType); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).count()).isEqualTo(1); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).messages().count()).isEqualTo(1); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).timers().count()).isEqualTo(0); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).externalWorkers().count()).isEqualTo(0); + + createSuspendedJobWithType(Job.JOB_TYPE_TIMER, handlerType); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).count()).isEqualTo(2); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).messages().count()).isEqualTo(1); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).timers().count()).isEqualTo(1); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).externalWorkers().count()).isEqualTo(0); + + createSuspendedJobWithType(Job.JOB_TYPE_EXTERNAL_WORKER, handlerType); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).count()).isEqualTo(3); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).messages().count()).isEqualTo(1); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).timers().count()).isEqualTo(1); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).externalWorkers().count()).isEqualTo(1); + + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType(handlerType).messages().singleResult().getId()); + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType(handlerType).timers().singleResult().getId()); + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType(handlerType).externalWorkers().singleResult().getId()); + assertThat(managementService.createSuspendedJobQuery().handlerType(handlerType).count()).isEqualTo(0); + } + @Test public void testSuspendedJobQueryByHandlerTypes() { @@ -1131,6 +1160,18 @@ public Void execute(CommandContext commandContext) { }); } + private void createSuspendedJobWithType(String type, String handlerType) { + managementService.executeCommand(commandContext -> { + JobServiceConfiguration jobServiceConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext).getJobServiceConfiguration(); + SuspendedJobEntityManager suspendedJobEntityManager = jobServiceConfiguration.getSuspendedJobEntityManager(); + SuspendedJobEntity job = suspendedJobEntityManager.create(); + job.setJobType(type); + job.setJobHandlerType(handlerType); + suspendedJobEntityManager.insert(job); + return null; + }); + } + private JobEntity createJobWithHandlerType(String handlerType) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); return commandExecutor.execute(new Command<>() { diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java index 14ed776b9ee..f7abfffeb2e 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java @@ -347,7 +347,7 @@ public SuspendedJobQueryImpl externalWorkers() { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyExternalWorkers() in the same query"); } - this.onlyTimers = true; + this.onlyExternalWorkers = true; return this; } From dcdb0a0940d4dd71184e1a4bc1fcc63a2268ee56 Mon Sep 17 00:00:00 2001 From: Valentin Zickner Date: Tue, 24 Mar 2026 21:29:13 +0100 Subject: [PATCH 2/3] add or() and endOr() for job queries --- .../api/mgmt/ExternalWorkerJobQueryTest.java | 113 +++++++ .../engine/test/api/mgmt/JobQueryTest.java | 309 ++++++++++++++++++ .../test/api/mgmt/TimerJobQueryTest.java | 78 +++++ .../org/flowable/job/api/BaseJobQuery.java | 10 + .../org/flowable/job/api/HistoryJobQuery.java | 10 + .../service/impl/DeadLetterJobQueryImpl.java | 227 +++++++++++-- .../impl/ExternalWorkerJobQueryImpl.java | 230 +++++++++++-- .../job/service/impl/HistoryJobQueryImpl.java | 113 ++++++- .../job/service/impl/JobQueryImpl.java | 233 +++++++++++-- .../service/impl/SuspendedJobQueryImpl.java | 251 ++++++++++++-- .../job/service/impl/TimerJobQueryImpl.java | 221 +++++++++++-- .../db/mapping/entity/DeadLetterJob.xml | 115 +++++++ .../db/mapping/entity/ExternalWorkerJob.xml | 128 ++++++++ .../service/db/mapping/entity/HistoryJob.xml | 49 +++ .../job/service/db/mapping/entity/Job.xml | 118 +++++++ .../db/mapping/entity/SuspendedJob.xml | 118 +++++++ .../service/db/mapping/entity/TimerJob.xml | 112 +++++++ 17 files changed, 2257 insertions(+), 178 deletions(-) diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java index ebd267fe022..869695b234e 100755 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java @@ -700,4 +700,117 @@ public Void execute(CommandContext commandContext) { }); } + @Test + @Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml") + public void testOrQuery() { + ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + ProcessInstance processInstance2 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + + ExternalWorkerJob orderJob1 = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance1.getId()) + .elementId("externalOrder") + .singleResult(); + ExternalWorkerJob customerJob1 = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance1.getId()) + .elementId("externalCustomer1") + .singleResult(); + ExternalWorkerJob orderJob2 = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance2.getId()) + .elementId("externalOrder") + .singleResult(); + assertThat(orderJob1).isNotNull(); + assertThat(customerJob1).isNotNull(); + assertThat(orderJob2).isNotNull(); + + // OR query: match by jobId(orderJob1) OR processInstanceId(processInstance2) + List jobs = managementService.createExternalWorkerJobQuery() + .or() + .jobId(orderJob1.getId()) + .processInstanceId(processInstance2.getId()) + .endOr() + .list(); + // orderJob1 + both jobs from processInstance2 + assertThat(jobs).hasSize(3); + assertThat(jobs).extracting(ExternalWorkerJob::getId) + .contains(orderJob1.getId(), orderJob2.getId()); + + // OR query: match by specific job IDs using jobId OR elementId scoped to a process + jobs = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance1.getId()) + .or() + .jobId(orderJob1.getId()) + .elementId("externalCustomer1") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(ExternalWorkerJob::getId) + .containsExactlyInAnyOrder(orderJob1.getId(), customerJob1.getId()); + + // OR with no match + assertThat(managementService.createExternalWorkerJobQuery() + .or() + .processInstanceId("nonexistent") + .executionId("nonexistent") + .endOr() + .count()).isZero(); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml") + public void testOrWithAndQuery() { + ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + ProcessInstance processInstance2 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + + // AND (processInstanceId) + OR (elementId OR elementId) + List jobs = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance1.getId()) + .or() + .elementId("externalOrder") + .elementId("externalCustomer1") + .endOr() + .list(); + // In OR the last setter wins, so only externalCustomer1 matches within OR scope + // Combined with AND processInstanceId, returns 1 job + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getElementId()).isEqualTo("externalCustomer1"); + + // Test with different OR fields + ExternalWorkerJob orderJob1 = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance1.getId()) + .elementId("externalOrder") + .singleResult(); + + jobs = managementService.createExternalWorkerJobQuery() + .processInstanceId(processInstance1.getId()) + .or() + .jobId(orderJob1.getId()) + .elementId("externalCustomer1") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + + // AND with non-matching processInstanceId + OR should return empty + assertThat(managementService.createExternalWorkerJobQuery() + .processInstanceId("nonexistent") + .or() + .elementId("externalOrder") + .elementId("externalCustomer1") + .endOr() + .count()).isZero(); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml") + public void testOrQueryErrors() { + runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + + assertThatThrownBy(() -> managementService.createExternalWorkerJobQuery().or().or()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("the query is already in an or statement"); + + assertThatThrownBy(() -> managementService.createExternalWorkerJobQuery().endOr()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("endOr() can only be called after calling or()"); + } + } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java index e4b7ec76a5a..7d16feb76ab 100755 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java @@ -1023,6 +1023,315 @@ public void testExternalWorkerJobQueryWithoutScopeType() { }); } + @Test + public void testJobQueryOrQuery() { + // The setUp creates 1 message job in ACT_RU_JOB + JobEntity extraJob = createJobWithHandlerType("testHandler"); + try { + // OR query: match by id OR handler type + List jobs = managementService.createJobQuery() + .or() + .jobId(messageId) + .handlerType("testHandler") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(JobInfo::getId) + .containsExactlyInAnyOrder(messageId, extraJob.getId()); + + // OR query with no match + assertThat(managementService.createJobQuery() + .or() + .processInstanceId("nonexistent") + .executionId("nonexistent") + .endOr() + .count()).isZero(); + } finally { + managementService.deleteJob(extraJob.getId()); + } + } + + @Test + public void testJobQueryOrWithAndQuery() { + JobEntity extraJob = createJobWithHandlerType("testHandler"); + try { + // AND + OR: the AND narrows, the OR broadens within its scope + // jobId(messageId) AND (handlerType=testHandler OR jobId=messageId) + List jobs = managementService.createJobQuery() + .jobId(messageId) + .or() + .handlerType("testHandler") + .jobId(messageId) + .endOr() + .list(); + // Only messageId matches the AND + OR + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(messageId); + } finally { + managementService.deleteJob(extraJob.getId()); + } + } + + @Test + public void testJobQueryOrQueryErrors() { + // or() twice should throw + assertThatThrownBy(() -> managementService.createJobQuery().or().or()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("the query is already in an or statement"); + + // endOr() without or() should throw + assertThatThrownBy(() -> managementService.createJobQuery().endOr()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("endOr() can only be called after calling or()"); + } + + @Test + public void testDeadLetterJobOrQuery() { + createDeadLetterJobWithHandlerType("dlHandler1"); + createDeadLetterJobWithHandlerType("dlHandler2"); + try { + Job dl1 = managementService.createDeadLetterJobQuery().handlerType("dlHandler1").singleResult(); + Job dl2 = managementService.createDeadLetterJobQuery().handlerType("dlHandler2").singleResult(); + assertThat(dl1).isNotNull(); + assertThat(dl2).isNotNull(); + + // OR query: match by jobId OR handlerType + List jobs = managementService.createDeadLetterJobQuery() + .or() + .jobId(dl1.getId()) + .handlerType("dlHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(JobInfo::getId) + .containsExactlyInAnyOrder(dl1.getId(), dl2.getId()); + + // OR with no match + assertThat(managementService.createDeadLetterJobQuery() + .or() + .processInstanceId("nonexistent") + .executionId("nonexistent") + .endOr() + .count()).isZero(); + } finally { + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("dlHandler1").singleResult().getId()); + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("dlHandler2").singleResult().getId()); + } + } + + @Test + public void testDeadLetterJobOrWithAndQuery() { + createDeadLetterJobWithHandlerType("dlHandler1"); + createDeadLetterJobWithHandlerType("dlHandler2"); + try { + Job dl1 = managementService.createDeadLetterJobQuery().handlerType("dlHandler1").singleResult(); + Job dl2 = managementService.createDeadLetterJobQuery().handlerType("dlHandler2").singleResult(); + + // AND (jobId=dl1) + OR (jobId=dl1 OR handlerType=dlHandler2) + // dl1 matches the AND and the OR (via jobId), so returns dl1 + List jobs = managementService.createDeadLetterJobQuery() + .jobId(dl1.getId()) + .or() + .jobId(dl1.getId()) + .handlerType("dlHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(dl1.getId()); + + // AND (handlerType=dlHandler1) + OR (jobId=dl1 OR jobId=dl2) - only dl1 matches both + jobs = managementService.createDeadLetterJobQuery() + .handlerType("dlHandler1") + .or() + .jobId(dl1.getId()) + .jobId(dl2.getId()) + .endOr() + .list(); + // OR last setter wins: jobId=dl2. AND handlerType=dlHandler1 + OR jobId=dl2 -> dl2 has dlHandler2, no match + // So this returns 0 - let's use different fields instead + assertThat(jobs).isEmpty(); + + // Better test: AND (handlerType=dlHandler1) + OR (jobId=dl1 OR handlerType=dlHandler2) + // dl1 matches AND (dlHandler1) and OR (via jobId=dl1) + jobs = managementService.createDeadLetterJobQuery() + .handlerType("dlHandler1") + .or() + .jobId(dl1.getId()) + .handlerType("dlHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(dl1.getId()); + + // Non-matching AND + valid OR returns nothing + assertThat(managementService.createDeadLetterJobQuery() + .handlerType("nonexistent") + .or() + .jobId(dl1.getId()) + .handlerType("dlHandler2") + .endOr() + .count()).isZero(); + } finally { + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("dlHandler1").singleResult().getId()); + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("dlHandler2").singleResult().getId()); + } + } + + @Test + public void testDeadLetterJobOrQueryErrors() { + assertThatThrownBy(() -> managementService.createDeadLetterJobQuery().or().or()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("the query is already in an or statement"); + + assertThatThrownBy(() -> managementService.createDeadLetterJobQuery().endOr()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("endOr() can only be called after calling or()"); + } + + @Test + public void testSuspendedJobOrQuery() { + createSuspendedJobWithHandlerType("susHandler1"); + createSuspendedJobWithHandlerType("susHandler2"); + try { + Job sus1 = managementService.createSuspendedJobQuery().handlerType("susHandler1").singleResult(); + Job sus2 = managementService.createSuspendedJobQuery().handlerType("susHandler2").singleResult(); + assertThat(sus1).isNotNull(); + assertThat(sus2).isNotNull(); + + List jobs = managementService.createSuspendedJobQuery() + .or() + .jobId(sus1.getId()) + .handlerType("susHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(JobInfo::getId) + .containsExactlyInAnyOrder(sus1.getId(), sus2.getId()); + + // OR with no match + assertThat(managementService.createSuspendedJobQuery() + .or() + .processInstanceId("nonexistent") + .executionId("nonexistent") + .endOr() + .count()).isZero(); + } finally { + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("susHandler1").singleResult().getId()); + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("susHandler2").singleResult().getId()); + } + } + + @Test + public void testSuspendedJobOrWithAndQuery() { + createSuspendedJobWithHandlerType("susHandler1"); + createSuspendedJobWithHandlerType("susHandler2"); + try { + Job sus1 = managementService.createSuspendedJobQuery().handlerType("susHandler1").singleResult(); + Job sus2 = managementService.createSuspendedJobQuery().handlerType("susHandler2").singleResult(); + + // AND (jobId=sus1) + OR (jobId=sus1 OR handlerType=susHandler2) + List jobs = managementService.createSuspendedJobQuery() + .jobId(sus1.getId()) + .or() + .jobId(sus1.getId()) + .handlerType("susHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(sus1.getId()); + } finally { + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("susHandler1").singleResult().getId()); + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("susHandler2").singleResult().getId()); + } + } + + @Test + public void testSuspendedJobOrQueryErrors() { + assertThatThrownBy(() -> managementService.createSuspendedJobQuery().or().or()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("the query is already in an or statement"); + + assertThatThrownBy(() -> managementService.createSuspendedJobQuery().endOr()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("endOr() can only be called after calling or()"); + } + + @Test + public void testHistoryJobOrQuery() { + createHistoryobWithHandlerType("histHandler1"); + createHistoryobWithHandlerType("histHandler2"); + try { + HistoryJob hj1 = managementService.createHistoryJobQuery().handlerType("histHandler1").singleResult(); + HistoryJob hj2 = managementService.createHistoryJobQuery().handlerType("histHandler2").singleResult(); + + // OR query: match by jobId OR handlerType + List jobs = managementService.createHistoryJobQuery() + .or() + .jobId(hj1.getId()) + .handlerType("histHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(HistoryJob::getId) + .containsExactlyInAnyOrder(hj1.getId(), hj2.getId()); + + // OR with no match + assertThat(managementService.createHistoryJobQuery() + .or() + .jobId("nonexistent") + .handlerType("nonexistent") + .endOr() + .count()).isZero(); + } finally { + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("histHandler1").singleResult().getId()); + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("histHandler2").singleResult().getId()); + } + } + + @Test + public void testHistoryJobOrWithAndQuery() { + createHistoryobWithHandlerType("histHandler1"); + createHistoryobWithHandlerType("histHandler2"); + try { + HistoryJob hj1 = managementService.createHistoryJobQuery().handlerType("histHandler1").singleResult(); + + // AND (handlerType=histHandler1) + OR (jobId=hj1 OR handlerType=histHandler2) + // hj1 matches AND and OR (via jobId) + List jobs = managementService.createHistoryJobQuery() + .handlerType("histHandler1") + .or() + .jobId(hj1.getId()) + .handlerType("histHandler2") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(hj1.getId()); + + // Non-matching AND + valid OR returns nothing + assertThat(managementService.createHistoryJobQuery() + .handlerType("nonexistent") + .or() + .jobId(hj1.getId()) + .handlerType("histHandler2") + .endOr() + .count()).isZero(); + } finally { + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("histHandler1").singleResult().getId()); + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("histHandler2").singleResult().getId()); + } + } + + @Test + public void testHistoryJobOrQueryErrors() { + assertThatThrownBy(() -> managementService.createHistoryJobQuery().or().or()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("the query is already in an or statement"); + + assertThatThrownBy(() -> managementService.createHistoryJobQuery().endOr()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("endOr() can only be called after calling or()"); + } + // helper //////////////////////////////////////////////////////////// private void setRetries(final String processInstanceId, final int retries) { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java index 7234265d80a..bd3321e0981 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.common.engine.impl.interceptor.CommandExecutor; @@ -210,6 +211,83 @@ public void testByInvalidCorrelationId() { assertThat(managementService.createTimerJobQuery().correlationId("invalid").count()).isZero(); } + @Test + public void testOrQuery() { + // Get specific timer jobs by element id, scoped to this test's process instance + Job timerA = managementService.createTimerJobQuery().processInstanceId(processInstanceId).elementId("timerA").singleResult(); + Job timerB = managementService.createTimerJobQuery().processInstanceId(processInstanceId).elementId("timerB").singleResult(); + Job timerC = managementService.createTimerJobQuery().processInstanceId(processInstanceId).elementId("timerC").singleResult(); + assertThat(timerA).isNotNull(); + assertThat(timerB).isNotNull(); + assertThat(timerC).isNotNull(); + + // OR query: match by jobId OR elementId + List jobs = managementService.createTimerJobQuery() + .or() + .jobId(timerA.getId()) + .elementId("timerB") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(JobInfo::getId) + .containsExactlyInAnyOrder(timerA.getId(), timerB.getId()); + + // OR query: match by executionId OR elementId + jobs = managementService.createTimerJobQuery() + .or() + .executionId(timerA.getExecutionId()) + .elementId("timerC") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(JobInfo::getId) + .containsExactlyInAnyOrder(timerA.getId(), timerC.getId()); + + // OR with no match + assertThat(managementService.createTimerJobQuery() + .or() + .processInstanceId("nonexistent") + .executionId("nonexistent") + .endOr() + .count()).isZero(); + } + + @Test + public void testOrWithAndQuery() { + Job timerA = managementService.createTimerJobQuery().elementId("timerA").singleResult(); + Job timerB = managementService.createTimerJobQuery().elementId("timerB").singleResult(); + + // AND (processInstanceId) + OR (jobId or elementId) + List jobs = managementService.createTimerJobQuery() + .processInstanceId(processInstanceId) + .or() + .jobId(timerA.getId()) + .elementId("timerB") + .endOr() + .list(); + assertThat(jobs).hasSize(2); + + // AND (wrong processInstanceId) + OR should return nothing + assertThat(managementService.createTimerJobQuery() + .processInstanceId("nonexistent") + .or() + .jobId(timerA.getId()) + .elementId("timerB") + .endOr() + .count()).isZero(); + } + + @Test + public void testOrQueryErrors() { + assertThatThrownBy(() -> managementService.createTimerJobQuery().or().or()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("the query is already in an or statement"); + + assertThatThrownBy(() -> managementService.createTimerJobQuery().endOr()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("endOr() can only be called after calling or()"); + } + private void createTimerJobWithHandlerType(String handlerType) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); commandExecutor.execute(new Command() { diff --git a/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/BaseJobQuery.java b/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/BaseJobQuery.java index 277ae31c4b2..18054b37567 100644 --- a/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/BaseJobQuery.java +++ b/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/BaseJobQuery.java @@ -181,6 +181,16 @@ public interface BaseJobQuery, T extends Job> exten */ U jobWithoutTenantId(); + /** + * Begin an OR statement. Make sure you invoke the endOr method at the end of your OR statement. + */ + U or(); + + /** + * End an OR statement. + */ + U endOr(); + // sorting ////////////////////////////////////////// /** diff --git a/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/HistoryJobQuery.java b/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/HistoryJobQuery.java index 19642adfa86..87f094f946c 100644 --- a/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/HistoryJobQuery.java +++ b/modules/flowable-job-service-api/src/main/java/org/flowable/job/api/HistoryJobQuery.java @@ -84,6 +84,16 @@ public interface HistoryJobQuery extends Query { */ HistoryJobQuery unlocked(); + /** + * Begin an OR statement. Make sure you invoke the endOr method at the end of your OR statement. + */ + HistoryJobQuery or(); + + /** + * End an OR statement. + */ + HistoryJobQuery endOr(); + // sorting ////////////////////////////////////////// /** diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/DeadLetterJobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/DeadLetterJobQueryImpl.java index b139f3e0672..264ea57c1af 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/DeadLetterJobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/DeadLetterJobQueryImpl.java @@ -14,10 +14,12 @@ package org.flowable.job.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -72,6 +74,10 @@ public class DeadLetterJobQueryImpl extends AbstractQuery orQueryObjects = new ArrayList<>(); + protected DeadLetterJobQueryImpl currentOrQueryObject; + protected boolean inOrStatement; + public DeadLetterJobQueryImpl() { } @@ -90,7 +96,11 @@ public DeadLetterJobQueryImpl jobId(String jobId) { if (jobId == null) { throw new FlowableIllegalArgumentException("Provided job id is null"); } - this.id = jobId; + if (inOrStatement) { + this.currentOrQueryObject.id = jobId; + } else { + this.id = jobId; + } return this; } @@ -99,7 +109,11 @@ public DeadLetterJobQuery jobIds(Collection jobIds) { if (jobIds == null) { throw new FlowableIllegalArgumentException("Provided job id list is null"); } - this.jobIds = jobIds; + if (inOrStatement) { + this.currentOrQueryObject.jobIds = jobIds; + } else { + this.jobIds = jobIds; + } return this; } @@ -108,13 +122,21 @@ public DeadLetterJobQueryImpl processInstanceId(String processInstanceId) { if (processInstanceId == null) { throw new FlowableIllegalArgumentException("Provided process instance id is null"); } - this.processInstanceId = processInstanceId; + if (inOrStatement) { + this.currentOrQueryObject.processInstanceId = processInstanceId; + } else { + this.processInstanceId = processInstanceId; + } return this; } @Override public DeadLetterJobQueryImpl withoutProcessInstanceId() { - this.withoutProcessInstanceId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutProcessInstanceId = true; + } else { + this.withoutProcessInstanceId = true; + } return this; } @@ -123,7 +145,11 @@ public DeadLetterJobQueryImpl processDefinitionId(String processDefinitionId) { if (processDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided process definition id is null"); } - this.processDefinitionId = processDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionId = processDefinitionId; + } else { + this.processDefinitionId = processDefinitionId; + } return this; } @@ -132,7 +158,11 @@ public DeadLetterJobQueryImpl processDefinitionKey(String processDefinitionKey) if (processDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided process definition key is null"); } - this.processDefinitionKey = processDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionKey = processDefinitionKey; + } else { + this.processDefinitionKey = processDefinitionKey; + } return this; } @@ -141,7 +171,11 @@ public DeadLetterJobQueryImpl category(String category) { if (category == null) { throw new FlowableIllegalArgumentException("Provided category is null"); } - this.category = category; + if (inOrStatement) { + this.currentOrQueryObject.category = category; + } else { + this.category = category; + } return this; } @@ -150,7 +184,11 @@ public DeadLetterJobQueryImpl categoryLike(String categoryLike) { if (categoryLike == null) { throw new FlowableIllegalArgumentException("Provided categoryLike is null"); } - this.categoryLike = categoryLike; + if (inOrStatement) { + this.currentOrQueryObject.categoryLike = categoryLike; + } else { + this.categoryLike = categoryLike; + } return this; } @@ -159,7 +197,11 @@ public DeadLetterJobQueryImpl elementId(String elementId) { if (elementId == null) { throw new FlowableIllegalArgumentException("Provided element id is null"); } - this.elementId = elementId; + if (inOrStatement) { + this.currentOrQueryObject.elementId = elementId; + } else { + this.elementId = elementId; + } return this; } @@ -168,7 +210,11 @@ public DeadLetterJobQueryImpl elementName(String elementName) { if (elementName == null) { throw new FlowableIllegalArgumentException("Provided element name is null"); } - this.elementName = elementName; + if (inOrStatement) { + this.currentOrQueryObject.elementName = elementName; + } else { + this.elementName = elementName; + } return this; } @@ -177,13 +223,21 @@ public DeadLetterJobQueryImpl scopeId(String scopeId) { if (scopeId == null) { throw new FlowableIllegalArgumentException("Provided scope id is null"); } - this.scopeId = scopeId; + if (inOrStatement) { + this.currentOrQueryObject.scopeId = scopeId; + } else { + this.scopeId = scopeId; + } return this; } @Override public DeadLetterJobQueryImpl withoutScopeId() { - this.withoutScopeId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeId = true; + } else { + this.withoutScopeId = true; + } return this; } @@ -192,7 +246,11 @@ public DeadLetterJobQueryImpl subScopeId(String subScopeId) { if (subScopeId == null) { throw new FlowableIllegalArgumentException("Provided sub scope id is null"); } - this.subScopeId = subScopeId; + if (inOrStatement) { + this.currentOrQueryObject.subScopeId = subScopeId; + } else { + this.subScopeId = subScopeId; + } return this; } @@ -201,13 +259,21 @@ public DeadLetterJobQueryImpl scopeType(String scopeType) { if (scopeType == null) { throw new FlowableIllegalArgumentException("Provided scope type is null"); } - this.scopeType = scopeType; + if (inOrStatement) { + this.currentOrQueryObject.scopeType = scopeType; + } else { + this.scopeType = scopeType; + } return this; } @Override public DeadLetterJobQueryImpl withoutScopeType() { - this.withoutScopeType = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeType = true; + } else { + this.withoutScopeType = true; + } return this; } @@ -216,7 +282,11 @@ public DeadLetterJobQueryImpl scopeDefinitionId(String scopeDefinitionId) { if (scopeDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided scope definitionid is null"); } - this.scopeDefinitionId = scopeDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.scopeDefinitionId = scopeDefinitionId; + } else { + this.scopeDefinitionId = scopeDefinitionId; + } return this; } @@ -245,7 +315,11 @@ public DeadLetterJobQueryImpl caseDefinitionKey(String caseDefinitionKey) { if (caseDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided case definition key null"); } - this.caseDefinitionKey = caseDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.caseDefinitionKey = caseDefinitionKey; + } else { + this.caseDefinitionKey = caseDefinitionKey; + } return this; } @@ -264,7 +338,11 @@ public DeadLetterJobQuery correlationId(String correlationId) { if (correlationId == null) { throw new FlowableIllegalArgumentException("Provided correlationId is null"); } - this.correlationId = correlationId; + if (inOrStatement) { + this.currentOrQueryObject.correlationId = correlationId; + } else { + this.correlationId = correlationId; + } return this; } @@ -273,7 +351,11 @@ public DeadLetterJobQueryImpl executionId(String executionId) { if (executionId == null) { throw new FlowableIllegalArgumentException("Provided execution id is null"); } - this.executionId = executionId; + if (inOrStatement) { + this.currentOrQueryObject.executionId = executionId; + } else { + this.executionId = executionId; + } return this; } @@ -282,7 +364,11 @@ public DeadLetterJobQueryImpl handlerType(String handlerType) { if (handlerType == null) { throw new FlowableIllegalArgumentException("Provided handlerType is null"); } - this.handlerType = handlerType; + if (inOrStatement) { + this.currentOrQueryObject.handlerType = handlerType; + } else { + this.handlerType = handlerType; + } return this; } @@ -291,13 +377,21 @@ public DeadLetterJobQuery handlerTypes(Collection handlerTypes) { if (handlerTypes == null) { throw new FlowableIllegalArgumentException("Provided handlerTypes are null"); } - this.handlerTypes = handlerTypes; + if (inOrStatement) { + this.currentOrQueryObject.handlerTypes = handlerTypes; + } else { + this.handlerTypes = handlerTypes; + } return this; } @Override public DeadLetterJobQueryImpl executable() { - executable = true; + if (inOrStatement) { + this.currentOrQueryObject.executable = true; + } else { + this.executable = true; + } return this; } @@ -310,7 +404,11 @@ public DeadLetterJobQueryImpl timers() { if (onlyExternalWorkers) { throw new FlowableIllegalArgumentException("Cannot combine onlyExternalWorkers() with onlyMessages() in the same query"); } - this.onlyTimers = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyTimers = true; + } else { + this.onlyTimers = true; + } return this; } @@ -324,7 +422,11 @@ public DeadLetterJobQueryImpl messages() { throw new FlowableIllegalArgumentException("Cannot combine onlyExternalWorkers() with onlyMessages() in the same query"); } - this.onlyMessages = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyMessages = true; + } else { + this.onlyMessages = true; + } return this; } @@ -338,7 +440,11 @@ public DeadLetterJobQueryImpl externalWorkers() { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyExternalWorkers() in the same query"); } - this.onlyExternalWorkers = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyExternalWorkers = true; + } else { + this.onlyExternalWorkers = true; + } return this; } @@ -348,7 +454,11 @@ public DeadLetterJobQueryImpl duedateHigherThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateHigherThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateHigherThan = date; + } else { + this.duedateHigherThan = date; + } return this; } @@ -357,13 +467,21 @@ public DeadLetterJobQueryImpl duedateLowerThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateLowerThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateLowerThan = date; + } else { + this.duedateLowerThan = date; + } return this; } @Override public DeadLetterJobQueryImpl withException() { - this.withException = true; + if (inOrStatement) { + this.currentOrQueryObject.withException = true; + } else { + this.withException = true; + } return this; } @@ -372,7 +490,11 @@ public DeadLetterJobQueryImpl exceptionMessage(String exceptionMessage) { if (exceptionMessage == null) { throw new FlowableIllegalArgumentException("Provided exception message is null"); } - this.exceptionMessage = exceptionMessage; + if (inOrStatement) { + this.currentOrQueryObject.exceptionMessage = exceptionMessage; + } else { + this.exceptionMessage = exceptionMessage; + } return this; } @@ -381,7 +503,11 @@ public DeadLetterJobQueryImpl jobTenantId(String tenantId) { if (tenantId == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantId = tenantId; + if (inOrStatement) { + this.currentOrQueryObject.tenantId = tenantId; + } else { + this.tenantId = tenantId; + } return this; } @@ -390,13 +516,46 @@ public DeadLetterJobQueryImpl jobTenantIdLike(String tenantIdLike) { if (tenantIdLike == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantIdLike = tenantIdLike; + if (inOrStatement) { + this.currentOrQueryObject.tenantIdLike = tenantIdLike; + } else { + this.tenantIdLike = tenantIdLike; + } return this; } @Override public DeadLetterJobQueryImpl jobWithoutTenantId() { - this.withoutTenantId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutTenantId = true; + } else { + this.withoutTenantId = true; + } + return this; + } + + @Override + public DeadLetterJobQuery or() { + if (inOrStatement) { + throw new FlowableException("the query is already in an or statement"); + } + inOrStatement = true; + if (commandContext != null) { + currentOrQueryObject = new DeadLetterJobQueryImpl(commandContext, jobServiceConfiguration); + } else { + currentOrQueryObject = new DeadLetterJobQueryImpl(commandExecutor, jobServiceConfiguration); + } + orQueryObjects.add(currentOrQueryObject); + return this; + } + + @Override + public DeadLetterJobQuery endOr() { + if (!inOrStatement) { + throw new FlowableException("endOr() can only be called after calling or()"); + } + inOrStatement = false; + currentOrQueryObject = null; return this; } @@ -590,4 +749,8 @@ public Date getDuedateHigherThanOrEqual() { public Date getDuedateLowerThanOrEqual() { return duedateLowerThanOrEqual; } + + public List getOrQueryObjects() { + return orQueryObjects; + } } diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/ExternalWorkerJobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/ExternalWorkerJobQueryImpl.java index 7aaa3add0ad..f4d3136edb1 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/ExternalWorkerJobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/ExternalWorkerJobQueryImpl.java @@ -14,10 +14,12 @@ package org.flowable.job.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -73,6 +75,10 @@ public class ExternalWorkerJobQueryImpl extends AbstractQuery orQueryObjects = new ArrayList<>(); + protected ExternalWorkerJobQueryImpl currentOrQueryObject; + protected boolean inOrStatement; + public ExternalWorkerJobQueryImpl() { } @@ -91,7 +97,11 @@ public ExternalWorkerJobQuery jobId(String jobId) { if (jobId == null) { throw new FlowableIllegalArgumentException("Provided job id is null"); } - this.id = jobId; + if (inOrStatement) { + this.currentOrQueryObject.id = jobId; + } else { + this.id = jobId; + } return this; } @@ -100,7 +110,11 @@ public ExternalWorkerJobQuery jobIds(Collection jobIds) { if (jobIds == null) { throw new FlowableIllegalArgumentException("Provided job id list is null"); } - this.jobIds = jobIds; + if (inOrStatement) { + this.currentOrQueryObject.jobIds = jobIds; + } else { + this.jobIds = jobIds; + } return this; } @@ -109,13 +123,21 @@ public ExternalWorkerJobQuery processInstanceId(String processInstanceId) { if (processInstanceId == null) { throw new FlowableIllegalArgumentException("Provided process instance id is null"); } - this.processInstanceId = processInstanceId; + if (inOrStatement) { + this.currentOrQueryObject.processInstanceId = processInstanceId; + } else { + this.processInstanceId = processInstanceId; + } return this; } @Override public ExternalWorkerJobQuery withoutProcessInstanceId() { - this.withoutProcessInstanceId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutProcessInstanceId = true; + } else { + this.withoutProcessInstanceId = true; + } return this; } @@ -124,7 +146,11 @@ public ExternalWorkerJobQuery processDefinitionId(String processDefinitionId) { if (processDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided process definition id is null"); } - this.processDefinitionId = processDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionId = processDefinitionId; + } else { + this.processDefinitionId = processDefinitionId; + } return this; } @@ -133,7 +159,11 @@ public ExternalWorkerJobQuery processDefinitionKey(String processDefinitionKey) if (processDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided process definition key is null"); } - this.processDefinitionKey = processDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionKey = processDefinitionKey; + } else { + this.processDefinitionKey = processDefinitionKey; + } return this; } @@ -142,7 +172,11 @@ public ExternalWorkerJobQuery category(String category) { if (category == null) { throw new FlowableIllegalArgumentException("Provided category is null"); } - this.category = category; + if (inOrStatement) { + this.currentOrQueryObject.category = category; + } else { + this.category = category; + } return this; } @@ -151,7 +185,11 @@ public ExternalWorkerJobQuery categoryLike(String categoryLike) { if (categoryLike == null) { throw new FlowableIllegalArgumentException("Provided categoryLike is null"); } - this.categoryLike = categoryLike; + if (inOrStatement) { + this.currentOrQueryObject.categoryLike = categoryLike; + } else { + this.categoryLike = categoryLike; + } return this; } @@ -160,7 +198,11 @@ public ExternalWorkerJobQuery elementId(String elementId) { if (elementId == null) { throw new FlowableIllegalArgumentException("Provided element id is null"); } - this.elementId = elementId; + if (inOrStatement) { + this.currentOrQueryObject.elementId = elementId; + } else { + this.elementId = elementId; + } return this; } @@ -169,7 +211,11 @@ public ExternalWorkerJobQuery elementName(String elementName) { if (elementName == null) { throw new FlowableIllegalArgumentException("Provided element name is null"); } - this.elementName = elementName; + if (inOrStatement) { + this.currentOrQueryObject.elementName = elementName; + } else { + this.elementName = elementName; + } return this; } @@ -178,13 +224,21 @@ public ExternalWorkerJobQueryImpl scopeId(String scopeId) { if (scopeId == null) { throw new FlowableIllegalArgumentException("Provided scope id is null"); } - this.scopeId = scopeId; + if (inOrStatement) { + this.currentOrQueryObject.scopeId = scopeId; + } else { + this.scopeId = scopeId; + } return this; } @Override public ExternalWorkerJobQuery withoutScopeId() { - this.withoutScopeId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeId = true; + } else { + this.withoutScopeId = true; + } return this; } @@ -193,7 +247,11 @@ public ExternalWorkerJobQuery subScopeId(String subScopeId) { if (subScopeId == null) { throw new FlowableIllegalArgumentException("Provided sub scope id is null"); } - this.subScopeId = subScopeId; + if (inOrStatement) { + this.currentOrQueryObject.subScopeId = subScopeId; + } else { + this.subScopeId = subScopeId; + } return this; } @@ -202,13 +260,21 @@ public ExternalWorkerJobQueryImpl scopeType(String scopeType) { if (scopeType == null) { throw new FlowableIllegalArgumentException("Provided scope type is null"); } - this.scopeType = scopeType; + if (inOrStatement) { + this.currentOrQueryObject.scopeType = scopeType; + } else { + this.scopeType = scopeType; + } return this; } @Override public ExternalWorkerJobQueryImpl withoutScopeType() { - this.withoutScopeType = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeType = true; + } else { + this.withoutScopeType = true; + } return this; } @@ -217,7 +283,11 @@ public ExternalWorkerJobQuery scopeDefinitionId(String scopeDefinitionId) { if (scopeDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided scope definitionid is null"); } - this.scopeDefinitionId = scopeDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.scopeDefinitionId = scopeDefinitionId; + } else { + this.scopeDefinitionId = scopeDefinitionId; + } return this; } @@ -246,7 +316,11 @@ public ExternalWorkerJobQuery caseDefinitionKey(String caseDefinitionKey) { if (caseDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided case definition key is null"); } - this.caseDefinitionKey = caseDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.caseDefinitionKey = caseDefinitionKey; + } else { + this.caseDefinitionKey = caseDefinitionKey; + } return this; } @@ -265,7 +339,11 @@ public ExternalWorkerJobQuery correlationId(String correlationId) { if (correlationId == null) { throw new FlowableIllegalArgumentException("Provided correlationId is null"); } - this.correlationId = correlationId; + if (inOrStatement) { + this.currentOrQueryObject.correlationId = correlationId; + } else { + this.correlationId = correlationId; + } return this; } @@ -274,7 +352,11 @@ public ExternalWorkerJobQuery executionId(String executionId) { if (executionId == null) { throw new FlowableIllegalArgumentException("Provided execution id is null"); } - this.executionId = executionId; + if (inOrStatement) { + this.currentOrQueryObject.executionId = executionId; + } else { + this.executionId = executionId; + } return this; } @@ -283,7 +365,11 @@ public ExternalWorkerJobQuery handlerType(String handlerType) { if (handlerType == null) { throw new FlowableIllegalArgumentException("Provided handlerType is null"); } - this.handlerType = handlerType; + if (inOrStatement) { + this.currentOrQueryObject.handlerType = handlerType; + } else { + this.handlerType = handlerType; + } return this; } @@ -292,7 +378,11 @@ public ExternalWorkerJobQuery handlerTypes(Collection handlerTypes) { if (handlerTypes == null) { throw new FlowableIllegalArgumentException("Provided handlerTypes are null"); } - this.handlerTypes = handlerTypes; + if (inOrStatement) { + this.currentOrQueryObject.handlerTypes = handlerTypes; + } else { + this.handlerTypes = handlerTypes; + } return this; } @@ -301,7 +391,11 @@ public ExternalWorkerJobQuery duedateHigherThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateHigherThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateHigherThan = date; + } else { + this.duedateHigherThan = date; + } return this; } @@ -310,13 +404,21 @@ public ExternalWorkerJobQuery duedateLowerThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateLowerThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateLowerThan = date; + } else { + this.duedateLowerThan = date; + } return this; } @Override public ExternalWorkerJobQuery withException() { - this.withException = true; + if (inOrStatement) { + this.currentOrQueryObject.withException = true; + } else { + this.withException = true; + } return this; } @@ -325,7 +427,11 @@ public ExternalWorkerJobQuery exceptionMessage(String exceptionMessage) { if (exceptionMessage == null) { throw new FlowableIllegalArgumentException("Provided exception message is null"); } - this.exceptionMessage = exceptionMessage; + if (inOrStatement) { + this.currentOrQueryObject.exceptionMessage = exceptionMessage; + } else { + this.exceptionMessage = exceptionMessage; + } return this; } @@ -334,7 +440,11 @@ public ExternalWorkerJobQuery jobTenantId(String tenantId) { if (tenantId == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantId = tenantId; + if (inOrStatement) { + this.currentOrQueryObject.tenantId = tenantId; + } else { + this.tenantId = tenantId; + } return this; } @@ -343,13 +453,21 @@ public ExternalWorkerJobQuery jobTenantIdLike(String tenantIdLike) { if (tenantIdLike == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantIdLike = tenantIdLike; + if (inOrStatement) { + this.currentOrQueryObject.tenantIdLike = tenantIdLike; + } else { + this.tenantIdLike = tenantIdLike; + } return this; } @Override public ExternalWorkerJobQuery jobWithoutTenantId() { - this.withoutTenantId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutTenantId = true; + } else { + this.withoutTenantId = true; + } return this; } @@ -359,27 +477,69 @@ public ExternalWorkerJobQuery forUserOrGroups(String userId, Collection throw new FlowableIllegalArgumentException("at least one of userId or groups must be provided"); } - this.authorizedUser = userId; - this.authorizedGroups = groups; + if (inOrStatement) { + this.currentOrQueryObject.authorizedUser = userId; + this.currentOrQueryObject.authorizedGroups = groups; + } else { + this.authorizedUser = userId; + this.authorizedGroups = groups; + } return this; } @Override public ExternalWorkerJobQuery lockOwner(String lockOwner) { - this.lockOwner = lockOwner; + if (inOrStatement) { + this.currentOrQueryObject.lockOwner = lockOwner; + } else { + this.lockOwner = lockOwner; + } return this; } @Override public ExternalWorkerJobQuery locked() { - this.onlyLocked = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyLocked = true; + } else { + this.onlyLocked = true; + } return this; } @Override public ExternalWorkerJobQuery unlocked() { - this.onlyUnlocked = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyUnlocked = true; + } else { + this.onlyUnlocked = true; + } + return this; + } + + @Override + public ExternalWorkerJobQuery or() { + if (inOrStatement) { + throw new FlowableException("the query is already in an or statement"); + } + inOrStatement = true; + if (commandContext != null) { + currentOrQueryObject = new ExternalWorkerJobQueryImpl(commandContext, jobServiceConfiguration); + } else { + currentOrQueryObject = new ExternalWorkerJobQueryImpl(commandExecutor, jobServiceConfiguration); + } + orQueryObjects.add(currentOrQueryObject); + return this; + } + + @Override + public ExternalWorkerJobQuery endOr() { + if (!inOrStatement) { + throw new FlowableException("endOr() can only be called after calling or()"); + } + inOrStatement = false; + currentOrQueryObject = null; return this; } @@ -574,4 +734,8 @@ public boolean isOnlyUnlocked() { return onlyUnlocked; } + public List getOrQueryObjects() { + return orQueryObjects; + } + } diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java index f02d4fbaf1f..546d28a1f80 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java @@ -14,10 +14,12 @@ package org.flowable.job.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.common.engine.impl.interceptor.CommandExecutor; @@ -51,6 +53,10 @@ public class HistoryJobQueryImpl extends AbstractQuery orQueryObjects = new ArrayList<>(); + protected HistoryJobQueryImpl currentOrQueryObject; + protected boolean inOrStatement; + public HistoryJobQueryImpl() { } @@ -69,7 +75,11 @@ public HistoryJobQuery jobId(String jobId) { if (jobId == null) { throw new FlowableIllegalArgumentException("Provided job id is null"); } - this.id = jobId; + if (inOrStatement) { + this.currentOrQueryObject.id = jobId; + } else { + this.id = jobId; + } return this; } @@ -78,7 +88,11 @@ public HistoryJobQuery handlerType(String handlerType) { if (handlerType == null) { throw new FlowableIllegalArgumentException("Provided handlerType is null"); } - this.handlerType = handlerType; + if (inOrStatement) { + this.currentOrQueryObject.handlerType = handlerType; + } else { + this.handlerType = handlerType; + } return this; } @@ -87,13 +101,21 @@ public HistoryJobQuery handlerTypes(Collection handlerTypes) { if (handlerTypes == null) { throw new FlowableIllegalArgumentException("Provided handlerTypes are null"); } - this.handlerTypes = handlerTypes; + if (inOrStatement) { + this.currentOrQueryObject.handlerTypes = handlerTypes; + } else { + this.handlerTypes = handlerTypes; + } return this; } @Override public HistoryJobQuery withException() { - this.withException = true; + if (inOrStatement) { + this.currentOrQueryObject.withException = true; + } else { + this.withException = true; + } return this; } @@ -102,7 +124,11 @@ public HistoryJobQuery exceptionMessage(String exceptionMessage) { if (exceptionMessage == null) { throw new FlowableIllegalArgumentException("Provided exception message is null"); } - this.exceptionMessage = exceptionMessage; + if (inOrStatement) { + this.currentOrQueryObject.exceptionMessage = exceptionMessage; + } else { + this.exceptionMessage = exceptionMessage; + } return this; } @@ -111,7 +137,11 @@ public HistoryJobQuery scopeType(String scopeType) { if (scopeType == null) { throw new FlowableIllegalArgumentException("Provided scope type is null"); } - this.scopeType = scopeType; + if (inOrStatement) { + this.currentOrQueryObject.scopeType = scopeType; + } else { + this.scopeType = scopeType; + } return this; } @@ -120,7 +150,11 @@ public HistoryJobQuery jobTenantId(String tenantId) { if (tenantId == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantId = tenantId; + if (inOrStatement) { + this.currentOrQueryObject.tenantId = tenantId; + } else { + this.tenantId = tenantId; + } return this; } @@ -129,37 +163,86 @@ public HistoryJobQuery jobTenantIdLike(String tenantIdLike) { if (tenantIdLike == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantIdLike = tenantIdLike; + if (inOrStatement) { + this.currentOrQueryObject.tenantIdLike = tenantIdLike; + } else { + this.tenantIdLike = tenantIdLike; + } return this; } @Override public HistoryJobQuery jobWithoutTenantId() { - this.withoutTenantId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutTenantId = true; + } else { + this.withoutTenantId = true; + } return this; } @Override public HistoryJobQuery lockOwner(String lockOwner) { - this.lockOwner = lockOwner; + if (inOrStatement) { + this.currentOrQueryObject.lockOwner = lockOwner; + } else { + this.lockOwner = lockOwner; + } return this; } @Override public HistoryJobQuery locked() { - this.onlyLocked = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyLocked = true; + } else { + this.onlyLocked = true; + } return this; } @Override public HistoryJobQuery unlocked() { - this.onlyUnlocked = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyUnlocked = true; + } else { + this.onlyUnlocked = true; + } return this; } @Override public HistoryJobQuery withoutScopeType() { - this.withoutScopeType = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeType = true; + } else { + this.withoutScopeType = true; + } + return this; + } + + @Override + public HistoryJobQuery or() { + if (inOrStatement) { + throw new FlowableException("the query is already in an or statement"); + } + inOrStatement = true; + if (commandContext != null) { + currentOrQueryObject = new HistoryJobQueryImpl(commandContext, jobServiceConfiguration); + } else { + currentOrQueryObject = new HistoryJobQueryImpl(commandExecutor, jobServiceConfiguration); + } + orQueryObjects.add(currentOrQueryObject); + return this; + } + + @Override + public HistoryJobQuery endOr() { + if (!inOrStatement) { + throw new FlowableException("endOr() can only be called after calling or()"); + } + inOrStatement = false; + currentOrQueryObject = null; return this; } @@ -254,4 +337,8 @@ public boolean isWithoutScopeType() { return withoutScopeType; } + public List getOrQueryObjects() { + return orQueryObjects; + } + } diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/JobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/JobQueryImpl.java index c20184d38c7..17cc7dc2f6e 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/JobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/JobQueryImpl.java @@ -14,10 +14,12 @@ package org.flowable.job.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -75,6 +77,10 @@ public class JobQueryImpl extends AbstractQuery implements JobQue protected boolean onlyLocked; protected boolean onlyUnlocked; + protected List orQueryObjects = new ArrayList<>(); + protected JobQueryImpl currentOrQueryObject; + protected boolean inOrStatement; + public JobQueryImpl() { } @@ -93,7 +99,11 @@ public JobQuery jobId(String jobId) { if (jobId == null) { throw new FlowableIllegalArgumentException("Provided job id is null"); } - this.id = jobId; + if (inOrStatement) { + this.currentOrQueryObject.id = jobId; + } else { + this.id = jobId; + } return this; } @@ -102,7 +112,11 @@ public JobQuery jobIds(Collection jobIds) { if (jobIds == null) { throw new FlowableIllegalArgumentException("Provided job id list is null"); } - this.jobIds = jobIds; + if (inOrStatement) { + this.currentOrQueryObject.jobIds = jobIds; + } else { + this.jobIds = jobIds; + } return this; } @@ -111,13 +125,21 @@ public JobQueryImpl processInstanceId(String processInstanceId) { if (processInstanceId == null) { throw new FlowableIllegalArgumentException("Provided process instance id is null"); } - this.processInstanceId = processInstanceId; + if (inOrStatement) { + this.currentOrQueryObject.processInstanceId = processInstanceId; + } else { + this.processInstanceId = processInstanceId; + } return this; } @Override public JobQuery withoutProcessInstanceId() { - this.withoutProcessInstanceId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutProcessInstanceId = true; + } else { + this.withoutProcessInstanceId = true; + } return this; } @@ -126,7 +148,11 @@ public JobQueryImpl processDefinitionId(String processDefinitionId) { if (processDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided process definition id is null"); } - this.processDefinitionId = processDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionId = processDefinitionId; + } else { + this.processDefinitionId = processDefinitionId; + } return this; } @@ -135,7 +161,11 @@ public JobQueryImpl processDefinitionKey(String processDefinitionKey) { if (processDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided process definition key is null"); } - this.processDefinitionKey = processDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionKey = processDefinitionKey; + } else { + this.processDefinitionKey = processDefinitionKey; + } return this; } @@ -144,7 +174,11 @@ public JobQueryImpl category(String category) { if (category == null) { throw new FlowableIllegalArgumentException("Provided category is null"); } - this.category = category; + if (inOrStatement) { + this.currentOrQueryObject.category = category; + } else { + this.category = category; + } return this; } @@ -153,7 +187,11 @@ public JobQueryImpl categoryLike(String categoryLike) { if (categoryLike == null) { throw new FlowableIllegalArgumentException("Provided categoryLike is null"); } - this.categoryLike = categoryLike; + if (inOrStatement) { + this.currentOrQueryObject.categoryLike = categoryLike; + } else { + this.categoryLike = categoryLike; + } return this; } @@ -162,7 +200,11 @@ public JobQueryImpl elementId(String elementId) { if (elementId == null) { throw new FlowableIllegalArgumentException("Provided element id is null"); } - this.elementId = elementId; + if (inOrStatement) { + this.currentOrQueryObject.elementId = elementId; + } else { + this.elementId = elementId; + } return this; } @@ -171,7 +213,11 @@ public JobQueryImpl elementName(String elementName) { if (elementName == null) { throw new FlowableIllegalArgumentException("Provided element name is null"); } - this.elementName = elementName; + if (inOrStatement) { + this.currentOrQueryObject.elementName = elementName; + } else { + this.elementName = elementName; + } return this; } @@ -180,13 +226,21 @@ public JobQueryImpl scopeId(String scopeId) { if (scopeId == null) { throw new FlowableIllegalArgumentException("Provided scope id is null"); } - this.scopeId = scopeId; + if (inOrStatement) { + this.currentOrQueryObject.scopeId = scopeId; + } else { + this.scopeId = scopeId; + } return this; } @Override public JobQuery withoutScopeId() { - this.withoutScopeId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeId = true; + } else { + this.withoutScopeId = true; + } return this; } @@ -195,7 +249,11 @@ public JobQueryImpl subScopeId(String subScopeId) { if (subScopeId == null) { throw new FlowableIllegalArgumentException("Provided sub scope id is null"); } - this.subScopeId = subScopeId; + if (inOrStatement) { + this.currentOrQueryObject.subScopeId = subScopeId; + } else { + this.subScopeId = subScopeId; + } return this; } @@ -204,7 +262,11 @@ public JobQueryImpl scopeType(String scopeType) { if (scopeType == null) { throw new FlowableIllegalArgumentException("Provided scope type is null"); } - this.scopeType = scopeType; + if (inOrStatement) { + this.currentOrQueryObject.scopeType = scopeType; + } else { + this.scopeType = scopeType; + } return this; } @@ -213,7 +275,11 @@ public JobQueryImpl scopeDefinitionId(String scopeDefinitionId) { if (scopeDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided scope definitionid is null"); } - this.scopeDefinitionId = scopeDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.scopeDefinitionId = scopeDefinitionId; + } else { + this.scopeDefinitionId = scopeDefinitionId; + } return this; } @@ -242,7 +308,11 @@ public JobQueryImpl caseDefinitionKey(String caseDefinitionKey) { if (caseDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided case definition id is null"); } - this.caseDefinitionKey = caseDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.caseDefinitionKey = caseDefinitionKey; + } else { + this.caseDefinitionKey = caseDefinitionKey; + } return this; } @@ -261,7 +331,11 @@ public JobQueryImpl correlationId(String correlationId) { if (correlationId == null) { throw new FlowableIllegalArgumentException("Provided correlationId is null"); } - this.correlationId = correlationId; + if (inOrStatement) { + this.currentOrQueryObject.correlationId = correlationId; + } else { + this.correlationId = correlationId; + } return this; } @@ -270,7 +344,11 @@ public JobQueryImpl executionId(String executionId) { if (executionId == null) { throw new FlowableIllegalArgumentException("Provided execution id is null"); } - this.executionId = executionId; + if (inOrStatement) { + this.currentOrQueryObject.executionId = executionId; + } else { + this.executionId = executionId; + } return this; } @@ -279,7 +357,11 @@ public JobQueryImpl handlerType(String handlerType) { if (handlerType == null) { throw new FlowableIllegalArgumentException("Provided handlerType is null"); } - this.handlerType = handlerType; + if (inOrStatement) { + this.currentOrQueryObject.handlerType = handlerType; + } else { + this.handlerType = handlerType; + } return this; } @@ -288,7 +370,11 @@ public JobQuery handlerTypes(Collection handlerTypes) { if (handlerTypes == null) { throw new FlowableIllegalArgumentException("Provided handlerTypes are null"); } - this.handlerTypes = handlerTypes; + if (inOrStatement) { + this.currentOrQueryObject.handlerTypes = handlerTypes; + } else { + this.handlerTypes = handlerTypes; + } return this; } @@ -297,7 +383,11 @@ public JobQuery timers() { if (onlyMessages) { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyMessages() in the same query"); } - this.onlyTimers = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyTimers = true; + } else { + this.onlyTimers = true; + } return this; } @@ -306,7 +396,11 @@ public JobQuery messages() { if (onlyTimers) { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyMessages() in the same query"); } - this.onlyMessages = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyMessages = true; + } else { + this.onlyMessages = true; + } return this; } @@ -315,7 +409,11 @@ public JobQuery duedateHigherThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateHigherThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateHigherThan = date; + } else { + this.duedateHigherThan = date; + } return this; } @@ -324,13 +422,21 @@ public JobQuery duedateLowerThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateLowerThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateLowerThan = date; + } else { + this.duedateLowerThan = date; + } return this; } @Override public JobQuery withException() { - this.withException = true; + if (inOrStatement) { + this.currentOrQueryObject.withException = true; + } else { + this.withException = true; + } return this; } @@ -339,7 +445,11 @@ public JobQuery exceptionMessage(String exceptionMessage) { if (exceptionMessage == null) { throw new FlowableIllegalArgumentException("Provided exception message is null"); } - this.exceptionMessage = exceptionMessage; + if (inOrStatement) { + this.currentOrQueryObject.exceptionMessage = exceptionMessage; + } else { + this.exceptionMessage = exceptionMessage; + } return this; } @@ -348,7 +458,11 @@ public JobQuery jobTenantId(String tenantId) { if (tenantId == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantId = tenantId; + if (inOrStatement) { + this.currentOrQueryObject.tenantId = tenantId; + } else { + this.tenantId = tenantId; + } return this; } @@ -357,37 +471,86 @@ public JobQuery jobTenantIdLike(String tenantIdLike) { if (tenantIdLike == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantIdLike = tenantIdLike; + if (inOrStatement) { + this.currentOrQueryObject.tenantIdLike = tenantIdLike; + } else { + this.tenantIdLike = tenantIdLike; + } return this; } @Override public JobQuery jobWithoutTenantId() { - this.withoutTenantId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutTenantId = true; + } else { + this.withoutTenantId = true; + } return this; } @Override public JobQuery lockOwner(String lockOwner) { - this.lockOwner = lockOwner; + if (inOrStatement) { + this.currentOrQueryObject.lockOwner = lockOwner; + } else { + this.lockOwner = lockOwner; + } return this; } @Override public JobQuery locked() { - this.onlyLocked = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyLocked = true; + } else { + this.onlyLocked = true; + } return this; } @Override public JobQuery unlocked() { - this.onlyUnlocked = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyUnlocked = true; + } else { + this.onlyUnlocked = true; + } return this; } @Override public JobQuery withoutScopeType() { - this.withoutScopeType = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeType = true; + } else { + this.withoutScopeType = true; + } + return this; + } + + @Override + public JobQuery or() { + if (inOrStatement) { + throw new FlowableException("the query is already in an or statement"); + } + inOrStatement = true; + if (commandContext != null) { + currentOrQueryObject = new JobQueryImpl(commandContext, jobServiceConfiguration); + } else { + currentOrQueryObject = new JobQueryImpl(commandExecutor, jobServiceConfiguration); + } + orQueryObjects.add(currentOrQueryObject); + return this; + } + + @Override + public JobQuery endOr() { + if (!inOrStatement) { + throw new FlowableException("endOr() can only be called after calling or()"); + } + inOrStatement = false; + currentOrQueryObject = null; return this; } @@ -581,4 +744,8 @@ public boolean isOnlyUnlocked() { public boolean isWithoutScopeType() { return withoutScopeType; } + + public List getOrQueryObjects() { + return orQueryObjects; + } } diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java index f7abfffeb2e..4594669d89f 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/SuspendedJobQueryImpl.java @@ -14,10 +14,12 @@ package org.flowable.job.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -75,6 +77,10 @@ public class SuspendedJobQueryImpl extends AbstractQuery protected boolean retriesLeft; protected boolean noRetriesLeft; + protected List orQueryObjects = new ArrayList<>(); + protected SuspendedJobQueryImpl currentOrQueryObject; + protected boolean inOrStatement; + public SuspendedJobQueryImpl() { } @@ -93,7 +99,11 @@ public SuspendedJobQueryImpl jobId(String jobId) { if (jobId == null) { throw new FlowableIllegalArgumentException("Provided job id is null"); } - this.id = jobId; + if (inOrStatement) { + this.currentOrQueryObject.id = jobId; + } else { + this.id = jobId; + } return this; } @@ -102,7 +112,11 @@ public SuspendedJobQuery jobIds(Collection jobIds) { if (jobIds == null) { throw new FlowableIllegalArgumentException("Provided job id list is null"); } - this.jobIds = jobIds; + if (inOrStatement) { + this.currentOrQueryObject.jobIds = jobIds; + } else { + this.jobIds = jobIds; + } return this; } @@ -111,13 +125,21 @@ public SuspendedJobQueryImpl processInstanceId(String processInstanceId) { if (processInstanceId == null) { throw new FlowableIllegalArgumentException("Provided process instance id is null"); } - this.processInstanceId = processInstanceId; + if (inOrStatement) { + this.currentOrQueryObject.processInstanceId = processInstanceId; + } else { + this.processInstanceId = processInstanceId; + } return this; } @Override public SuspendedJobQueryImpl withoutProcessInstanceId() { - this.withoutProcessInstanceId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutProcessInstanceId = true; + } else { + this.withoutProcessInstanceId = true; + } return this; } @@ -126,7 +148,11 @@ public SuspendedJobQueryImpl processDefinitionId(String processDefinitionId) { if (processDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided process definition id is null"); } - this.processDefinitionId = processDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionId = processDefinitionId; + } else { + this.processDefinitionId = processDefinitionId; + } return this; } @@ -135,7 +161,11 @@ public SuspendedJobQueryImpl processDefinitionKey(String processDefinitionKey) { if (processDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided process definition key is null"); } - this.processDefinitionKey = processDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionKey = processDefinitionKey; + } else { + this.processDefinitionKey = processDefinitionKey; + } return this; } @@ -144,7 +174,11 @@ public SuspendedJobQueryImpl category(String category) { if (category == null) { throw new FlowableIllegalArgumentException("Provided category is null"); } - this.category = category; + if (inOrStatement) { + this.currentOrQueryObject.category = category; + } else { + this.category = category; + } return this; } @@ -153,7 +187,11 @@ public SuspendedJobQueryImpl categoryLike(String categoryLike) { if (categoryLike == null) { throw new FlowableIllegalArgumentException("Provided categoryLike is null"); } - this.categoryLike = categoryLike; + if (inOrStatement) { + this.currentOrQueryObject.categoryLike = categoryLike; + } else { + this.categoryLike = categoryLike; + } return this; } @@ -162,7 +200,11 @@ public SuspendedJobQueryImpl elementId(String elementId) { if (elementId == null) { throw new FlowableIllegalArgumentException("Provided element id is null"); } - this.elementId = elementId; + if (inOrStatement) { + this.currentOrQueryObject.elementId = elementId; + } else { + this.elementId = elementId; + } return this; } @@ -171,7 +213,11 @@ public SuspendedJobQueryImpl elementName(String elementName) { if (elementName == null) { throw new FlowableIllegalArgumentException("Provided element name is null"); } - this.elementName = elementName; + if (inOrStatement) { + this.currentOrQueryObject.elementName = elementName; + } else { + this.elementName = elementName; + } return this; } @@ -180,13 +226,21 @@ public SuspendedJobQueryImpl scopeId(String scopeId) { if (scopeId == null) { throw new FlowableIllegalArgumentException("Provided scope id is null"); } - this.scopeId = scopeId; + if (inOrStatement) { + this.currentOrQueryObject.scopeId = scopeId; + } else { + this.scopeId = scopeId; + } return this; } @Override public SuspendedJobQueryImpl withoutScopeId() { - this.withoutScopeId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeId = true; + } else { + this.withoutScopeId = true; + } return this; } @@ -195,7 +249,11 @@ public SuspendedJobQueryImpl subScopeId(String subScopeId) { if (subScopeId == null) { throw new FlowableIllegalArgumentException("Provided sub scope id is null"); } - this.subScopeId = subScopeId; + if (inOrStatement) { + this.currentOrQueryObject.subScopeId = subScopeId; + } else { + this.subScopeId = subScopeId; + } return this; } @@ -204,13 +262,21 @@ public SuspendedJobQueryImpl scopeType(String scopeType) { if (scopeType == null) { throw new FlowableIllegalArgumentException("Provided scope type is null"); } - this.scopeType = scopeType; + if (inOrStatement) { + this.currentOrQueryObject.scopeType = scopeType; + } else { + this.scopeType = scopeType; + } return this; } @Override public SuspendedJobQueryImpl withoutScopeType() { - this.withoutScopeType = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeType = true; + } else { + this.withoutScopeType = true; + } return this; } @@ -219,7 +285,11 @@ public SuspendedJobQueryImpl scopeDefinitionId(String scopeDefinitionId) { if (scopeDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided scope definitionid is null"); } - this.scopeDefinitionId = scopeDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.scopeDefinitionId = scopeDefinitionId; + } else { + this.scopeDefinitionId = scopeDefinitionId; + } return this; } @@ -248,7 +318,11 @@ public SuspendedJobQueryImpl caseDefinitionKey(String caseDefinitionKey) { if (caseDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided case definition key is null"); } - this.caseDefinitionKey = caseDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.caseDefinitionKey = caseDefinitionKey; + } else { + this.caseDefinitionKey = caseDefinitionKey; + } return this; } @@ -267,7 +341,11 @@ public SuspendedJobQueryImpl correlationId(String correlationId) { if (correlationId == null) { throw new FlowableIllegalArgumentException("Provided correlationId is null"); } - this.correlationId = correlationId; + if (inOrStatement) { + this.currentOrQueryObject.correlationId = correlationId; + } else { + this.correlationId = correlationId; + } return this; } @@ -276,7 +354,11 @@ public SuspendedJobQueryImpl executionId(String executionId) { if (executionId == null) { throw new FlowableIllegalArgumentException("Provided execution id is null"); } - this.executionId = executionId; + if (inOrStatement) { + this.currentOrQueryObject.executionId = executionId; + } else { + this.executionId = executionId; + } return this; } @@ -285,7 +367,11 @@ public SuspendedJobQueryImpl handlerType(String handlerType) { if (handlerType == null) { throw new FlowableIllegalArgumentException("Provided handlerType is null"); } - this.handlerType = handlerType; + if (inOrStatement) { + this.currentOrQueryObject.handlerType = handlerType; + } else { + this.handlerType = handlerType; + } return this; } @@ -294,19 +380,31 @@ public SuspendedJobQueryImpl handlerTypes(Collection handlerTypes) { if (handlerTypes == null) { throw new FlowableIllegalArgumentException("Provided handlerTypes are null"); } - this.handlerTypes = handlerTypes; + if (inOrStatement) { + this.currentOrQueryObject.handlerTypes = handlerTypes; + } else { + this.handlerTypes = handlerTypes; + } return this; } @Override public SuspendedJobQueryImpl withRetriesLeft() { - retriesLeft = true; + if (inOrStatement) { + this.currentOrQueryObject.retriesLeft = true; + } else { + retriesLeft = true; + } return this; } @Override public SuspendedJobQueryImpl executable() { - executable = true; + if (inOrStatement) { + this.currentOrQueryObject.executable = true; + } else { + executable = true; + } return this; } @@ -319,7 +417,11 @@ public SuspendedJobQueryImpl timers() { if (onlyExternalWorkers) { throw new FlowableIllegalArgumentException("Cannot combine onlyExternalWorkers() with onlyMessages() in the same query"); } - this.onlyTimers = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyTimers = true; + } else { + this.onlyTimers = true; + } return this; } @@ -333,7 +435,11 @@ public SuspendedJobQueryImpl messages() { throw new FlowableIllegalArgumentException("Cannot combine onlyExternalWorkers() with onlyMessages() in the same query"); } - this.onlyMessages = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyMessages = true; + } else { + this.onlyMessages = true; + } return this; } @@ -347,7 +453,11 @@ public SuspendedJobQueryImpl externalWorkers() { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyExternalWorkers() in the same query"); } - this.onlyExternalWorkers = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyExternalWorkers = true; + } else { + this.onlyExternalWorkers = true; + } return this; } @@ -356,7 +466,11 @@ public SuspendedJobQueryImpl duedateHigherThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateHigherThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateHigherThan = date; + } else { + this.duedateHigherThan = date; + } return this; } @@ -365,7 +479,11 @@ public SuspendedJobQueryImpl duedateLowerThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateLowerThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateLowerThan = date; + } else { + this.duedateLowerThan = date; + } return this; } @@ -377,7 +495,11 @@ public SuspendedJobQueryImpl duedateHigherThenOrEquals(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateHigherThanOrEqual = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateHigherThanOrEqual = date; + } else { + this.duedateHigherThanOrEqual = date; + } return this; } @@ -389,19 +511,31 @@ public SuspendedJobQueryImpl duedateLowerThenOrEquals(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateLowerThanOrEqual = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateLowerThanOrEqual = date; + } else { + this.duedateLowerThanOrEqual = date; + } return this; } @Override public SuspendedJobQueryImpl noRetriesLeft() { - noRetriesLeft = true; + if (inOrStatement) { + this.currentOrQueryObject.noRetriesLeft = true; + } else { + noRetriesLeft = true; + } return this; } @Override public SuspendedJobQueryImpl withException() { - this.withException = true; + if (inOrStatement) { + this.currentOrQueryObject.withException = true; + } else { + this.withException = true; + } return this; } @@ -410,7 +544,11 @@ public SuspendedJobQueryImpl exceptionMessage(String exceptionMessage) { if (exceptionMessage == null) { throw new FlowableIllegalArgumentException("Provided exception message is null"); } - this.exceptionMessage = exceptionMessage; + if (inOrStatement) { + this.currentOrQueryObject.exceptionMessage = exceptionMessage; + } else { + this.exceptionMessage = exceptionMessage; + } return this; } @@ -419,7 +557,11 @@ public SuspendedJobQueryImpl jobTenantId(String tenantId) { if (tenantId == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantId = tenantId; + if (inOrStatement) { + this.currentOrQueryObject.tenantId = tenantId; + } else { + this.tenantId = tenantId; + } return this; } @@ -428,13 +570,46 @@ public SuspendedJobQueryImpl jobTenantIdLike(String tenantIdLike) { if (tenantIdLike == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantIdLike = tenantIdLike; + if (inOrStatement) { + this.currentOrQueryObject.tenantIdLike = tenantIdLike; + } else { + this.tenantIdLike = tenantIdLike; + } return this; } @Override public SuspendedJobQueryImpl jobWithoutTenantId() { - this.withoutTenantId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutTenantId = true; + } else { + this.withoutTenantId = true; + } + return this; + } + + @Override + public SuspendedJobQuery or() { + if (inOrStatement) { + throw new FlowableException("the query is already in an or statement"); + } + inOrStatement = true; + if (commandContext != null) { + currentOrQueryObject = new SuspendedJobQueryImpl(commandContext, jobServiceConfiguration); + } else { + currentOrQueryObject = new SuspendedJobQueryImpl(commandExecutor, jobServiceConfiguration); + } + orQueryObjects.add(currentOrQueryObject); + return this; + } + + @Override + public SuspendedJobQuery endOr() { + if (!inOrStatement) { + throw new FlowableException("endOr() can only be called after calling or()"); + } + inOrStatement = false; + currentOrQueryObject = null; return this; } @@ -641,4 +816,8 @@ public boolean isRetriesLeft() { return retriesLeft; } + public List getOrQueryObjects() { + return orQueryObjects; + } + } diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java index ded9fa4b98e..70df4c0399f 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java @@ -14,10 +14,12 @@ package org.flowable.job.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -71,6 +73,10 @@ public class TimerJobQueryImpl extends AbstractQuery impleme protected String tenantIdLike; protected boolean withoutTenantId; + protected List orQueryObjects = new ArrayList<>(); + protected TimerJobQueryImpl currentOrQueryObject; + protected boolean inOrStatement; + public TimerJobQueryImpl() { } @@ -89,7 +95,11 @@ public TimerJobQueryImpl jobId(String jobId) { if (jobId == null) { throw new FlowableIllegalArgumentException("Provided job id is null"); } - this.id = jobId; + if (inOrStatement) { + this.currentOrQueryObject.id = jobId; + } else { + this.id = jobId; + } return this; } @@ -98,7 +108,11 @@ public TimerJobQuery jobIds(Collection jobIds) { if (jobIds == null) { throw new FlowableIllegalArgumentException("Provided job id list is null"); } - this.jobIds = jobIds; + if (inOrStatement) { + this.currentOrQueryObject.jobIds = jobIds; + } else { + this.jobIds = jobIds; + } return this; } @@ -107,13 +121,21 @@ public TimerJobQueryImpl processInstanceId(String processInstanceId) { if (processInstanceId == null) { throw new FlowableIllegalArgumentException("Provided process instance id is null"); } - this.processInstanceId = processInstanceId; + if (inOrStatement) { + this.currentOrQueryObject.processInstanceId = processInstanceId; + } else { + this.processInstanceId = processInstanceId; + } return this; } @Override public TimerJobQueryImpl withoutProcessInstanceId() { - this.withoutProcessInstanceId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutProcessInstanceId = true; + } else { + this.withoutProcessInstanceId = true; + } return this; } @@ -122,7 +144,11 @@ public TimerJobQueryImpl processDefinitionId(String processDefinitionId) { if (processDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided process definition id is null"); } - this.processDefinitionId = processDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionId = processDefinitionId; + } else { + this.processDefinitionId = processDefinitionId; + } return this; } @@ -131,7 +157,11 @@ public TimerJobQueryImpl processDefinitionKey(String processDefinitionKey) { if (processDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided process definition key is null"); } - this.processDefinitionKey = processDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.processDefinitionKey = processDefinitionKey; + } else { + this.processDefinitionKey = processDefinitionKey; + } return this; } @@ -140,7 +170,11 @@ public TimerJobQueryImpl category(String category) { if (category == null) { throw new FlowableIllegalArgumentException("Provided category is null"); } - this.category = category; + if (inOrStatement) { + this.currentOrQueryObject.category = category; + } else { + this.category = category; + } return this; } @@ -149,7 +183,11 @@ public TimerJobQueryImpl categoryLike(String categoryLike) { if (categoryLike == null) { throw new FlowableIllegalArgumentException("Provided categoryLike is null"); } - this.categoryLike = categoryLike; + if (inOrStatement) { + this.currentOrQueryObject.categoryLike = categoryLike; + } else { + this.categoryLike = categoryLike; + } return this; } @@ -158,7 +196,11 @@ public TimerJobQueryImpl elementId(String elementId) { if (elementId == null) { throw new FlowableIllegalArgumentException("Provided element id is null"); } - this.elementId = elementId; + if (inOrStatement) { + this.currentOrQueryObject.elementId = elementId; + } else { + this.elementId = elementId; + } return this; } @@ -167,7 +209,11 @@ public TimerJobQueryImpl elementName(String elementName) { if (elementName == null) { throw new FlowableIllegalArgumentException("Provided element name is null"); } - this.elementName = elementName; + if (inOrStatement) { + this.currentOrQueryObject.elementName = elementName; + } else { + this.elementName = elementName; + } return this; } @@ -176,13 +222,21 @@ public TimerJobQueryImpl scopeId(String scopeId) { if (scopeId == null) { throw new FlowableIllegalArgumentException("Provided scope id is null"); } - this.scopeId = scopeId; + if (inOrStatement) { + this.currentOrQueryObject.scopeId = scopeId; + } else { + this.scopeId = scopeId; + } return this; } @Override public TimerJobQueryImpl withoutScopeId() { - this.withoutScopeId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeId = true; + } else { + this.withoutScopeId = true; + } return this; } @@ -191,7 +245,11 @@ public TimerJobQueryImpl subScopeId(String subScopeId) { if (subScopeId == null) { throw new FlowableIllegalArgumentException("Provided sub scope id is null"); } - this.subScopeId = subScopeId; + if (inOrStatement) { + this.currentOrQueryObject.subScopeId = subScopeId; + } else { + this.subScopeId = subScopeId; + } return this; } @@ -200,13 +258,21 @@ public TimerJobQueryImpl scopeType(String scopeType) { if (scopeType == null) { throw new FlowableIllegalArgumentException("Provided scope type is null"); } - this.scopeType = scopeType; + if (inOrStatement) { + this.currentOrQueryObject.scopeType = scopeType; + } else { + this.scopeType = scopeType; + } return this; } @Override public TimerJobQueryImpl withoutScopeType() { - this.withoutScopeType = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutScopeType = true; + } else { + this.withoutScopeType = true; + } return this; } @@ -215,7 +281,11 @@ public TimerJobQueryImpl scopeDefinitionId(String scopeDefinitionId) { if (scopeDefinitionId == null) { throw new FlowableIllegalArgumentException("Provided scope definitionid is null"); } - this.scopeDefinitionId = scopeDefinitionId; + if (inOrStatement) { + this.currentOrQueryObject.scopeDefinitionId = scopeDefinitionId; + } else { + this.scopeDefinitionId = scopeDefinitionId; + } return this; } @@ -244,7 +314,11 @@ public TimerJobQueryImpl caseDefinitionKey(String caseDefinitionKey) { if (caseDefinitionKey == null) { throw new FlowableIllegalArgumentException("Provided case definition key is null"); } - this.caseDefinitionKey = caseDefinitionKey; + if (inOrStatement) { + this.currentOrQueryObject.caseDefinitionKey = caseDefinitionKey; + } else { + this.caseDefinitionKey = caseDefinitionKey; + } return this; } @@ -263,7 +337,11 @@ public TimerJobQueryImpl correlationId(String correlationId) { if (correlationId == null) { throw new FlowableIllegalArgumentException("Provided correlationId is null"); } - this.correlationId = correlationId; + if (inOrStatement) { + this.currentOrQueryObject.correlationId = correlationId; + } else { + this.correlationId = correlationId; + } return this; } @@ -272,7 +350,11 @@ public TimerJobQueryImpl executionId(String executionId) { if (executionId == null) { throw new FlowableIllegalArgumentException("Provided execution id is null"); } - this.executionId = executionId; + if (inOrStatement) { + this.currentOrQueryObject.executionId = executionId; + } else { + this.executionId = executionId; + } return this; } @@ -281,7 +363,11 @@ public TimerJobQueryImpl handlerType(String handlerType) { if (handlerType == null) { throw new FlowableIllegalArgumentException("Provided handlerType is null"); } - this.handlerType = handlerType; + if (inOrStatement) { + this.currentOrQueryObject.handlerType = handlerType; + } else { + this.handlerType = handlerType; + } return this; } @@ -290,13 +376,21 @@ public TimerJobQuery handlerTypes(Collection handlerTypes) { if (handlerTypes == null) { throw new FlowableIllegalArgumentException("Provided handlerTypes are null"); } - this.handlerTypes = handlerTypes; + if (inOrStatement) { + this.currentOrQueryObject.handlerTypes = handlerTypes; + } else { + this.handlerTypes = handlerTypes; + } return this; } @Override public TimerJobQueryImpl executable() { - executable = true; + if (inOrStatement) { + this.currentOrQueryObject.executable = true; + } else { + this.executable = true; + } return this; } @@ -305,7 +399,11 @@ public TimerJobQueryImpl timers() { if (onlyMessages) { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyMessages() in the same query"); } - this.onlyTimers = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyTimers = true; + } else { + this.onlyTimers = true; + } return this; } @@ -314,7 +412,11 @@ public TimerJobQueryImpl messages() { if (onlyTimers) { throw new FlowableIllegalArgumentException("Cannot combine onlyTimers() with onlyMessages() in the same query"); } - this.onlyMessages = true; + if (inOrStatement) { + this.currentOrQueryObject.onlyMessages = true; + } else { + this.onlyMessages = true; + } return this; } @@ -323,7 +425,11 @@ public TimerJobQueryImpl duedateHigherThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateHigherThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateHigherThan = date; + } else { + this.duedateHigherThan = date; + } return this; } @@ -332,13 +438,21 @@ public TimerJobQueryImpl duedateLowerThan(Date date) { if (date == null) { throw new FlowableIllegalArgumentException("Provided date is null"); } - this.duedateLowerThan = date; + if (inOrStatement) { + this.currentOrQueryObject.duedateLowerThan = date; + } else { + this.duedateLowerThan = date; + } return this; } @Override public TimerJobQueryImpl withException() { - this.withException = true; + if (inOrStatement) { + this.currentOrQueryObject.withException = true; + } else { + this.withException = true; + } return this; } @@ -347,7 +461,11 @@ public TimerJobQueryImpl exceptionMessage(String exceptionMessage) { if (exceptionMessage == null) { throw new FlowableIllegalArgumentException("Provided exception message is null"); } - this.exceptionMessage = exceptionMessage; + if (inOrStatement) { + this.currentOrQueryObject.exceptionMessage = exceptionMessage; + } else { + this.exceptionMessage = exceptionMessage; + } return this; } @@ -356,7 +474,11 @@ public TimerJobQueryImpl jobTenantId(String tenantId) { if (tenantId == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantId = tenantId; + if (inOrStatement) { + this.currentOrQueryObject.tenantId = tenantId; + } else { + this.tenantId = tenantId; + } return this; } @@ -365,13 +487,46 @@ public TimerJobQueryImpl jobTenantIdLike(String tenantIdLike) { if (tenantIdLike == null) { throw new FlowableIllegalArgumentException("Provided tenant id is null"); } - this.tenantIdLike = tenantIdLike; + if (inOrStatement) { + this.currentOrQueryObject.tenantIdLike = tenantIdLike; + } else { + this.tenantIdLike = tenantIdLike; + } return this; } @Override public TimerJobQueryImpl jobWithoutTenantId() { - this.withoutTenantId = true; + if (inOrStatement) { + this.currentOrQueryObject.withoutTenantId = true; + } else { + this.withoutTenantId = true; + } + return this; + } + + @Override + public TimerJobQuery or() { + if (inOrStatement) { + throw new FlowableException("the query is already in an or statement"); + } + inOrStatement = true; + if (commandContext != null) { + currentOrQueryObject = new TimerJobQueryImpl(commandContext, jobServiceConfiguration); + } else { + currentOrQueryObject = new TimerJobQueryImpl(commandExecutor, jobServiceConfiguration); + } + orQueryObjects.add(currentOrQueryObject); + return this; + } + + @Override + public TimerJobQuery endOr() { + if (!inOrStatement) { + throw new FlowableException("endOr() can only be called after calling or()"); + } + inOrStatement = false; + currentOrQueryObject = null; return this; } @@ -561,4 +716,8 @@ public Date getDuedateLowerThanOrEqual() { public boolean isExecutable() { return executable; } + + public List getOrQueryObjects() { + return orQueryObjects; + } } diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml index 7a920757919..d1f38ccc8d4 100644 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml @@ -350,6 +350,121 @@ and (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + and + + + RES.ID_ = #{orQueryObject.id, jdbcType=NVARCHAR} + + + or RES.ID_ IN + + #{jobId, jdbcType=NVARCHAR} + + + + or RES.PROCESS_INSTANCE_ID_ = #{orQueryObject.processInstanceId, jdbcType=NVARCHAR} + + + or RES.PROCESS_INSTANCE_ID_ IS NULL + + + or RES.EXECUTION_ID_ = #{orQueryObject.executionId, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ = #{orQueryObject.handlerType, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ in + + #{handlerType, jdbcType=NVARCHAR} + + + + or RES.PROC_DEF_ID_ = #{orQueryObject.processDefinitionId, jdbcType=NVARCHAR} + + + or RES.PROC_DEF_ID_ IN (select DEF.ID_ from ${prefix}ACT_RE_PROCDEF DEF where DEF.KEY_ = #{orQueryObject.processDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CATEGORY_ = #{orQueryObject.category, jdbcType=VARCHAR} + + + or RES.CATEGORY_ like #{orQueryObject.categoryLike, jdbcType=VARCHAR}${wildcardEscapeClause} + + + or RES.ELEMENT_ID_ = #{orQueryObject.elementId, jdbcType=NVARCHAR} + + + or RES.ELEMENT_NAME_ = #{orQueryObject.elementName, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ = #{orQueryObject.scopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ IS NULL + + + or RES.SUB_SCOPE_ID_ = #{orQueryObject.subScopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_TYPE_ = #{orQueryObject.scopeType, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ = #{orQueryObject.scopeDefinitionId, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ IN (select DEF.ID_ from ${prefix}ACT_CMMN_CASEDEF DEF where DEF.KEY_ = #{orQueryObject.caseDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CORRELATION_ID_ = #{orQueryObject.correlationId, jdbcType=NVARCHAR} + + + or (RES.DUEDATE_ is null or RES.DUEDATE_ <= #{orQueryObject.now, jdbcType=TIMESTAMP}) + + + or RES.TYPE_ = 'timer' + + + or RES.TYPE_ = 'message' + + + or RES.TYPE_ = 'externalWorker' + + + or RES.DUEDATE_ > #{orQueryObject.duedateHigherThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ < #{orQueryObject.duedateLowerThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ >= #{orQueryObject.duedateHigherThanOrEqual, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ <= #{orQueryObject.duedateLowerThanOrEqual, jdbcType=TIMESTAMP} + + + or (RES.EXCEPTION_MSG_ is not null or RES.EXCEPTION_STACK_ID_ is not null) + + + or RES.EXCEPTION_MSG_ = #{orQueryObject.exceptionMessage, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ = #{orQueryObject.tenantId, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ like #{orQueryObject.tenantIdLike, jdbcType=NVARCHAR}${wildcardEscapeClause} + + + or (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null) + + + or (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + + diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/ExternalWorkerJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/ExternalWorkerJob.xml index 6f1cda84f3c..ac1cfacb356 100755 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/ExternalWorkerJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/ExternalWorkerJob.xml @@ -293,6 +293,134 @@ ) + + + and + + + RES.ID_ = #{orQueryObject.id, jdbcType=NVARCHAR} + + + or RES.ID_ IN + + #{jobId, jdbcType=NVARCHAR} + + + + or RES.PROCESS_INSTANCE_ID_ = #{orQueryObject.processInstanceId, jdbcType=NVARCHAR} + + + or RES.PROCESS_INSTANCE_ID_ IS NULL + + + or RES.EXECUTION_ID_ = #{orQueryObject.executionId, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ = #{orQueryObject.handlerType, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ in + + #{handlerType, jdbcType=NVARCHAR} + + + + or RES.PROC_DEF_ID_ = #{orQueryObject.processDefinitionId, jdbcType=NVARCHAR} + + + or RES.PROC_DEF_ID_ IN (select DEF.ID_ from ${prefix}ACT_RE_PROCDEF DEF where DEF.KEY_ = #{orQueryObject.processDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CATEGORY_ = #{orQueryObject.category, jdbcType=VARCHAR} + + + or RES.CATEGORY_ like #{orQueryObject.categoryLike, jdbcType=VARCHAR}${wildcardEscapeClause} + + + or RES.ELEMENT_ID_ = #{orQueryObject.elementId, jdbcType=NVARCHAR} + + + or RES.ELEMENT_NAME_ = #{orQueryObject.elementName, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ = #{orQueryObject.scopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ IS NULL + + + or RES.SUB_SCOPE_ID_ = #{orQueryObject.subScopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_TYPE_ = #{orQueryObject.scopeType, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ = #{orQueryObject.scopeDefinitionId, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ IN (select DEF.ID_ from ${prefix}ACT_CMMN_CASEDEF DEF where DEF.KEY_ = #{orQueryObject.caseDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CORRELATION_ID_ = #{orQueryObject.correlationId, jdbcType=NVARCHAR} + + + or RES.DUEDATE_ > #{orQueryObject.duedateHigherThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ < #{orQueryObject.duedateLowerThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ >= #{orQueryObject.duedateHigherThanOrEqual, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ <= #{orQueryObject.duedateLowerThanOrEqual, jdbcType=TIMESTAMP} + + + or (RES.EXCEPTION_MSG_ is not null or RES.EXCEPTION_STACK_ID_ is not null) + + + or RES.EXCEPTION_MSG_ = #{orQueryObject.exceptionMessage, jdbcType=NVARCHAR} + + + or RES.LOCK_OWNER_ = #{orQueryObject.lockOwner, jdbcType=NVARCHAR} + + + or RES.LOCK_EXP_TIME_ is not null + + + or RES.LOCK_EXP_TIME_ is null + + + or RES.TENANT_ID_ = #{orQueryObject.tenantId, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ like #{orQueryObject.tenantIdLike, jdbcType=NVARCHAR}${wildcardEscapeClause} + + + or (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null) + + + or (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + or + + + exists (select ID_ from ${prefix}ACT_RU_IDENTITYLINK IDN where IDN.SCOPE_ID_ = RES.CORRELATION_ID_ and IDN.SCOPE_TYPE_ = 'externalWorker' and IDN.USER_ID_ = #{orQueryObject.authorizedUser, jdbcType=NVARCHAR}) + + + OR exists (select ID_ from ${prefix}ACT_RU_IDENTITYLINK IDN where IDN.SCOPE_ID_ = RES.CORRELATION_ID_ and IDN.SCOPE_TYPE_ = 'externalWorker' and IDN.GROUP_ID_ IN + + #{groupId, jdbcType=NVARCHAR} + + ) + + + + + + diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/HistoryJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/HistoryJob.xml index e0b9b1b1574..c733cf7d21f 100755 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/HistoryJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/HistoryJob.xml @@ -129,6 +129,55 @@ and (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + and + + + RES.ID_ = #{orQueryObject.id, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ = #{orQueryObject.handlerType, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ in + + #{handlerType, jdbcType=NVARCHAR} + + + + or (RES.EXCEPTION_MSG_ is not null or RES.EXCEPTION_STACK_ID_ is not null) + + + or RES.EXCEPTION_MSG_ = #{orQueryObject.exceptionMessage, jdbcType=NVARCHAR} + + + or RES.SCOPE_TYPE_ = #{orQueryObject.scopeType, jdbcType=NVARCHAR} + + + or RES.LOCK_OWNER_ = #{orQueryObject.lockOwner, jdbcType=NVARCHAR} + + + or RES.LOCK_EXP_TIME_ is not null + + + or RES.LOCK_EXP_TIME_ is null + + + or RES.TENANT_ID_ = #{orQueryObject.tenantId, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ like #{orQueryObject.tenantIdLike, jdbcType=NVARCHAR}${wildcardEscapeClause} + + + or (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null) + + + or (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + + diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/Job.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/Job.xml index ab24cf1c5b5..c832ea26563 100755 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/Job.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/Job.xml @@ -245,6 +245,124 @@ and (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + and + + + RES.ID_ = #{orQueryObject.id, jdbcType=NVARCHAR} + + + or RES.ID_ IN + + #{jobId, jdbcType=NVARCHAR} + + + + or RES.PROCESS_INSTANCE_ID_ = #{orQueryObject.processInstanceId, jdbcType=NVARCHAR} + + + or RES.PROCESS_INSTANCE_ID_ IS NULL + + + or RES.EXECUTION_ID_ = #{orQueryObject.executionId, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ = #{orQueryObject.handlerType, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ in + + #{handlerType, jdbcType=NVARCHAR} + + + + or RES.PROC_DEF_ID_ = #{orQueryObject.processDefinitionId, jdbcType=NVARCHAR} + + + or RES.PROC_DEF_ID_ IN (select DEF.ID_ from ${prefix}ACT_RE_PROCDEF DEF where DEF.KEY_ = #{orQueryObject.processDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CATEGORY_ = #{orQueryObject.category, jdbcType=VARCHAR} + + + or RES.CATEGORY_ like #{orQueryObject.categoryLike, jdbcType=VARCHAR}${wildcardEscapeClause} + + + or RES.ELEMENT_ID_ = #{orQueryObject.elementId, jdbcType=NVARCHAR} + + + or RES.ELEMENT_NAME_ = #{orQueryObject.elementName, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ = #{orQueryObject.scopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ IS NULL + + + or RES.SUB_SCOPE_ID_ = #{orQueryObject.subScopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_TYPE_ = #{orQueryObject.scopeType, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ = #{orQueryObject.scopeDefinitionId, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ IN (select DEF.ID_ from ${prefix}ACT_CMMN_CASEDEF DEF where DEF.KEY_ = #{orQueryObject.caseDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CORRELATION_ID_ = #{orQueryObject.correlationId, jdbcType=NVARCHAR} + + + or RES.TYPE_ = 'timer' + + + or RES.TYPE_ = 'message' + + + or RES.DUEDATE_ > #{orQueryObject.duedateHigherThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ < #{orQueryObject.duedateLowerThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ >= #{orQueryObject.duedateHigherThanOrEqual, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ <= #{orQueryObject.duedateLowerThanOrEqual, jdbcType=TIMESTAMP} + + + or (RES.EXCEPTION_MSG_ is not null or RES.EXCEPTION_STACK_ID_ is not null) + + + or RES.EXCEPTION_MSG_ = #{orQueryObject.exceptionMessage, jdbcType=NVARCHAR} + + + or RES.LOCK_OWNER_ = #{orQueryObject.lockOwner, jdbcType=NVARCHAR} + + + or RES.LOCK_EXP_TIME_ is not null + + + or RES.LOCK_EXP_TIME_ is null + + + or RES.TENANT_ID_ = #{orQueryObject.tenantId, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ like #{orQueryObject.tenantIdLike, jdbcType=NVARCHAR}${wildcardEscapeClause} + + + or (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null) + + + or (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + + diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml index 1f25b202a3e..cd195644bb2 100644 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml @@ -368,6 +368,124 @@ and (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + and + + + RES.ID_ = #{orQueryObject.id, jdbcType=NVARCHAR} + + + or RES.ID_ IN + + #{jobId, jdbcType=NVARCHAR} + + + + or RES.PROCESS_INSTANCE_ID_ = #{orQueryObject.processInstanceId, jdbcType=NVARCHAR} + + + or RES.PROCESS_INSTANCE_ID_ IS NULL + + + or RES.EXECUTION_ID_ = #{orQueryObject.executionId, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ = #{orQueryObject.handlerType, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ in + + #{handlerType, jdbcType=NVARCHAR} + + + + or RES.PROC_DEF_ID_ = #{orQueryObject.processDefinitionId, jdbcType=NVARCHAR} + + + or RES.PROC_DEF_ID_ IN (select DEF.ID_ from ${prefix}ACT_RE_PROCDEF DEF where DEF.KEY_ = #{orQueryObject.processDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CATEGORY_ = #{orQueryObject.category, jdbcType=VARCHAR} + + + or RES.CATEGORY_ like #{orQueryObject.categoryLike, jdbcType=VARCHAR}${wildcardEscapeClause} + + + or RES.ELEMENT_ID_ = #{orQueryObject.elementId, jdbcType=NVARCHAR} + + + or RES.ELEMENT_NAME_ = #{orQueryObject.elementName, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ = #{orQueryObject.scopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ IS NULL + + + or RES.SUB_SCOPE_ID_ = #{orQueryObject.subScopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_TYPE_ = #{orQueryObject.scopeType, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ = #{orQueryObject.scopeDefinitionId, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ IN (select DEF.ID_ from ${prefix}ACT_CMMN_CASEDEF DEF where DEF.KEY_ = #{orQueryObject.caseDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CORRELATION_ID_ = #{orQueryObject.correlationId, jdbcType=NVARCHAR} + + + or RES.RETRIES_ > 0 + + + or RES.RETRIES_ <= 0 + + + or RES.TYPE_ = 'timer' + + + or RES.TYPE_ = 'message' + + + or RES.TYPE_ = 'externalWorker' + + + or RES.DUEDATE_ > #{orQueryObject.duedateHigherThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ < #{orQueryObject.duedateLowerThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ >= #{orQueryObject.duedateHigherThanOrEqual, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ <= #{orQueryObject.duedateLowerThanOrEqual, jdbcType=TIMESTAMP} + + + or (RES.EXCEPTION_MSG_ is not null or RES.EXCEPTION_STACK_ID_ is not null) + + + or RES.EXCEPTION_MSG_ = #{orQueryObject.exceptionMessage, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ = #{orQueryObject.tenantId, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ like #{orQueryObject.tenantIdLike, jdbcType=NVARCHAR}${wildcardEscapeClause} + + + or (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null) + + + or (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + + diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/TimerJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/TimerJob.xml index 1999c51a8e5..1724e508d75 100644 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/TimerJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/TimerJob.xml @@ -169,6 +169,118 @@ and (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + and + + + RES.ID_ = #{orQueryObject.id, jdbcType=NVARCHAR} + + + or RES.ID_ IN + + #{jobId, jdbcType=NVARCHAR} + + + + or RES.PROCESS_INSTANCE_ID_ = #{orQueryObject.processInstanceId, jdbcType=NVARCHAR} + + + or RES.PROCESS_INSTANCE_ID_ IS NULL + + + or RES.EXECUTION_ID_ = #{orQueryObject.executionId, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ = #{orQueryObject.handlerType, jdbcType=NVARCHAR} + + + or RES.HANDLER_TYPE_ in + + #{handlerType, jdbcType=NVARCHAR} + + + + or RES.PROC_DEF_ID_ = #{orQueryObject.processDefinitionId, jdbcType=NVARCHAR} + + + or RES.PROC_DEF_ID_ IN (select DEF.ID_ from ${prefix}ACT_RE_PROCDEF DEF where DEF.KEY_ = #{orQueryObject.processDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CATEGORY_ = #{orQueryObject.category, jdbcType=VARCHAR} + + + or RES.CATEGORY_ like #{orQueryObject.categoryLike, jdbcType=VARCHAR}${wildcardEscapeClause} + + + or RES.ELEMENT_ID_ = #{orQueryObject.elementId, jdbcType=NVARCHAR} + + + or RES.ELEMENT_NAME_ = #{orQueryObject.elementName, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ = #{orQueryObject.scopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_ID_ IS NULL + + + or RES.SUB_SCOPE_ID_ = #{orQueryObject.subScopeId, jdbcType=NVARCHAR} + + + or RES.SCOPE_TYPE_ = #{orQueryObject.scopeType, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ = #{orQueryObject.scopeDefinitionId, jdbcType=NVARCHAR} + + + or RES.SCOPE_DEFINITION_ID_ IN (select DEF.ID_ from ${prefix}ACT_CMMN_CASEDEF DEF where DEF.KEY_ = #{orQueryObject.caseDefinitionKey, jdbcType=NVARCHAR}) + + + or RES.CORRELATION_ID_ = #{orQueryObject.correlationId, jdbcType=NVARCHAR} + + + or RES.DUEDATE_ <= #{orQueryObject.now, jdbcType=TIMESTAMP} + + + or RES.TYPE_ = 'timer' + + + or RES.TYPE_ = 'message' + + + or RES.DUEDATE_ > #{orQueryObject.duedateHigherThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ < #{orQueryObject.duedateLowerThan, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ >= #{orQueryObject.duedateHigherThanOrEqual, jdbcType=TIMESTAMP} + + + or RES.DUEDATE_ <= #{orQueryObject.duedateLowerThanOrEqual, jdbcType=TIMESTAMP} + + + or (RES.EXCEPTION_MSG_ is not null or RES.EXCEPTION_STACK_ID_ is not null) + + + or RES.EXCEPTION_MSG_ = #{orQueryObject.exceptionMessage, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ = #{orQueryObject.tenantId, jdbcType=NVARCHAR} + + + or RES.TENANT_ID_ like #{orQueryObject.tenantIdLike, jdbcType=NVARCHAR}${wildcardEscapeClause} + + + or (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null) + + + or (RES.SCOPE_TYPE_ = '' or RES.SCOPE_TYPE_ is null) + + + + From de6f15acdaf8e3267c447c7495fd4e2bd54d76ca Mon Sep 17 00:00:00 2001 From: Valentin Zickner Date: Wed, 25 Mar 2026 10:26:27 +0100 Subject: [PATCH 3/3] add or() tests combined by and behavior --- .../api/mgmt/ExternalWorkerJobQueryTest.java | 47 +++++++ .../engine/test/api/mgmt/JobQueryTest.java | 130 +++++++++++++++++- .../test/api/mgmt/TimerJobQueryTest.java | 24 ++++ 3 files changed, 194 insertions(+), 7 deletions(-) diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java index 869695b234e..a62690b60a5 100755 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.java @@ -813,4 +813,51 @@ public void testOrQueryErrors() { .hasMessageContaining("endOr() can only be called after calling or()"); } + @Test + @Deployment(resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml") + public void testConsecutiveOrQuery() { + ProcessInstance pi1 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + ProcessInstance pi2 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + ProcessInstance pi3 = runtimeService.startProcessInstanceByKey("externalWorkerJobQueryTest"); + + ExternalWorkerJob job1Order = managementService.createExternalWorkerJobQuery() + .processInstanceId(pi1.getId()).elementId("externalOrder").singleResult(); + ExternalWorkerJob job2Order = managementService.createExternalWorkerJobQuery() + .processInstanceId(pi2.getId()).elementId("externalOrder").singleResult(); + + // First OR group matches {job1Order, all pi2 jobs}, second matches {all pi2 jobs, all pi3 jobs} + // Intersection = pi2's jobs (2 jobs) + List jobs = managementService.createExternalWorkerJobQuery() + .or() + .jobId(job1Order.getId()) + .processInstanceId(pi2.getId()) + .endOr() + .or() + .processInstanceId(pi2.getId()) + .processInstanceId(pi3.getId()) + .endOr() + .list(); + // Second OR group: last setter wins -> processInstanceId=pi3. So second group matches pi3 only. + // Intersection of {job1Order, pi2 jobs} AND {pi3 jobs} = empty + assertThat(jobs).hasSize(0); + + // Let's use different fields instead: + jobs = managementService.createExternalWorkerJobQuery() + .or() + .jobId(job1Order.getId()) + .processInstanceId(pi2.getId()) + .endOr() + .or() + .jobId(job2Order.getId()) + .processInstanceId(pi1.getId()) + .endOr() + .list(); + // First group: {job1Order} union {pi2's 2 jobs} = {job1Order, job2Order, job2Customer} + // Second group: {job2Order} union {pi1's 2 jobs} = {job2Order, job1Order, job1Customer} + // Intersection: {job1Order, job2Order} - both appear in both groups + assertThat(jobs).hasSize(2); + assertThat(jobs).extracting(ExternalWorkerJob::getId) + .containsExactlyInAnyOrder(job1Order.getId(), job2Order.getId()); + } + } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java index 7d16feb76ab..3dfc949f317 100755 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/JobQueryTest.java @@ -430,8 +430,8 @@ public void testDeadletterQueryByHandlerTypes() { public void testHistoryJobQueryByHandlerTypes() { List testTypes = new ArrayList<>(); - createHistoryobWithHandlerType("Type1"); - createHistoryobWithHandlerType("Type2"); + createHistoryJobWithHandlerType("Type1"); + createHistoryJobWithHandlerType("Type2"); assertThat(managementService.createHistoryJobQuery().handlerType("Type1").singleResult()).isNotNull(); assertThat(managementService.createHistoryJobQuery().handlerType("Type2").singleResult()).isNotNull(); @@ -1258,8 +1258,8 @@ public void testSuspendedJobOrQueryErrors() { @Test public void testHistoryJobOrQuery() { - createHistoryobWithHandlerType("histHandler1"); - createHistoryobWithHandlerType("histHandler2"); + createHistoryJobWithHandlerType("histHandler1"); + createHistoryJobWithHandlerType("histHandler2"); try { HistoryJob hj1 = managementService.createHistoryJobQuery().handlerType("histHandler1").singleResult(); HistoryJob hj2 = managementService.createHistoryJobQuery().handlerType("histHandler2").singleResult(); @@ -1290,8 +1290,8 @@ public void testHistoryJobOrQuery() { @Test public void testHistoryJobOrWithAndQuery() { - createHistoryobWithHandlerType("histHandler1"); - createHistoryobWithHandlerType("histHandler2"); + createHistoryJobWithHandlerType("histHandler1"); + createHistoryJobWithHandlerType("histHandler2"); try { HistoryJob hj1 = managementService.createHistoryJobQuery().handlerType("histHandler1").singleResult(); @@ -1332,6 +1332,122 @@ public void testHistoryJobOrQueryErrors() { .hasMessageContaining("endOr() can only be called after calling or()"); } + @Test + public void testJobQueryConsecutiveOrQuery() { + JobEntity jobA = createJobWithHandlerType("consOrA"); + JobEntity jobB = createJobWithHandlerType("consOrB"); + JobEntity jobC = createJobWithHandlerType("consOrC"); + try { + // First OR group matches {A, B}, second matches {B, C} -> intersection = {B} + List jobs = managementService.createJobQuery() + .or() + .jobId(jobA.getId()) + .handlerType("consOrB") + .endOr() + .or() + .jobId(jobB.getId()) + .handlerType("consOrC") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(jobB.getId()); + } finally { + managementService.deleteJob(jobA.getId()); + managementService.deleteJob(jobB.getId()); + managementService.deleteJob(jobC.getId()); + } + } + + @Test + public void testDeadLetterJobConsecutiveOrQuery() { + createDeadLetterJobWithHandlerType("consOrDlA"); + createDeadLetterJobWithHandlerType("consOrDlB"); + createDeadLetterJobWithHandlerType("consOrDlC"); + try { + Job dlA = managementService.createDeadLetterJobQuery().handlerType("consOrDlA").singleResult(); + Job dlB = managementService.createDeadLetterJobQuery().handlerType("consOrDlB").singleResult(); + Job dlC = managementService.createDeadLetterJobQuery().handlerType("consOrDlC").singleResult(); + + // First OR group matches {A, B}, second matches {B, C} -> intersection = {B} + List jobs = managementService.createDeadLetterJobQuery() + .or() + .jobId(dlA.getId()) + .handlerType("consOrDlB") + .endOr() + .or() + .jobId(dlB.getId()) + .handlerType("consOrDlC") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(dlB.getId()); + } finally { + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("consOrDlA").singleResult().getId()); + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("consOrDlB").singleResult().getId()); + managementService.deleteDeadLetterJob(managementService.createDeadLetterJobQuery().handlerType("consOrDlC").singleResult().getId()); + } + } + + @Test + public void testSuspendedJobConsecutiveOrQuery() { + createSuspendedJobWithHandlerType("consOrSusA"); + createSuspendedJobWithHandlerType("consOrSusB"); + createSuspendedJobWithHandlerType("consOrSusC"); + try { + Job susA = managementService.createSuspendedJobQuery().handlerType("consOrSusA").singleResult(); + Job susB = managementService.createSuspendedJobQuery().handlerType("consOrSusB").singleResult(); + Job susC = managementService.createSuspendedJobQuery().handlerType("consOrSusC").singleResult(); + + // First OR group matches {A, B}, second matches {B, C} -> intersection = {B} + List jobs = managementService.createSuspendedJobQuery() + .or() + .jobId(susA.getId()) + .handlerType("consOrSusB") + .endOr() + .or() + .jobId(susB.getId()) + .handlerType("consOrSusC") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(susB.getId()); + } finally { + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("consOrSusA").singleResult().getId()); + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("consOrSusB").singleResult().getId()); + managementService.deleteSuspendedJob(managementService.createSuspendedJobQuery().handlerType("consOrSusC").singleResult().getId()); + } + } + + @Test + public void testHistoryJobConsecutiveOrQuery() { + createHistoryJobWithHandlerType("consOrHistA"); + createHistoryJobWithHandlerType("consOrHistB"); + createHistoryJobWithHandlerType("consOrHistC"); + try { + HistoryJob hjA = managementService.createHistoryJobQuery().handlerType("consOrHistA").singleResult(); + HistoryJob hjB = managementService.createHistoryJobQuery().handlerType("consOrHistB").singleResult(); + HistoryJob hjC = managementService.createHistoryJobQuery().handlerType("consOrHistC").singleResult(); + + // First OR group matches {A, B}, second matches {B, C} -> intersection = {B} + List jobs = managementService.createHistoryJobQuery() + .or() + .jobId(hjA.getId()) + .handlerType("consOrHistB") + .endOr() + .or() + .jobId(hjB.getId()) + .handlerType("consOrHistC") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(hjB.getId()); + } finally { + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("consOrHistA").singleResult().getId()); + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("consOrHistB").singleResult().getId()); + managementService.deleteHistoryJob(managementService.createHistoryJobQuery().handlerType("consOrHistC").singleResult().getId()); + } + } + // helper //////////////////////////////////////////////////////////// private void setRetries(final String processInstanceId, final int retries) { @@ -1525,7 +1641,7 @@ public Void execute(CommandContext commandContext) { }); } - private void createHistoryobWithHandlerType(String handlerType) { + private void createHistoryJobWithHandlerType(String handlerType) { HistoryJobEntity historyJobEntity = managementService.executeCommand((Command) commandContext -> { JobServiceConfiguration jobServiceConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext).getJobServiceConfiguration(); HistoryJobService historyJobService = jobServiceConfiguration.getHistoryJobService(); diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java index bd3321e0981..b26778a22e2 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/mgmt/TimerJobQueryTest.java @@ -288,6 +288,30 @@ public void testOrQueryErrors() { .hasMessageContaining("endOr() can only be called after calling or()"); } + @Test + public void testConsecutiveOrQuery() { + Job timerA = managementService.createTimerJobQuery().processInstanceId(processInstanceId).elementId("timerA").singleResult(); + Job timerB = managementService.createTimerJobQuery().processInstanceId(processInstanceId).elementId("timerB").singleResult(); + Job timerC = managementService.createTimerJobQuery().processInstanceId(processInstanceId).elementId("timerC").singleResult(); + assertThat(timerA).isNotNull(); + assertThat(timerB).isNotNull(); + assertThat(timerC).isNotNull(); + + // First OR group matches {A, B}, second matches {B, C} -> intersection = {B} + List jobs = managementService.createTimerJobQuery() + .or() + .jobId(timerA.getId()) + .elementId("timerB") + .endOr() + .or() + .jobId(timerB.getId()) + .elementId("timerC") + .endOr() + .list(); + assertThat(jobs).hasSize(1); + assertThat(jobs.get(0).getId()).isEqualTo(timerB.getId()); + } + private void createTimerJobWithHandlerType(String handlerType) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); commandExecutor.execute(new Command() {