Skip to content

Commit

Permalink
Merge branch 'main' of github.com:flowable/flowable-engine
Browse files Browse the repository at this point in the history
  • Loading branch information
tijsrademakers committed May 31, 2024
2 parents fdc4f2e + dd1710b commit 90b4b50
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ protected boolean handleEventSubscription(CmmnRuntimeService cmmnRuntimeService,

if (Objects.equals(startCorrelationConfiguration, CmmnXmlConstants.START_EVENT_CORRELATION_STORE_AS_UNIQUE_REFERENCE_ID)) {

CorrelationKey correlationKeyWithAllParameters = getCorrelationKeyWithAllParameters(correlationKeys);
CorrelationKey correlationKeyWithAllParameters = getCorrelationKeyWithAllParameters(correlationKeys, eventInstance);

CaseDefinition caseDefinition = cmmnEngineConfiguration.getCmmnRepositoryService().getCaseDefinition(eventSubscription.getScopeDefinitionId());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class JobResource extends JobBaseResource {
private static final String EXECUTE_ACTION = "execute";
private static final String MOVE_ACTION = "move";
private static final String MOVE_TO_HISTORY_JOB_ACTION = "moveToHistoryJob";
private static final String RESCHEDULE_ACTION = "reschedule";

@Autowired
protected CmmnRestResponseFactory restResponseFactory;
Expand Down Expand Up @@ -228,27 +229,39 @@ public void executeJobAction(@ApiParam(name = "jobId") @PathVariable String jobI
throw new FlowableObjectNotFoundException("Could not find a job with id '" + jobId + "'.", Job.class);
}
}
@ApiOperation(value = "Move a single timer job", tags = { "Jobs" }, code = 204)

@ApiOperation(value = "Execute a single job action (move or reschedule)", tags = { "Jobs" }, code = 204)
@ApiResponses(value = {
@ApiResponse(code = 204, message = "Indicates the timer job was moved. Response-body is intentionally empty."),
@ApiResponse(code = 204, message = "Indicates the timer job action was executed. Response-body is intentionally empty."),
@ApiResponse(code = 404, message = "Indicates the requested job was not found."),
@ApiResponse(code = 500, message = "Indicates the an exception occurred while executing the job. The status-description contains additional detail about the error. The full error-stacktrace can be fetched later on if needed.")
})
@PostMapping("/cmmn-management/timer-jobs/{jobId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void executeTimerJobAction(@ApiParam(name = "jobId") @PathVariable String jobId, @RequestBody RestActionRequest actionRequest) {
if (actionRequest == null || !MOVE_ACTION.equals(actionRequest.getAction())) {
throw new FlowableIllegalArgumentException("Invalid action, only 'move' is supported.");
public void executeTimerJobAction(@ApiParam(name = "jobId") @PathVariable String jobId, @RequestBody TimerJobActionRequest actionRequest) {
if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || RESCHEDULE_ACTION.equals(actionRequest.getAction()))) {
throw new FlowableIllegalArgumentException("Invalid action, only 'move' or 'reschedule' are supported.");
}

Job job = getTimerJobById(jobId);

try {
managementService.moveTimerToExecutableJob(job.getId());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
if (MOVE_ACTION.equals(actionRequest.getAction())) {
try {
managementService.moveTimerToExecutableJob(job.getId());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
}
} else if (RESCHEDULE_ACTION.equals(actionRequest.getAction())) {
if (actionRequest.getDueDate() == null) {
throw new FlowableIllegalArgumentException("Invalid reschedule timer action. Reschedule timer actions must have a valid due date");
}
try {
managementService.rescheduleTimeDateValueJob(job.getId(), actionRequest.getDueDate());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.cmmn.rest.service.api.management;

import org.flowable.cmmn.rest.service.api.RestActionRequest;

public class TimerJobActionRequest extends RestActionRequest {

private String dueDate;

public String getDueDate() {
return dueDate;
}

public void setDueDate(String dueDate) {
this.dueDate = dueDate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

import org.apache.http.HttpStatus;
Expand Down Expand Up @@ -203,4 +207,32 @@ public void testExecuteJob() throws Exception {
// Job should be executed
assertThat(managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult()).isNull();
}

/**
* Test rescheduling a single timer job
*/
@Test
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/management/timerEventListenerCase.cmmn" })
public void testRescheduleTimerJob() throws Exception {
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("testTimerExpression").start();
Job timerJob = managementService.createTimerJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
assertThat(timerJob).isNotNull();

ObjectNode requestNode = objectMapper.createObjectNode();
requestNode.put("action", "reschedule");

LocalDateTime newDueDate = LocalDateTime.now().plusDays(1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
String dueDateString = newDueDate.format(formatter);
requestNode.put("dueDate", dueDateString);

HttpPost httpPost = new HttpPost(SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_TIMER_JOB, timerJob.getId()));
httpPost.setEntity(new StringEntity(requestNode.toString()));
CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_NO_CONTENT);
closeResponse(response);

timerJob = managementService.createTimerJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
Date expectedDueDate = Date.from(newDueDate.atZone(ZoneId.systemDefault()).toInstant());
assertThat(timerJob.getDuedate()).isCloseTo(expectedDueDate, 1000);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ protected void handleEventSubscription(RuntimeService runtimeService, EventSubsc

if (Objects.equals(startCorrelationConfiguration, BpmnXMLConstants.START_EVENT_CORRELATION_STORE_AS_UNIQUE_REFERENCE_ID)) {

CorrelationKey correlationKeyWithAllParameters = getCorrelationKeyWithAllParameters(correlationKeys);
CorrelationKey correlationKeyWithAllParameters = getCorrelationKeyWithAllParameters(correlationKeys, eventInstance);

ProcessDefinition processDefinition = processEngineConfiguration.getRepositoryService()
.getProcessDefinition(eventSubscription.getProcessDefinitionId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.stream.Collectors;

import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.FlowableIllegalStateException;
import org.flowable.common.engine.impl.AbstractEngineConfiguration;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants;
Expand Down Expand Up @@ -143,13 +144,16 @@ protected EventRegistry getEventRegistry() {
return eventRegistryEngineConfiguration.getEventRegistry();
}

protected CorrelationKey getCorrelationKeyWithAllParameters(Collection<CorrelationKey> correlationKeys) {
protected CorrelationKey getCorrelationKeyWithAllParameters(Collection<CorrelationKey> correlationKeys, EventInstance eventInstance) {
CorrelationKey result = null;
for (CorrelationKey correlationKey : correlationKeys) {
if (result == null || (correlationKey.getParameterInstances().size() >= result.getParameterInstances().size()) ) {
result = correlationKey;
}
}
if (result == null) {
throw new FlowableIllegalStateException(String.format("Event definition %s does not contain correlation parameters. Cannot verify if instance already exists.", eventInstance.getEventKey()));
}
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class JobResource extends JobBaseResource {
private static final String EXECUTE_ACTION = "execute";
private static final String MOVE_ACTION = "move";
private static final String MOVE_TO_HISTORY_JOB_ACTION = "moveToHistoryJob";
private static final String RESCHEDULE_ACTION = "reschedule";

@Autowired
protected RestResponseFactory restResponseFactory;
Expand Down Expand Up @@ -270,26 +271,38 @@ public void executeHistoryJob(@ApiParam(name = "jobId") @PathVariable String job
}
}

@ApiOperation(value = "Move a single timer job", tags = { "Jobs" }, code = 204)
@ApiOperation(value = "Execute a single job action (move or reschedule)", tags = { "Jobs" }, code = 204)
@ApiResponses(value = {
@ApiResponse(code = 204, message = "Indicates the timer job was moved. Response-body is intentionally empty."),
@ApiResponse(code = 204, message = "Indicates the timer job action was executed. Response-body is intentionally empty."),
@ApiResponse(code = 404, message = "Indicates the requested job was not found."),
@ApiResponse(code = 500, message = "Indicates the an exception occurred while executing the job. The status-description contains additional detail about the error. The full error-stacktrace can be fetched later on if needed.")
})
@PostMapping("/management/timer-jobs/{jobId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void executeTimerJobAction(@ApiParam(name = "jobId") @PathVariable String jobId, @RequestBody RestActionRequest actionRequest) {
if (actionRequest == null || !MOVE_ACTION.equals(actionRequest.getAction())) {
throw new FlowableIllegalArgumentException("Invalid action, only 'move' is supported.");
public void executeTimerJobAction(@ApiParam(name = "jobId") @PathVariable String jobId, @RequestBody TimerJobActionRequest actionRequest) {
if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || RESCHEDULE_ACTION.equals(actionRequest.getAction()))) {
throw new FlowableIllegalArgumentException("Invalid action, only 'move' or 'reschedule' are supported.");
}

Job job = getTimerJobById(jobId);

try {
managementService.moveTimerToExecutableJob(job.getId());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
if (MOVE_ACTION.equals(actionRequest.getAction())) {
try {
managementService.moveTimerToExecutableJob(job.getId());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
}
} else if (RESCHEDULE_ACTION.equals(actionRequest.getAction())) {
if (actionRequest.getDueDate() == null) {
throw new FlowableIllegalArgumentException("Invalid reschedule timer action. Reschedule timer actions must have a valid due date.");
}
try {
managementService.rescheduleTimeDateJob(job.getId(), actionRequest.getDueDate());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.rest.service.api.management;

import org.flowable.rest.service.api.RestActionRequest;

public class TimerJobActionRequest extends RestActionRequest {

private String dueDate;

public String getDueDate() {
return dueDate;
}

public void setDueDate(String dueDate) {
this.dueDate = dueDate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;

Expand All @@ -24,6 +27,7 @@
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.cmd.ChangeDeploymentTenantIdCmd;
Expand Down Expand Up @@ -471,4 +475,32 @@ public void deleteUnexistingSuspendedJob() throws Exception {
CloseableHttpResponse response = executeRequest(httpDelete, HttpStatus.SC_NOT_FOUND);
closeResponse(response);
}

/**
* Test rescheduling a single timer job
*/
@Test
@Deployment(resources = { "org/flowable/rest/service/api/management/JobResourceTest.testTimerProcess.bpmn20.xml" })
public void testRescheduleTimerJob() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("timerProcess");
Job timerJob = managementService.createTimerJobQuery().processInstanceId(processInstance.getId()).singleResult();
assertThat(timerJob).isNotNull();

ObjectNode requestNode = objectMapper.createObjectNode();
requestNode.put("action", "reschedule");

LocalDateTime newDueDate = LocalDateTime.now().plusDays(1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
String dueDateString = newDueDate.format(formatter);
requestNode.put("dueDate", dueDateString);

HttpPost httpPost = new HttpPost(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TIMER_JOB, timerJob.getId()));
httpPost.setEntity(new StringEntity(requestNode.toString()));
CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_NO_CONTENT);
closeResponse(response);

timerJob = managementService.createTimerJobQuery().processInstanceId(processInstance.getId()).singleResult();
Date expectedDueDate = Date.from(newDueDate.atZone(ZoneId.systemDefault()).toInstant());
assertThat(timerJob.getDuedate()).isCloseTo(expectedDueDate, 1000);
}
}

0 comments on commit 90b4b50

Please sign in to comment.